diff --git a/README.md b/README.md index 038188766..6a3a06217 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Play Local / Downloaded Songs Support :open_file_folder:
High Quality mp3 / m4a / flac Format :fire:
Lyrics Support :pencil:
+ SponsorBlock Support :scissors:
No Ads :no_entry_sign:
No Subscriptions :dollar:
12 Supported Languages :us:
diff --git a/analysis_options.yaml b/analysis_options.yaml index 9ac9620e8..af3b8bcd2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -43,6 +43,7 @@ linter: - non_constant_identifier_names - noop_primitive_operations - null_closures + - omit_local_variable_types - overridden_fields - package_api_docs - package_names @@ -64,9 +65,13 @@ linter: - prefer_single_quotes - prefer_typing_uninitialized_variables - recursive_getters + - require_trailing_commas - sized_box_for_whitespace - slash_for_doc_comments - sort_child_properties_last + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first - test_types_in_equals - throw_in_finally - type_init_formals @@ -88,5 +93,6 @@ linter: - use_is_even_rather_than_modulo - use_named_constants - use_rethrow_when_possible + - use_super_parameters - valid_regexps - void_checks \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index a9f5e732c..8afd08325 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -60,6 +60,7 @@ android { targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } signingConfigs { @@ -91,4 +92,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:multidex:1.0.3' } diff --git a/flutter_launcher_icons.yaml b/flutter_launcher_icons.yaml deleted file mode 100644 index aa8e3147a..000000000 --- a/flutter_launcher_icons.yaml +++ /dev/null @@ -1,7 +0,0 @@ -flutter_icons: - android: "launcher_icon" - adaptive_icon_background: "#191919" - adaptive_icon_foreground: "assets/images/ic_launcher_foreground.png" - adaptive_icon_round: "assets/images/ic_launcher_round.png" - image_path: "assets/images/ic_launcher.png" - ios: true \ No newline at end of file diff --git a/flutter_native_splash.yaml b/flutter_native_splash.yaml deleted file mode 100644 index 8d4dfb253..000000000 --- a/flutter_native_splash.yaml +++ /dev/null @@ -1,8 +0,0 @@ - flutter_native_splash: - image: assets/images/splash.png - color: "151515" - - - android_12: - image: assets/images/splash.png - color: "151515" \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 000000000..076a59c5f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 000000000..4b3d1cf37 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 000000000..5970594cf Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 000000000..14c9e4a59 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 000000000..1982a706b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 000000000..81fd8742e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/lib/API/musify.dart b/lib/API/musify.dart index e89d0cbac..169a99791 100644 --- a/lib/API/musify.dart +++ b/lib/API/musify.dart @@ -19,6 +19,8 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart'; final yt = YoutubeExplode(); final OnAudioQuery _audioQuery = OnAudioQuery(); +final random = Random(); + List ytplaylists = []; List searchedList = []; List playlists = []; @@ -30,14 +32,14 @@ List localSongs = []; final lyrics = ValueNotifier('null'); String _lastLyricsUrl = ''; +String _alternateApiUrl = ''; int id = 0; Future fetchSongsList(String searchQuery) async { final List list = await yt.search.search(searchQuery); - searchedList = []; - for (final s in list) { - searchedList.add( + searchedList = [ + for (final s in list) returnSongLayout( 0, s.id.toString(), @@ -48,9 +50,9 @@ Future fetchSongsList(String searchQuery) async { s.thumbnails.lowResUrl.toString(), s.thumbnails.maxResUrl.toString(), s.title.split('-')[0].toString(), - ), - ); - } + ) + ]; + return searchedList; } @@ -58,7 +60,7 @@ Future get10Music(dynamic playlistid) async { final List playlistSongs = await getData('cache', 'playlist10Songs$playlistid') ?? []; if (playlistSongs.isEmpty) { - int index = 0; + var index = 0; await for (final song in yt.playlists.getVideos(playlistid).take(10)) { playlistSongs.add( returnSongLayout( @@ -154,47 +156,25 @@ Future searchPlaylist(String query) async { } return playlists - .where((playlist) => - playlist['title'].toLowerCase().contains(query.toLowerCase())) + .where( + (playlist) => + playlist['title'].toLowerCase().contains(query.toLowerCase()), + ) .toList(); } Future getRandomSong() async { - if (playlists.isEmpty) { - playlists = - json.decode(await rootBundle.loadString('assets/db/playlists.db.json')) - as List; - } - final random = Random(); - final playlistId = playlists[random.nextInt(playlists.length)]['ytid']; - final playlistSongs = - await getData('cache', 'playlistSongs$playlistId') ?? []; + final playlistId = 'PLgzTt0k8mXzEk586ze4BjvDXR7c-TUSnx'; + final List playlistSongs = await getSongsFromPlaylist(playlistId); - if (playlistSongs.isEmpty) { - final songs = await yt.playlists.getVideos(playlistId).take(5).toList(); - final choosedSong = songs[random.nextInt(playlistSongs.length)]; - - return returnSongLayout( - 0, - choosedSong.id.toString(), - formatSongTitle( - choosedSong.title.split('-')[choosedSong.title.split('-').length - 1], - ), - choosedSong.thumbnails.standardResUrl, - choosedSong.thumbnails.lowResUrl, - choosedSong.thumbnails.maxResUrl, - choosedSong.title.split('-')[0], - ); - } else { - return playlistSongs[random.nextInt(playlistSongs.length)]; - } + return playlistSongs[random.nextInt(playlistSongs.length)]; } Future getSongsFromPlaylist(dynamic playlistid) async { final List playlistSongs = await getData('cache', 'playlistSongs$playlistid') ?? []; if (playlistSongs.isEmpty) { - int index = 0; + var index = 0; await for (final song in yt.playlists.getVideos(playlistid)) { playlistSongs.add( returnSongLayout( @@ -221,13 +201,13 @@ Future setActivePlaylist(List plist) async { if (plist is List) { activePlaylist = []; id = 0; - final List activeTempPlaylist = []; - for (final song in plist) { - activeTempPlaylist.add(songModelToMediaItem(song, song.data)); - } + final activeTempPlaylist = [ + for (final song in plist) songModelToMediaItem(song, song.data) + ]; + await MyAudioHandler().addQueueItems(activeTempPlaylist); - await play(); + play(); } else { activePlaylist = plist; id = 0; @@ -253,14 +233,13 @@ Future getPlaylistInfoForWidget(dynamic id) async { return playlist; } -Future getSongUrl(dynamic songId) async { - final manifest = await yt.videos.streamsClient.getManifest(songId); - return manifest.audioOnly.withHighestBitrate().url.toString(); -} - -Future getSongStream(dynamic songId) async { +Future getSong(dynamic songId, bool geturl) async { final manifest = await yt.videos.streamsClient.getManifest(songId); - return manifest.audioOnly.withHighestBitrate(); + if (geturl) { + return manifest.audioOnly.withHighestBitrate().url.toString(); + } else { + return manifest.audioOnly.withHighestBitrate(); + } } Future getSongDetails(dynamic songIndex, dynamic songId) async { @@ -277,33 +256,87 @@ Future getSongDetails(dynamic songIndex, dynamic songId) async { } Future> getLocalSongs() async { - // DEFAULT: - // SongSortType.TITLE, - // OrderType.ASC_OR_SMALLER, - // UriType.EXTERNAL, - if (localSongs.isEmpty) { - if (await Permission.storage.request().isGranted) { - localSongs = await _audioQuery.querySongs(uriType: UriType.EXTERNAL); - localSongs.addAll(await _audioQuery.querySongs( - path: await ExtStorageProvider.getExtStorage(dirName: 'Musify'))); - } + if (await Permission.storage.request().isGranted) { + localSongs = [ + ...await _audioQuery.querySongs(uriType: UriType.EXTERNAL), + ...await _audioQuery.querySongs( + path: await ExtStorageProvider.getExtStorage(dirName: 'Musify'), + ) + ]; } return localSongs; } +Future>> getSkipSegments(String id) async { + try { + final res = await http.get( + Uri( + scheme: 'https', + host: 'sponsor.ajay.app', + path: '/api/skipSegments', + queryParameters: { + 'videoID': id, + 'category': [ + 'sponsor', + 'selfpromo', + 'interaction', + 'intro', + 'outro', + 'music_offtopic' + ], + 'actionType': 'skip' + }, + ), + ); + if (res.body != 'Not Found') { + final data = jsonDecode(res.body); + final segments = data.map((obj) { + return Map.castFrom({ + 'start': obj['segment'].first.toInt(), + 'end': obj['segment'].last.toInt(), + }); + }).toList(); + return List.castFrom>(segments); + } else { + return List.castFrom>([]); + } + } catch (e, stack) { + debugPrint('$e $stack'); + return List.castFrom>([]); + } +} + +Future getAlternateApiUrl() async { + final response = await http.get( + Uri.parse('https://jsapi.apiary.io/apis/lyricsovh'), + headers: {'Accept': 'application/json'}, + ).timeout(const Duration(seconds: 10)); + + if (response.statusCode == 200) { + final proxyResponse = await json.decode(response.body); + if (proxyResponse['urls']['proxy'] != null) { + _alternateApiUrl = proxyResponse['urls']['proxy']; + } + } +} + Future getSongLyrics(String artist, String title) async { - if (_lastLyricsUrl != - 'https://api.lyrics.ovh/v1/$artist/${title.split(" (")[0].split("|")[0].trim()}') { - if (await getData('cache', - 'lyrics-https://api.lyrics.ovh/v1/$artist/${title.split(" (")[0].split("|")[0].trim()}') != - null) { - lyrics.value = await getData('cache', - 'lyrics-https://api.lyrics.ovh/v1/$artist/${title.split(" (")[0].split("|")[0].trim()}'); + String currentApiUrl; + if (_alternateApiUrl != '') { + currentApiUrl = + '$_alternateApiUrl${artist.replaceAll(RegExp(r'[^\w\s]+'), '')}/${title.split(" (")[0].split("|")[0].trim().replaceAll(RegExp(r'[^\w\s]+'), '')}'; + } else { + currentApiUrl = + 'https://api.lyrics.ovh/v1/${artist.replaceAll(RegExp(r'[^\w\s]+'), '')}/${title.split(" (")[0].split("|")[0].trim().replaceAll(RegExp(r'[^\w\s]+'), '')}'; + } + + if (_lastLyricsUrl != currentApiUrl) { + if (await getData('cache', 'lyrics-$artist-$title') != null) { + lyrics.value = await getData('cache', 'lyrics-$artist-$title'); } else { lyrics.value = 'null'; - _lastLyricsUrl = - 'https://api.lyrics.ovh/v1/$artist/${title.split(" (")[0].split("|")[0].trim()}'; + _lastLyricsUrl = currentApiUrl; final response = await http.get( Uri.parse(_lastLyricsUrl), headers: {'Accept': 'application/json'}, @@ -313,8 +346,11 @@ Future getSongLyrics(String artist, String title) async { final lyricsResponse = await json.decode(response.body); if (lyricsResponse['lyrics'] != null) { lyrics.value = lyricsResponse['lyrics'].toString(); - addOrUpdateData('cache', 'lyrics-$_lastLyricsUrl', - lyricsResponse['lyrics'].toString()); + addOrUpdateData( + 'cache', + 'lyrics-$artist-$title', + lyricsResponse['lyrics'].toString(), + ); } else { lyrics.value = 'not found'; } diff --git a/lib/customWidgets/custom_animated_bottom_bar.dart b/lib/customWidgets/custom_animated_bottom_bar.dart index eaeaf7bc1..24b88e451 100644 --- a/lib/customWidgets/custom_animated_bottom_bar.dart +++ b/lib/customWidgets/custom_animated_bottom_bar.dart @@ -3,7 +3,7 @@ import 'package:musify/ui/rootPage.dart'; class CustomAnimatedBottomBar extends StatelessWidget { CustomAnimatedBottomBar({ - Key? key, + super.key, this.showElevation = true, this.onTap, this.selectedItemColor, @@ -17,7 +17,7 @@ class CustomAnimatedBottomBar extends StatelessWidget { this.curve = Curves.easeOutQuint, this.radius = BorderRadius.zero, required this.items, - }) : super(key: key); + }); final Color? backgroundColor; final bool showElevation; final List items; diff --git a/lib/customWidgets/delayed_display.dart b/lib/customWidgets/delayed_display.dart index 67bdeff98..39e863a50 100644 --- a/lib/customWidgets/delayed_display.dart +++ b/lib/customWidgets/delayed_display.dart @@ -5,6 +5,16 @@ import 'dart:async'; import 'package:flutter/material.dart'; class DelayedDisplay extends StatefulWidget { + /// DelayedDisplay constructor + const DelayedDisplay({ + required this.child, + this.delay = Duration.zero, + this.fadingDuration = const Duration(milliseconds: 800), + this.slidingCurve = Curves.decelerate, + this.slidingBeginOffset = const Offset(0, 0.35), + this.fadeIn = true, + }); + /// Child that will be displayed with the animation and delay final Widget child; @@ -23,16 +33,6 @@ class DelayedDisplay extends StatefulWidget { /// If true, make the child appear, disappear otherwise. Default to true. final bool fadeIn; - /// DelayedDisplay constructor - const DelayedDisplay({ - required this.child, - this.delay = Duration.zero, - this.fadingDuration = const Duration(milliseconds: 800), - this.slidingCurve = Curves.decelerate, - this.slidingBeginOffset = const Offset(0, 0.35), - this.fadeIn = true, - }); - @override _DelayedDisplayState createState() => _DelayedDisplayState(); } @@ -74,7 +74,7 @@ class _DelayedDisplayState extends State duration: opacityTransitionDuration, ); - final CurvedAnimation curvedAnimation = CurvedAnimation( + final curvedAnimation = CurvedAnimation( curve: slidingCurve, parent: _opacityController, ); diff --git a/lib/customWidgets/setting_bar.dart b/lib/customWidgets/setting_bar.dart index fe1c36bc6..e7e84ab09 100644 --- a/lib/customWidgets/setting_bar.dart +++ b/lib/customWidgets/setting_bar.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:musify/style/appColors.dart'; class SettingBar extends StatelessWidget { + SettingBar(this.tileName, this.tileIcon, this.onTap); + final Function() onTap; final String tileName; final IconData tileIcon; - SettingBar(this.tileName, this.tileIcon, this.onTap); - @override Widget build(BuildContext context) { return Padding( diff --git a/lib/customWidgets/song_bar.dart b/lib/customWidgets/song_bar.dart index b9fba600c..63f79911d 100644 --- a/lib/customWidgets/song_bar.dart +++ b/lib/customWidgets/song_bar.dart @@ -3,10 +3,11 @@ import 'package:flutter/material.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:musify/API/musify.dart'; import 'package:musify/services/audio_manager.dart'; +import 'package:musify/services/download_manager.dart'; import 'package:musify/style/appColors.dart'; class SongBar extends StatelessWidget { - SongBar(this.song, this.moveBackAfterPlay, {Key? key}) : super(key: key); + SongBar(this.song, this.moveBackAfterPlay, {super.key}); late final dynamic song; late final bool moveBackAfterPlay; @@ -29,10 +30,10 @@ class SongBar extends StatelessWidget { Navigator.pop(context); } }, - splashColor: accent, - hoverColor: accent, - focusColor: accent, - highlightColor: accent, + splashColor: accent.withOpacity(0.4), + hoverColor: accent.withOpacity(0.4), + focusColor: accent.withOpacity(0.4), + highlightColor: accent.withOpacity(0.4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -65,9 +66,10 @@ class SongBar extends StatelessWidget { .replaceAll('"', '"') .replaceAll('&', '&'), style: TextStyle( - color: accent, - fontSize: 16, - fontWeight: FontWeight.w700), + color: accent, + fontSize: 16, + fontWeight: FontWeight.w700, + ), ), ), const SizedBox( @@ -79,9 +81,10 @@ class SongBar extends StatelessWidget { overflow: TextOverflow.ellipsis, song['more_info']['singers'].toString(), style: const TextStyle( - color: Colors.white70, - fontWeight: FontWeight.w400, - fontSize: 14), + color: Colors.white70, + fontWeight: FontWeight.w400, + fontSize: 14, + ), ), ), ], diff --git a/lib/customWidgets/spinner.dart b/lib/customWidgets/spinner.dart index 892fdff52..47f05f0f7 100644 --- a/lib/customWidgets/spinner.dart +++ b/lib/customWidgets/spinner.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:musify/style/appColors.dart'; class Spinner extends StatelessWidget { - const Spinner({Key? key}) : super(key: key); + const Spinner({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/helper/material_color_creator.dart b/lib/helper/material_color_creator.dart index 0a83c7cd4..e722f486b 100644 --- a/lib/helper/material_color_creator.dart +++ b/lib/helper/material_color_creator.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; MaterialColor createMaterialColor(Color color) { final List strengths = [.05]; - final Map swatch = {}; - final int r = color.red, g = color.green, b = color.blue; + final swatch = {}; + final r = color.red, g = color.green, b = color.blue; - for (int i = 1; i < 10; i++) { + for (var i = 1; i < 10; i++) { strengths.add(0.1 * i); } for (var strength in strengths) { - final double ds = 0.5 - strength; + final ds = 0.5 - strength; swatch[(strength * 1000).round()] = Color.fromRGBO( r + ((ds < 0 ? r : (255 - r)) * ds).round(), g + ((ds < 0 ? g : (255 - g)) * ds).round(), diff --git a/lib/helper/version.dart b/lib/helper/version.dart index bbe200989..e4248825a 100644 --- a/lib/helper/version.dart +++ b/lib/helper/version.dart @@ -33,9 +33,8 @@ Future downloadAppUpdates() async { } else { dlUrl = map['url'].toString(); } - final String? dlPath = - await ExtStorageProvider.getExtStorage(dirName: 'Download'); - final File file = File('${dlPath!}/Musify.apk'); + final dlPath = await ExtStorageProvider.getExtStorage(dirName: 'Download'); + final file = File('${dlPath!}/Musify.apk'); if (await file.exists()) { await file.delete(); } diff --git a/lib/main.dart b/lib/main.dart index 81e30e6a4..651a2073e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -16,18 +17,21 @@ import 'package:musify/ui/rootPage.dart'; import 'package:package_info_plus/package_info_plus.dart'; GetIt getIt = GetIt.instance; +bool _interrupted = false; class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); static Future setLocale(BuildContext context, Locale newLocale) async { - final _MyAppState state = context.findAncestorStateOfType<_MyAppState>()!; + final state = context.findAncestorStateOfType<_MyAppState>()!; state.changeLanguage(newLocale); } static Future setAccentColor( - BuildContext context, Color newAccentColor) async { - final _MyAppState state = context.findAncestorStateOfType<_MyAppState>()!; + BuildContext context, + Color newAccentColor, + ) async { + final state = context.findAncestorStateOfType<_MyAppState>()!; state.changeAccentColor(newAccentColor); } @@ -54,7 +58,7 @@ class _MyAppState extends State { void initState() { super.initState(); getLocalSongs(); - final Map codes = { + final codes = { 'English': 'en', 'Georgian': 'ka', 'Chinese': 'zh', @@ -68,14 +72,18 @@ class _MyAppState extends State { 'Turkish': 'tr', 'Ukrainian': 'uk', }; - _locale = Locale(codes[Hive.box('settings') - .get('language', defaultValue: 'English') as String]!); + _locale = Locale( + codes[Hive.box('settings').get('language', defaultValue: 'English') + as String]!, + ); + + FlutterDownloader.registerCallback(downloadCallback); } @override void dispose() { Hive.close(); - audioPlayer!.dispose(); + audioPlayer.dispose(); super.dispose(); } @@ -152,25 +160,35 @@ void main() async { await Hive.openBox('settings'); await Hive.openBox('user'); await Hive.openBox('cache'); - await FlutterDownloader.initialize( - debug: - true, // optional: set to false to disable printing logs to console (default: true) - ignoreSsl: - true // option: set to false to disable working with http links (default: false) - , - ); - FlutterDownloader.registerCallback(TestClass.callback); - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - version = packageInfo.version; - await enableBooster(); await initialisation(); + await getAlternateApiUrl(); runApp(const MyApp()); } Future initialisation() async { final session = await AudioSession.instance; await session.configure(const AudioSessionConfiguration.music()); - final AudioHandler audioHandler = await AudioService.init( + session.interruptionEventStream.listen((event) { + if (event.begin) { + if (audioPlayer.playing) { + pause(); + _interrupted = true; + } + } else { + switch (event.type) { + case AudioInterruptionType.pause: + case AudioInterruptionType.duck: + if (!audioPlayer.playing && _interrupted) { + play(); + } + break; + case AudioInterruptionType.unknown: + break; + } + _interrupted = false; + } + }); + final audioHandler = await AudioService.init( builder: MyAudioHandler.new, config: const AudioServiceConfig( androidNotificationChannelId: 'com.gokadzev.musify', @@ -181,9 +199,14 @@ Future initialisation() async { ), ); getIt.registerSingleton(audioHandler); + await enableBooster(); + await FlutterDownloader.initialize( + debug: kDebugMode, + ignoreSsl: true, + ); + final packageInfo = await PackageInfo.fromPlatform(); + version = packageInfo.version; } -// ignore: avoid_classes_with_only_static_members -class TestClass { - static void callback(String id, DownloadTaskStatus status, int progress) {} -} +@pragma('vm:entry-point') +void downloadCallback(String id, DownloadTaskStatus status, int progress) {} diff --git a/lib/services/audio_handler.dart b/lib/services/audio_handler.dart index a4cee8004..2352f496f 100644 --- a/lib/services/audio_handler.dart +++ b/lib/services/audio_handler.dart @@ -2,31 +2,33 @@ import 'package:audio_service/audio_service.dart'; import 'package:flutter/widgets.dart'; import 'package:just_audio/just_audio.dart'; import 'package:musify/API/musify.dart'; +import 'package:musify/helper/mediaitem.dart'; import 'package:musify/services/audio_manager.dart'; class MyAudioHandler extends BaseAudioHandler { - final ConcatenatingAudioSource _playlist = - ConcatenatingAudioSource(children: []); - MyAudioHandler() { _loadEmptyPlaylist(); _notifyAudioHandlerAboutPlaybackEvents(); _listenForDurationChanges(); _listenForCurrentSongIndexChanges(); _listenForSequenceStateChanges(); + _listenForPositionChanges(); + _listenProcessingStates(); } + final ConcatenatingAudioSource _playlist = + ConcatenatingAudioSource(children: []); Future _loadEmptyPlaylist() async { try { - await audioPlayer!.setAudioSource(_playlist); + await audioPlayer.setAudioSource(_playlist); } catch (e) { debugPrint('Error: $e'); } } void _notifyAudioHandlerAboutPlaybackEvents() { - audioPlayer!.playbackEventStream.listen((PlaybackEvent event) { - final bool playing = audioPlayer!.playing; + audioPlayer.playbackEventStream.listen((PlaybackEvent event) { + final playing = audioPlayer.playing; playbackState.add( playbackState.value.copyWith( controls: [ @@ -45,54 +47,96 @@ class MyAudioHandler extends BaseAudioHandler { ProcessingState.buffering: AudioProcessingState.buffering, ProcessingState.ready: AudioProcessingState.ready, ProcessingState.completed: AudioProcessingState.completed, - }[audioPlayer!.processingState]!, + }[audioPlayer.processingState]!, repeatMode: const { LoopMode.off: AudioServiceRepeatMode.none, LoopMode.one: AudioServiceRepeatMode.one, LoopMode.all: AudioServiceRepeatMode.all, - }[audioPlayer!.loopMode]!, - shuffleMode: (audioPlayer!.shuffleModeEnabled) + }[audioPlayer.loopMode]!, + shuffleMode: (audioPlayer.shuffleModeEnabled) ? AudioServiceShuffleMode.all : AudioServiceShuffleMode.none, playing: playing, - updatePosition: audioPlayer!.position, - bufferedPosition: audioPlayer!.bufferedPosition, - speed: audioPlayer!.speed, + updatePosition: audioPlayer.position, + bufferedPosition: audioPlayer.bufferedPosition, + speed: audioPlayer.speed, queueIndex: event.currentIndex, ), ); }); } + void _listenProcessingStates() { + audioPlayer.playerStateStream.listen((state) async { + playerState.value = state; + if (state.processingState == ProcessingState.completed) { + await pause(); + await audioPlayer.seek(Duration.zero); + } + }); + } + void _listenForDurationChanges() { - audioPlayer!.durationStream.listen((duration) { - var index = audioPlayer!.currentIndex; - final List newQueue = queue.value; + audioPlayer.durationStream.listen((duration) { + var index = audioPlayer.currentIndex; + final newQueue = queue.value; if (index == null || newQueue.isEmpty) return; - if (audioPlayer!.shuffleModeEnabled) { - index = audioPlayer!.shuffleIndices![index]; + if (audioPlayer.shuffleModeEnabled) { + index = audioPlayer.shuffleIndices![index]; } - final MediaItem oldMediaItem = newQueue[index]; - final MediaItem newMediaItem = oldMediaItem.copyWith(duration: duration); + final oldMediaItem = newQueue[index]; + final newMediaItem = oldMediaItem.copyWith(duration: duration); newQueue[index] = newMediaItem; queue.add(newQueue); mediaItem.add(newMediaItem); }); } + bool canBeSkipped = false; + + void _listenForPositionChanges() { + audioPlayer.positionStream.listen((position) async { + if (playerState.value.processingState != ProcessingState.loading && + audioPlayer.duration != null && + position.inSeconds == audioPlayer.duration!.inSeconds - 5) { + if (!hasNext && playNextSongAutomatically.value) { + final randomSong = await getRandomSong(); + final randomSongUrl = await getSong(randomSong['ytid'], true); + await addQueueItem(mapToMediaItem(randomSong, randomSongUrl)); + } + } else if (playerState.value.processingState != ProcessingState.loading && + audioPlayer.duration != null && + position.inSeconds == audioPlayer.duration!.inSeconds - 1) { + canBeSkipped = true; + } else if (playerState.value.processingState != ProcessingState.loading && + audioPlayer.duration != null && + position.inSeconds == audioPlayer.duration!.inSeconds) { + if (canBeSkipped && hasNext) { + await skipToNext(); + canBeSkipped = false; + } else if (canBeSkipped && + !hasNext && + playNextSongAutomatically.value) { + await play(); + canBeSkipped = false; + } + } + }); + } + void _listenForCurrentSongIndexChanges() { - audioPlayer!.currentIndexStream.listen((index) { - final List playlist = queue.value; + audioPlayer.currentIndexStream.listen((index) { + final playlist = queue.value; if (index == null || playlist.isEmpty) return; - if (audioPlayer!.shuffleModeEnabled) { - index = audioPlayer!.shuffleIndices![index]; + if (audioPlayer.shuffleModeEnabled) { + index = audioPlayer.shuffleIndices![index]; } mediaItem.add(playlist[index]); }); } void _listenForSequenceStateChanges() { - audioPlayer!.sequenceStateStream.listen((SequenceState? sequenceState) { + audioPlayer.sequenceStateStream.listen((SequenceState? sequenceState) { final sequence = sequenceState?.effectiveSequence; if (sequence == null || sequence.isEmpty) return; final items = sequence.map((source) => source.tag as MediaItem); @@ -111,8 +155,14 @@ class MyAudioHandler extends BaseAudioHandler { } @override - Future addQueueItem(MediaItem mediaItem) async { - final audioSource = _createAudioSource(mediaItem); + Future addQueueItem(MediaItem mediaItem, [start, end]) async { + final dynamic audioSource; + if (start != null || end != null) { + audioSource = _createClippingAudioSource(mediaItem, start, end); + } else { + audioSource = _createAudioSource(mediaItem); + } + await _playlist.add(audioSource); final newQueue = queue.value..add(mediaItem); @@ -126,6 +176,42 @@ class MyAudioHandler extends BaseAudioHandler { ); } + ClippingAudioSource _createClippingAudioSource( + MediaItem mediaItem, [ + start, + end, + ]) { + if (start != null && end == null) { + return ClippingAudioSource( + start: start, + tag: mediaItem, + child: AudioSource.uri( + Uri.parse(mediaItem.extras!['url'].toString()), + tag: mediaItem, + ), + ); + } else if (end != null && start == null) { + return ClippingAudioSource( + end: end, + tag: mediaItem, + child: AudioSource.uri( + Uri.parse(mediaItem.extras!['url'].toString()), + tag: mediaItem, + ), + ); + } else { + return ClippingAudioSource( + start: start, + end: end, + tag: mediaItem, + child: AudioSource.uri( + Uri.parse(mediaItem.extras!['url'].toString()), + tag: mediaItem, + ), + ); + } + } + @override Future removeQueueItemAt(int index) async { await _playlist.removeAt(index); @@ -135,39 +221,35 @@ class MyAudioHandler extends BaseAudioHandler { } @override - Future play() => audioPlayer!.play(); - - @override - Future pause() => audioPlayer!.pause(); + Future play() => audioPlayer.play(); @override - Future seek(Duration position) => audioPlayer!.seek(position); + Future pause() => audioPlayer.pause(); @override - Future skipToQueueItem(int index) async { - late int ind; - if (index < 0 || index >= queue.value.length) return; - if (audioPlayer!.shuffleModeEnabled) { - ind = audioPlayer!.shuffleIndices![index]; - } - await audioPlayer!.seek(Duration.zero, index: ind); - } + Future seek(Duration position) => audioPlayer.seek(position); @override Future skipToNext() async { if (activePlaylist.isEmpty) { - await audioPlayer!.seekToNext(); + await audioPlayer.seekToNext(); } else { - await playNext(); + if (id + 1 <= activePlaylist.length) { + await playSong(activePlaylist[id + 1]); + id = id + 1; + } } } @override Future skipToPrevious() async { if (activePlaylist.isEmpty) { - await audioPlayer!.seekToPrevious(); + await audioPlayer.seekToPrevious(); } else { - await playPrevious(); + if (id - 1 >= 0) { + await playSong(activePlaylist[id - 1]); + id = id - 1; + } } } @@ -175,14 +257,14 @@ class MyAudioHandler extends BaseAudioHandler { Future setRepeatMode(AudioServiceRepeatMode repeatMode) async { switch (repeatMode) { case AudioServiceRepeatMode.none: - await audioPlayer!.setLoopMode(LoopMode.off); + await audioPlayer.setLoopMode(LoopMode.off); break; case AudioServiceRepeatMode.one: - await audioPlayer!.setLoopMode(LoopMode.one); + await audioPlayer.setLoopMode(LoopMode.one); break; case AudioServiceRepeatMode.group: case AudioServiceRepeatMode.all: - await audioPlayer!.setLoopMode(LoopMode.all); + await audioPlayer.setLoopMode(LoopMode.all); break; } } @@ -190,24 +272,24 @@ class MyAudioHandler extends BaseAudioHandler { @override Future setShuffleMode(AudioServiceShuffleMode shuffleMode) async { if (shuffleMode == AudioServiceShuffleMode.none) { - await audioPlayer!.setShuffleModeEnabled(false); + await audioPlayer.setShuffleModeEnabled(false); } else { - await audioPlayer!.shuffle(); - await audioPlayer!.setShuffleModeEnabled(true); + await audioPlayer.shuffle(); + await audioPlayer.setShuffleModeEnabled(true); } } @override Future customAction(String name, [Map? extras]) async { if (name == 'dispose') { - await audioPlayer!.dispose(); + await audioPlayer.dispose(); await super.stop(); } } @override Future stop() async { - await audioPlayer!.stop(); + await audioPlayer.stop(); return super.stop(); } } diff --git a/lib/services/audio_manager.dart b/lib/services/audio_manager.dart index 4fee6a2c2..a27c88690 100644 --- a/lib/services/audio_manager.dart +++ b/lib/services/audio_manager.dart @@ -1,26 +1,21 @@ import 'dart:async'; -import 'dart:io'; import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:hive/hive.dart'; import 'package:just_audio/just_audio.dart'; import 'package:musify/API/musify.dart'; import 'package:musify/helper/mediaitem.dart'; import 'package:musify/main.dart'; import 'package:musify/services/audio_handler.dart'; -import 'package:musify/services/ext_storage.dart'; -import 'package:musify/style/appColors.dart'; +import 'package:musify/services/data_manager.dart'; import 'package:musify/ui/player.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:youtube_explode_dart/youtube_explode_dart.dart'; final _equalizer = AndroidEqualizer(); final _loudnessEnhancer = AndroidLoudnessEnhancer(); final _audioHandler = getIt(); -AudioPlayer? audioPlayer = AudioPlayer( +AudioPlayer audioPlayer = AudioPlayer( audioPipeline: AudioPipeline( androidAudioEffects: [ _loudnessEnhancer, @@ -32,16 +27,23 @@ AudioPlayer? audioPlayer = AudioPlayer( final durationNotifier = ValueNotifier(Duration.zero); final shuffleNotifier = ValueNotifier(false); final repeatNotifier = ValueNotifier(false); +final playerState = ValueNotifier(audioPlayer.playerState); final prefferedFileExtension = ValueNotifier( - Hive.box('settings').get('audioFileType', defaultValue: 'mp3') as String); -final playNextSongAutomatically = ValueNotifier(false); + Hive.box('settings').get('audioFileType', defaultValue: 'mp3') as String, +); +final playNextSongAutomatically = ValueNotifier( + Hive.box('settings').get('playNextSongAutomatically', defaultValue: false), +); +final sponsorBlockSupport = ValueNotifier( + Hive.box('settings').get('sponsorBlockSupport', defaultValue: false), +); bool get hasNext => activePlaylist.isEmpty - ? audioPlayer!.hasNext + ? audioPlayer.hasNext : id + 1 <= activePlaylist.length; bool get hasPrevious => - activePlaylist.isEmpty ? audioPlayer!.hasPrevious : id - 1 >= 0; + activePlaylist.isEmpty ? audioPlayer.hasPrevious : id - 1 >= 0; String get durationText => duration != null ? duration.toString().split('.').first : ''; @@ -51,104 +53,73 @@ String get positionText => bool isMuted = false; -Future downloadSong(dynamic song) async { - PermissionStatus status = await Permission.storage.status; - if (status.isDenied) { - await [ - Permission.storage, - Permission.accessMediaLocation, - Permission.mediaLibrary, - ].request(); - status = await Permission.storage.status; - if (status.isPermanentlyDenied) { - await openAppSettings(); - } - } - final filename = song['title'] - .replaceAll(r'\', '') - .replaceAll('/', '') - .replaceAll('*', '') - .replaceAll('?', '') - .replaceAll('"', '') - .replaceAll('<', '') - .replaceAll('>', '') - .replaceAll('|', '') + - '.' + - prefferedFileExtension.value; - - String filepath = ''; - final String? dlPath = - await ExtStorageProvider.getExtStorage(dirName: 'Musify'); - try { - await File('${dlPath!}/$filename') - .create(recursive: true) - .then((value) => filepath = value.path); - } catch (e) { - await [Permission.manageExternalStorage].request(); - await File('${dlPath!}/$filename') - .create(recursive: true) - .then((value) => filepath = value.path); - } - await Fluttertoast.showToast( - msg: 'Download Started!', - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - backgroundColor: accent, - textColor: accent != const Color(0xFFFFFFFF) ? Colors.white : Colors.black, - fontSize: 14, - ); - final audioStream = await getSongStream(song['ytid'].toString()); - final File file = File(filepath); - final fileStream = file.openWrite(); - await yt.videos.streamsClient.get(audioStream as StreamInfo).pipe(fileStream); - await fileStream.flush(); - await fileStream.close(); - - debugPrint('Done'); - await Fluttertoast.showToast( - msg: 'Download Completed!', - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - backgroundColor: accent, - textColor: accent != const Color(0xFFFFFFFF) ? Colors.white : Colors.black, - fontSize: 14, - ); -} - Future playSong(Map song) async { if (song['ytid'].length == 0) { await MyAudioHandler() .addQueueItem(mapToMediaItem(song, song['songUrl'].toString())); } else { - final songUrl = await getSongUrl(song['ytid']); - await MyAudioHandler().addQueueItem(mapToMediaItem(song, songUrl)); + final songUrl = await getSong(song['ytid'], true); + + if (sponsorBlockSupport.value) { + final segments = await getSkipSegments(song['ytid']); + if (segments.isNotEmpty) { + if (segments.length == 1) { + await MyAudioHandler().addQueueItem( + mapToMediaItem(song, songUrl), + Duration(seconds: segments[0]['end']!), + ); + } else { + await MyAudioHandler().addQueueItem( + mapToMediaItem(song, songUrl), + Duration(seconds: segments[0]['end']!), + Duration(seconds: segments[1]['start']!), + ); + } + } else { + await MyAudioHandler().addQueueItem(mapToMediaItem(song, songUrl)); + } + } else { + await MyAudioHandler().addQueueItem(mapToMediaItem(song, songUrl)); + } } - await play(); + play(); } Future changeShuffleStatus() async { if (shuffleNotifier.value == true) { - await audioPlayer?.setShuffleModeEnabled(false); + await audioPlayer.setShuffleModeEnabled(false); } else { - await audioPlayer?.setShuffleModeEnabled(true); + await audioPlayer.setShuffleModeEnabled(true); } } void changeAutoPlayNextStatus() { if (playNextSongAutomatically.value == false) { playNextSongAutomatically.value = true; + addOrUpdateData('settings', 'playNextSongAutomatically', true); } else { playNextSongAutomatically.value = false; + addOrUpdateData('settings', 'playNextSongAutomatically', false); } } Future changeLoopStatus() async { if (repeatNotifier.value == false) { repeatNotifier.value = true; - await audioPlayer?.setLoopMode(LoopMode.one); + await audioPlayer.setLoopMode(LoopMode.one); } else { repeatNotifier.value = false; - await audioPlayer?.setLoopMode(LoopMode.off); + await audioPlayer.setLoopMode(LoopMode.off); + } +} + +void changeSponsorBlockStatus() { + if (sponsorBlockSupport.value == false) { + sponsorBlockSupport.value = true; + addOrUpdateData('settings', 'sponsorBlockSupport', true); + } else { + sponsorBlockSupport.value = false; + addOrUpdateData('settings', 'sponsorBlockSupport', false); } } @@ -157,38 +128,20 @@ Future enableBooster() async { await _loudnessEnhancer.setTargetGain(1); } -Future? play() => _audioHandler.play(); +void play() => _audioHandler.play(); -Future? pause() => _audioHandler.pause(); +void pause() => _audioHandler.pause(); -Future? stop() => _audioHandler.stop(); +void stop() => _audioHandler.stop(); -Future playNext() async { - if (activePlaylist.isEmpty) { - await _audioHandler.skipToNext(); - } else { - if (id + 1 <= activePlaylist.length) { - await playSong(activePlaylist[id + 1]); - id = id + 1; - } - } -} +void playNext() => _audioHandler.skipToNext(); -Future playPrevious() async { - if (activePlaylist.isEmpty) { - await _audioHandler.skipToPrevious(); - } else { - if (id - 1 >= 0) { - await playSong(activePlaylist[id - 1]); - id = id - 1; - } - } -} +void playPrevious() => _audioHandler.skipToPrevious(); Future mute(bool muted) async { if (muted) { - await audioPlayer?.setVolume(0); + await audioPlayer.setVolume(0); } else { - await audioPlayer?.setVolume(1); + await audioPlayer.setVolume(1); } } diff --git a/lib/services/data_manager.dart b/lib/services/data_manager.dart index ae9250957..5a10cee6c 100644 --- a/lib/services/data_manager.dart +++ b/lib/services/data_manager.dart @@ -37,20 +37,19 @@ void clearCache() async { } Future backupData() async { - final List boxNames = ['user', 'settings']; - final String? dlPath = - await ExtStorageProvider.getExtStorage(dirName: 'Musify/Data'); + final boxNames = ['user', 'settings']; + final dlPath = await ExtStorageProvider.getExtStorage(dirName: 'Musify/Data'); - for (int i = 0; i < boxNames.length; i++) { - await Hive.openBox(boxNames[i].toString()); + for (var i = 0; i < boxNames.length; i++) { + await Hive.openBox(boxNames[i]); try { - await File(Hive.box(boxNames[i].toString()).path!) + await File(Hive.box(boxNames[i]).path!) .copy('$dlPath/${boxNames[i]}Data.hive'); } catch (e) { await [ Permission.manageExternalStorage, ].request(); - await File(Hive.box(boxNames[i].toString()).path!) + await File(Hive.box(boxNames[i]).path!) .copy('$dlPath/${boxNames[i]}Data.hive'); return 'Permissions problem, if you already gave requested permission, Backup data again!'; } @@ -59,14 +58,14 @@ Future backupData() async { } Future restoreData() async { - final List boxNames = ['user', 'settings']; - final String? uplPath = + final boxNames = ['user', 'settings']; + final uplPath = await ExtStorageProvider.getExtStorage(dirName: 'Musify/Data'); - for (int i = 0; i < boxNames.length; i++) { - await Hive.openBox(boxNames[i].toString()); + for (var i = 0; i < boxNames.length; i++) { + await Hive.openBox(boxNames[i]); try { - final Box box = await Hive.openBox(boxNames[i].toString()); + final box = await Hive.openBox(boxNames[i]); final boxPath = box.path; await File('${uplPath!}/${boxNames[i]}Data.hive').copy(boxPath!); } catch (e) { diff --git a/lib/services/download_manager.dart b/lib/services/download_manager.dart new file mode 100644 index 000000000..66a759107 --- /dev/null +++ b/lib/services/download_manager.dart @@ -0,0 +1,84 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:musify/API/musify.dart'; +import 'package:musify/services/audio_manager.dart'; +import 'package:musify/services/ext_storage.dart'; +import 'package:musify/style/appColors.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; + +Future downloadSong(dynamic song) async { + var status = await Permission.storage.status; + if (status.isDenied) { + await [ + Permission.storage, + Permission.accessMediaLocation, + Permission.mediaLibrary, + ].request(); + status = await Permission.storage.status; + if (status.isPermanentlyDenied) { + await openAppSettings(); + } + } + + await Fluttertoast.showToast( + msg: 'Download Started!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + backgroundColor: accent, + textColor: accent != const Color(0xFFFFFFFF) ? Colors.white : Colors.black, + fontSize: 14, + ); + + final filename = song['title'] + .replaceAll(r'\', '') + .replaceAll('/', '') + .replaceAll('*', '') + .replaceAll('?', '') + .replaceAll('"', '') + .replaceAll('<', '') + .replaceAll('>', '') + .replaceAll('|', '') + + '.' + + prefferedFileExtension.value; + + var filepath = ''; + final dlPath = await ExtStorageProvider.getExtStorage(dirName: 'Musify'); + try { + await File('${dlPath!}/$filename') + .create(recursive: true) + .then((value) => filepath = value.path); + await downloadFileFromYT(filename, filepath, dlPath, song); + } catch (e) { + await [Permission.manageExternalStorage].request(); + await File('${dlPath!}/$filename') + .create(recursive: true) + .then((value) => filepath = value.path); + await downloadFileFromYT(filename, filepath, dlPath, song); + } + + await Fluttertoast.showToast( + msg: 'Download Completed!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + backgroundColor: accent, + textColor: accent != const Color(0xFFFFFFFF) ? Colors.white : Colors.black, + fontSize: 14, + ); +} + +Future downloadFileFromYT( + String filename, + String filepath, + String dlPath, + dynamic song, +) async { + final audioStream = await getSong(song['ytid'].toString(), false); + final file = File(filepath); + final fileStream = file.openWrite(); + await yt.videos.streamsClient.get(audioStream as StreamInfo).pipe(fileStream); + await fileStream.flush(); + await fileStream.close(); +} diff --git a/lib/services/ext_storage.dart b/lib/services/ext_storage.dart index eea1eb16f..8ea5571e3 100644 --- a/lib/services/ext_storage.dart +++ b/lib/services/ext_storage.dart @@ -29,7 +29,7 @@ class ExtStorageProvider { directory = await getExternalStorageDirectory(); // getting main path - final String newPath = directory!.path + final newPath = directory!.path .replaceFirst('Android/data/com.gokadzev.musify/files', dirName); directory = Directory(newPath); diff --git a/lib/ui/aboutPage.dart b/lib/ui/aboutPage.dart index 212b8e7fa..b0a6fb534 100644 --- a/lib/ui/aboutPage.dart +++ b/lib/ui/aboutPage.dart @@ -4,7 +4,7 @@ import 'package:musify/helper/version.dart'; import 'package:musify/style/appColors.dart'; class AboutPage extends StatelessWidget { - const AboutPage({Key? key}) : super(key: key); + const AboutPage({super.key}); @override Widget build(BuildContext context) { @@ -35,7 +35,7 @@ class AboutPage extends StatelessWidget { } class AboutCards extends StatelessWidget { - const AboutCards({Key? key}) : super(key: key); + const AboutCards({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/homePage.dart b/lib/ui/homePage.dart index 9c2b3f20d..a6945c201 100644 --- a/lib/ui/homePage.dart +++ b/lib/ui/homePage.dart @@ -102,17 +102,19 @@ class _HomePageState extends State { if (data.hasError) { // print(data.error); return Center( - child: Text( - 'Error!', - style: TextStyle(color: accent, fontSize: 18), - )); + child: Text( + 'Error!', + style: TextStyle(color: accent, fontSize: 18), + ), + ); } if (!data.hasData) { return Center( - child: Text( - 'Nothing Found!', - style: TextStyle(color: accent, fontSize: 18), - )); + child: Text( + 'Nothing Found!', + style: TextStyle(color: accent, fontSize: 18), + ), + ); } return Wrap( children: [ @@ -171,7 +173,7 @@ class CubeContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final Size size = MediaQuery.of(context).size; + final size = MediaQuery.of(context).size; return DelayedDisplay( delay: const Duration(milliseconds: 200), fadingDuration: const Duration(milliseconds: 400), diff --git a/lib/ui/localSongsPage.dart b/lib/ui/localSongsPage.dart index 089f93e8e..55249c224 100644 --- a/lib/ui/localSongsPage.dart +++ b/lib/ui/localSongsPage.dart @@ -56,10 +56,10 @@ class _LocalSongsPageState extends State { Future fetch() async { final list = []; - final int _count = localSongs.length; + final _count = localSongs.length; final n = min(_itemsPerPage, _count - _currentPage * _itemsPerPage); await Future.delayed(const Duration(seconds: 1), () { - for (int i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { list.add(localSongs[_currentLastLoadedId]); _currentLastLoadedId++; } @@ -165,9 +165,10 @@ class _LocalSongsPageState extends State { child: Text( AppLocalizations.of(context)!.playAll.toUpperCase(), style: TextStyle( - color: accent != const Color(0xFFFFFFFF) - ? Colors.white - : Colors.black), + color: accent != const Color(0xFFFFFFFF) + ? Colors.white + : Colors.black, + ), ), ), ], @@ -177,111 +178,115 @@ class _LocalSongsPageState extends State { ), const Padding(padding: EdgeInsets.only(top: 40)), FutureBuilder( - future: getLocalSongs(), - builder: (context, data) { - return data.hasData - ? ListView.builder( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - addAutomaticKeepAlives: - false, // may be problem with lazyload if it implemented - addRepaintBoundaries: false, - // Need to display a loading tile if more items are coming - itemCount: _hasMore - ? _songsList.length + 1 - : _songsList.length, - itemBuilder: (BuildContext context, int index) { - if (index >= _songsList.length) { - if (!_isLoading) { - _loadMore(); - } - return const Spinner(); + future: getLocalSongs(), + builder: (context, data) { + return data.hasData + ? ListView.builder( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + addAutomaticKeepAlives: + false, // may be problem with lazyload if it implemented + addRepaintBoundaries: false, + // Need to display a loading tile if more items are coming + itemCount: _hasMore + ? _songsList.length + 1 + : _songsList.length, + itemBuilder: (BuildContext context, int index) { + if (index >= _songsList.length) { + if (!_isLoading) { + _loadMore(); } + return const Spinner(); + } - final lsong = { - 'id': index, - 'ytid': '', - 'title': localSongs[index].displayName, - 'image': '', - 'lowResImage': '', - 'highResImage': '', - 'songUrl': localSongs[index].data, - 'album': '', - 'type': 'song', - 'localSongId': localSongs[index].id, - 'more_info': { - 'primary_artists': '', - 'singers': '', - } - }; + final lsong = { + 'id': index, + 'ytid': '', + 'title': localSongs[index].displayName, + 'image': '', + 'lowResImage': '', + 'highResImage': '', + 'songUrl': localSongs[index].data, + 'album': '', + 'type': 'song', + 'localSongId': localSongs[index].id, + 'more_info': { + 'primary_artists': '', + 'singers': '', + } + }; - return Container( - padding: const EdgeInsets.only( - left: 12, right: 12, bottom: 15), - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () { - playSong(lsong); - }, - splashColor: accent, - hoverColor: accent, - focusColor: accent, - highlightColor: accent, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - QueryArtworkWidget( - id: lsong['localSongId'] as int, - type: ArtworkType.AUDIO, - artworkWidth: 70, - artworkHeight: 70, - artworkFit: BoxFit.cover, - artworkBorder: - BorderRadius.circular(8), - nullArtworkWidget: DecoratedBox( - decoration: BoxDecoration( - color: accent, - borderRadius: - BorderRadius.circular(8)), - child: Icon( - MdiIcons.musicNoteOutline, - size: 70, - color: accent != - const Color(0xFFFFFFFF) - ? Colors.white - : Colors.black, - ), - ), - keepOldArtwork: true, + return Container( + padding: const EdgeInsets.only( + left: 12, + right: 12, + bottom: 15, + ), + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () { + playSong(lsong); + }, + splashColor: accent, + hoverColor: accent, + focusColor: accent, + highlightColor: accent, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + QueryArtworkWidget( + id: lsong['localSongId'] as int, + type: ArtworkType.AUDIO, + artworkWidth: 70, + artworkHeight: 70, + artworkFit: BoxFit.cover, + artworkBorder: BorderRadius.circular(8), + nullArtworkWidget: DecoratedBox( + decoration: BoxDecoration( + color: accent, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + MdiIcons.musicNoteOutline, + size: 70, + color: accent != const Color(0xFFFFFFFF) + ? Colors.white + : Colors.black, + ), + ), + keepOldArtwork: true, + ), + Flexible( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only( + left: 15, ), - Flexible( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Container( - alignment: - Alignment.centerLeft, - padding: - const EdgeInsets.only( - left: 15), - child: Text( - overflow: - TextOverflow.ellipsis, - lsong['title'].toString(), - style: TextStyle( - color: accent), - ), - ), - ], + child: Text( + overflow: TextOverflow.ellipsis, + lsong['title'].toString(), + style: TextStyle( + color: accent, ), ), - ]))); - }, - ) - : const Spinner(); - }) + ), + ], + ), + ), + ], + ), + ), + ); + }, + ) + : const Spinner(); + }, + ) ], ), ), diff --git a/lib/ui/player.dart b/lib/ui/player.dart index 023108bb0..83a47409c 100644 --- a/lib/ui/player.dart +++ b/lib/ui/player.dart @@ -10,6 +10,7 @@ import 'package:musify/API/musify.dart'; import 'package:musify/customWidgets/spinner.dart'; import 'package:musify/helper/mediaitem.dart'; import 'package:musify/services/audio_manager.dart'; +import 'package:musify/services/download_manager.dart'; import 'package:musify/style/appColors.dart'; import 'package:on_audio_query/on_audio_query.dart'; @@ -36,9 +37,9 @@ class AudioAppState extends State { void initState() { super.initState(); - positionSubscription = audioPlayer?.positionStream + positionSubscription = audioPlayer.positionStream .listen((p) => {if (mounted) setState(() => position = p)}); - durationSubscription = audioPlayer?.durationStream.listen( + durationSubscription = audioPlayer.durationStream.listen( (d) => { if (mounted) {setState(() => duration = d)} }, @@ -83,7 +84,7 @@ class AudioAppState extends State { child: Padding( padding: EdgeInsets.only(top: size.height * 0.012), child: StreamBuilder( - stream: audioPlayer!.sequenceStateStream, + stream: audioPlayer.sequenceStateStream, builder: (context, snapshot) { final state = snapshot.data; if (state?.sequence.isEmpty ?? true) { @@ -192,7 +193,9 @@ class AudioAppState extends State { ), Padding( padding: EdgeInsets.only( - top: size.height * 0.04, bottom: size.height * 0.01), + top: size.height * 0.04, + bottom: size.height * 0.01, + ), child: Column( children: [ Text( @@ -267,7 +270,7 @@ class AudioAppState extends State { value: position?.inMilliseconds.toDouble() ?? 0.0, onChanged: (double? value) { setState(() { - audioPlayer!.seek( + audioPlayer.seek( Duration( milliseconds: value!.round(), ), @@ -288,19 +291,36 @@ class AudioAppState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (metadata.extras['ytid'].toString().isNotEmpty) - IconButton( - padding: EdgeInsets.zero, - icon: const Icon( - MdiIcons.download, - color: Colors.white, - ), - iconSize: size.width * 0.056, - splashColor: Colors.transparent, - onPressed: () { - downloadSong( - mediaItemToMap(metadata as MediaItem), - ); - }, + Column( + children: [ + IconButton( + padding: EdgeInsets.zero, + icon: const Icon( + MdiIcons.download, + color: Colors.white, + ), + iconSize: size.width * 0.056, + splashColor: Colors.transparent, + onPressed: () { + downloadSong( + mediaItemToMap(metadata as MediaItem), + ); + }, + ), + IconButton( + padding: EdgeInsets.zero, + icon: Icon( + sponsorBlockSupport.value + ? MdiIcons.playCircle + : MdiIcons.playCircleOutline, + color: Colors.white, + ), + iconSize: size.width * 0.056, + splashColor: Colors.transparent, + onPressed: () => + setState(changeSponsorBlockStatus), + ), + ], ), IconButton( padding: EdgeInsets.zero, @@ -329,20 +349,45 @@ class AudioAppState extends State { color: accent, borderRadius: BorderRadius.circular(100), ), - child: StreamBuilder( - stream: audioPlayer!.playerStateStream, - builder: (context, snapshot) { - if (snapshot.hasData) { - final playerState = snapshot.data; - return _playerControllers(playerState!, size); - } else { + child: ValueListenableBuilder( + valueListenable: playerState, + builder: (_, value, __) { + if (value.processingState == + ProcessingState.loading || + value.processingState == + ProcessingState.buffering) { return Container( margin: const EdgeInsets.all(8), width: size.width * 0.08, height: size.width * 0.08, child: const CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( - Color.fromARGB(255, 0, 0, 0)), + Color.fromARGB(255, 0, 0, 0), + ), + ), + ); + } else if (value.playing != true) { + return IconButton( + icon: const Icon(MdiIcons.play), + iconSize: size.width * 0.1, + onPressed: play, + splashColor: Colors.transparent, + ); + } else if (value.processingState != + ProcessingState.completed) { + return IconButton( + icon: const Icon(MdiIcons.pause), + iconSize: size.width * 0.1, + onPressed: pause, + splashColor: Colors.transparent, + ); + } else { + return IconButton( + icon: const Icon(MdiIcons.replay), + iconSize: size.width * 0.056, + onPressed: () => audioPlayer.seek( + Duration.zero, + index: audioPlayer.effectiveIndices!.first, ), ); } @@ -373,21 +418,6 @@ class AudioAppState extends State { if (metadata.extras['ytid'].toString().isNotEmpty) Column( children: [ - ValueListenableBuilder( - valueListenable: playNextSongAutomatically, - builder: (_, value, __) { - return IconButton( - padding: EdgeInsets.zero, - icon: Icon( - MdiIcons.chevronRight, - color: value ? accent : Colors.white, - ), - iconSize: size.width * 0.056, - splashColor: Colors.transparent, - onPressed: changeAutoPlayNextStatus, - ); - }, - ), ValueListenableBuilder( valueListenable: songLikeStatus, builder: (_, value, __) { @@ -416,6 +446,23 @@ class AudioAppState extends State { } }, ), + ValueListenableBuilder( + valueListenable: playNextSongAutomatically, + builder: (_, value, __) { + return IconButton( + padding: EdgeInsets.zero, + icon: Icon( + value + ? MdiIcons.skipNextCircle + : MdiIcons.skipNextCircleOutline, + color: value ? accent : Colors.white, + ), + iconSize: size.width * 0.056, + splashColor: Colors.transparent, + onPressed: changeAutoPlayNextStatus, + ); + }, + ), ], ), ], @@ -511,7 +558,8 @@ class AudioAppState extends State { ); } else if (value == 'null') { return const SizedBox( - child: Spinner()); + child: Spinner(), + ); } else { return Padding( padding: const EdgeInsets.only( @@ -575,41 +623,4 @@ class AudioAppState extends State { ) ], ); - - Widget _playerControllers(PlayerState playerState, Size size) { - final processingState = playerState.processingState; - if (processingState == ProcessingState.loading || - processingState == ProcessingState.buffering) { - return Container( - margin: const EdgeInsets.all(8), - width: size.width * 0.08, - height: size.width * 0.08, - child: const CircularProgressIndicator( - valueColor: - AlwaysStoppedAnimation(Color.fromARGB(255, 0, 0, 0)), - ), - ); - } else if (audioPlayer!.playing != true) { - return IconButton( - icon: const Icon(MdiIcons.play), - iconSize: size.width * 0.1, - onPressed: play, - splashColor: Colors.transparent, - ); - } else if (processingState != ProcessingState.completed) { - return IconButton( - icon: const Icon(MdiIcons.pause), - iconSize: size.width * 0.1, - onPressed: pause, - splashColor: Colors.transparent, - ); - } else { - return IconButton( - icon: const Icon(MdiIcons.replay), - iconSize: 64.0, - onPressed: () => audioPlayer! - .seek(Duration.zero, index: audioPlayer!.effectiveIndices!.first), - ); - } - } } diff --git a/lib/ui/playlistPage.dart b/lib/ui/playlistPage.dart index ce4b415a2..ddd7700b3 100644 --- a/lib/ui/playlistPage.dart +++ b/lib/ui/playlistPage.dart @@ -11,7 +11,7 @@ import 'package:musify/customWidgets/spinner.dart'; import 'package:musify/style/appColors.dart'; class PlaylistPage extends StatefulWidget { - const PlaylistPage({Key? key, required this.playlist}) : super(key: key); + const PlaylistPage({super.key, required this.playlist}); final dynamic playlist; @override @@ -60,10 +60,10 @@ class _PlaylistPageState extends State { Future fetch() async { final list = []; - final int _count = widget.playlist['list'].length as int; + final _count = widget.playlist['list'].length as int; final n = min(_itemsPerPage, _count - _currentPage * _itemsPerPage); await Future.delayed(const Duration(seconds: 1), () { - for (int i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { list.add(widget.playlist['list'][_currentLastLoadedId]); _currentLastLoadedId++; } @@ -198,9 +198,10 @@ class _PlaylistPageState extends State { child: Text( AppLocalizations.of(context)!.playAll.toUpperCase(), style: TextStyle( - color: accent != const Color(0xFFFFFFFF) - ? Colors.white - : Colors.black), + color: accent != const Color(0xFFFFFFFF) + ? Colors.white + : Colors.black, + ), ), ), ], diff --git a/lib/ui/playlistsPage.dart b/lib/ui/playlistsPage.dart index 79e0eeac8..a02770efa 100644 --- a/lib/ui/playlistsPage.dart +++ b/lib/ui/playlistsPage.dart @@ -48,7 +48,11 @@ class _PlaylistsPageState extends State { children: [ Padding( padding: const EdgeInsets.only( - top: 12, bottom: 20, left: 12, right: 12), + top: 12, + bottom: 20, + left: 12, + right: 12, + ), child: TextField( onSubmitted: (String value) { search(); @@ -190,12 +194,12 @@ class _PlaylistsPageState extends State { class GetPlaylist extends StatelessWidget { const GetPlaylist({ - Key? key, + super.key, required this.index, required this.image, required this.title, required this.id, - }) : super(key: key); + }); final int index; final dynamic image; final String title; @@ -203,7 +207,7 @@ class GetPlaylist extends StatelessWidget { @override Widget build(BuildContext context) { - final Size size = MediaQuery.of(context).size; + final size = MediaQuery.of(context).size; return DelayedDisplay( delay: const Duration(milliseconds: 200), fadingDuration: const Duration(milliseconds: 400), diff --git a/lib/ui/rootPage.dart b/lib/ui/rootPage.dart index 857162ca6..c9146995a 100644 --- a/lib/ui/rootPage.dart +++ b/lib/ui/rootPage.dart @@ -4,7 +4,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:just_audio/just_audio.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:musify/API/musify.dart'; import 'package:musify/customWidgets/custom_animated_bottom_bar.dart'; import 'package:musify/helper/version.dart'; import 'package:musify/services/audio_manager.dart'; @@ -30,7 +29,6 @@ class AppState extends State { @override void initState() { super.initState(); - initAudioPlayer(); checkAppUpdates().then( (value) => { if (value == true) @@ -50,23 +48,6 @@ class AppState extends State { ); } - void initAudioPlayer() { - audioPlayer!.processingStateStream.listen((state) async { - if (state == ProcessingState.completed) { - await pause(); - await audioPlayer!.seek(Duration.zero); - if (hasNext) { - if (activePlaylist.isEmpty && playNextSongAutomatically.value) { - await playSong(await getRandomSong()); - } else { - await playSong(activePlaylist[id + 1]); - id = id + 1; - } - } - } - }); - } - @override Widget build(BuildContext context) { final pages = [ @@ -88,7 +69,7 @@ class AppState extends State { } Widget getFooter() { - final List items = [ + final items = [ BottomNavBarItem( icon: const Icon(MdiIcons.homeOutline), activeIcon: const Icon(MdiIcons.home), @@ -130,7 +111,7 @@ class AppState extends State { mainAxisSize: MainAxisSize.min, children: [ StreamBuilder( - stream: audioPlayer!.sequenceStateStream, + stream: audioPlayer.sequenceStateStream, builder: (context, snapshot) { final state = snapshot.data; if (state?.sequence.isEmpty ?? true) { @@ -249,14 +230,13 @@ class AppState extends State { const Spacer(), Padding( padding: const EdgeInsets.only(right: 8), - child: StreamBuilder( - stream: audioPlayer!.playerStateStream, - builder: (context, snapshot) { - if (snapshot.hasData) { - final playerState = snapshot.data; - return _playerControllers( - playerState!, MediaQuery.of(context).size); - } else { + child: ValueListenableBuilder( + valueListenable: playerState, + builder: (_, value, __) { + if (value.processingState == + ProcessingState.loading || + value.processingState == + ProcessingState.buffering) { return Container( margin: const EdgeInsets.all(8), width: MediaQuery.of(context).size.width * 0.08, @@ -267,6 +247,31 @@ class AppState extends State { AlwaysStoppedAnimation(accent), ), ); + } else if (value.playing != true) { + return IconButton( + icon: Icon(MdiIcons.play, color: accent), + iconSize: 45, + onPressed: play, + splashColor: Colors.transparent, + ); + } else if (value.processingState != + ProcessingState.completed) { + return IconButton( + icon: Icon(MdiIcons.pause, color: accent), + iconSize: 45, + onPressed: pause, + splashColor: Colors.transparent, + ); + } else { + return IconButton( + icon: Icon(MdiIcons.replay, color: accent), + iconSize: 45, + onPressed: () => audioPlayer.seek( + Duration.zero, + index: audioPlayer.effectiveIndices!.first, + ), + splashColor: Colors.transparent, + ); } }, ), @@ -295,41 +300,4 @@ class AppState extends State { ), ); } - - Widget _playerControllers(PlayerState playerState, Size size) { - final processingState = playerState.processingState; - if (processingState == ProcessingState.loading || - processingState == ProcessingState.buffering) { - return Container( - margin: const EdgeInsets.all(8), - width: size.width * 0.08, - height: size.width * 0.08, - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(accent), - ), - ); - } else if (audioPlayer!.playing != true) { - return IconButton( - icon: Icon(MdiIcons.play, color: accent), - iconSize: 45, - onPressed: play, - splashColor: Colors.transparent, - ); - } else if (processingState != ProcessingState.completed) { - return IconButton( - icon: Icon(MdiIcons.pause, color: accent), - iconSize: 45, - onPressed: pause, - splashColor: Colors.transparent, - ); - } else { - return IconButton( - icon: Icon(MdiIcons.replay, color: accent), - iconSize: 45, - onPressed: () => audioPlayer! - .seek(Duration.zero, index: audioPlayer!.effectiveIndices!.first), - splashColor: Colors.transparent, - ); - } - } } diff --git a/lib/ui/searchPage.dart b/lib/ui/searchPage.dart index 9395dcdb1..2285fc65b 100644 --- a/lib/ui/searchPage.dart +++ b/lib/ui/searchPage.dart @@ -20,7 +20,7 @@ class _SearchPageState extends State { final FocusNode _inputNode = FocusNode(); Future search() async { - final String searchQuery = _searchBar.text; + final searchQuery = _searchBar.text; if (searchQuery.isEmpty) { setState(() { searchedList = []; @@ -88,30 +88,36 @@ class _SearchPageState extends State { borderSide: BorderSide(color: accent), ), suffixIcon: ValueListenableBuilder( - valueListenable: _fetchingSongs, - builder: (_, value, __) { - if (value == true) { - return IconButton( - icon: const SizedBox( - height: 18, width: 18, child: Spinner()), - color: accent, - onPressed: () { - search(); - FocusManager.instance.primaryFocus?.unfocus(); - }); - } else { - return IconButton( - icon: Icon( - Icons.search, - color: accent, - ), - color: accent, - onPressed: () { - search(); - FocusManager.instance.primaryFocus?.unfocus(); - }); - } - }), + valueListenable: _fetchingSongs, + builder: (_, value, __) { + if (value == true) { + return IconButton( + icon: const SizedBox( + height: 18, + width: 18, + child: Spinner(), + ), + color: accent, + onPressed: () { + search(); + FocusManager.instance.primaryFocus?.unfocus(); + }, + ); + } else { + return IconButton( + icon: Icon( + Icons.search, + color: accent, + ), + color: accent, + onPressed: () { + search(); + FocusManager.instance.primaryFocus?.unfocus(); + }, + ); + } + }, + ), border: InputBorder.none, hintText: '${AppLocalizations.of(context)!.search}...', hintStyle: TextStyle( diff --git a/lib/ui/settingsPage.dart b/lib/ui/settingsPage.dart index 80c7fd95b..ec26dd368 100644 --- a/lib/ui/settingsPage.dart +++ b/lib/ui/settingsPage.dart @@ -53,7 +53,7 @@ class SettingsCards extends StatelessWidget { backgroundColor: Colors.transparent, context: context, builder: (BuildContext context) { - final List colors = [ + final colors = [ 0xFFFFFFFF, 0xFFFFCDD2, 0xFFF8BBD0, @@ -124,7 +124,9 @@ class SettingsCards extends StatelessWidget { colors[index], ); MyApp.setAccentColor( - context, Color(colors[index])); + context, + Color(colors[index]), + ); Fluttertoast.showToast( backgroundColor: accent, textColor: @@ -172,7 +174,7 @@ class SettingsCards extends StatelessWidget { backgroundColor: Colors.transparent, context: context, builder: (BuildContext context) { - final Map codes = { + final codes = { 'English': 'en', 'Georgian': 'ka', 'Chinese': 'zh', @@ -187,7 +189,7 @@ class SettingsCards extends StatelessWidget { 'Ukrainian': 'uk', }; - final List availableLanguages = [ + final availableLanguages = [ 'English', 'Georgian', 'Chinese', diff --git a/lib/ui/userLikedSongsPage.dart b/lib/ui/userLikedSongsPage.dart index 0bd80b1f6..935e056e0 100644 --- a/lib/ui/userLikedSongsPage.dart +++ b/lib/ui/userLikedSongsPage.dart @@ -6,7 +6,7 @@ import 'package:musify/customWidgets/song_bar.dart'; import 'package:musify/style/appColors.dart'; class UserLikedSongs extends StatefulWidget { - const UserLikedSongs({Key? key}) : super(key: key); + const UserLikedSongs({super.key}); @override State createState() => _UserLikedSongsState(); @@ -119,9 +119,10 @@ class _UserLikedSongsState extends State { child: Text( AppLocalizations.of(context)!.playAll.toUpperCase(), style: TextStyle( - color: accent != const Color(0xFFFFFFFF) - ? Colors.white - : Colors.black), + color: accent != const Color(0xFFFFFFFF) + ? Colors.white + : Colors.black, + ), ), ), ], diff --git a/lib/ui/userPlaylistsPage.dart b/lib/ui/userPlaylistsPage.dart index 2be83876a..6f5da1c4b 100644 --- a/lib/ui/userPlaylistsPage.dart +++ b/lib/ui/userPlaylistsPage.dart @@ -6,7 +6,7 @@ import 'package:musify/style/appColors.dart'; import 'package:musify/ui/playlistsPage.dart'; class UserPlaylistsPage extends StatefulWidget { - const UserPlaylistsPage({Key? key}) : super(key: key); + const UserPlaylistsPage({super.key}); @override State createState() => _UserPlaylistsPageState(); @@ -40,7 +40,7 @@ class _UserPlaylistsPageState extends State { showDialog( context: context, builder: (BuildContext context) { - String id = ''; + var id = ''; return AlertDialog( backgroundColor: accent, content: Stack( diff --git a/pubspec.lock b/pubspec.lock index b3ca7c644..d7b3ee162 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,6 +92,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" clock: dependency: transitive description: @@ -140,7 +154,7 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.2" + version: "6.1.4" flutter: dependency: "direct main" description: flutter @@ -166,14 +180,14 @@ packages: name: flutter_downloader url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.3" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.3" + version: "0.10.0" flutter_localizations: dependency: "direct main" description: flutter @@ -244,7 +258,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.4" + version: "0.13.5" http_parser: dependency: transitive description: @@ -377,7 +391,7 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.4.3" + version: "1.4.3+1" package_info_plus_linux: dependency: transitive description: @@ -433,7 +447,7 @@ packages: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.17" + version: "2.0.19" path_provider_ios: dependency: transitive description: @@ -468,7 +482,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" pedantic: dependency: transitive description: @@ -564,7 +578,7 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.3+1" sqflite_common: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d1f239ce9..c03fa93db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,25 +32,25 @@ dependencies: cached_network_image: ^3.2.1 flutter: sdk: flutter - flutter_downloader: ^1.8.1 + flutter_downloader: ^1.8.3 flutter_localizations: sdk: flutter fluttertoast: ^8.0.9 get_it: ^7.2.0 hive: ^2.2.3 hive_flutter: ^1.1.0 - http: ^0.13.4 + http: ^0.13.5 intl: ^0.17.0 just_audio: ^0.9.28 material_design_icons_flutter: ^5.0.6996 on_audio_query: ^2.6.1 - package_info_plus: ^1.4.3 + package_info_plus: ^1.4.3+1 path_provider: ^2.0.11 permission_handler: ^10.0.0 youtube_explode_dart: ^1.12.0 dev_dependencies: - flutter_launcher_icons: ^0.9.3 + flutter_launcher_icons: ^0.10.0 flutter_native_splash: ^2.2.7 flutter_test: sdk: flutter @@ -81,4 +81,22 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/db/playlists.db.json + + +flutter_native_splash: + image: assets/images/splash.png + color: "151515" + + + android_12: + image: assets/images/splash.png + color: "151515" + +flutter_icons: + android: "launcher_icon" + adaptive_icon_background: "#191919" + adaptive_icon_foreground: "assets/images/ic_launcher_foreground.png" + adaptive_icon_round: "assets/images/ic_launcher_round.png" + image_path: "assets/images/ic_launcher.png" + ios: true diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 71a3716b7..000000000 --- a/renovate.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "labels": ["dependencies"], - "extends": [ - "config:base" - ] -} \ No newline at end of file