Skip to content

Commit

Permalink
feat: Ability to change download location added
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Aug 3, 2022
1 parent cb58166 commit 816707c
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 118 deletions.
1 change: 1 addition & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-keep class androidx.lifecycle.DefaultLifecycleObserver
30 changes: 30 additions & 0 deletions lib/components/Settings/Settings.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
236 changes: 120 additions & 116 deletions lib/components/Shared/DownloadTrackButton.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -30,141 +29,146 @@ 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;
if ((kIsMobile) &&
!await Permission.storage.isGranted &&
!await Permission.storage.isPermanentlyDenied) {
final status = await Permission.storage.request();
if (!status.isGranted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Couldn't download track. Not enough permissions"),
),
try {
if (track == null || outputFile.value == null) return;
if ((kIsMobile) &&
!await Permission.storage.isGranted &&
!await Permission.storage.isPermanentlyDenied) {
final status = await Permission.storage.request();
if (!status.isGranted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Couldn't download track. Not enough permissions"),
),
);
return;
}
}
StreamManifest manifest = await yt.videos.streamsClient
.getManifest((track as SpotubeTrack).ytTrack.url);

File outputLyricsFile = File(
path.join(preferences.downloadLocation, "$fileName-lyrics.txt"));

if (await outputFile.value!.exists()) {
final shouldReplace = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Track Already Exists"),
content: const Text(
"Do you want to replace the already downloaded track?"),
actions: [
TextButton(
child: const Text("No"),
onPressed: () {
Navigator.pop(context, false);
},
),
TextButton(
child: const Text("Yes"),
onPressed: () {
Navigator.pop(context, true);
},
)
],
);
},
);
return;
if (shouldReplace != true) return;
}
}
StreamManifest manifest = await yt.videos.streamsClient
.getManifest((track as SpotubeTrack).ytTrack.url);

File outputLyricsFile =
File(path.join(downloadFolder.value!, "$fileName-lyrics.txt"));

if (await outputFile.value!.exists()) {
final shouldReplace = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Track Already Exists"),
content: const Text(
"Do you want to replace the already downloaded track?"),
actions: [
TextButton(
child: const Text("No"),
onPressed: () {
Navigator.pop(context, false);
},
),
TextButton(
child: const Text("Yes"),
onPressed: () {
Navigator.pop(context, true);
},
)
],

final audioStream = yt.videos.streamsClient
.get(
manifest.audioOnly
.where((audio) => audio.codec.mimeType == "audio/mp4")
.withHighestBitrate(),
)
.asBroadcastStream();

final statusCb = audioStream.listen(
(event) {
if (status.value != TrackStatus.downloading) {
status.value = TrackStatus.downloading;
}
},
onDone: () async {
status.value = TrackStatus.done;
await Future.delayed(
const Duration(seconds: 3),
() {
if (status.value == TrackStatus.done) {
status.value = TrackStatus.idle;
}
},
);
},
);
if (shouldReplace != true) return;
}

final audioStream = yt.videos.streamsClient
.get(
manifest.audioOnly
.where((audio) => audio.codec.mimeType == "audio/mp4")
.withHighestBitrate(),
)
.asBroadcastStream();

final statusCb = audioStream.listen(
(event) {
if (status.value != TrackStatus.downloading) {
status.value = TrackStatus.downloading;
}
},
onDone: () async {
status.value = TrackStatus.done;
await Future.delayed(
const Duration(seconds: 3),
() {
if (status.value == TrackStatus.done) {
status.value = TrackStatus.idle;
}
},
);
},
);
if (!await outputFile.value!.exists()) {
await outputFile.value!.create(recursive: true);
}

if (!await outputFile.value!.exists()) {
await outputFile.value!.create(recursive: true);
}
IOSink outputFileStream = outputFile.value!.openWrite();
await audioStream.pipe(outputFileStream);
await outputFileStream.flush();
await outputFileStream.close().then((value) async {
if (status.value == TrackStatus.downloading) {
status.value = TrackStatus.done;
await Future.delayed(
const Duration(seconds: 3),
() {
if (status.value == TrackStatus.done) {
status.value = TrackStatus.idle;
}
},
);
}
return statusCb.cancel();
});

IOSink outputFileStream = outputFile.value!.openWrite();
await audioStream.pipe(outputFileStream);
await outputFileStream.flush();
await outputFileStream.close().then((value) async {
if (status.value == TrackStatus.downloading) {
status.value = TrackStatus.done;
await Future.delayed(
const Duration(seconds: 3),
() {
if (status.value == TrackStatus.done) {
status.value = TrackStatus.idle;
}
},
if (preferences.saveTrackLyrics && playback.track != null) {
if (!await outputLyricsFile.exists()) {
await outputLyricsFile.create(recursive: true);
}
final lyrics = await ServiceUtils.getLyrics(
playback.track!.name!,
playback.track!.artists
?.map((s) => s.name)
.whereNotNull()
.toList() ??
[],
apiKey: preferences.geniusAccessToken,
optimizeQuery: true,
);
if (lyrics != null) {
await outputLyricsFile.writeAsString(
"$lyrics\n\nPowered by genius.com",
mode: FileMode.writeOnly,
);
}
}
return statusCb.cancel();
});

if (preferences.saveTrackLyrics && playback.track != null) {
if (!await outputLyricsFile.exists()) {
await outputLyricsFile.create(recursive: true);
}
final lyrics = await ServiceUtils.getLyrics(
playback.track!.name!,
playback.track!.artists?.map((s) => s.name).whereNotNull().toList() ??
[],
apiKey: preferences.geniusAccessToken,
optimizeQuery: true,
} on FileSystemException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.red,
content: Text("Download Failed. ${e.message} ${e.path}"),
),
);
if (lyrics != null) {
await outputLyricsFile.writeAsString(
"$lyrics\n\nPowered by genius.com",
mode: FileMode.writeOnly,
);
}
}
}, [
track,
Expand All @@ -173,7 +177,7 @@ class DownloadTrackButton extends HookConsumerWidget {
preferences.saveTrackLyrics,
playback.track,
outputFile.value,
downloadFolder.value,
preferences.downloadLocation,
fileName
]);

Expand Down
Loading

0 comments on commit 816707c

Please sign in to comment.