From b179d2a9451d5ac834a83e6ac81e9c5c3424554f Mon Sep 17 00:00:00 2001 From: Demizo <73141750+Demizo@users.noreply.github.com> Date: Fri, 2 Sep 2022 15:20:59 -0500 Subject: [PATCH 1/3] Organized Settings With Headers Settings are now organized into headers. Account and donations have been moved to the top to improve the experience of first time users. --- lib/components/Settings/Settings.dart | 318 ++++++++++++++------------ 1 file changed, 174 insertions(+), 144 deletions(-) diff --git a/lib/components/Settings/Settings.dart b/lib/components/Settings/Settings.dart index 278d6ea5..3346de56 100644 --- a/lib/components/Settings/Settings.dart +++ b/lib/components/Settings/Settings.dart @@ -63,6 +63,95 @@ class Settings extends HookConsumerWidget { constraints: const BoxConstraints(maxWidth: 1366), child: ListView( children: [ + AdaptiveListTile( + leading: const Icon( + Icons.favorite_border_rounded, + color: Colors.pink, + ), + title: const AutoSizeText( + "We know you Love Spotube", + maxLines: 1, + style: TextStyle( + color: Colors.pink, + fontWeight: FontWeight.bold, + ), + ), + trailing: (context, update) => ElevatedButton.icon( + icon: const Icon(Icons.favorite_outline_rounded), + label: const Text("Please Sponsor/Donate"), + style: ElevatedButton.styleFrom( + primary: Colors.red[100], + onPrimary: Colors.pinkAccent, + padding: const EdgeInsets.all(15), + ), + onPressed: () { + launchUrlString( + "https://opencollective.com/spotube", + mode: LaunchMode.externalApplication, + ); + }, + ), + ), + const Text( + " Account", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + if (auth.isAnonymous) + AdaptiveListTile( + leading: Icon( + Icons.login_rounded, + color: Theme.of(context).primaryColor, + ), + title: AutoSizeText( + "Login with your Spotify account", + style: TextStyle( + color: Theme.of(context).primaryColor, + ), + ), + trailing: (context, update) => ElevatedButton( + child: Text("Connect with Spotify".toUpperCase()), + onPressed: () { + GoRouter.of(context).push("/login"); + }, + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25.0), + ), + ), + ), + ), + ), + if (auth.isLoggedIn) + Builder(builder: (context) { + Auth auth = ref.watch(authProvider); + return ListTile( + leading: const Icon(Icons.logout_rounded), + title: const AutoSizeText( + "Log out of this account", + maxLines: 1, + ), + trailing: ElevatedButton( + child: const Text("Logout"), + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(Colors.red), + foregroundColor: + MaterialStateProperty.all(Colors.white), + ), + onPressed: () async { + auth.logout(); + GoRouter.of(context).pop(); + }, + ), + ); + }), + const Text( + " Appearance", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), AdaptiveListTile( leading: const Icon(Icons.dark_mode_outlined), title: const Text("Theme"), @@ -122,6 +211,55 @@ class Settings extends HookConsumerWidget { ), onTap: pickColorScheme(ColorSchemeType.background), ), + const Text( + " Playback", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + AdaptiveListTile( + leading: const Icon(Icons.multitrack_audio_rounded), + title: const Text("Audio Quality"), + trailing: (context, update) => + DropdownButton( + value: preferences.audioQuality, + items: const [ + DropdownMenuItem( + child: Text( + "High", + ), + value: AudioQuality.high, + ), + DropdownMenuItem( + child: Text("Low"), + value: AudioQuality.low, + ), + ], + onChanged: (value) { + if (value != null) { + preferences.setAudioQuality(value); + update?.call(() {}); + } + }, + ), + ), + ListTile( + leading: const Icon(Icons.fast_forward_rounded), + title: const Text( + "Skip non-music segments (SponsorBlock)", + ), + trailing: Switch.adaptive( + activeColor: Theme.of(context).primaryColor, + value: preferences.skipSponsorSegments, + onChanged: (state) { + preferences.setSkipSponsorSegments(state); + }, + ), + ), + const Text( + " Search", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), AdaptiveListTile( leading: const Icon(Icons.shopping_bag_rounded), title: Text( @@ -155,16 +293,6 @@ class Settings extends HookConsumerWidget { ), ), ), - ListTile( - leading: const Icon(Icons.file_download_outlined), - title: const Text("Download Location"), - subtitle: Text(preferences.downloadLocation), - trailing: ElevatedButton( - child: const Icon(Icons.folder_rounded), - onPressed: pickDownloadLocation, - ), - onTap: pickDownloadLocation, - ), AdaptiveListTile( leading: const Icon(Icons.screen_search_desktop_rounded), title: const AutoSizeText( @@ -195,66 +323,6 @@ class Settings extends HookConsumerWidget { ), ), ), - ListTile( - leading: const Icon(Icons.fast_forward_rounded), - title: const Text( - "Skip non-music segments (SponsorBlock)", - ), - trailing: Switch.adaptive( - activeColor: Theme.of(context).primaryColor, - value: preferences.skipSponsorSegments, - onChanged: (state) { - preferences.setSkipSponsorSegments(state); - }, - ), - ), - ListTile( - leading: const Icon(Icons.lyrics_rounded), - title: const Text("Download lyrics along with the Track"), - trailing: Switch.adaptive( - activeColor: Theme.of(context).primaryColor, - value: preferences.saveTrackLyrics, - onChanged: (state) { - preferences.setSaveTrackLyrics(state); - }, - ), - ), - if (auth.isAnonymous) - AdaptiveListTile( - leading: Icon( - Icons.login_rounded, - color: Theme.of(context).primaryColor, - ), - title: AutoSizeText( - "Login with your Spotify account", - style: TextStyle( - color: Theme.of(context).primaryColor, - ), - ), - trailing: (context, update) => ElevatedButton( - child: Text("Connect with Spotify".toUpperCase()), - onPressed: () { - GoRouter.of(context).push("/login"); - }, - style: ButtonStyle( - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(25.0), - ), - ), - ), - ), - ), - ListTile( - leading: const Icon(Icons.update_rounded), - title: const Text("Check for Update"), - trailing: Switch.adaptive( - activeColor: Theme.of(context).primaryColor, - value: preferences.checkUpdate, - onChanged: (checked) => - preferences.setCheckUpdate(checked), - ), - ), AdaptiveListTile( leading: const Icon(Icons.low_priority_rounded), title: const AutoSizeText( @@ -290,83 +358,45 @@ class Settings extends HookConsumerWidget { }, ), ), - AdaptiveListTile( - leading: const Icon(Icons.multitrack_audio_rounded), - title: const Text("Audio Quality"), - trailing: (context, update) => - DropdownButton( - value: preferences.audioQuality, - items: const [ - DropdownMenuItem( - child: Text( - "High", - ), - value: AudioQuality.high, - ), - DropdownMenuItem( - child: Text("Low"), - value: AudioQuality.low, - ), - ], - onChanged: (value) { - if (value != null) { - preferences.setAudioQuality(value); - update?.call(() {}); - } + const Text( + " Downloads", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + ListTile( + leading: const Icon(Icons.file_download_outlined), + title: const Text("Download Location"), + subtitle: Text(preferences.downloadLocation), + trailing: ElevatedButton( + child: const Icon(Icons.folder_rounded), + onPressed: pickDownloadLocation, + ), + onTap: pickDownloadLocation, + ), + ListTile( + leading: const Icon(Icons.lyrics_rounded), + title: const Text("Download lyrics along with the Track"), + trailing: Switch.adaptive( + activeColor: Theme.of(context).primaryColor, + value: preferences.saveTrackLyrics, + onChanged: (state) { + preferences.setSaveTrackLyrics(state); }, ), ), - if (auth.isLoggedIn) - Builder(builder: (context) { - Auth auth = ref.watch(authProvider); - return ListTile( - leading: const Icon(Icons.logout_rounded), - title: const AutoSizeText( - "Log out of this account", - maxLines: 1, - ), - trailing: ElevatedButton( - child: const Text("Logout"), - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(Colors.red), - foregroundColor: - MaterialStateProperty.all(Colors.white), - ), - onPressed: () async { - auth.logout(); - GoRouter.of(context).pop(); - }, - ), - ); - }), - AdaptiveListTile( - leading: const Icon( - Icons.favorite_border_rounded, - color: Colors.pink, - ), - title: const AutoSizeText( - "We know you Love Spotube", - maxLines: 1, - style: TextStyle( - color: Colors.pink, - fontWeight: FontWeight.bold, - ), - ), - trailing: (context, update) => ElevatedButton.icon( - icon: const Icon(Icons.favorite_outline_rounded), - label: const Text("Please Sponsor/Donate"), - style: ElevatedButton.styleFrom( - primary: Colors.red[100], - onPrimary: Colors.pinkAccent, - padding: const EdgeInsets.all(15), - ), - onPressed: () { - launchUrlString( - "https://opencollective.com/spotube", - mode: LaunchMode.externalApplication, - ); - }, + const Text( + " About", + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + ListTile( + leading: const Icon(Icons.update_rounded), + title: const Text("Check for Update"), + trailing: Switch.adaptive( + activeColor: Theme.of(context).primaryColor, + value: preferences.checkUpdate, + onChanged: (checked) => + preferences.setCheckUpdate(checked), ), ), const About() From 97dcd8b6e2ef4a1f6e14a9501e0df5146e1ed637 Mon Sep 17 00:00:00 2001 From: Demizo <73141750+Demizo@users.noreply.github.com> Date: Fri, 2 Sep 2022 23:32:57 -0500 Subject: [PATCH 2/3] Moved support button to about section support button is now im the about section --- lib/components/Settings/Settings.dart | 58 +++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/components/Settings/Settings.dart b/lib/components/Settings/Settings.dart index 3346de56..9bc35839 100644 --- a/lib/components/Settings/Settings.dart +++ b/lib/components/Settings/Settings.dart @@ -63,35 +63,6 @@ class Settings extends HookConsumerWidget { constraints: const BoxConstraints(maxWidth: 1366), child: ListView( children: [ - AdaptiveListTile( - leading: const Icon( - Icons.favorite_border_rounded, - color: Colors.pink, - ), - title: const AutoSizeText( - "We know you Love Spotube", - maxLines: 1, - style: TextStyle( - color: Colors.pink, - fontWeight: FontWeight.bold, - ), - ), - trailing: (context, update) => ElevatedButton.icon( - icon: const Icon(Icons.favorite_outline_rounded), - label: const Text("Please Sponsor/Donate"), - style: ElevatedButton.styleFrom( - primary: Colors.red[100], - onPrimary: Colors.pinkAccent, - padding: const EdgeInsets.all(15), - ), - onPressed: () { - launchUrlString( - "https://opencollective.com/spotube", - mode: LaunchMode.externalApplication, - ); - }, - ), - ), const Text( " Account", style: @@ -389,6 +360,35 @@ class Settings extends HookConsumerWidget { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), + AdaptiveListTile( + leading: const Icon( + Icons.favorite_border_rounded, + color: Colors.pink, + ), + title: const AutoSizeText( + "We know you Love Spotube", + maxLines: 1, + style: TextStyle( + color: Colors.pink, + fontWeight: FontWeight.bold, + ), + ), + trailing: (context, update) => ElevatedButton.icon( + icon: const Icon(Icons.favorite_outline_rounded), + label: const Text("Please Sponsor/Donate"), + style: ElevatedButton.styleFrom( + primary: Colors.red[100], + onPrimary: Colors.pinkAccent, + padding: const EdgeInsets.all(15), + ), + onPressed: () { + launchUrlString( + "https://opencollective.com/spotube", + mode: LaunchMode.externalApplication, + ); + }, + ), + ), ListTile( leading: const Icon(Icons.update_rounded), title: const Text("Check for Update"), From 2ab1fba3d64147e3c5cf34756dce1cf6046d410a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 3 Sep 2022 13:50:33 +0600 Subject: [PATCH 3/3] fix(downloader): downloaded track is corrupted for tagging --- lib/components/Library/UserLocalTracks.dart | 154 +++++++++++--------- lib/models/Id3Tags.dart | 14 ++ lib/provider/Downloader.dart | 11 +- pubspec.lock | 9 +- 4 files changed, 111 insertions(+), 77 deletions(-) diff --git a/lib/components/Library/UserLocalTracks.dart b/lib/components/Library/UserLocalTracks.dart index 8648467e..152b1a71 100644 --- a/lib/components/Library/UserLocalTracks.dart +++ b/lib/components/Library/UserLocalTracks.dart @@ -39,74 +39,96 @@ const imgMimeToExt = { }; final localTracksProvider = FutureProvider>((ref) async { - final downloadDir = Directory( - ref.watch(userPreferencesProvider.select((s) => s.downloadLocation)), - ); - if (!await downloadDir.exists()) { - await downloadDir.create(recursive: true); + try { + final downloadDir = Directory( + ref.watch(userPreferencesProvider.select((s) => s.downloadLocation)), + ); + if (!await downloadDir.exists()) { + await downloadDir.create(recursive: true); + return []; + } + final entities = downloadDir.listSync(recursive: true); + + final filesWithMetadata = (await Future.wait( + entities.map((e) => File(e.path)).where((file) { + final mimetype = lookupMimeType(file.path); + return mimetype != null && supportedAudioTypes.contains(mimetype); + }).map( + (f) async { + try { + final bytes = f.readAsBytes(); + final mp3Instance = MP3Instance(await bytes); + + bool isParsed = false; + try { + isParsed = mp3Instance.parseTagsSync(); + } catch (e, stack) { + getLogger(MP3Instance).e("[parseTagsSync]", e, stack); + } + + final imageFile = isParsed + ? File(join( + (await getTemporaryDirectory()).path, + "spotube", + basenameWithoutExtension(f.path) + + imgMimeToExt[mp3Instance.metaTags["APIC"]?["mime"] ?? + "image/jpeg"]!, + )) + : null; + if (imageFile != null && + !await imageFile.exists() && + mp3Instance.metaTags["APIC"]?["base64"] != null) { + await imageFile.create(recursive: true); + await imageFile.writeAsBytes( + base64Decode( + mp3Instance.metaTags["APIC"]["base64"], + ), + mode: FileMode.writeOnly, + ); + } + Duration duration; + try { + duration = MP3Processor.fromBytes(await bytes).duration; + } catch (e, stack) { + getLogger(MP3Processor).e("[Parsing Mp3]", e, stack); + duration = Duration.zero; + } + + final metadata = await tagProcessor.getTagsFromByteArray(bytes); + return { + "metadata": metadata, + "file": f, + "art": imageFile?.path, + "duration": duration, + }; + } catch (e, stack) { + getLogger(FutureProvider).e("[Fetching metadata]", e, stack); + return { + "metadata": [], + "file": f, + "duration": Duration.zero, + }; + } + }, + ), + )); + + final tracks = filesWithMetadata + .map( + (fileWithMetadata) => TypeConversionUtils.localTrack_X_Track( + fileWithMetadata["metadata"] as List, + fileWithMetadata["file"] as File, + fileWithMetadata["duration"] as Duration, + fileWithMetadata["art"] as String?, + ), + ) + .toList(); + + return tracks; + } catch (e, stack) { + getLogger(FutureProvider).e("[LocalTracksProvider]", e, stack); return []; } - final entities = downloadDir.listSync(recursive: true); - final filesWithMetadata = (await Future.wait( - entities.map((e) => File(e.path)).where((file) { - final mimetype = lookupMimeType(file.path); - return mimetype != null && supportedAudioTypes.contains(mimetype); - }).map( - (f) async { - final bytes = f.readAsBytes(); - final mp3Instance = MP3Instance(await bytes); - - final imageFile = mp3Instance.parseTagsSync() - ? File(join( - (await getTemporaryDirectory()).path, - "spotube", - basenameWithoutExtension(f.path) + - imgMimeToExt[ - mp3Instance.metaTags["APIC"]?["mime"] ?? "image/jpeg"]!, - )) - : null; - if (imageFile != null && - !await imageFile.exists() && - mp3Instance.metaTags["APIC"]?["base64"] != null) { - await imageFile.create(recursive: true); - await imageFile.writeAsBytes( - base64Decode( - mp3Instance.metaTags["APIC"]["base64"], - ), - mode: FileMode.writeOnly, - ); - } - Duration duration; - try { - duration = MP3Processor.fromBytes(await bytes).duration; - } catch (e, stack) { - getLogger(MP3Processor).e("[Parsing Mp3]", e, stack); - duration = Duration.zero; - } - - final metadata = await tagProcessor.getTagsFromByteArray(bytes); - return { - "metadata": metadata, - "file": f, - "art": imageFile?.path, - "duration": duration, - }; - }, - ), - )); - - final tracks = filesWithMetadata - .map( - (fileWithMetadata) => TypeConversionUtils.localTrack_X_Track( - fileWithMetadata["metadata"] as List, - fileWithMetadata["file"] as File, - fileWithMetadata["duration"] as Duration, - fileWithMetadata["art"] as String?, - ), - ) - .toList(); - - return tracks; }); class UserLocalTracks extends HookConsumerWidget { diff --git a/lib/models/Id3Tags.dart b/lib/models/Id3Tags.dart index 746773ab..c2d0f885 100644 --- a/lib/models/Id3Tags.dart +++ b/lib/models/Id3Tags.dart @@ -64,6 +64,20 @@ class Id3Tags { "genre": genre, "picture": picture, }; + + String? get artist => tpe2; + String? get year => tdrc; + + Map toAndroidJson(String artwork) { + return { + "title": title ?? "Unknown", + "artist": artist ?? "Unknown", + "album": album ?? "Unknown", + "genre": genre ?? "Unknown", + "artwork": artwork, + "year": year ?? "Unknown", + }; + } } extension CommentJson on Comment { diff --git a/lib/provider/Downloader.dart b/lib/provider/Downloader.dart index 68634e31..c8f4f238 100644 --- a/lib/provider/Downloader.dart +++ b/lib/provider/Downloader.dart @@ -16,6 +16,7 @@ import 'package:spotube/models/SpotubeTrack.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/YouTube.dart'; +import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart' hide Comment; @@ -96,11 +97,15 @@ class Downloader with ChangeNotifier { "[addToQueue] Download of ${file.path} is done successfully", ); + // Tagging isn't supported in Android currently + if (kIsAndroid) return; + + final imageUri = TypeConversionUtils.image_X_UrlString( + track.album?.images ?? [], + ); final response = await get( Uri.parse( - TypeConversionUtils.image_X_UrlString( - track.album?.images ?? [], - ), + imageUri, ), ); final picture = AttachedPicture.base64( diff --git a/pubspec.lock b/pubspec.lock index 9922b11b..a0dcd9c8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -463,13 +463,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - eztags: - dependency: "direct main" - description: - name: eztags - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" fading_edge_scrollview: dependency: transitive description: @@ -1417,5 +1410,5 @@ packages: source: hosted version: "1.11.0" sdks: - dart: ">=2.17.5 <3.0.0" + dart: ">=2.17.1 <3.0.0" flutter: ">=3.0.0"