diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..6d4f1531 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +## 1.0.1 + +* Some Network Exceptions handled +* Ignore battery enable option for notification issues +* Some Minor changes & bug fixes + +## 1.0.0 + +* initial release. \ No newline at end of file diff --git a/lib/services/audio_handler.dart b/lib/services/audio_handler.dart index 2ad69852..288a9b54 100644 --- a/lib/services/audio_handler.dart +++ b/lib/services/audio_handler.dart @@ -34,9 +34,10 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { final _player = AudioPlayer(); // ignore: prefer_typing_uninitialized_variables var currentIndex; - late String currentSongUrl; + late String? currentSongUrl; bool isPlayingUsingLockCachingSource = false; bool loopModeEnabled = false; + var networkErrorPause = false; final _playList = ConcatenatingAudioSource( children: [], @@ -112,13 +113,22 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { printERROR('Error message: ${e.message}'); } else { printERROR('An error occurred: $e'); + final box = Hive.box("songsUrlCache"); + if (box.containsKey(mediaItem.value!.id)) { + if (isExpired(url: box.get(mediaItem.value!.id)[1])) { + await customAction("playByIndex", {'index': currentIndex}); + return; + } + } + if (isPlayingUsingLockCachingSource && + e.toString().contains("Connection closed while receiving data")) { + Duration curPos = _player.position; + await _player.stop(); + await _player.seek(curPos, index: 0); + await _player.play(); + } await _player.stop(); - await customAction("setSourceNPlay", - {'mediaItem': queue.value[currentIndex], 'retry': true}); - // Duration curPos = _player.position; - // await _player.stop(); - // await _player.seek(curPos,index:0); - // await _player.play(); + networkErrorPause = true; } }); } @@ -219,8 +229,8 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { final currentQueue = queue.value; final currentSong = mediaItem.value; final itemIndex = currentQueue.indexOf(mediaItem_); - if(currentIndex>itemIndex){ - currentIndex -=1; + if (currentIndex > itemIndex) { + currentIndex -= 1; } currentQueue.remove(mediaItem_); queue.add(currentQueue); @@ -228,7 +238,24 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { } @override - Future play() => _player.play(); + Future play() async { + if (currentSongUrl == null) { + await customAction("playByIndex", {'index': currentIndex}); + return; + } + // Workaround for network error pause in case of PlayingUsingLockCachingSource + if (isPlayingUsingLockCachingSource && networkErrorPause) { + await _player.play(); + Future.delayed(const Duration(seconds: 2)).then((value) { + if (_player.playing) { + networkErrorPause = false; + } + }); + await _player.play(); + return; + } + await _player.play(); + } @override Future pause() => _player.pause(); @@ -250,7 +277,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { if (queue.value.length > currentIndex + 1) { _player.seek(Duration.zero); await customAction("playByIndex", {'index': currentIndex + 1}); - }else{ + } else { _player.seek(Duration.zero); _player.pause(); } @@ -283,8 +310,12 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { currentIndex = extras!['index']; final currentSong = queue.value[currentIndex]; mediaItem.add(currentSong); - currentSong.extras!['url'] = await checkNGetUrl(currentSong.id); - currentSongUrl = currentSong.extras!['url']; + final url = await checkNGetUrl(currentSong.id); + currentSongUrl = url; + if (url == null) { + return; + } + currentSong.extras!['url'] = url; playbackState.add(playbackState.value.copyWith(queueIndex: currentIndex)); await _playList.add(_createAudioSource(currentSong)); @@ -306,14 +337,16 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { } else if (name == 'setSourceNPlay') { await _playList.clear(); final currMed = (extras!['mediaItem'] as MediaItem); - if (!extras['retry']) { - currentIndex = 0; - mediaItem.add(currMed); - queue.add([currMed]); + currentIndex = 0; + mediaItem.add(currMed); + queue.add([currMed]); + final url = (await checkNGetUrl(currMed.id,useNewInstanceOfExplode: true)); + currentSongUrl = url; + if (url == null) { + return; } - currentSongUrl = - (await checkNGetUrl(currMed.id, generateNewUrl: extras['retry']))!; - currMed.extras!['url'] = currentSongUrl; + currentSongUrl = url; + currMed.extras!['url'] = url; await _playList.add(_createAudioSource(currMed)); await _player.play(); cacheNextSongUrl(); @@ -330,7 +363,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { mediaItem.add(currentItem); currentIndex = 0; cacheNextSongUrl(); - }else if(name == "reorderQueue"){ + } else if (name == "reorderQueue") { printINFO("Reorder queue"); final oldIndex = extras!['oldIndex']; int newIndex = extras['newIndex']; @@ -338,7 +371,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { if (oldIndex < newIndex) { newIndex--; } - + final currentQueue = queue.value; final currentItem = currentQueue[currentIndex]; final item = currentQueue.removeAt( @@ -359,15 +392,16 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { Future cacheNextSongUrl() async { if (queue.value.length > currentIndex + 1) { - await checkNGetUrl((queue.value[currentIndex+1]).id); + await checkNGetUrl((queue.value[currentIndex + 1]).id); printINFO("Next Song Url Cached"); } } +// Work around used [useNewInstanceOfExplode = false] to Fix Connection closed before full header was received issue Future checkNGetUrl(String songId, - {bool generateNewUrl = false}) async { + {bool useNewInstanceOfExplode = false}) async { final songsCacheBox = Hive.box("SongsCache"); - if (songsCacheBox.containsKey(songId) && !generateNewUrl) { + if (songsCacheBox.containsKey(songId)) { printINFO("Got Song from cachedbox ($songId)"); return "file://$_cacheDir/cachedSongs/$songId.mp3"; } else { @@ -375,20 +409,30 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin { final songsUrlCacheBox = Hive.box("SongsUrlCache"); final qualityIndex = Hive.box('AppPrefs').get('streamingQuality'); final musicServices = Get.find(); - List url = []; - if (songsUrlCacheBox.containsKey(songId) && !generateNewUrl) { + dynamic url; + if (songsUrlCacheBox.containsKey(songId)) { if (isExpired(url: songsUrlCacheBox.get(songId)[qualityIndex])) { - url = (await musicServices.getSongUri(songId))!; - songsUrlCacheBox.put(songId, url); + url = useNewInstanceOfExplode + ? await MusicServices(false).getSongUri(songId) + : (await musicServices.getSongUri(songId)); + if (url != null) songsUrlCacheBox.put(songId, url); } else { url = songsUrlCacheBox.get(songId); } } else { - url = (await musicServices.getSongUri(songId))!; - songsUrlCacheBox.put(songId, url); - printINFO("Url cached in Box for songId $songId"); + url = useNewInstanceOfExplode + ? await MusicServices(false).getSongUri(songId) + : (await musicServices.getSongUri(songId)); + if (url != null) { + songsUrlCacheBox.put(songId, url); + printINFO("Url cached in Box for songId $songId"); + } } - return url[qualityIndex]; + return url != null ? url[qualityIndex] : null; } } } + +class UrlError extends Error { + String message() => 'Unable to fetch url'; +} diff --git a/lib/services/music_service.dart b/lib/services/music_service.dart index 7e9886a9..83cae2ed 100644 --- a/lib/services/music_service.dart +++ b/lib/services/music_service.dart @@ -106,6 +106,7 @@ class MusicServices extends getx.GetxService { Future _sendRequest(String action, Map data, {additionalParams = ""}) async { //print("$baseUrl$action$fixedParms$additionalParams data:$data"); + try{ final response = await dio.post("$baseUrl$action$fixedParms$additionalParams", options: Options( @@ -118,6 +119,9 @@ class MusicServices extends getx.GetxService { } else { return _sendRequest(action, data, additionalParams: additionalParams); } + }on DioError{ + throw NetworkError(); + } } // Future>> @@ -620,3 +624,7 @@ class MusicServices extends getx.GetxService { return artist; } } + +class NetworkError extends Error{ + final message = "Network Error !"; +} diff --git a/lib/services/nav_parser.dart b/lib/services/nav_parser.dart index 5fbdc910..11f5e0e2 100644 --- a/lib/services/nav_parser.dart +++ b/lib/services/nav_parser.dart @@ -648,7 +648,7 @@ List parseSearchResults(List results, dynamic parseSearchResult(Map data, List searchResultTypes, String? resultType, String category) { - if (resultType != null && resultType.contains("playlist")) { + if ((resultType != null && resultType.contains("playlist"))|| category.contains("playlists")) { resultType = 'playlist'; } int defaultOffset = (resultType == null) ? 2 : 0; diff --git a/lib/ui/home.dart b/lib/ui/home.dart index 3f32fc74..360e50c1 100644 --- a/lib/ui/home.dart +++ b/lib/ui/home.dart @@ -141,7 +141,15 @@ class Home extends StatelessWidget { SizedBox( width: 40, child: InkWell( - onTap: playerController.next, + onTap: (playerController + .currentQueue.isEmpty || (playerController + .currentQueue + .last + .id == + playerController + .currentSong.value!.id)) + ? null + : playerController.next, child: Icon( Icons.skip_next_rounded, color: Theme.of(context) @@ -179,7 +187,8 @@ class Home extends StatelessWidget { iconSize: 35.0, onPressed: controller.play, ); - } else if (buttonState == PlayButtonState.playing) { + } else if (buttonState == PlayButtonState.playing || + buttonState == PlayButtonState.loading) { return IconButton( icon: Icon( Icons.pause_rounded, @@ -195,7 +204,7 @@ class Home extends StatelessWidget { color: Theme.of(context).textTheme.titleMedium!.color, ), iconSize: 35.0, - onPressed: (){}, + onPressed: () {}, ); } }); diff --git a/lib/ui/player/Player.dart b/lib/ui/player/Player.dart index d0ddb7be..1f4eb600 100644 --- a/lib/ui/player/Player.dart +++ b/lib/ui/player/Player.dart @@ -356,7 +356,8 @@ class Player extends StatelessWidget { iconSize: 40.0, onPressed: controller.play, ); - } else if (buttonState == PlayButtonState.playing) { + } else if (buttonState == PlayButtonState.playing || + buttonState == PlayButtonState.loading) { return IconButton( icon: const Icon(Icons.pause_rounded), iconSize: 40.0, @@ -386,14 +387,16 @@ class Player extends StatelessWidget { } Widget _nextButton(PlayerController playerController, BuildContext context) { - return IconButton( - icon: Icon( - Icons.skip_next_rounded, - color: Theme.of(context).textTheme.titleMedium!.color, - ), - iconSize: 30, - onPressed: () async { - await playerController.next(); - }, - ); + return Obx(() { + final isLastSong = playerController.currentQueue.isEmpty || + (playerController.currentQueue.last.id == + playerController.currentSong.value!.id); + return IconButton( + icon: Icon( + Icons.skip_next_rounded, + color: Theme.of(context).textTheme.titleMedium!.color, + ), + iconSize: 30, + onPressed: isLastSong ? null : playerController.next); + }); } diff --git a/lib/ui/player/player_controller.dart b/lib/ui/player/player_controller.dart index e2697899..b10cb722 100644 --- a/lib/ui/player/player_controller.dart +++ b/lib/ui/player/player_controller.dart @@ -177,18 +177,22 @@ class PlayerController extends GetxController { currentSong.value = mediaItem; _playerPanelCheck(); await _audioHandler.customAction( - "setSourceNPlay", {'mediaItem': mediaItem, 'retry': false}); + "setSourceNPlay", {'mediaItem': mediaItem}); } ///enqueueSong append a song to current queue ///if current queue is empty, push the song into Queue and play that song Future enqueueSong(MediaItem mediaItem) async { //check if song is available in cache and allocate - await _audioHandler.addQueueItem(mediaItem); + await enqueueSongList([mediaItem]); } ///enqueueSongList method add song List to current queue Future enqueueSongList(List mediaItems) async { + if(currentQueue.isEmpty){ + await playPlayListSong(mediaItems, 0); + return; + } for(MediaItem item in mediaItems){ if(!currentQueue.contains(item)){ _audioHandler.addQueueItem(item); @@ -200,7 +204,7 @@ class PlayerController extends GetxController { currentSong.value = mediaItem; _playerPanelCheck(); await _audioHandler.customAction( - "setSourceNPlay", {'mediaItem': mediaItem, 'retry': false}); + "setSourceNPlay", {'mediaItem': mediaItem}); } Future playPlayListSong(List mediaItems, int index) async { diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart index 07b0ee2e..7c55684f 100644 --- a/lib/ui/screens/home_screen.dart +++ b/lib/ui/screens/home_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:harmonymusic/helper.dart'; import 'package:harmonymusic/ui/player/player_controller.dart'; import 'package:harmonymusic/ui/widgets/content_list_widget_item.dart'; import 'package:harmonymusic/ui/widgets/create_playlist_dialog.dart'; @@ -27,6 +28,7 @@ class _HomeScreenState extends State { Get.find(); @override Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; return Scaffold( floatingActionButton: Visibility( visible: true, @@ -66,7 +68,7 @@ class _HomeScreenState extends State { homeScreenController.tabIndex.value, //_selectedIndex, onDestinationSelected: homeScreenController.onTabSelected, minWidth: 60, - leading: const SizedBox(height: 60), + leading: SizedBox(height: size.height < 750 ? 30 : 60), labelType: NavigationRailLabelType.all, //backgroundColor: Colors.green, destinations: [ @@ -104,7 +106,7 @@ class _HomeScreenState extends State { currentChild!, child: Center( key: ValueKey(homeScreenController.tabIndex.value), - child: bodyItem(), + child: const Body(), ), ), ), @@ -114,28 +116,100 @@ class _HomeScreenState extends State { ); } - Widget bodyItem() { + NavigationRailDestination railDestination(String label) { + return NavigationRailDestination( + icon: const SizedBox.shrink(), + label: Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: RotatedBox(quarterTurns: -1, child: Text(label))), + ); + } +} + +class Body extends StatelessWidget { + const Body({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final homeScreenController = Get.find(); + final size = MediaQuery.of(context).size; + final topPadding = size.height < 750 ? 80.0 : 85.0; if (homeScreenController.tabIndex.value == 0) { return Padding( padding: const EdgeInsets.only(left: 5.0), child: SingleChildScrollView( - padding: const EdgeInsets.only(bottom: 90, top: 90), - child: Obx(() { - return Column( - children: homeScreenController.isContentFetched.value - ? (homeScreenController.homeContentList).map((element) { - if (element.runtimeType.toString() == "QuickPicks") { - //return contentWidget(); - return QuickPicksWidget(content: element); - } else { - return ContentListWidget( - content: element, - ); - } - }).toList() - : [const HomeShimmer()], - ); - }), + padding: + EdgeInsets.only(bottom: 90, top:topPadding ), + child: homeScreenController.networkError.isTrue + ? SizedBox( + height: MediaQuery.of(context).size.height - 180, + child: Column( + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Home", + style: Theme.of(context).textTheme.titleLarge, + ), + ), + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Oops Network Error!", + style: + Theme.of(context).textTheme.titleMedium, + ), + const SizedBox( + height: 10, + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 15, vertical: 10), + decoration: BoxDecoration( + color: Theme.of(context) + .textTheme + .titleLarge! + .color, + borderRadius: BorderRadius.circular(10)), + child: InkWell( + onTap: () { + homeScreenController.init(); + }, + child: Text( + "Retry!", + style: TextStyle( + color: Theme.of(context).canvasColor), + ), + ), + ), + ]), + ), + ) + ], + ), + ) + : Obx(() { + return Column( + children: homeScreenController.isContentFetched.value + ? (homeScreenController.homeContentList).map((element) { + if (element.runtimeType.toString() == + "QuickPicks") { + //return contentWidget(); + return QuickPicksWidget(content: element); + } else { + return ContentListWidget( + content: element, + ); + } + }).toList() + : [const HomeShimmer()], + ); + }), ), ); } else if (homeScreenController.tabIndex.value == 1) { @@ -190,15 +264,6 @@ class _HomeScreenState extends State { ); } } - - NavigationRailDestination railDestination(String label) { - return NavigationRailDestination( - icon: const SizedBox.shrink(), - label: Padding( - padding: const EdgeInsets.symmetric(vertical: 5), - child: RotatedBox(quarterTurns: -1, child: Text(label))), - ); - } } class LibraryArtistWidget extends StatelessWidget { @@ -291,15 +356,15 @@ class PlaylistNAlbumLibraryWidget extends StatelessWidget { ? libralbumCntrller.libraryAlbums.isNotEmpty : librplstCntrller.libraryPlaylists.isNotEmpty) ? GridView.builder( - physics: const BouncingScrollPhysics(), + physics: const BouncingScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: ((size.width-60) / itemWidth).ceil(), + crossAxisCount: ((size.width - 60) / itemWidth).ceil(), childAspectRatio: (itemWidth / itemHeight), ), controller: ScrollController(keepScrollOffset: false), shrinkWrap: true, scrollDirection: Axis.vertical, - padding: const EdgeInsets.only(bottom: 70,top:10), + padding: const EdgeInsets.only(bottom: 70, top: 10), itemCount: isAlbumContent ? libralbumCntrller.libraryAlbums.length : librplstCntrller.libraryPlaylists.length, diff --git a/lib/ui/screens/home_screen_controller.dart b/lib/ui/screens/home_screen_controller.dart index f94bbbeb..20e3921e 100644 --- a/lib/ui/screens/home_screen_controller.dart +++ b/lib/ui/screens/home_screen_controller.dart @@ -1,5 +1,6 @@ import 'package:get/get.dart'; +import 'package:harmonymusic/helper.dart'; import 'package:harmonymusic/models/album.dart'; import 'package:harmonymusic/models/playlist.dart'; import 'package:harmonymusic/models/quick_picks.dart'; @@ -10,21 +11,30 @@ class HomeScreenController extends GetxController { final isContentFetched = false.obs; final homeContentList = [].obs; final tabIndex = 0.obs; + final networkError = false.obs; HomeScreenController() { - _init(); + init(); } - Future _init() async { - final homeContentListMap = await _musicServices.getHome(limit: 10); - //debugPrint(homeContentListMap,wrapWidth: 1024); - _setHomeContentList(homeContentListMap); - isContentFetched.value = true; + Future init() async { + networkError.value = false; + try { + final homeContentListMap = await _musicServices.getHome(limit: 10); + _setHomeContentList(homeContentListMap); + isContentFetched.value = true; + // ignore: unused_catch_stack + } on NetworkError catch (_, e) { + printERROR("Home Content not loaded due to ${_.message}"); + await Future.delayed(const Duration(seconds: 1)); + networkError.value = true; + } } void _setHomeContentList(List contents) { for (var content in contents) { if ((content["title"]).contains("Videos") || - (content["title"]).contains("videos") || content["contents"][1]==null) { + (content["title"]).contains("videos") || + content["contents"][1] == null) { } else if (content["title"] == "Quick picks") { homeContentList.add(QuickPicks.fromJson(content)); } else if (content["contents"][0].containsKey("playlistId")) { @@ -41,9 +51,9 @@ class HomeScreenController extends GetxController { } } - void onTabSelected(int index){ + void onTabSelected(int index) { tabIndex.value = index; } - void getRelatedArtist() {} + void getRelatedArtist() {} } diff --git a/lib/ui/screens/search_result_screen_controller.dart b/lib/ui/screens/search_result_screen_controller.dart index 614320ff..d24093ca 100644 --- a/lib/ui/screens/search_result_screen_controller.dart +++ b/lib/ui/screens/search_result_screen_controller.dart @@ -1,4 +1,3 @@ - import 'package:get/get.dart'; import 'package:harmonymusic/services/music_service.dart'; @@ -41,22 +40,25 @@ class SearchResultScreenController extends GetxController { } Future _getInitSearchResult() async { - queryString.value = Get.arguments; - resultContent.value = await musicServices.search(queryString.value); - final allKeys = resultContent.keys.where((element) => ([ - "Songs", - "Videos", - "Albums", - "Featured playlists", - "Community playlists", - "Artists" - ]).contains(element)); - railItems.value = List.from(allKeys); - final len = railItems - .where((element) => element.contains("playlists")) - .length; - final calH = 30 + (railItems.length+1-len)*123 + len*150.0; - railitemHeight.value = calH >= railitemHeight.value ? calH : railitemHeight.value; - isResultContentFetced.value = true; + final args = Get.arguments; + if (args != null) { + queryString.value = args; + resultContent.value = await musicServices.search(args); + final allKeys = resultContent.keys.where((element) => ([ + "Songs", + "Videos", + "Albums", + "Featured playlists", + "Community playlists", + "Artists" + ]).contains(element)); + railItems.value = List.from(allKeys); + final len = + railItems.where((element) => element.contains("playlists")).length; + final calH = 30 + (railItems.length + 1 - len) * 123 + len * 150.0; + railitemHeight.value = + calH >= railitemHeight.value ? calH : railitemHeight.value; + isResultContentFetced.value = true; + } } } diff --git a/lib/ui/screens/search_screen.dart b/lib/ui/screens/search_screen.dart index 5e6cbe17..04b1a710 100644 --- a/lib/ui/screens/search_screen.dart +++ b/lib/ui/screens/search_screen.dart @@ -53,7 +53,12 @@ class SearchScreen extends StatelessWidget { TextField( textCapitalization: TextCapitalization.sentences, controller: searchScreenController.textInputController, + textInputAction: TextInputAction.search, onChanged: searchScreenController.onChanged, + onSubmitted: (val) { + Get.toNamed(ScreenNavigationSetup.searchResultScreen, + id: ScreenNavigationSetup.id, arguments: val); + }, autofocus: true, cursorColor: Theme.of(context).textTheme.bodySmall!.color, decoration: const InputDecoration( @@ -64,13 +69,14 @@ class SearchScreen extends StatelessWidget { ), Expanded( child: Obx(() => ListView.builder( - padding: const EdgeInsets.only(top: 5), + padding: const EdgeInsets.only(top: 5), physics: const BouncingScrollPhysics( parent: AlwaysScrollableScrollPhysics()), itemCount: searchScreenController.suggestionList.length, itemBuilder: (context, index) => ListTile( - contentPadding: const EdgeInsets.only(left: 10,right: 20), + contentPadding: + const EdgeInsets.only(left: 10, right: 20), onTap: () { Get.toNamed( ScreenNavigationSetup.searchResultScreen, @@ -82,9 +88,17 @@ class SearchScreen extends StatelessWidget { searchScreenController.suggestionList[index]), trailing: InkWell( onTap: () { - searchScreenController.suggestionInput(searchScreenController.suggestionList[index]); + searchScreenController.suggestionInput( + searchScreenController + .suggestionList[index]); }, - child: Icon(Icons.north_west_rounded,color: Theme.of(context).textTheme.titleMedium!.color,), + child: Icon( + Icons.north_west_rounded, + color: Theme.of(context) + .textTheme + .titleMedium! + .color, + ), ), ), ))) diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart index a960d4e2..777ca833 100644 --- a/lib/ui/screens/settings_screen.dart +++ b/lib/ui/screens/settings_screen.dart @@ -14,7 +14,7 @@ class SettingsScreen extends StatelessWidget { Widget build(BuildContext context) { final settingsController = Get.find(); return Padding( - padding: const EdgeInsets.only(top: 90.0), + padding: const EdgeInsets.only(top: 90.0,left: 5), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -27,21 +27,24 @@ class SettingsScreen extends StatelessWidget { ), Expanded( child: ListView( - padding: EdgeInsets.zero, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only(bottom: 90), children: [ ListTile( + contentPadding: const EdgeInsets.only(left:5,right:10), title: const Text("Theme mode"), subtitle: Obx( - () => Text(settingsController.themeModetype.value == - ThemeType.dynamic - ? "dynamic" - : settingsController.themeModetype.value == - ThemeType.system - ? "system default" + () => Text( + settingsController.themeModetype.value == + ThemeType.dynamic + ? "dynamic" : settingsController.themeModetype.value == - ThemeType.dark - ? "dark" - : "light", + ThemeType.system + ? "system default" + : settingsController.themeModetype.value == + ThemeType.dark + ? "dark" + : "light", style: Theme.of(context).textTheme.bodyMedium), ), onTap: () => showDialog( @@ -50,6 +53,7 @@ class SettingsScreen extends StatelessWidget { ), ), ListTile( + contentPadding: const EdgeInsets.only(left:5,right:10), title: const Text("Cache songs"), subtitle: Text( "Caching songs while playing for future/offline playback, it will take additional space on your device", @@ -60,17 +64,20 @@ class SettingsScreen extends StatelessWidget { onChanged: settingsController.toggleCachingSongsValue), )), ListTile( + contentPadding: const EdgeInsets.only(left:5,right:10), title: const Text("Skip Silence"), - subtitle: - Text("Silence will be skipped in music playback.",style: Theme.of(context).textTheme.bodyMedium), + subtitle: Text("Silence will be skipped in music playback.", + style: Theme.of(context).textTheme.bodyMedium), trailing: Obx( () => Switch( value: settingsController.skipSilenceEnabled.value, onChanged: settingsController.toggleSkipSilence), )), ListTile( + contentPadding: const EdgeInsets.only(left:5,right:10), title: const Text("Streaming Quality"), - subtitle: Text("Quality of music stream",style: Theme.of(context).textTheme.bodyMedium), + subtitle: Text("Quality of music stream", + style: Theme.of(context).textTheme.bodyMedium), trailing: Obx( () => DropdownButton( dropdownColor: Theme.of(context).cardColor, @@ -89,9 +96,35 @@ class SettingsScreen extends StatelessWidget { ), ), ListTile( + contentPadding: const EdgeInsets.only(left:5,right:10), + title: const Text("Ignore battery optimization"), + onTap: settingsController.isIgnoringBatteryOptimizations.isFalse + ? settingsController.enableIgnoringBatteryOptimizations + : null, + subtitle: Obx(() => RichText( + text: TextSpan( + text: + "Status: ${settingsController.isIgnoringBatteryOptimizations.isTrue ? "Enabled" : "Disblaled"}\n", + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: + "If you are facing notification issues or playback stopped by system optimization, please enable this option", + style: Theme.of(context).textTheme.bodyMedium), + ], + ), + )), + ), + ListTile( + contentPadding: const EdgeInsets.only(left:5,right:10), title: const Text("Github"), subtitle: Text( - "View Github source code \nif you like this project, don't forget to give a ⭐${((Get.find().playerPanelMinHeight.value) == 0) ? "" : "\n\nV 1.0.0 by anandnet"}",style: Theme.of(context).textTheme.bodyMedium,), + "View Github source code \nif you like this project, don't forget to give a ⭐${((Get.find().playerPanelMinHeight.value) == 0) ? "" : "\n\nV 1.0.1 by anandnet"}", + style: Theme.of(context).textTheme.bodyMedium, + ), isThreeLine: true, onTap: () { launchUrl( @@ -106,7 +139,10 @@ class SettingsScreen extends StatelessWidget { )), Padding( padding: const EdgeInsets.only(bottom: 20.0), - child: Text("V 1.0.0 by anandnet",style: Theme.of(context).textTheme.bodySmall,), + child: Text( + "V 1.0.1 by anandnet", + style: Theme.of(context).textTheme.bodySmall, + ), ), ], ), @@ -173,7 +209,9 @@ class ThemeSelectorDialog extends StatelessWidget { required value}) { return Obx(() => ListTile( visualDensity: const VisualDensity(vertical: -4), - onTap: (){controller.onThemeChange(value);}, + onTap: () { + controller.onThemeChange(value); + }, leading: Radio( value: value, groupValue: controller.themeModetype.value, diff --git a/lib/ui/screens/settings_screen_controller.dart b/lib/ui/screens/settings_screen_controller.dart index 23565149..2ec67eb9 100644 --- a/lib/ui/screens/settings_screen_controller.dart +++ b/lib/ui/screens/settings_screen_controller.dart @@ -1,3 +1,4 @@ +import 'package:android_power_manager/android_power_manager.dart'; import 'package:get/get.dart'; import 'package:harmonymusic/helper.dart'; import 'package:harmonymusic/services/music_service.dart'; @@ -11,18 +12,22 @@ class SettingsScreenController extends GetxController { final themeModetype = ThemeType.dynamic.obs; final skipSilenceEnabled = false.obs; final streamingQuality = AudioQuality.High.obs; + final isIgnoringBatteryOptimizations = false.obs; @override void onInit() { _setInitValue(); super.onInit(); } - void _setInitValue() { + Future _setInitValue() async { cacheSongs.value = setBox.get('cacheSongs'); themeModetype.value = ThemeType.values[setBox.get('themeModeType')]; skipSilenceEnabled.value = setBox.get("skipSilenceEnabled"); streamingQuality.value = AudioQuality.values[setBox.get('streamingQuality')]; + isIgnoringBatteryOptimizations.value = + (await AndroidPowerManager.isIgnoringBatteryOptimizations)!; + printINFO(isIgnoringBatteryOptimizations); } void setStreamingQuality(dynamic val) { @@ -31,7 +36,6 @@ class SettingsScreenController extends GetxController { } void onThemeChange(dynamic val) { - printERROR("hereee"); setBox.put('themeModeType', ThemeType.values.indexOf(val)); themeModetype.value = val; Get.find().changeThemeModeType(val); @@ -47,4 +51,9 @@ class SettingsScreenController extends GetxController { setBox.put('skipSilenceEnabled', val); skipSilenceEnabled.value = val; } + + Future enableIgnoringBatteryOptimizations() async { + await AndroidPowerManager.requestIgnoreBatteryOptimizations(); + isIgnoringBatteryOptimizations.value = (await AndroidPowerManager.isIgnoringBatteryOptimizations)!; + } } diff --git a/lib/ui/widgets/songinfo_bottom_sheet.dart b/lib/ui/widgets/songinfo_bottom_sheet.dart index 3e22b278..68713c24 100644 --- a/lib/ui/widgets/songinfo_bottom_sheet.dart +++ b/lib/ui/widgets/songinfo_bottom_sheet.dart @@ -74,10 +74,10 @@ class SongInfoBottomSheet extends StatelessWidget { leading: const Icon(Icons.merge), title: const Text("Enqueue this song"), onTap: () { - final playerCntrller = Get.find(); - playerCntrller.currentQueue.isEmpty - ? playerCntrller.pushSongToQueue(song) - : playerCntrller.enqueueSong(song); + Get.find().enqueueSong(song).whenComplete( + () => ScaffoldMessenger.of(context).showSnackBar(snackbar( + context, "Song enqueued!", + size: SanckBarSize.MEDIUM))); Navigator.of(context).pop(); }, ), @@ -115,8 +115,9 @@ class SongInfoBottomSheet extends StatelessWidget { songInfoController .removeSongFromPlaylist(song, playlist!) .whenComplete(() => ScaffoldMessenger.of(context) - .showSnackBar(snackbar(context, - "Removed from ${playlist!.title}", size: SanckBarSize.MEDIUM))); + .showSnackBar(snackbar( + context, "Removed from ${playlist!.title}", + size: SanckBarSize.MEDIUM))); }, ) : const SizedBox.shrink(), @@ -129,12 +130,14 @@ class SongInfoBottomSheet extends StatelessWidget { Navigator.of(context).pop(); final plarcntr = Get.find(); if (plarcntr.currentSong.value!.id == song.id) { - ScaffoldMessenger.of(context).showSnackBar(snackbar(context, - "You can't remove currently playing song", size: SanckBarSize.BIG)); + ScaffoldMessenger.of(context).showSnackBar(snackbar( + context, "You can't remove currently playing song", + size: SanckBarSize.BIG)); } else { Get.find().removeFromQueue(song); - ScaffoldMessenger.of(context).showSnackBar( - snackbar(context, "Removed from queue !", size: SanckBarSize.MEDIUM)); + ScaffoldMessenger.of(context).showSnackBar(snackbar( + context, "Removed from queue !", + size: SanckBarSize.MEDIUM)); } }) : const SizedBox.shrink(), @@ -210,7 +213,7 @@ class SongInfoController extends GetxController { try { final plstCntroller = Get.find(); plstCntroller.addNRemoveItemsinList(item, action: 'remove'); - // ignore: empty_catches + // ignore: empty_catches } catch (e) {} //Updating Library song list in frontend if (playlist.playlistId == "SongsCache") { diff --git a/pubspec.lock b/pubspec.lock index 9f3a38d2..a41edb9a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + android_power_manager: + dependency: "direct main" + description: + name: android_power_manager + sha256: "7c9e6374dc44c8fce28277fe0f7682cbfda35240ce7c81a664bc7f3c2710adf1" + url: "https://pub.dev" + source: hosted + version: "1.0.0" async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ef785a80..04f69e85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0 +version: 1.0.1 environment: sdk: '>=2.19.1 <3.0.0' @@ -51,6 +51,7 @@ dependencies: flutter_keyboard_visibility: ^5.4.1 url_launcher: ^6.1.11 share_plus: ^7.0.1 + android_power_manager: ^1.0.0 dev_dependencies: flutter_test: