mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: Ability to change download location added
This commit is contained in:
parent
cb58166244
commit
816707c643
1
android/app/proguard-rules.pro
vendored
Normal file
1
android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
-keep class androidx.lifecycle.DefaultLifecycleObserver
|
@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -31,9 +34,18 @@ class Settings extends HookConsumerWidget {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
final pickDownloadLocation = useCallback(() async {
|
||||||
|
final dirStr = await FilePicker.platform.getDirectoryPath(
|
||||||
|
dialogTitle: "Download Location",
|
||||||
|
);
|
||||||
|
if (dirStr == null) return;
|
||||||
|
preferences.setDownloadLocation(dirStr);
|
||||||
|
}, [preferences.downloadLocation]);
|
||||||
|
|
||||||
var ytSearchFormatController = useTextEditingController(
|
var ytSearchFormatController = useTextEditingController(
|
||||||
text: preferences.ytSearchFormat,
|
text: preferences.ytSearchFormat,
|
||||||
);
|
);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: PageWindowTitleBar(
|
appBar: PageWindowTitleBar(
|
||||||
@ -148,6 +160,24 @@ class Settings extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text("Download Location"),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
preferences.downloadLocation,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Icon(Icons.folder_rounded),
|
||||||
|
onPressed: pickDownloadLocation,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: pickDownloadLocation,
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 15.0,
|
horizontal: 15.0,
|
||||||
|
@ -11,7 +11,6 @@ import 'package:spotube/utils/platform.dart';
|
|||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -30,28 +29,20 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
YoutubeExplode yt = useMemoized(() => YoutubeExplode());
|
YoutubeExplode yt = useMemoized(() => YoutubeExplode());
|
||||||
|
|
||||||
final outputFile = useState<File?>(null);
|
final outputFile = useState<File?>(null);
|
||||||
final downloadFolder = useState<String?>(null);
|
|
||||||
String fileName =
|
String fileName =
|
||||||
"${track?.name} - ${TypeConversionUtils.artists_X_String<Artist>(track?.artists ?? [])}";
|
"${track?.name} - ${TypeConversionUtils.artists_X_String<Artist>(track?.artists ?? [])}";
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
(() async {
|
(() async {
|
||||||
downloadFolder.value = path.join(
|
|
||||||
Platform.isAndroid
|
|
||||||
? "/storage/emulated/0/Download"
|
|
||||||
: (await path_provider.getDownloadsDirectory())!.path,
|
|
||||||
"Spotube");
|
|
||||||
|
|
||||||
outputFile.value =
|
outputFile.value =
|
||||||
File(path.join(downloadFolder.value!, "$fileName.mp3"));
|
File(path.join(preferences.downloadLocation, "$fileName.mp3"));
|
||||||
}());
|
}());
|
||||||
return null;
|
return null;
|
||||||
}, [fileName, track]);
|
}, [fileName, track, preferences.downloadLocation]);
|
||||||
|
|
||||||
final _downloadTrack = useCallback(() async {
|
final _downloadTrack = useCallback(() async {
|
||||||
if (track == null ||
|
try {
|
||||||
outputFile.value == null ||
|
if (track == null || outputFile.value == null) return;
|
||||||
downloadFolder.value == null) return;
|
|
||||||
if ((kIsMobile) &&
|
if ((kIsMobile) &&
|
||||||
!await Permission.storage.isGranted &&
|
!await Permission.storage.isGranted &&
|
||||||
!await Permission.storage.isPermanentlyDenied) {
|
!await Permission.storage.isPermanentlyDenied) {
|
||||||
@ -59,7 +50,8 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
if (!status.isGranted) {
|
if (!status.isGranted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text("Couldn't download track. Not enough permissions"),
|
content:
|
||||||
|
Text("Couldn't download track. Not enough permissions"),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -68,8 +60,8 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
StreamManifest manifest = await yt.videos.streamsClient
|
StreamManifest manifest = await yt.videos.streamsClient
|
||||||
.getManifest((track as SpotubeTrack).ytTrack.url);
|
.getManifest((track as SpotubeTrack).ytTrack.url);
|
||||||
|
|
||||||
File outputLyricsFile =
|
File outputLyricsFile = File(
|
||||||
File(path.join(downloadFolder.value!, "$fileName-lyrics.txt"));
|
path.join(preferences.downloadLocation, "$fileName-lyrics.txt"));
|
||||||
|
|
||||||
if (await outputFile.value!.exists()) {
|
if (await outputFile.value!.exists()) {
|
||||||
final shouldReplace = await showDialog<bool>(
|
final shouldReplace = await showDialog<bool>(
|
||||||
@ -154,7 +146,10 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
final lyrics = await ServiceUtils.getLyrics(
|
final lyrics = await ServiceUtils.getLyrics(
|
||||||
playback.track!.name!,
|
playback.track!.name!,
|
||||||
playback.track!.artists?.map((s) => s.name).whereNotNull().toList() ??
|
playback.track!.artists
|
||||||
|
?.map((s) => s.name)
|
||||||
|
.whereNotNull()
|
||||||
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
apiKey: preferences.geniusAccessToken,
|
apiKey: preferences.geniusAccessToken,
|
||||||
optimizeQuery: true,
|
optimizeQuery: true,
|
||||||
@ -166,6 +161,15 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} on FileSystemException catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
content: Text("Download Failed. ${e.message} ${e.path}"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
track,
|
track,
|
||||||
status,
|
status,
|
||||||
@ -173,7 +177,7 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
preferences.saveTrackLyrics,
|
preferences.saveTrackLyrics,
|
||||||
playback.track,
|
playback.track,
|
||||||
outputFile.value,
|
outputFile.value,
|
||||||
downloadFolder.value,
|
preferences.downloadLocation,
|
||||||
fileName
|
fileName
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart';
|
import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart';
|
||||||
import 'package:spotube/models/SpotubeTrack.dart';
|
import 'package:spotube/models/SpotubeTrack.dart';
|
||||||
import 'package:spotube/models/generated_secrets.dart';
|
import 'package:spotube/models/generated_secrets.dart';
|
||||||
@ -9,6 +11,7 @@ import 'package:spotube/provider/Playback.dart';
|
|||||||
import 'package:spotube/utils/PersistedChangeNotifier.dart';
|
import 'package:spotube/utils/PersistedChangeNotifier.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
class UserPreferences extends PersistedChangeNotifier {
|
class UserPreferences extends PersistedChangeNotifier {
|
||||||
ThemeMode themeMode;
|
ThemeMode themeMode;
|
||||||
@ -23,6 +26,9 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
MaterialColor accentColorScheme;
|
MaterialColor accentColorScheme;
|
||||||
MaterialColor backgroundColorScheme;
|
MaterialColor backgroundColorScheme;
|
||||||
bool skipSponsorSegments;
|
bool skipSponsorSegments;
|
||||||
|
|
||||||
|
String downloadLocation;
|
||||||
|
|
||||||
UserPreferences({
|
UserPreferences({
|
||||||
required this.geniusAccessToken,
|
required this.geniusAccessToken,
|
||||||
required this.recommendationMarket,
|
required this.recommendationMarket,
|
||||||
@ -35,7 +41,16 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
this.trackMatchAlgorithm = SpotubeTrackMatchAlgorithm.authenticPopular,
|
this.trackMatchAlgorithm = SpotubeTrackMatchAlgorithm.authenticPopular,
|
||||||
this.audioQuality = AudioQuality.high,
|
this.audioQuality = AudioQuality.high,
|
||||||
this.skipSponsorSegments = true,
|
this.skipSponsorSegments = true,
|
||||||
}) : super();
|
this.downloadLocation = "",
|
||||||
|
}) : super() {
|
||||||
|
if (downloadLocation.isEmpty) {
|
||||||
|
_getDefaultDownloadDirectory().then(
|
||||||
|
(value) {
|
||||||
|
downloadLocation = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setThemeMode(ThemeMode mode) {
|
void setThemeMode(ThemeMode mode) {
|
||||||
themeMode = mode;
|
themeMode = mode;
|
||||||
@ -103,8 +118,22 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
updatePersistence();
|
updatePersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDownloadLocation(String downloadDir) {
|
||||||
|
if (downloadDir.isEmpty) return;
|
||||||
|
downloadLocation = downloadDir;
|
||||||
|
notifyListeners();
|
||||||
|
updatePersistence();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getDefaultDownloadDirectory() async {
|
||||||
|
if (Platform.isAndroid) return "/storage/emulated/0/Download/Spotube";
|
||||||
|
return getDownloadsDirectory().then((dir) {
|
||||||
|
return path.join(dir!.path, "Spotube");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) {
|
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async {
|
||||||
saveTrackLyrics = map["saveTrackLyrics"] ?? false;
|
saveTrackLyrics = map["saveTrackLyrics"] ?? false;
|
||||||
recommendationMarket = map["recommendationMarket"] ?? recommendationMarket;
|
recommendationMarket = map["recommendationMarket"] ?? recommendationMarket;
|
||||||
checkUpdate = map["checkUpdate"] ?? checkUpdate;
|
checkUpdate = map["checkUpdate"] ?? checkUpdate;
|
||||||
@ -126,6 +155,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
? AudioQuality.values[map["audioQuality"]]
|
? AudioQuality.values[map["audioQuality"]]
|
||||||
: audioQuality;
|
: audioQuality;
|
||||||
skipSponsorSegments = map["skipSponsorSegments"] ?? skipSponsorSegments;
|
skipSponsorSegments = map["skipSponsorSegments"] ?? skipSponsorSegments;
|
||||||
|
downloadLocation =
|
||||||
|
map["downloadLocation"] ?? await _getDefaultDownloadDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -142,6 +173,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
"trackMatchAlgorithm": trackMatchAlgorithm.index,
|
"trackMatchAlgorithm": trackMatchAlgorithm.index,
|
||||||
"audioQuality": audioQuality.index,
|
"audioQuality": audioQuality.index,
|
||||||
"skipSponsorSegments": skipSponsorSegments,
|
"skipSponsorSegments": skipSponsorSegments,
|
||||||
|
"downloadLocation": downloadLocation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -477,6 +477,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.2"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.6.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -552,6 +559,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -65,6 +65,7 @@ dependencies:
|
|||||||
audioplayers: ^1.0.1
|
audioplayers: ^1.0.1
|
||||||
introduction_screen: ^3.0.2
|
introduction_screen: ^3.0.2
|
||||||
audio_session: ^0.1.9
|
audio_session: ^0.1.9
|
||||||
|
file_picker: ^4.6.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user