mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45: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_hooks/flutter_hooks.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(
|
||||
text: preferences.ytSearchFormat,
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
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: const EdgeInsets.symmetric(
|
||||
horizontal: 15.0,
|
||||
|
@ -11,7 +11,6 @@ import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.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:permission_handler/permission_handler.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
@ -30,28 +29,20 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
YoutubeExplode yt = useMemoized(() => YoutubeExplode());
|
||||
|
||||
final outputFile = useState<File?>(null);
|
||||
final downloadFolder = useState<String?>(null);
|
||||
String fileName =
|
||||
"${track?.name} - ${TypeConversionUtils.artists_X_String<Artist>(track?.artists ?? [])}";
|
||||
|
||||
useEffect(() {
|
||||
(() async {
|
||||
downloadFolder.value = path.join(
|
||||
Platform.isAndroid
|
||||
? "/storage/emulated/0/Download"
|
||||
: (await path_provider.getDownloadsDirectory())!.path,
|
||||
"Spotube");
|
||||
|
||||
outputFile.value =
|
||||
File(path.join(downloadFolder.value!, "$fileName.mp3"));
|
||||
File(path.join(preferences.downloadLocation, "$fileName.mp3"));
|
||||
}());
|
||||
return null;
|
||||
}, [fileName, track]);
|
||||
}, [fileName, track, preferences.downloadLocation]);
|
||||
|
||||
final _downloadTrack = useCallback(() async {
|
||||
if (track == null ||
|
||||
outputFile.value == null ||
|
||||
downloadFolder.value == null) return;
|
||||
try {
|
||||
if (track == null || outputFile.value == null) return;
|
||||
if ((kIsMobile) &&
|
||||
!await Permission.storage.isGranted &&
|
||||
!await Permission.storage.isPermanentlyDenied) {
|
||||
@ -59,7 +50,8 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
if (!status.isGranted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Couldn't download track. Not enough permissions"),
|
||||
content:
|
||||
Text("Couldn't download track. Not enough permissions"),
|
||||
),
|
||||
);
|
||||
return;
|
||||
@ -68,8 +60,8 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
StreamManifest manifest = await yt.videos.streamsClient
|
||||
.getManifest((track as SpotubeTrack).ytTrack.url);
|
||||
|
||||
File outputLyricsFile =
|
||||
File(path.join(downloadFolder.value!, "$fileName-lyrics.txt"));
|
||||
File outputLyricsFile = File(
|
||||
path.join(preferences.downloadLocation, "$fileName-lyrics.txt"));
|
||||
|
||||
if (await outputFile.value!.exists()) {
|
||||
final shouldReplace = await showDialog<bool>(
|
||||
@ -154,7 +146,10 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
}
|
||||
final lyrics = await ServiceUtils.getLyrics(
|
||||
playback.track!.name!,
|
||||
playback.track!.artists?.map((s) => s.name).whereNotNull().toList() ??
|
||||
playback.track!.artists
|
||||
?.map((s) => s.name)
|
||||
.whereNotNull()
|
||||
.toList() ??
|
||||
[],
|
||||
apiKey: preferences.geniusAccessToken,
|
||||
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,
|
||||
status,
|
||||
@ -173,7 +177,7 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
preferences.saveTrackLyrics,
|
||||
playback.track,
|
||||
outputFile.value,
|
||||
downloadFolder.value,
|
||||
preferences.downloadLocation,
|
||||
fileName
|
||||
]);
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.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/models/SpotubeTrack.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:collection/collection.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class UserPreferences extends PersistedChangeNotifier {
|
||||
ThemeMode themeMode;
|
||||
@ -23,6 +26,9 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
MaterialColor accentColorScheme;
|
||||
MaterialColor backgroundColorScheme;
|
||||
bool skipSponsorSegments;
|
||||
|
||||
String downloadLocation;
|
||||
|
||||
UserPreferences({
|
||||
required this.geniusAccessToken,
|
||||
required this.recommendationMarket,
|
||||
@ -35,7 +41,16 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
this.trackMatchAlgorithm = SpotubeTrackMatchAlgorithm.authenticPopular,
|
||||
this.audioQuality = AudioQuality.high,
|
||||
this.skipSponsorSegments = true,
|
||||
}) : super();
|
||||
this.downloadLocation = "",
|
||||
}) : super() {
|
||||
if (downloadLocation.isEmpty) {
|
||||
_getDefaultDownloadDirectory().then(
|
||||
(value) {
|
||||
downloadLocation = value;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void setThemeMode(ThemeMode mode) {
|
||||
themeMode = mode;
|
||||
@ -103,8 +118,22 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
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
|
||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) {
|
||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async {
|
||||
saveTrackLyrics = map["saveTrackLyrics"] ?? false;
|
||||
recommendationMarket = map["recommendationMarket"] ?? recommendationMarket;
|
||||
checkUpdate = map["checkUpdate"] ?? checkUpdate;
|
||||
@ -126,6 +155,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
? AudioQuality.values[map["audioQuality"]]
|
||||
: audioQuality;
|
||||
skipSponsorSegments = map["skipSponsorSegments"] ?? skipSponsorSegments;
|
||||
downloadLocation =
|
||||
map["downloadLocation"] ?? await _getDefaultDownloadDirectory();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -142,6 +173,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
"trackMatchAlgorithm": trackMatchAlgorithm.index,
|
||||
"audioQuality": audioQuality.index,
|
||||
"skipSponsorSegments": skipSponsorSegments,
|
||||
"downloadLocation": downloadLocation,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
14
pubspec.lock
14
pubspec.lock
@ -477,6 +477,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -552,6 +559,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -65,6 +65,7 @@ dependencies:
|
||||
audioplayers: ^1.0.1
|
||||
introduction_screen: ^3.0.2
|
||||
audio_session: ^0.1.9
|
||||
file_picker: ^4.6.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user