From d1845a38d1d858b0d7f1c06697e618d4750381cf Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Tue, 24 Sep 2024 22:30:18 +0200 Subject: [PATCH] run dart format --- .../add_to_playlist_button.dart | 5 +- ...bum_screen_content_flexible_space_bar.dart | 2 +- .../AlbumScreen/download_dialog.dart | 2 +- .../AlbumScreen/song_list_tile.dart | 16 +- ...eep_screen_on_while_charging_selector.dart | 3 +- .../MusicScreen/alphabet_item_list.dart | 3 +- lib/components/PlayerScreen/album_chip.dart | 4 +- lib/components/PlayerScreen/artist_chip.dart | 3 +- .../PlayerScreen/player_buttons.dart | 9 +- .../player_buttons_repeating.dart | 3 +- .../PlayerScreen/player_buttons_shuffle.dart | 7 +- .../player_screen_album_image.dart | 10 +- .../player_split_screen_scaffold.dart | 3 +- .../PlayerScreen/progress_slider.dart | 39 +- lib/components/PlayerScreen/queue_button.dart | 10 +- lib/components/PlayerScreen/queue_list.dart | 3 +- .../PlayerScreen/queue_source_helper.dart | 5 +- .../PlayerScreen/song_name_content.dart | 3 +- ...me_normalization_ios_base_gain_editor.dart | 3 +- lib/components/album_image.dart | 14 +- lib/components/now_playing_bar.dart | 270 ++++---- lib/components/print_duration.dart | 12 +- lib/main.dart | 2 +- lib/models/finamp_models.dart | 168 +++-- .../customization_settings_screen.dart | 26 +- lib/screens/interaction_settings_screen.dart | 10 +- lib/screens/layout_settings_screen.dart | 10 +- lib/screens/lyrics_screen.dart | 63 +- lib/screens/lyrics_settings_screen.dart | 8 +- lib/screens/player_settings_screen.dart | 3 +- lib/screens/settings_screen.dart | 169 ++--- lib/services/android_auto_helper.dart | 631 +++++++++++------- lib/services/audio_service_helper.dart | 10 +- lib/services/finamp_settings_helper.dart | 5 +- lib/services/jellyfin_api_helper.dart | 9 +- lib/services/playback_history_service.dart | 7 +- lib/services/queue_service.dart | 108 +-- 37 files changed, 974 insertions(+), 684 deletions(-) diff --git a/lib/components/AddToPlaylistScreen/add_to_playlist_button.dart b/lib/components/AddToPlaylistScreen/add_to_playlist_button.dart index 0de33bace..e50178ab0 100644 --- a/lib/components/AddToPlaylistScreen/add_to_playlist_button.dart +++ b/lib/components/AddToPlaylistScreen/add_to_playlist_button.dart @@ -72,12 +72,13 @@ class _AddToPlaylistButtonState extends ConsumerState { return GlobalSnackbar.message((context) => AppLocalizations.of(context)!.notAvailableInOfflineMode); } - + bool inPlaylist = queueItemInPlaylist(widget.queueItem); await showPlaylistActionsMenu( context: context, item: widget.item!, - parentPlaylist: inPlaylist ? widget.queueItem!.source.item : null, + parentPlaylist: + inPlaylist ? widget.queueItem!.source.item : null, usePlayerTheme: true, ); }), diff --git a/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart b/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart index 2fb1f154d..558b64fde 100644 --- a/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart +++ b/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart @@ -437,4 +437,4 @@ class AlbumScreenContentFlexibleSpaceBar extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/components/AlbumScreen/download_dialog.dart b/lib/components/AlbumScreen/download_dialog.dart index 2c9b58cff..5648f0a62 100644 --- a/lib/components/AlbumScreen/download_dialog.dart +++ b/lib/components/AlbumScreen/download_dialog.dart @@ -236,4 +236,4 @@ class _DownloadDialogState extends State { ], ); } -} \ No newline at end of file +} diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 4ee8d61b9..8d2eb95fa 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -281,12 +281,12 @@ class _SongListTileState extends ConsumerState List offlineItems; // If we're on the songs tab, just get all of the downloaded items offlineItems = await downloadsService.getAllSongs( - // nameFilter: widget.searchTerm, - viewFilter: finampUserHelper.currentUser?.currentView?.id, - nullableViewFilters: - settings.showDownloadsWithUnknownLibrary, - onlyFavorites: - settings.onlyShowFavourite && settings.trackOfflineFavorites, + // nameFilter: widget.searchTerm, + viewFilter: finampUserHelper.currentUser?.currentView?.id, + nullableViewFilters: + settings.showDownloadsWithUnknownLibrary, + onlyFavorites: settings.onlyShowFavourite && + settings.trackOfflineFavorites, ); var items = offlineItems @@ -308,8 +308,8 @@ class _SongListTileState extends ConsumerState source: QueueItemSource( name: QueueItemSourceName( type: widget.item.name != null - ? QueueItemSourceNameType.mix - : QueueItemSourceNameType.instantMix, + ? QueueItemSourceNameType.mix + : QueueItemSourceNameType.instantMix, localizationParameter: widget.item.name ?? "", ), type: QueueItemSourceType.allSongs, diff --git a/lib/components/InteractionSettingsScreen/keep_screen_on_while_charging_selector.dart b/lib/components/InteractionSettingsScreen/keep_screen_on_while_charging_selector.dart index e89969c15..a2713ce38 100644 --- a/lib/components/InteractionSettingsScreen/keep_screen_on_while_charging_selector.dart +++ b/lib/components/InteractionSettingsScreen/keep_screen_on_while_charging_selector.dart @@ -15,7 +15,8 @@ class KeepScreenOnWhilePluggedInSelector extends StatelessWidget { builder: (_, box, __) { return SwitchListTile.adaptive( title: Text(AppLocalizations.of(context)!.keepScreenOnWhilePluggedIn), - subtitle: Text(AppLocalizations.of(context)!.keepScreenOnWhilePluggedInSubtitle), + subtitle: Text( + AppLocalizations.of(context)!.keepScreenOnWhilePluggedInSubtitle), value: FinampSettingsHelper.finampSettings.keepScreenOnWhilePluggedIn, onChanged: (value) { FinampSettingsHelper.setKeepScreenOnWhileCharging(value); diff --git a/lib/components/MusicScreen/alphabet_item_list.dart b/lib/components/MusicScreen/alphabet_item_list.dart index 34a83f840..f7da9d606 100644 --- a/lib/components/MusicScreen/alphabet_item_list.dart +++ b/lib/components/MusicScreen/alphabet_item_list.dart @@ -86,7 +86,8 @@ class _AlphabetListState extends State { onPointerUp: (x) => updateSelected(x.localPosition, Drag.end), behavior: HitTestBehavior.opaque, child: Semantics( - excludeSemantics: true, // replace child semantics with custom semantics + excludeSemantics: + true, // replace child semantics with custom semantics child: Column( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( diff --git a/lib/components/PlayerScreen/album_chip.dart b/lib/components/PlayerScreen/album_chip.dart index c8fc5d4e5..77a2a8762 100644 --- a/lib/components/PlayerScreen/album_chip.dart +++ b/lib/components/PlayerScreen/album_chip.dart @@ -88,8 +88,8 @@ class _AlbumChipContent extends StatelessWidget { (album) => Navigator.of(context).pushNamed( AlbumScreen.routeName, arguments: album!.baseItem!)) - : () => jellyfinApiHelper.getItemById(item.albumId!).then((album) => - Navigator.of(context) + : () => jellyfinApiHelper.getItemById(item.albumId!).then( + (album) => Navigator.of(context) .pushNamed(AlbumScreen.routeName, arguments: album)), child: Padding( padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4), diff --git a/lib/components/PlayerScreen/artist_chip.dart b/lib/components/PlayerScreen/artist_chip.dart index d06c3b466..244c483f6 100644 --- a/lib/components/PlayerScreen/artist_chip.dart +++ b/lib/components/PlayerScreen/artist_chip.dart @@ -106,7 +106,8 @@ class ArtistChip extends ConsumerWidget { final BaseItemDto? localArtist; if (artist != null && FinampSettingsHelper.finampSettings.showArtistChipImage) { - localArtist = ref.watch(artistItemProvider(artist!.id)).valueOrNull ?? artist; + localArtist = + ref.watch(artistItemProvider(artist!.id)).valueOrNull ?? artist; } else { localArtist = artist; } diff --git a/lib/components/PlayerScreen/player_buttons.dart b/lib/components/PlayerScreen/player_buttons.dart index 1217e9608..40ba65de8 100644 --- a/lib/components/PlayerScreen/player_buttons.dart +++ b/lib/components/PlayerScreen/player_buttons.dart @@ -36,7 +36,8 @@ class PlayerButtons extends StatelessWidget { PlayerButtonsRepeating(), Semantics.fromProperties( properties: SemanticsProperties( - label: AppLocalizations.of(context)!.skipToPreviousTrackButtonTooltip, + label: AppLocalizations.of(context)! + .skipToPreviousTrackButtonTooltip, button: true, ), container: true, @@ -53,7 +54,8 @@ class PlayerButtons extends StatelessWidget { ), Semantics.fromProperties( properties: SemanticsProperties( - label: AppLocalizations.of(context)!.togglePlaybackButtonTooltip, + label: + AppLocalizations.of(context)!.togglePlaybackButtonTooltip, button: true, ), container: true, @@ -88,7 +90,8 @@ class PlayerButtons extends StatelessWidget { ), Semantics.fromProperties( properties: SemanticsProperties( - label: AppLocalizations.of(context)!.skipToNextTrackButtonTooltip, + label: AppLocalizations.of(context)! + .skipToNextTrackButtonTooltip, button: true, ), container: true, diff --git a/lib/components/PlayerScreen/player_buttons_repeating.dart b/lib/components/PlayerScreen/player_buttons_repeating.dart index e0c852051..72a0f890f 100644 --- a/lib/components/PlayerScreen/player_buttons_repeating.dart +++ b/lib/components/PlayerScreen/player_buttons_repeating.dart @@ -24,7 +24,8 @@ class PlayerButtonsRepeating extends StatelessWidget { stream: mediaStateStream, builder: (BuildContext context, AsyncSnapshot snapshot) { return IconButton( - tooltip: "${getLocalizedLoopMode(context, queueService.loopMode)}. ${AppLocalizations.of(context)!.genericToggleButtonTooltip}", + tooltip: + "${getLocalizedLoopMode(context, queueService.loopMode)}. ${AppLocalizations.of(context)!.genericToggleButtonTooltip}", onPressed: () async { FeedbackHelper.feedback(FeedbackType.light); queueService.toggleLoopMode(); diff --git a/lib/components/PlayerScreen/player_buttons_shuffle.dart b/lib/components/PlayerScreen/player_buttons_shuffle.dart index 896a44692..71dcfa654 100644 --- a/lib/components/PlayerScreen/player_buttons_shuffle.dart +++ b/lib/components/PlayerScreen/player_buttons_shuffle.dart @@ -23,7 +23,8 @@ class PlayerButtonsShuffle extends StatelessWidget { stream: mediaStateStream, builder: (BuildContext context, AsyncSnapshot snapshot) { return IconButton( - tooltip: getLocalizedPlaybackOrder(context, _queueService.playbackOrder), + tooltip: + getLocalizedPlaybackOrder(context, _queueService.playbackOrder), onPressed: () async { FeedbackHelper.feedback(FeedbackType.light); _queueService.togglePlaybackOrder(); @@ -38,7 +39,8 @@ class PlayerButtonsShuffle extends StatelessWidget { ); } - String getLocalizedPlaybackOrder(BuildContext context, FinampPlaybackOrder playbackOrder) { + String getLocalizedPlaybackOrder( + BuildContext context, FinampPlaybackOrder playbackOrder) { switch (playbackOrder) { case FinampPlaybackOrder.linear: return AppLocalizations.of(context)!.playbackOrderLinearButtonTooltip; @@ -46,5 +48,4 @@ class PlayerButtonsShuffle extends StatelessWidget { return AppLocalizations.of(context)!.playbackOrderShuffledButtonTooltip; } } - } diff --git a/lib/components/PlayerScreen/player_screen_album_image.dart b/lib/components/PlayerScreen/player_screen_album_image.dart index 22a07f335..ed5ba7956 100644 --- a/lib/components/PlayerScreen/player_screen_album_image.dart +++ b/lib/components/PlayerScreen/player_screen_album_image.dart @@ -36,8 +36,11 @@ class PlayerScreenAlbumImage extends ConsumerWidget { final currentTrack = snapshot.data!.currentTrack; return Semantics( - label: AppLocalizations.of(context)!.playerAlbumArtworkTooltip(currentTrack?.item.title ?? AppLocalizations.of(context)!.unknownName), - excludeSemantics: true, // replace child semantics with custom semantics + label: AppLocalizations.of(context)!.playerAlbumArtworkTooltip( + currentTrack?.item.title ?? + AppLocalizations.of(context)!.unknownName), + excludeSemantics: + true, // replace child semantics with custom semantics container: true, child: GestureDetector( onSecondaryTapDown: (_) async { @@ -58,7 +61,8 @@ class PlayerScreenAlbumImage extends ConsumerWidget { child: SimpleGestureDetector( //TODO replace with PageView, this is just a placeholder onTap: () { - final audioService = GetIt.instance(); + final audioService = + GetIt.instance(); audioService.togglePlayback(); FeedbackHelper.feedback(FeedbackType.selection); }, diff --git a/lib/components/PlayerScreen/player_split_screen_scaffold.dart b/lib/components/PlayerScreen/player_split_screen_scaffold.dart index 2251683d6..8476f4837 100644 --- a/lib/components/PlayerScreen/player_split_screen_scaffold.dart +++ b/lib/components/PlayerScreen/player_split_screen_scaffold.dart @@ -134,8 +134,7 @@ Widget buildPlayerSplitScreenScaffold(BuildContext context, Widget? widget) { arguments: x.arguments); return EmptyRoute(); }, - observers: [KeepScreenOnObserver()] - ), + observers: [KeepScreenOnObserver()]), )), ) ]); diff --git a/lib/components/PlayerScreen/progress_slider.dart b/lib/components/PlayerScreen/progress_slider.dart index ba9975797..1f697dec4 100644 --- a/lib/components/PlayerScreen/progress_slider.dart +++ b/lib/components/PlayerScreen/progress_slider.dart @@ -136,8 +136,10 @@ class _ProgressSliderDuration extends StatelessWidget { @override Widget build(BuildContext context) { final showRemaining = Platform.isIOS || Platform.isMacOS; - final currentPosition = Duration(seconds: (position.inMilliseconds / 1000).round()); - final roundedDuration = Duration(seconds: ((itemDuration?.inMilliseconds ?? 0) / 1000).round()); + final currentPosition = + Duration(seconds: (position.inMilliseconds / 1000).round()); + final roundedDuration = + Duration(seconds: ((itemDuration?.inMilliseconds ?? 0) / 1000).round()); return Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -151,10 +153,9 @@ class _ProgressSliderDuration extends StatelessWidget { Text( printDuration( // display remaining time if on iOS or macOS - showRemaining ? - (roundedDuration - currentPosition) - : roundedDuration - , + showRemaining + ? (roundedDuration - currentPosition) + : roundedDuration, isRemaining: showRemaining, ), style: Theme.of(context).textTheme.bodySmall?.copyWith( @@ -230,15 +231,23 @@ class __PlaybackProgressSliderState .clamp(0, widget.mediaItem!.duration!.inMicroseconds.toDouble()) .toDouble(), semanticFormatterCallback: (double value) { - final positionFullMinutes = Duration(microseconds: value.toInt()).inMinutes % 60; - final positionFullHours = Duration(microseconds: value.toInt()).inHours; - final positionSeconds = Duration(microseconds: value.toInt()).inSeconds % 60; + final positionFullMinutes = + Duration(microseconds: value.toInt()).inMinutes % 60; + final positionFullHours = + Duration(microseconds: value.toInt()).inHours; + final positionSeconds = + Duration(microseconds: value.toInt()).inSeconds % 60; final durationFullHours = (widget.mediaItem?.duration?.inHours ?? 0); - final durationFullMinutes = (widget.mediaItem?.duration?.inMinutes ?? 0) % 60; - final durationSeconds = (widget.mediaItem?.duration?.inSeconds ?? 0) % 60; - final positionString = "${positionFullHours > 0 ? "$positionFullHours ${AppLocalizations.of(context)!.hours} " : ""}${positionFullMinutes > 0 ? "$positionFullMinutes ${AppLocalizations.of(context)!.minutes} " : ""}$positionSeconds ${AppLocalizations.of(context)!.seconds}"; - final durationString = "${durationFullHours > 0 ? "$durationFullHours ${AppLocalizations.of(context)!.hours} " : ""}${durationFullMinutes > 0 ? "$durationFullMinutes ${AppLocalizations.of(context)!.minutes} " : ""}$durationSeconds ${AppLocalizations.of(context)!.seconds}"; - return AppLocalizations.of(context)!.timeFractionTooltip(positionString, durationString); + final durationFullMinutes = + (widget.mediaItem?.duration?.inMinutes ?? 0) % 60; + final durationSeconds = + (widget.mediaItem?.duration?.inSeconds ?? 0) % 60; + final positionString = + "${positionFullHours > 0 ? "$positionFullHours ${AppLocalizations.of(context)!.hours} " : ""}${positionFullMinutes > 0 ? "$positionFullMinutes ${AppLocalizations.of(context)!.minutes} " : ""}$positionSeconds ${AppLocalizations.of(context)!.seconds}"; + final durationString = + "${durationFullHours > 0 ? "$durationFullHours ${AppLocalizations.of(context)!.hours} " : ""}${durationFullMinutes > 0 ? "$durationFullMinutes ${AppLocalizations.of(context)!.minutes} " : ""}$durationSeconds ${AppLocalizations.of(context)!.seconds}"; + return AppLocalizations.of(context)! + .timeFractionTooltip(positionString, durationString); }, secondaryTrackValue: widget.mediaItem?.extras?["downloadedSongPath"] == null @@ -274,7 +283,7 @@ class __PlaybackProgressSliderState // Seek to the new position await _audioHandler .seek(Duration(microseconds: newValue.toInt())); - + // Clear drag value so that the slider uses the play // duration again. if (mounted) { diff --git a/lib/components/PlayerScreen/queue_button.dart b/lib/components/PlayerScreen/queue_button.dart index b6e503564..67432f878 100644 --- a/lib/components/PlayerScreen/queue_button.dart +++ b/lib/components/PlayerScreen/queue_button.dart @@ -13,10 +13,10 @@ class QueueButton extends StatelessWidget { @override Widget build(BuildContext context) { return SimpleButton( - text: AppLocalizations.of(context)!.queue, - icon: TablerIcons.playlist, - onPressed: () { - showQueueBottomSheet(context); - }); + text: AppLocalizations.of(context)!.queue, + icon: TablerIcons.playlist, + onPressed: () { + showQueueBottomSheet(context); + }); } } diff --git a/lib/components/PlayerScreen/queue_list.dart b/lib/components/PlayerScreen/queue_list.dart index 374b683c7..8deb199c0 100644 --- a/lib/components/PlayerScreen/queue_list.dart +++ b/lib/components/PlayerScreen/queue_list.dart @@ -804,7 +804,8 @@ class _CurrentTrackState extends State { width: (screenSize.width - 2 * horizontalPadding - albumImageSize) * - ((playbackPosition?.inMilliseconds ?? 0) / + ((playbackPosition?.inMilliseconds ?? + 0) / (mediaState?.mediaItem ?.duration ?? const Duration( diff --git a/lib/components/PlayerScreen/queue_source_helper.dart b/lib/components/PlayerScreen/queue_source_helper.dart index 4625c15e8..874f13318 100644 --- a/lib/components/PlayerScreen/queue_source_helper.dart +++ b/lib/components/PlayerScreen/queue_source_helper.dart @@ -102,7 +102,8 @@ Future removeFromPlaylist(BuildContext context, BaseItemDto item, // re-sync playlist to delete removed item if not required anymore final downloadsService = GetIt.instance(); unawaited(downloadsService.resync( - DownloadStub.fromItem(type: DownloadItemType.collection, item: parent), + DownloadStub.fromItem( + type: DownloadItemType.collection, item: parent), null, keepSlow: true)); @@ -112,9 +113,7 @@ Future removeFromPlaylist(BuildContext context, BaseItemDto item, (context) => AppLocalizations.of(context)!.removedFromPlaylist, isConfirmation: true); return true; - } catch (err) { - GlobalSnackbar.error(err); return false; } diff --git a/lib/components/PlayerScreen/song_name_content.dart b/lib/components/PlayerScreen/song_name_content.dart index 3a12bfaaf..1234d6485 100644 --- a/lib/components/PlayerScreen/song_name_content.dart +++ b/lib/components/PlayerScreen/song_name_content.dart @@ -59,7 +59,8 @@ class SongNameContent extends StatelessWidget { ), child: Semantics.fromProperties( properties: SemanticsProperties( - label: "${currentTrack.item.title} (${AppLocalizations.of(context)!.title})", + label: + "${currentTrack.item.title} (${AppLocalizations.of(context)!.title})", ), excludeSemantics: true, container: true, diff --git a/lib/components/VolumeNormalizationSettingsScreen/volume_normalization_ios_base_gain_editor.dart b/lib/components/VolumeNormalizationSettingsScreen/volume_normalization_ios_base_gain_editor.dart index 72c543e3b..c4ffc55d1 100644 --- a/lib/components/VolumeNormalizationSettingsScreen/volume_normalization_ios_base_gain_editor.dart +++ b/lib/components/VolumeNormalizationSettingsScreen/volume_normalization_ios_base_gain_editor.dart @@ -29,7 +29,8 @@ class _VolumeNormalizationIOSBaseGainEditorState child: TextField( controller: _controller, textAlign: TextAlign.center, - keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true), + keyboardType: const TextInputType.numberWithOptions( + decimal: true, signed: true), onChanged: (value) { final valueDouble = double.tryParse(value); diff --git a/lib/components/album_image.dart b/lib/components/album_image.dart index b7c6e6fd9..076c6409b 100644 --- a/lib/components/album_image.dart +++ b/lib/components/album_image.dart @@ -94,14 +94,17 @@ class AlbumImage extends ConsumerWidget { // Because of this, we convert the logical pixels to physical pixels by multiplying by the device's DPI. final MediaQueryData mediaQuery = MediaQuery.of(context); physicalWidth = - (constraints.maxWidth * mediaQuery.devicePixelRatio).toInt(); + (constraints.maxWidth * mediaQuery.devicePixelRatio) + .toInt(); physicalHeight = - (constraints.maxHeight * mediaQuery.devicePixelRatio).toInt(); + (constraints.maxHeight * mediaQuery.devicePixelRatio) + .toInt(); // If using grid music screen view without fixed size tiles, and if the view is resizable due // to being on desktop and using split screen, then clamp album size to reduce server requests when resizing. if ((!(Platform.isIOS || Platform.isAndroid) || usingPlayerSplitScreen) && - !FinampSettingsHelper.finampSettings.useFixedSizeGridTiles && + !FinampSettingsHelper + .finampSettings.useFixedSizeGridTiles && FinampSettingsHelper.finampSettings.contentViewType == ContentViewType.grid) { physicalWidth = @@ -110,7 +113,7 @@ class AlbumImage extends ConsumerWidget { exp((log(physicalHeight) * 3).ceil() / 3).toInt(); } } - + var image = Container( decoration: decoration, child: BareAlbumImage( @@ -123,7 +126,8 @@ class AlbumImage extends ConsumerWidget { imageProviderCallback: themeCallback == null ? null : (image) => themeCallback!( - FinampTheme.fromImageDeferred(image, item?.blurHash)), + FinampTheme.fromImageDeferred( + image, item?.blurHash)), placeholderBuilder: placeholderBuilder), ); return disabled diff --git a/lib/components/now_playing_bar.dart b/lib/components/now_playing_bar.dart index 03e002501..bb4172afd 100644 --- a/lib/components/now_playing_bar.dart +++ b/lib/components/now_playing_bar.dart @@ -48,25 +48,20 @@ class NowPlayingBar extends ConsumerWidget { ]); Color getProgressBackgroundColor(BuildContext context) { - return FinampSettingsHelper.finampSettings.showProgressOnNowPlayingBar ? - Color.alphaBlend( - Theme.of(context).brightness == Brightness.dark - ? IconTheme.of(context) - .color! - .withOpacity(0.35) - : IconTheme.of(context) - .color! - .withOpacity(0.5), - Theme.of(context).brightness == - Brightness.dark - ? Colors.black - : Colors.white - ) : - IconTheme.of(context).color!.withOpacity(0.85); + return FinampSettingsHelper.finampSettings.showProgressOnNowPlayingBar + ? Color.alphaBlend( + Theme.of(context).brightness == Brightness.dark + ? IconTheme.of(context).color!.withOpacity(0.35) + : IconTheme.of(context).color!.withOpacity(0.5), + Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.white) + : IconTheme.of(context).color!.withOpacity(0.85); } Widget buildLoadingQueueBar(BuildContext context, Function()? retryCallback) { - final progressBackgroundColor = getProgressBackgroundColor(context).withOpacity(0.5); + final progressBackgroundColor = + getProgressBackgroundColor(context).withOpacity(0.5); return SimpleGestureDetector( onVerticalSwipe: (direction) { @@ -140,7 +135,6 @@ class NowPlayingBar extends ConsumerWidget { Widget buildNowPlayingBar( BuildContext context, FinampQueueItem currentTrack) { - final audioHandler = GetIt.instance(); Duration? playbackPosition; @@ -151,7 +145,7 @@ class NowPlayingBar extends ConsumerWidget { : null; final progressBackgroundColor = getProgressBackgroundColor(context); - + return SafeArea( child: Padding( padding: const EdgeInsets.only(left: 12.0, bottom: 12.0, right: 12.0), @@ -161,7 +155,8 @@ class NowPlayingBar extends ConsumerWidget { button: true, ), child: SimpleGestureDetector( - onTap: () => Navigator.of(context).pushNamed(PlayerScreen.routeName), + onTap: () => + Navigator.of(context).pushNamed(PlayerScreen.routeName), child: Dismissible( key: const Key("NowPlayingBarDismiss"), direction: FinampSettingsHelper.finampSettings.disableGesture @@ -209,7 +204,8 @@ class NowPlayingBar extends ConsumerWidget { child: StreamBuilder( stream: mediaStateStream .where((event) => event.mediaItem != null), - initialData: MediaState(audioHandler.mediaItem.valueOrNull, + initialData: MediaState( + audioHandler.mediaItem.valueOrNull, audioHandler.playbackState.value), builder: (context, snapshot) { final MediaState mediaState = snapshot.data!; @@ -252,13 +248,16 @@ class NowPlayingBar extends ConsumerWidget { color: Color.fromRGBO(0, 0, 0, 0.3), ), child: IconButton( - tooltip: AppLocalizations.of(context)!.togglePlaybackButtonTooltip, + tooltip: AppLocalizations.of( + context)! + .togglePlaybackButtonTooltip, onPressed: () { FeedbackHelper.feedback( FeedbackType.light); audioHandler.togglePlayback(); }, - icon: mediaState.playbackState.playing + icon: mediaState + .playbackState.playing ? const Icon( TablerIcons.player_pause, size: 32, @@ -274,20 +273,24 @@ class NowPlayingBar extends ConsumerWidget { Expanded( child: Stack( children: [ - if (FinampSettingsHelper.finampSettings.showProgressOnNowPlayingBar) + if (FinampSettingsHelper.finampSettings + .showProgressOnNowPlayingBar) Positioned( left: 0, top: 0, child: StreamBuilder( stream: AudioService.position, initialData: audioHandler - .playbackState.value.position, + .playbackState + .value + .position, builder: (context, snapshot) { if (snapshot.hasData) { playbackPosition = snapshot.data; final screenSize = - MediaQuery.of(context).size; + MediaQuery.of(context) + .size; return Container( // rather hacky workaround, using LayoutBuilder would be nice but I couldn't get it to work... width: max( @@ -301,22 +304,25 @@ class NowPlayingBar extends ConsumerWidget { (mediaState.mediaItem ?.duration ?? const Duration( - seconds: - 0)) + seconds: 0)) .inMilliseconds)), height: albumImageSize, - decoration: ShapeDecoration( - color: IconTheme.of(context) - .color! - .withOpacity(0.75), + decoration: + ShapeDecoration( + color: IconTheme.of( + context) + .color! + .withOpacity(0.75), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: - Radius.circular(12), + Radius.circular( + 12), bottomRight: - Radius.circular(12), + Radius.circular( + 12), ), ), ), @@ -337,7 +343,8 @@ class NowPlayingBar extends ConsumerWidget { padding: const EdgeInsets.only( left: 12, right: 4), child: Column( - mainAxisSize: MainAxisSize.min, + mainAxisSize: + MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: @@ -363,7 +370,8 @@ class NowPlayingBar extends ConsumerWidget { child: Text( processArtist( currentTrack - .item.artist, + .item + .artist, context), style: TextStyle( color: Colors @@ -380,89 +388,118 @@ class NowPlayingBar extends ConsumerWidget { ), ), StreamBuilder( - stream: - AudioService - .position, - initialData: - audioHandler - .playbackState - .value - .position, - builder: (context, snapshot) { - if (snapshot.hasData) { - playbackPosition = - snapshot.data; - final positionFullMinutes = (playbackPosition?.inMinutes ?? 0) % 60; - final positionFullHours = (playbackPosition?.inHours ?? 0); - final positionSeconds = (playbackPosition?.inSeconds ?? 0) % 60; - final durationFullHours = (mediaState.mediaItem?.duration?.inHours ?? 0); - final durationFullMinutes = (mediaState.mediaItem?.duration?.inMinutes ?? 0) % 60; - final durationSeconds = (mediaState.mediaItem?.duration?.inSeconds ?? 0) % 60; - return Semantics.fromProperties( - properties: SemanticsProperties( - label: "${positionFullHours > 0 ? "$positionFullHours hours " : ""}${positionFullMinutes > 0 ? "$positionFullMinutes minutes " : ""}$positionSeconds seconds of ${durationFullHours > 0 ? "$durationFullHours hours " : ""}${durationFullMinutes > 0 ? "$durationFullMinutes minutes " : ""}$durationSeconds seconds", - ), - excludeSemantics: true, - container: true, - child: Row( - children: [ + stream: AudioService + .position, + initialData: + audioHandler + .playbackState + .value + .position, + builder: (context, + snapshot) { + if (snapshot + .hasData) { + playbackPosition = + snapshot + .data; + final positionFullMinutes = + (playbackPosition?.inMinutes ?? + 0) % + 60; + final positionFullHours = + (playbackPosition + ?.inHours ?? + 0); + final positionSeconds = + (playbackPosition?.inSeconds ?? + 0) % + 60; + final durationFullHours = (mediaState + .mediaItem + ?.duration + ?.inHours ?? + 0); + final durationFullMinutes = + (mediaState.mediaItem?.duration?.inMinutes ?? + 0) % + 60; + final durationSeconds = + (mediaState.mediaItem?.duration?.inSeconds ?? + 0) % + 60; + return Semantics + .fromProperties( + properties: + SemanticsProperties( + label: + "${positionFullHours > 0 ? "$positionFullHours hours " : ""}${positionFullMinutes > 0 ? "$positionFullMinutes minutes " : ""}$positionSeconds seconds of ${durationFullHours > 0 ? "$durationFullHours hours " : ""}${durationFullMinutes > 0 ? "$durationFullMinutes minutes " : ""}$durationSeconds seconds", + ), + excludeSemantics: + true, + container: + true, + child: Row( + children: [ Text( - printDuration(playbackPosition, leadingZeroes: false), - style: TextStyle( - fontSize: 14, - fontWeight: - FontWeight - .w400, - color: Colors - .white - .withOpacity( - 0.8), - ), + printDuration( + playbackPosition, + leadingZeroes: + false), + style: + TextStyle( + fontSize: + 14, + fontWeight: + FontWeight.w400, + color: Colors + .white + .withOpacity(0.8), ), - const SizedBox( - width: 2), - Text( - '/', - style: TextStyle( - color: Colors - .white - .withOpacity( - 0.8), - fontSize: 14, - fontWeight: - FontWeight - .w400, ), - ), - const SizedBox( - width: 2), - Text( - // '3:44', - (mediaState.mediaItem?.duration - ?.inHours ?? - 0.0) >= - 1.0 - ? "${mediaState.mediaItem?.duration?.inHours.toString()}:${((mediaState.mediaItem?.duration?.inMinutes ?? 0) % 60).toString().padLeft(2, '0')}:${((mediaState.mediaItem?.duration?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}" - : "${mediaState.mediaItem?.duration?.inMinutes.toString()}:${((mediaState.mediaItem?.duration?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}", - style: TextStyle( - color: Colors - .white - .withOpacity( - 0.8), - fontSize: 14, - fontWeight: - FontWeight - .w400, + const SizedBox( + width: + 2), + Text( + '/', + style: + TextStyle( + color: Colors + .white + .withOpacity(0.8), + fontSize: + 14, + fontWeight: + FontWeight.w400, + ), + ), + const SizedBox( + width: + 2), + Text( + // '3:44', + (mediaState.mediaItem?.duration?.inHours ?? 0.0) >= + 1.0 + ? "${mediaState.mediaItem?.duration?.inHours.toString()}:${((mediaState.mediaItem?.duration?.inMinutes ?? 0) % 60).toString().padLeft(2, '0')}:${((mediaState.mediaItem?.duration?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}" + : "${mediaState.mediaItem?.duration?.inMinutes.toString()}:${((mediaState.mediaItem?.duration?.inSeconds ?? 0) % 60).toString().padLeft(2, '0')}", + style: + TextStyle( + color: Colors + .white + .withOpacity(0.8), + fontSize: + 14, + fontWeight: + FontWeight.w400, + ), ), - ), - ], - ), - ); - } else { - return const SizedBox.shrink(); - } - } - ) + ], + ), + ); + } else { + return const SizedBox + .shrink(); + } + }) ], ), ], @@ -474,8 +511,9 @@ class NowPlayingBar extends ConsumerWidget { MainAxisAlignment.end, children: [ Padding( - padding: const EdgeInsets.only( - top: 4.0, right: 4.0), + padding: + const EdgeInsets.only( + top: 4.0, right: 4.0), child: AddToPlaylistButton( item: currentTrackBaseItem, queueItem: currentTrack, diff --git a/lib/components/print_duration.dart b/lib/components/print_duration.dart index 2c06fc6ca..eb9a934af 100644 --- a/lib/components/print_duration.dart +++ b/lib/components/print_duration.dart @@ -1,5 +1,6 @@ /// Flutter doesn't have a nice way of formatting durations for some reason so I stole this code from StackOverflow -String printDuration(Duration? duration, { +String printDuration( + Duration? duration, { bool isRemaining = false, bool leadingZeroes = true, }) { @@ -9,12 +10,15 @@ String printDuration(Duration? duration, { String twoDigits(int n) => n.toString().padLeft(2, "0"); final minutes = duration.inMinutes.remainder(60); - String twoDigitMinutes = leadingZeroes ? twoDigits(minutes) : minutes.toString(); + String twoDigitMinutes = + leadingZeroes ? twoDigits(minutes) : minutes.toString(); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); String durationString; if (duration.inHours >= 1) { - String twoDigitHours = leadingZeroes ? twoDigits(duration.inHours) : duration.inHours.toString(); + String twoDigitHours = leadingZeroes + ? twoDigits(duration.inHours) + : duration.inHours.toString(); durationString = "$twoDigitHours:$twoDigitMinutes:$twoDigitSeconds"; } else { durationString = "$twoDigitMinutes:$twoDigitSeconds"; @@ -23,6 +27,6 @@ String printDuration(Duration? duration, { if (isRemaining) { durationString = "-$durationString"; } - + return durationString; } diff --git a/lib/main.dart b/lib/main.dart index e76a12faf..298d1e92f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -117,7 +117,7 @@ void main() async { : LocaleHelper.locale.toString()) : "en_US"; await initializeDateFormatting(localeString, null); - + runApp(const Finamp()); } } diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index 8404ebbfc..41d13e3f7 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -116,85 +116,87 @@ const _keepScreenOnWhilePluggedIn = true; @HiveType(typeId: 28) class FinampSettings { - FinampSettings({ - this.isOffline = _isOfflineDefault, - this.shouldTranscode = _shouldTranscodeDefault, - this.transcodeBitrate = _transcodeBitrateDefault, - // downloadLocations is required since the other values can be created with - // default values. create() is used to return a FinampSettings with - // downloadLocations. - required this.downloadLocations, - this.androidStopForegroundOnPause = _androidStopForegroundOnPauseDefault, - required this.showTabs, - this.onlyShowFavourite = _isFavouriteDefault, - this.sortBy = SortBy.sortName, - this.sortOrder = SortOrder.ascending, - this.songShuffleItemCount = _songShuffleItemCountDefault, - this.volumeNormalizationActive = _volumeNormalizationActiveDefault, - this.volumeNormalizationIOSBaseGain = - _volumeNormalizationIOSBaseGainDefault, - this.volumeNormalizationMode = _volumeNormalizationModeDefault, - this.contentViewType = _contentViewType, - this.playbackSpeedVisibility = _playbackSpeedVisibility, - this.contentGridViewCrossAxisCountPortrait = - _contentGridViewCrossAxisCountPortrait, - this.contentGridViewCrossAxisCountLandscape = - _contentGridViewCrossAxisCountLandscape, - this.showTextOnGridView = _showTextOnGridView, - this.sleepTimerSeconds = _sleepTimerSeconds, - required this.downloadLocationsMap, - this.useCoverAsBackground = _useCoverAsBackground, - this.playerScreenCoverMinimumPadding = _playerScreenCoverMinimumPadding, - this.hideSongArtistsIfSameAsAlbumArtists = - _hideSongArtistsIfSameAsAlbumArtists, - this.showArtistsTopSongs = _showArtistsTopSongs, - this.bufferDurationSeconds = _bufferDurationSeconds, - required this.tabSortBy, - required this.tabSortOrder, - this.loopMode = _defaultLoopMode, - this.playbackSpeed = _defaultPlaybackSpeed, - this.tabOrder = _tabOrder, - this.autoloadLastQueueOnStartup = _autoLoadLastQueueOnStartup, - this.hasCompletedBlurhashImageMigration = true, - this.hasCompletedBlurhashImageMigrationIdFix = true, - this.hasCompleteddownloadsServiceMigration = true, - this.requireWifiForDownloads = false, - this.onlyShowFullyDownloaded = false, - this.showDownloadsWithUnknownLibrary = true, - this.maxConcurrentDownloads = 10, - this.downloadWorkers = 5, - this.resyncOnStartup = _defaultResyncOnStartup, - this.preferQuickSyncs = true, - this.hasCompletedIsarUserMigration = true, - this.downloadTranscodingCodec, - this.downloadTranscodeBitrate, - this.shouldTranscodeDownloads = _shouldTranscodeDownloadsDefault, - this.shouldRedownloadTranscodes = _shouldRedownloadTranscodesDefault, - this.swipeInsertQueueNext = _swipeInsertQueueNext, - this.useFixedSizeGridTiles = false, - this.fixedGridTileSize = _fixedGridTileSizeDefault, - this.allowSplitScreen = true, - this.splitScreenPlayerWidth = _defaultSplitScreenPlayerWidth, - this.enableVibration = _enableVibration, - this.prioritizeCoverFactor = _prioritizeCoverFactor, - this.suppressPlayerPadding = _suppressPlayerPadding, - this.hidePlayerBottomActions = _hidePlayerBottomActions, - this.reportQueueToServer = _reportQueueToServerDefault, - this.periodicPlaybackSessionUpdateFrequencySeconds = - _periodicPlaybackSessionUpdateFrequencySecondsDefault, - this.showArtistChipImage = _showArtistChipImage, - this.trackOfflineFavorites = _trackOfflineFavoritesDefault, - this.showProgressOnNowPlayingBar = _showProgressOnNowPlayingBarDefault, - this.startInstantMixForIndividualTracks = _startInstantMixForIndividualTracksDefault, - this.showLyricsTimestamps = _showLyricsTimestampsDefault, - this.lyricsAlignment = _lyricsAlignmentDefault, - this.lyricsFontSize = _lyricsFontSizeDefault, - this.showLyricsScreenAlbumPrelude = _showLyricsScreenAlbumPreludeDefault, - this.showStopButtonOnMediaNotification = _showStopButtonOnMediaNotificationDefault, - this.showSeekControlsOnMediaNotification = _showSeekControlsOnMediaNotificationDefault, - this.keepScreenOnOption = _keepScreenOnOption, - this.keepScreenOnWhilePluggedIn = _keepScreenOnWhilePluggedIn - }); + FinampSettings( + {this.isOffline = _isOfflineDefault, + this.shouldTranscode = _shouldTranscodeDefault, + this.transcodeBitrate = _transcodeBitrateDefault, + // downloadLocations is required since the other values can be created with + // default values. create() is used to return a FinampSettings with + // downloadLocations. + required this.downloadLocations, + this.androidStopForegroundOnPause = _androidStopForegroundOnPauseDefault, + required this.showTabs, + this.onlyShowFavourite = _isFavouriteDefault, + this.sortBy = SortBy.sortName, + this.sortOrder = SortOrder.ascending, + this.songShuffleItemCount = _songShuffleItemCountDefault, + this.volumeNormalizationActive = _volumeNormalizationActiveDefault, + this.volumeNormalizationIOSBaseGain = + _volumeNormalizationIOSBaseGainDefault, + this.volumeNormalizationMode = _volumeNormalizationModeDefault, + this.contentViewType = _contentViewType, + this.playbackSpeedVisibility = _playbackSpeedVisibility, + this.contentGridViewCrossAxisCountPortrait = + _contentGridViewCrossAxisCountPortrait, + this.contentGridViewCrossAxisCountLandscape = + _contentGridViewCrossAxisCountLandscape, + this.showTextOnGridView = _showTextOnGridView, + this.sleepTimerSeconds = _sleepTimerSeconds, + required this.downloadLocationsMap, + this.useCoverAsBackground = _useCoverAsBackground, + this.playerScreenCoverMinimumPadding = _playerScreenCoverMinimumPadding, + this.hideSongArtistsIfSameAsAlbumArtists = + _hideSongArtistsIfSameAsAlbumArtists, + this.showArtistsTopSongs = _showArtistsTopSongs, + this.bufferDurationSeconds = _bufferDurationSeconds, + required this.tabSortBy, + required this.tabSortOrder, + this.loopMode = _defaultLoopMode, + this.playbackSpeed = _defaultPlaybackSpeed, + this.tabOrder = _tabOrder, + this.autoloadLastQueueOnStartup = _autoLoadLastQueueOnStartup, + this.hasCompletedBlurhashImageMigration = true, + this.hasCompletedBlurhashImageMigrationIdFix = true, + this.hasCompleteddownloadsServiceMigration = true, + this.requireWifiForDownloads = false, + this.onlyShowFullyDownloaded = false, + this.showDownloadsWithUnknownLibrary = true, + this.maxConcurrentDownloads = 10, + this.downloadWorkers = 5, + this.resyncOnStartup = _defaultResyncOnStartup, + this.preferQuickSyncs = true, + this.hasCompletedIsarUserMigration = true, + this.downloadTranscodingCodec, + this.downloadTranscodeBitrate, + this.shouldTranscodeDownloads = _shouldTranscodeDownloadsDefault, + this.shouldRedownloadTranscodes = _shouldRedownloadTranscodesDefault, + this.swipeInsertQueueNext = _swipeInsertQueueNext, + this.useFixedSizeGridTiles = false, + this.fixedGridTileSize = _fixedGridTileSizeDefault, + this.allowSplitScreen = true, + this.splitScreenPlayerWidth = _defaultSplitScreenPlayerWidth, + this.enableVibration = _enableVibration, + this.prioritizeCoverFactor = _prioritizeCoverFactor, + this.suppressPlayerPadding = _suppressPlayerPadding, + this.hidePlayerBottomActions = _hidePlayerBottomActions, + this.reportQueueToServer = _reportQueueToServerDefault, + this.periodicPlaybackSessionUpdateFrequencySeconds = + _periodicPlaybackSessionUpdateFrequencySecondsDefault, + this.showArtistChipImage = _showArtistChipImage, + this.trackOfflineFavorites = _trackOfflineFavoritesDefault, + this.showProgressOnNowPlayingBar = _showProgressOnNowPlayingBarDefault, + this.startInstantMixForIndividualTracks = + _startInstantMixForIndividualTracksDefault, + this.showLyricsTimestamps = _showLyricsTimestampsDefault, + this.lyricsAlignment = _lyricsAlignmentDefault, + this.lyricsFontSize = _lyricsFontSizeDefault, + this.showLyricsScreenAlbumPrelude = _showLyricsScreenAlbumPreludeDefault, + this.showStopButtonOnMediaNotification = + _showStopButtonOnMediaNotificationDefault, + this.showSeekControlsOnMediaNotification = + _showSeekControlsOnMediaNotificationDefault, + this.keepScreenOnOption = _keepScreenOnOption, + this.keepScreenOnWhilePluggedIn = _keepScreenOnWhilePluggedIn}); @HiveField(0, defaultValue: _isOfflineDefault) bool isOffline; @@ -479,7 +481,6 @@ class FinampSettings { SortOrder getSortOrder(TabContentType tabType) { return tabSortOrder[tabType] ?? SortOrder.ascending; } - } enum CustomPlaybackActions { @@ -682,7 +683,6 @@ enum TabContentType { throw const FormatException("Unsupported itemType"); } } - } @HiveType(typeId: 39) @@ -2074,7 +2074,6 @@ enum MediaItemParentType { @JsonSerializable() @HiveType(typeId: 69) class MediaItemId { - MediaItemId({ required this.contentType, required this.parentType, @@ -2089,7 +2088,7 @@ class MediaItemId { MediaItemParentType parentType; @HiveField(2) - String? itemId; + String? itemId; @HiveField(3) String? parentId; @@ -2103,7 +2102,6 @@ class MediaItemId { String toString() { return jsonEncode(toJson()); } - } @HiveType(typeId: 70) @@ -2235,4 +2233,4 @@ enum KeepScreenOnOption { return AppLocalizations.of(context)!.keepScreenOnWhileLyrics; } } -} \ No newline at end of file +} diff --git a/lib/screens/customization_settings_screen.dart b/lib/screens/customization_settings_screen.dart index d8b7d7f21..d89050353 100644 --- a/lib/screens/customization_settings_screen.dart +++ b/lib/screens/customization_settings_screen.dart @@ -39,8 +39,7 @@ class _CustomizationSettingsScreenState body: ListView( children: [ const PlaybackSpeedControlVisibilityDropdownListTile(), - if (!Platform.isIOS) - const ShowStopButtonOnMediaNotificationToggle(), + if (!Platform.isIOS) const ShowStopButtonOnMediaNotificationToggle(), const ShowSeekControlsOnMediaNotificationToggle(), ], ), @@ -56,12 +55,14 @@ class ShowStopButtonOnMediaNotificationToggle extends StatelessWidget { return ValueListenableBuilder>( valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (context, box, child) { - bool? showStopButtonOnMediaNotification = box.get("FinampSettings")?.showStopButtonOnMediaNotification; + bool? showStopButtonOnMediaNotification = + box.get("FinampSettings")?.showStopButtonOnMediaNotification; return SwitchListTile.adaptive( - title: Text(AppLocalizations.of(context)!.showStopButtonOnMediaNotificationTitle), - subtitle: - Text(AppLocalizations.of(context)!.showStopButtonOnMediaNotificationSubtitle), + title: Text(AppLocalizations.of(context)! + .showStopButtonOnMediaNotificationTitle), + subtitle: Text(AppLocalizations.of(context)! + .showStopButtonOnMediaNotificationSubtitle), value: showStopButtonOnMediaNotification ?? false, onChanged: showStopButtonOnMediaNotification == null ? null @@ -85,19 +86,22 @@ class ShowSeekControlsOnMediaNotificationToggle extends StatelessWidget { return ValueListenableBuilder>( valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (context, box, child) { - bool? showSeekControlsOnMediaNotification = box.get("FinampSettings")?.showSeekControlsOnMediaNotification; + bool? showSeekControlsOnMediaNotification = + box.get("FinampSettings")?.showSeekControlsOnMediaNotification; return SwitchListTile.adaptive( - title: Text(AppLocalizations.of(context)!.showSeekControlsOnMediaNotificationTitle), - subtitle: - Text(AppLocalizations.of(context)!.showSeekControlsOnMediaNotificationSubtitle), + title: Text(AppLocalizations.of(context)! + .showSeekControlsOnMediaNotificationTitle), + subtitle: Text(AppLocalizations.of(context)! + .showSeekControlsOnMediaNotificationSubtitle), value: showSeekControlsOnMediaNotification ?? false, onChanged: showSeekControlsOnMediaNotification == null ? null : (value) { FinampSettings finampSettingsTemp = box.get("FinampSettings")!; - finampSettingsTemp.showSeekControlsOnMediaNotification = value; + finampSettingsTemp.showSeekControlsOnMediaNotification = + value; box.put("FinampSettings", finampSettingsTemp); }, ); diff --git a/lib/screens/interaction_settings_screen.dart b/lib/screens/interaction_settings_screen.dart index 11c947a29..bed1cc124 100644 --- a/lib/screens/interaction_settings_screen.dart +++ b/lib/screens/interaction_settings_screen.dart @@ -45,12 +45,14 @@ class StartInstantMixForIndividualTracksSwitch extends StatelessWidget { return ValueListenableBuilder>( valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (_, box, __) { - bool? startInstantMixForIndividualTracks = box.get("FinampSettings")?.startInstantMixForIndividualTracks; + bool? startInstantMixForIndividualTracks = + box.get("FinampSettings")?.startInstantMixForIndividualTracks; return SwitchListTile.adaptive( - title: Text(AppLocalizations.of(context)!.startInstantMixForIndividualTracksSwitchTitle), - subtitle: - Text(AppLocalizations.of(context)!.startInstantMixForIndividualTracksSwitchSubtitle), + title: Text(AppLocalizations.of(context)! + .startInstantMixForIndividualTracksSwitchTitle), + subtitle: Text(AppLocalizations.of(context)! + .startInstantMixForIndividualTracksSwitchSubtitle), value: startInstantMixForIndividualTracks ?? false, onChanged: startInstantMixForIndividualTracks == null ? null diff --git a/lib/screens/layout_settings_screen.dart b/lib/screens/layout_settings_screen.dart index 22d1bc4be..398e60eb9 100644 --- a/lib/screens/layout_settings_screen.dart +++ b/lib/screens/layout_settings_screen.dart @@ -183,12 +183,14 @@ class ShowProgressOnNowPlayingBarToggle extends StatelessWidget { return ValueListenableBuilder>( valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (context, box, child) { - bool? showProgressOnNowPlayingBar = box.get("FinampSettings")?.showProgressOnNowPlayingBar; + bool? showProgressOnNowPlayingBar = + box.get("FinampSettings")?.showProgressOnNowPlayingBar; return SwitchListTile.adaptive( - title: Text(AppLocalizations.of(context)!.showProgressOnNowPlayingBarTitle), - subtitle: - Text(AppLocalizations.of(context)!.showProgressOnNowPlayingBarSubtitle), + title: Text( + AppLocalizations.of(context)!.showProgressOnNowPlayingBarTitle), + subtitle: Text(AppLocalizations.of(context)! + .showProgressOnNowPlayingBarSubtitle), value: showProgressOnNowPlayingBar ?? false, onChanged: showProgressOnNowPlayingBar == null ? null diff --git a/lib/screens/lyrics_screen.dart b/lib/screens/lyrics_screen.dart index 9f0ac532e..609d07f4c 100644 --- a/lib/screens/lyrics_screen.dart +++ b/lib/screens/lyrics_screen.dart @@ -234,7 +234,6 @@ class _LyricsViewState extends ConsumerState @override Widget build(BuildContext context) { - final audioHandler = GetIt.instance(); final metadata = ref.watch(currentTrackMetadataProvider).unwrapPrevious(); @@ -283,7 +282,8 @@ class _LyricsViewState extends ConsumerState message: "Loading lyrics...", icon: TablerIcons.microphone_2, ); - } else if (!metadata.hasValue || metadata.value == null || + } else if (!metadata.hasValue || + metadata.value == null || metadata.value!.hasLyrics && metadata.value!.lyrics == null && !metadata.isLoading) { @@ -382,7 +382,11 @@ class _LyricsViewState extends ConsumerState child: Center( child: SizedBox( height: constraints.maxHeight * 0.55, - child: (finampSettings?.showLyricsScreenAlbumPrelude ?? true) ? const PlayerScreenAlbumImage() : null)), + child: (finampSettings + ?.showLyricsScreenAlbumPrelude ?? + true) + ? const PlayerScreenAlbumImage() + : null)), ), ), AutoScrollTag( @@ -465,17 +469,14 @@ class _LyricLine extends ConsumerWidget { onTap: isSynchronized ? onTap : null, child: Padding( padding: EdgeInsets.symmetric(vertical: isSynchronized ? 10.0 : 6.0), - child: - Text.rich( - textAlign: lyricsAlignmentToTextAlign(finampSettings?.lyricsAlignment ?? LyricsAlignment.start), + child: Text.rich( + textAlign: lyricsAlignmentToTextAlign( + finampSettings?.lyricsAlignment ?? LyricsAlignment.start), softWrap: true, - TextSpan( - children: [ - if ( - line.start != null - && (line.text?.trim().isNotEmpty ?? false) - && (finampSettings?.showLyricsTimestamps ?? true) - ) + TextSpan(children: [ + if (line.start != null && + (line.text?.trim().isNotEmpty ?? false) && + (finampSettings?.showLyricsTimestamps ?? true)) WidgetSpan( alignment: PlaceholderAlignment.bottom, child: Padding( @@ -487,27 +488,33 @@ class _LyricLine extends ConsumerWidget { ? Colors.grey : Theme.of(context).textTheme.bodyLarge!.color, fontSize: 16, - height: 1.75 * (lyricsFontSizeToSize(finampSettings?.lyricsFontSize ?? LyricsFontSize.medium) / 26), + height: 1.75 * + (lyricsFontSizeToSize( + finampSettings?.lyricsFontSize ?? + LyricsFontSize.medium) / + 26), ), ), ), ), TextSpan( - text: line.text ?? "", - style: TextStyle( - color: lowlightLine - ? Colors.grey - : Theme.of(context).textTheme.bodyLarge!.color, - fontWeight: lowlightLine || !isSynchronized - ? FontWeight.normal - : FontWeight.w500, - letterSpacing: lowlightLine || !isSynchronized - ? 0.05 - : -0.045, // keep text width consistent across the different weights - fontSize: lyricsFontSizeToSize(finampSettings?.lyricsFontSize ?? LyricsFontSize.medium) * (isSynchronized ? 1.0 : 0.75), - height: 1.25, - ), + text: line.text ?? "", + style: TextStyle( + color: lowlightLine + ? Colors.grey + : Theme.of(context).textTheme.bodyLarge!.color, + fontWeight: lowlightLine || !isSynchronized + ? FontWeight.normal + : FontWeight.w500, + letterSpacing: lowlightLine || !isSynchronized + ? 0.05 + : -0.045, // keep text width consistent across the different weights + fontSize: lyricsFontSizeToSize(finampSettings?.lyricsFontSize ?? + LyricsFontSize.medium) * + (isSynchronized ? 1.0 : 0.75), + height: 1.25, ), + ), ]), ), ), diff --git a/lib/screens/lyrics_settings_screen.dart b/lib/screens/lyrics_settings_screen.dart index e90138db8..29647d720 100644 --- a/lib/screens/lyrics_settings_screen.dart +++ b/lib/screens/lyrics_settings_screen.dart @@ -129,7 +129,6 @@ class LyricsFontSizeSelector extends StatelessWidget { } } - class ShowLyricsScreenAlbumPreludeToggle extends StatelessWidget { const ShowLyricsScreenAlbumPreludeToggle({super.key}); @@ -142,9 +141,10 @@ class ShowLyricsScreenAlbumPreludeToggle extends StatelessWidget { box.get("FinampSettings")?.showLyricsScreenAlbumPrelude; return SwitchListTile.adaptive( - title: Text(AppLocalizations.of(context)!.showLyricsScreenAlbumPreludeTitle), - subtitle: - Text(AppLocalizations.of(context)!.showLyricsScreenAlbumPreludeSubtitle), + title: Text( + AppLocalizations.of(context)!.showLyricsScreenAlbumPreludeTitle), + subtitle: Text(AppLocalizations.of(context)! + .showLyricsScreenAlbumPreludeSubtitle), value: showLyricsScreenAlbumPrelude ?? false, onChanged: showLyricsScreenAlbumPrelude == null ? null diff --git a/lib/screens/player_settings_screen.dart b/lib/screens/player_settings_screen.dart index 9fe353da2..40dce3330 100644 --- a/lib/screens/player_settings_screen.dart +++ b/lib/screens/player_settings_screen.dart @@ -71,7 +71,8 @@ class hidePlayerBottomActionsSwitch extends StatelessWidget { return SwitchListTile.adaptive( title: Text(AppLocalizations.of(context)!.hidePlayerBottomActions), - subtitle: Text(AppLocalizations.of(context)!.hidePlayerBottomActionsSubtitle), + subtitle: Text( + AppLocalizations.of(context)!.hidePlayerBottomActionsSubtitle), value: hideQueue ?? false, onChanged: hideQueue == null ? null diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index e3cca288c..4947b8d9c 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -46,97 +46,100 @@ class SettingsScreen extends StatelessWidget { final applicationLegalese = AppLocalizations.of(context)!.applicationLegalese(repoLink); PackageInfo packageInfo = await PackageInfo.fromPlatform(); - + ThemeData theme = Theme.of(context); const linkStyle = TextStyle( color: Colors.blue, decoration: TextDecoration.underline, ); - + showAboutDialog( - context: context, - applicationName: packageInfo.appName, - applicationVersion: packageInfo.version, - applicationIcon: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Image.asset( - 'images/finamp_cropped.png', - width: 56, - height: 56, - ), - ), - applicationLegalese: applicationLegalese, - children: [ - const SizedBox(height: 20), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: TextStyle(color: theme.textTheme.bodyMedium!.color), - children: [ - TextSpan( - text: localizations.finampTagline, - style: const TextStyle(fontStyle: FontStyle.italic, fontWeight: FontWeight.w500), - ), - const TextSpan( - text: '\n\n', - ), - TextSpan( - text: localizations.aboutContributionPrompt, - ), - const TextSpan( - text: '\n\n', - ), - TextSpan( - text: '${localizations.aboutContributionLink}\n', - ), - TextSpan( - text: repoLink, - style: linkStyle, - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(Uri.parse(repoLink)); - }, - ), - const TextSpan( - text: '\n\n', - ), - TextSpan( - text: '${localizations.aboutTranslations}\n', - ), - TextSpan( - text: translationsLink, - style: linkStyle, - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(Uri.parse(translationsLink)); - }, - ), - const TextSpan( - text: '\n\n', - ), - TextSpan( - text: '${localizations.aboutReleaseNotes}\n', - ), - TextSpan( - text: releaseNotesLink, - style: linkStyle, - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(Uri.parse(releaseNotesLink)); - }, - ), - const TextSpan( - text: '\n\n\n', - ), - TextSpan( - text: localizations.aboutThanks, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ], + context: context, + applicationName: packageInfo.appName, + applicationVersion: packageInfo.version, + applicationIcon: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Image.asset( + 'images/finamp_cropped.png', + width: 56, + height: 56, ), ), - ] - ); + applicationLegalese: applicationLegalese, + children: [ + const SizedBox(height: 20), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: TextStyle( + color: theme.textTheme.bodyMedium!.color), + children: [ + TextSpan( + text: localizations.finampTagline, + style: const TextStyle( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w500), + ), + const TextSpan( + text: '\n\n', + ), + TextSpan( + text: localizations.aboutContributionPrompt, + ), + const TextSpan( + text: '\n\n', + ), + TextSpan( + text: '${localizations.aboutContributionLink}\n', + ), + TextSpan( + text: repoLink, + style: linkStyle, + recognizer: TapGestureRecognizer() + ..onTap = () async { + await launchUrl(Uri.parse(repoLink)); + }, + ), + const TextSpan( + text: '\n\n', + ), + TextSpan( + text: '${localizations.aboutTranslations}\n', + ), + TextSpan( + text: translationsLink, + style: linkStyle, + recognizer: TapGestureRecognizer() + ..onTap = () async { + await launchUrl(Uri.parse(translationsLink)); + }, + ), + const TextSpan( + text: '\n\n', + ), + TextSpan( + text: '${localizations.aboutReleaseNotes}\n', + ), + TextSpan( + text: releaseNotesLink, + style: linkStyle, + recognizer: TapGestureRecognizer() + ..onTap = () async { + await launchUrl(Uri.parse(releaseNotesLink)); + }, + ), + const TextSpan( + text: '\n\n\n', + ), + TextSpan( + text: localizations.aboutThanks, + style: + const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + ]); }, ), ) diff --git a/lib/services/android_auto_helper.dart b/lib/services/android_auto_helper.dart index 0ace9d234..5b4aa79e8 100644 --- a/lib/services/android_auto_helper.dart +++ b/lib/services/android_auto_helper.dart @@ -20,13 +20,11 @@ class AndroidAutoSearchQuery { Map? extras; AndroidAutoSearchQuery(this.rawQuery, this.extras); - } class AndroidAutoHelper { - static final _androidAutoHelperLogger = Logger("AndroidAutoHelper"); - + final _finampUserHelper = GetIt.instance(); final _jellyfinApiHelper = GetIt.instance(); final _downloadsService = GetIt.instance(); @@ -41,7 +39,8 @@ class AndroidAutoHelper { AndroidAutoSearchQuery? get lastSearchQuery => _lastSearchQuery; Future getParentFromId(String parentId) async { - final downloadedParent = await _downloadsService.getCollectionInfo(id: parentId); + final downloadedParent = + await _downloadsService.getCollectionInfo(id: parentId); if (downloadedParent != null) { return downloadedParent.baseItem; } else if (FinampSettingsHelper.finampSettings.isOffline) { @@ -52,20 +51,24 @@ class AndroidAutoHelper { } Future> getBaseItems(MediaItemId itemId) async { - // limit amount so it doesn't crash / take forever on large libraries const onlineModeLimit = 250; const offlineModeLimit = 1000; - final sortBy = FinampSettingsHelper.finampSettings.getTabSortBy(itemId.contentType); - final sortOrder = FinampSettingsHelper.finampSettings.getSortOrder(itemId.contentType); + final sortBy = + FinampSettingsHelper.finampSettings.getTabSortBy(itemId.contentType); + final sortOrder = + FinampSettingsHelper.finampSettings.getSortOrder(itemId.contentType); // if we are in offline mode and in root parent/collection, display all matching downloaded parents - if (FinampSettingsHelper.finampSettings.isOffline && itemId.parentType == MediaItemParentType.rootCollection) { + if (FinampSettingsHelper.finampSettings.isOffline && + itemId.parentType == MediaItemParentType.rootCollection) { List baseItems = []; - for (final downloadedParent in await _downloadsService.getAllCollections()) { + for (final downloadedParent + in await _downloadsService.getAllCollections()) { if (baseItems.length >= offlineModeLimit) break; - if (downloadedParent.baseItem != null && downloadedParent.baseItemType == itemId.contentType.itemType) { + if (downloadedParent.baseItem != null && + downloadedParent.baseItemType == itemId.contentType.itemType) { baseItems.add(downloadedParent.baseItem!); } } @@ -75,48 +78,59 @@ class AndroidAutoHelper { // use downloaded parent only in offline mode // otherwise we only play downloaded songs from albums/collections, not all of them // downloaded songs will be played from device when resolving them to media items - if (FinampSettingsHelper.finampSettings.isOffline && itemId.parentType == MediaItemParentType.collection) { - + if (FinampSettingsHelper.finampSettings.isOffline && + itemId.parentType == MediaItemParentType.collection) { if (itemId.contentType == TabContentType.genres) { final genreBaseItem = await getParentFromId(itemId.itemId!); - - final List genreAlbums = (await _downloadsService.getAllCollections( - baseTypeFilter: BaseItemDtoType.album, - relatedTo: genreBaseItem)).toList() - .map((e) => e.baseItem).whereNotNull().toList(); - genreAlbums.sort((a, b) => (a.premiereDate ?? "") - .compareTo(b.premiereDate ?? "")); + + final List genreAlbums = + (await _downloadsService.getAllCollections( + baseTypeFilter: BaseItemDtoType.album, + relatedTo: genreBaseItem)) + .toList() + .map((e) => e.baseItem) + .whereNotNull() + .toList(); + genreAlbums.sort( + (a, b) => (a.premiereDate ?? "").compareTo(b.premiereDate ?? "")); return genreAlbums; } else if (itemId.contentType == TabContentType.artists) { - final artistBaseItem = await getParentFromId(itemId.itemId!); - - final List artistAlbums = (await _downloadsService.getAllCollections( - baseTypeFilter: BaseItemDtoType.album, - relatedTo: artistBaseItem)).toList() - .map((e) => e.baseItem).whereNotNull().toList(); - artistAlbums.sort((a, b) => (a.premiereDate ?? "") - .compareTo(b.premiereDate ?? "")); + + final List artistAlbums = + (await _downloadsService.getAllCollections( + baseTypeFilter: BaseItemDtoType.album, + relatedTo: artistBaseItem)) + .toList() + .map((e) => e.baseItem) + .whereNotNull() + .toList(); + artistAlbums.sort( + (a, b) => (a.premiereDate ?? "").compareTo(b.premiereDate ?? "")); final List allSongs = []; for (var album in artistAlbums) { - allSongs.addAll(await _downloadsService - .getCollectionSongs(album, playable: true)); + allSongs.addAll(await _downloadsService.getCollectionSongs(album, + playable: true)); } return allSongs; } else { - var downloadedParent = await _downloadsService.getCollectionInfo(id: itemId.itemId); + var downloadedParent = + await _downloadsService.getCollectionInfo(id: itemId.itemId); if (downloadedParent != null && downloadedParent.baseItem != null) { - final downloadedItems = await _downloadsService.getCollectionSongs(downloadedParent.baseItem!); + final downloadedItems = await _downloadsService + .getCollectionSongs(downloadedParent.baseItem!); if (downloadedItems.length >= offlineModeLimit) { - downloadedItems.removeRange(offlineModeLimit, downloadedItems.length - 1); + downloadedItems.removeRange( + offlineModeLimit, downloadedItems.length - 1); } // only sort items if we are not playing them - return _isPlayable(contentType: itemId.contentType) ? downloadedItems : sortItems(downloadedItems, sortBy, sortOrder); + return _isPlayable(contentType: itemId.contentType) + ? downloadedItems + : sortItems(downloadedItems, sortBy, sortOrder); } } - } // fetch the online version if we can't get offline version @@ -155,10 +169,18 @@ class AndroidAutoHelper { // if parent id is defined, use that to get items. // otherwise, use the current view as fallback to ensure we get the correct items. final parentItem = itemId.parentType == MediaItemParentType.collection - ? BaseItemDto(id: itemId.itemId!, type: itemId.contentType.itemType.idString) - : (itemId.contentType == TabContentType.playlists ? null : _finampUserHelper.currentUser?.currentView); - - final items = await _jellyfinApiHelper.getItems(parentItem: parentItem, sortBy: sortBy.jellyfinName(itemId.contentType), sortOrder: sortOrder.toString(), includeItemTypes: includeItemTypes, limit: onlineModeLimit); + ? BaseItemDto( + id: itemId.itemId!, type: itemId.contentType.itemType.idString) + : (itemId.contentType == TabContentType.playlists + ? null + : _finampUserHelper.currentUser?.currentView); + + final items = await _jellyfinApiHelper.getItems( + parentItem: parentItem, + sortBy: sortBy.jellyfinName(itemId.contentType), + sortOrder: sortOrder.toString(), + includeItemTypes: includeItemTypes, + limit: onlineModeLimit); return items ?? []; } @@ -170,7 +192,9 @@ class AndroidAutoHelper { final List recentMediaItems = []; for (final item in recentItems) { if (item.baseItem == null) continue; - final mediaItem = await queueService.generateMediaItem(item.baseItem!, parentType: MediaItemParentType.collection, isPlayable: _isPlayable); + final mediaItem = await queueService.generateMediaItem(item.baseItem!, + parentType: MediaItemParentType.collection, + isPlayable: _isPlayable); recentMediaItems.add(mediaItem); } return recentMediaItems; @@ -180,11 +204,11 @@ class AndroidAutoHelper { } } - Future> searchItems(AndroidAutoSearchQuery searchQuery) async { + Future> searchItems( + AndroidAutoSearchQuery searchQuery) async { final queueService = GetIt.instance(); try { - final searchFuture = Future.wait([ _searchPlaylists(searchQuery, limit: 3), _searchTracks(searchQuery, limit: 5), @@ -192,35 +216,69 @@ class AndroidAutoHelper { _searchArtists(searchQuery, limit: 3), ]); - final [playlistResults, trackResults, albumResults, artistResults] = await searchFuture; + final [playlistResults, trackResults, albumResults, artistResults] = + await searchFuture; final List allSearchResults = playlistResults - .followedBy(trackResults) - .followedBy(albumResults) - .followedBy(artistResults) - .toList(); - + .followedBy(trackResults) + .followedBy(albumResults) + .followedBy(artistResults) + .toList(); + final List mediaItems = []; for (final item in allSearchResults) { - final mediaItem = await queueService.generateMediaItem(item, parentType: MediaItemParentType.collection, parentId: item.parentId, isPlayable: _isPlayable); + final mediaItem = await queueService.generateMediaItem(item, + parentType: MediaItemParentType.collection, + parentId: item.parentId, + isPlayable: _isPlayable); // assign a group hint based on the item type, so Android Auto can group search results by type - switch(item.type) { + switch (item.type) { case "Audio": - mediaItem.extras?["android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = GlobalSnackbar.materialAppScaffoldKey.currentContext != null ? AppLocalizations.of(GlobalSnackbar.materialAppScaffoldKey.currentContext!)!.songs : "Tracks"; + mediaItem.extras?[ + "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = + GlobalSnackbar.materialAppScaffoldKey.currentContext != null + ? AppLocalizations.of(GlobalSnackbar + .materialAppScaffoldKey.currentContext!)! + .songs + : "Tracks"; break; case "MusicAlbum": - mediaItem.extras?["android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = GlobalSnackbar.materialAppScaffoldKey.currentContext != null ? AppLocalizations.of(GlobalSnackbar.materialAppScaffoldKey.currentContext!)!.albums : "Albums"; + mediaItem.extras?[ + "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = + GlobalSnackbar.materialAppScaffoldKey.currentContext != null + ? AppLocalizations.of(GlobalSnackbar + .materialAppScaffoldKey.currentContext!)! + .albums + : "Albums"; break; case "MusicArtist": - mediaItem.extras?["android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = GlobalSnackbar.materialAppScaffoldKey.currentContext != null ? AppLocalizations.of(GlobalSnackbar.materialAppScaffoldKey.currentContext!)!.artists : "Artists"; + mediaItem.extras?[ + "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = + GlobalSnackbar.materialAppScaffoldKey.currentContext != null + ? AppLocalizations.of(GlobalSnackbar + .materialAppScaffoldKey.currentContext!)! + .artists + : "Artists"; break; case "MusicGenre": - mediaItem.extras?["android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = GlobalSnackbar.materialAppScaffoldKey.currentContext != null ? AppLocalizations.of(GlobalSnackbar.materialAppScaffoldKey.currentContext!)!.genres : "Genres"; + mediaItem.extras?[ + "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = + GlobalSnackbar.materialAppScaffoldKey.currentContext != null + ? AppLocalizations.of(GlobalSnackbar + .materialAppScaffoldKey.currentContext!)! + .genres + : "Genres"; break; case "Playlist": - mediaItem.extras?["android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = GlobalSnackbar.materialAppScaffoldKey.currentContext != null ? AppLocalizations.of(GlobalSnackbar.materialAppScaffoldKey.currentContext!)!.playlists : "Playlists"; + mediaItem.extras?[ + "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"] = + GlobalSnackbar.materialAppScaffoldKey.currentContext != null + ? AppLocalizations.of(GlobalSnackbar + .materialAppScaffoldKey.currentContext!)! + .playlists + : "Playlists"; break; default: break; @@ -243,20 +301,25 @@ class AndroidAutoHelper { if (searchQuery.rawQuery.isEmpty) { return await shuffleAllSongs(); } - + BaseItemDtoType? itemType = TabContentType.songs.itemType; String? enhancedQuery; bool searchForPlaylists = false; - if (searchQuery.extras?["android.intent.extra.album"] != null && searchQuery.extras?["android.intent.extra.artist"] != null && searchQuery.extras?["android.intent.extra.title"] != null) { + if (searchQuery.extras?["android.intent.extra.album"] != null && + searchQuery.extras?["android.intent.extra.artist"] != null && + searchQuery.extras?["android.intent.extra.title"] != null) { // if all metadata is provided, search for song itemType = TabContentType.songs.itemType; enhancedQuery = searchQuery.extras?["android.intent.extra.title"]; - } else if (searchQuery.extras?["android.intent.extra.album"] != null && searchQuery.extras?["android.intent.extra.artist"] != null && searchQuery.extras?["android.intent.extra.title"] == null) { + } else if (searchQuery.extras?["android.intent.extra.album"] != null && + searchQuery.extras?["android.intent.extra.artist"] != null && + searchQuery.extras?["android.intent.extra.title"] == null) { // if only album is provided, search for album itemType = TabContentType.albums.itemType; enhancedQuery = searchQuery.extras?["android.intent.extra.album"]; - } else if (searchQuery.extras?["android.intent.extra.artist"] != null && searchQuery.extras?["android.intent.extra.title"] == null) { + } else if (searchQuery.extras?["android.intent.extra.artist"] != null && + searchQuery.extras?["android.intent.extra.title"] == null) { // if only artist is provided, search for artist itemType = TabContentType.artists.itemType; enhancedQuery = searchQuery.extras?["android.intent.extra.artist"]; @@ -265,27 +328,32 @@ class AndroidAutoHelper { searchForPlaylists = true; } - _androidAutoHelperLogger.info("Searching for: $itemType that matches query '${enhancedQuery ?? searchQuery.rawQuery}'${searchForPlaylists ? ", including (and preferring) playlists" : ""}"); + _androidAutoHelperLogger.info( + "Searching for: $itemType that matches query '${enhancedQuery ?? searchQuery.rawQuery}'${searchForPlaylists ? ", including (and preferring) playlists" : ""}"); - final searchTerm = searchForPlaylists ? - searchQuery.rawQuery.trim() : // always use the raw query for searching playlists - enhancedQuery?.trim() ?? searchQuery.rawQuery.trim(); + final searchTerm = searchForPlaylists + ? searchQuery.rawQuery.trim() + : // always use the raw query for searching playlists + enhancedQuery?.trim() ?? searchQuery.rawQuery.trim(); if (searchForPlaylists) { try { List? searchResult; if (FinampSettingsHelper.finampSettings.isOffline) { - List? offlineItems = await _downloadsService.getAllCollections( - nameFilter: searchTerm, - baseTypeFilter: TabContentType.playlists.itemType, - fullyDownloaded: false, - viewFilter: finampUserHelper.currentUser?.currentView?.id, - childViewFilter: null, - nullableViewFilters: FinampSettingsHelper.finampSettings.showDownloadsWithUnknownLibrary, - onlyFavorites: false); - - searchResult = offlineItems.map((e) => e.baseItem).whereNotNull().toList(); + List? offlineItems = + await _downloadsService.getAllCollections( + nameFilter: searchTerm, + baseTypeFilter: TabContentType.playlists.itemType, + fullyDownloaded: false, + viewFilter: finampUserHelper.currentUser?.currentView?.id, + childViewFilter: null, + nullableViewFilters: FinampSettingsHelper + .finampSettings.showDownloadsWithUnknownLibrary, + onlyFavorites: false); + + searchResult = + offlineItems.map((e) => e.baseItem).whereNotNull().toList(); } else { searchResult = await jellyfinApiHelper.getItems( parentItem: null, // always use global playlists @@ -297,20 +365,28 @@ class AndroidAutoHelper { } if (searchResult?.isNotEmpty ?? false) { - final playlist = searchResult![0]; List? items; - if (FinampSettingsHelper.finampSettings.isOffline) { - items = await _downloadsService.getCollectionSongs(playlist, playable: true); + if (FinampSettingsHelper.finampSettings.isOffline) { + items = await _downloadsService.getCollectionSongs(playlist, + playable: true); } else { - items = await _jellyfinApiHelper.getItems(parentItem: playlist, includeItemTypes: TabContentType.songs.itemType.idString, sortBy: "ParentIndexNumber,IndexNumber,SortName", sortOrder: "Ascending", limit: 200); + items = await _jellyfinApiHelper.getItems( + parentItem: playlist, + includeItemTypes: TabContentType.songs.itemType.idString, + sortBy: "ParentIndexNumber,IndexNumber,SortName", + sortOrder: "Ascending", + limit: 200); } - - _androidAutoHelperLogger.info("Playing playlist: ${playlist.name} (${items?.length} songs)"); - await queueService.startPlayback(items: items ?? [], source: QueueItemSource( + _androidAutoHelperLogger.info( + "Playing playlist: ${playlist.name} (${items?.length} songs)"); + + await queueService.startPlayback( + items: items ?? [], + source: QueueItemSource( type: QueueItemSourceType.playlist, name: QueueItemSourceName( type: QueueItemSourceNameType.preTranslated, @@ -318,20 +394,19 @@ class AndroidAutoHelper { id: playlist.id, item: playlist, ), - order: FinampPlaybackOrder.linear, //TODO add a setting that sets the default (because Android Auto doesn't give use the prompt as an extra), or use the current order? + order: FinampPlaybackOrder + .linear, //TODO add a setting that sets the default (because Android Auto doesn't give use the prompt as an extra), or use the current order? ); - } else { - _androidAutoHelperLogger.warning("No playlists found for query: ${enhancedQuery ?? searchQuery.rawQuery}"); + _androidAutoHelperLogger.warning( + "No playlists found for query: ${enhancedQuery ?? searchQuery.rawQuery}"); } - } catch (e) { _androidAutoHelperLogger.warning("Couldn't search for playlists: $e"); } } try { - // first try with any metadata we could get (could be corrected based on metadata or localizations, or just the raw query) List? searchResult = await _getResults( searchTerm: searchTerm, @@ -339,50 +414,75 @@ class AndroidAutoHelper { ); if (searchResult == null || searchResult.isEmpty) { - _androidAutoHelperLogger.warning("No search results found for search term: $searchTerm)"); + _androidAutoHelperLogger + .warning("No search results found for search term: $searchTerm)"); if (enhancedQuery != null) { - // if we got additional metadata, we already tried searching with it // now try searching with the raw query searchResult = await _getResults( searchTerm: searchQuery.rawQuery.trim(), itemTypes: [itemType], ); - } - + if (searchResult == null || searchResult.isEmpty) { - _androidAutoHelperLogger.warning("No search results found for search term (raw query): ${searchQuery.rawQuery}"); + _androidAutoHelperLogger.warning( + "No search results found for search term (raw query): ${searchQuery.rawQuery}"); return; } } final selectedResult = searchResult.firstWhere((element) { - if (itemType == TabContentType.songs.itemType && searchQuery.extras?["android.intent.extra.artist"] != null) { - return element.albumArtists?.any((artist) => (artist.name?.isNotEmpty ?? false) && (searchQuery.extras?["android.intent.extra.artist"]?.toString().toLowerCase().contains(artist.name?.toLowerCase() ?? "") ?? false)) ?? false; - } else if (itemType == TabContentType.songs.itemType && searchQuery.extras?["android.intent.extra.artist"] != null) { - return element.albumArtists?.any((artist) => (artist.name?.isNotEmpty ?? false) && (searchQuery.extras?["android.intent.extra.artist"]?.toString().toLowerCase().contains(artist.name?.toLowerCase() ?? "") ?? false)) ?? false; + if (itemType == TabContentType.songs.itemType && + searchQuery.extras?["android.intent.extra.artist"] != null) { + return element.albumArtists?.any((artist) => + (artist.name?.isNotEmpty ?? false) && + (searchQuery.extras?["android.intent.extra.artist"] + ?.toString() + .toLowerCase() + .contains(artist.name?.toLowerCase() ?? "") ?? + false)) ?? + false; + } else if (itemType == TabContentType.songs.itemType && + searchQuery.extras?["android.intent.extra.artist"] != null) { + return element.albumArtists?.any((artist) => + (artist.name?.isNotEmpty ?? false) && + (searchQuery.extras?["android.intent.extra.artist"] + ?.toString() + .toLowerCase() + .contains(artist.name?.toLowerCase() ?? "") ?? + false)) ?? + false; } else { return false; } - }, orElse: () => searchResult![0] - ); + }, orElse: () => searchResult![0]); + + _androidAutoHelperLogger + .info("Playing from search: ${selectedResult.name}"); - _androidAutoHelperLogger.info("Playing from search: ${selectedResult.name}"); - if (itemType == TabContentType.albums.itemType) { final album = selectedResult; List? items; - if (FinampSettingsHelper.finampSettings.isOffline) { - items = await _downloadsService.getCollectionSongs(album, playable: true); + if (FinampSettingsHelper.finampSettings.isOffline) { + items = + await _downloadsService.getCollectionSongs(album, playable: true); } else { - items = await _jellyfinApiHelper.getItems(parentItem: album, includeItemTypes: TabContentType.songs.itemType.idString, sortBy: "ParentIndexNumber,IndexNumber,SortName", sortOrder: "Ascending", limit: 200); + items = await _jellyfinApiHelper.getItems( + parentItem: album, + includeItemTypes: TabContentType.songs.itemType.idString, + sortBy: "ParentIndexNumber,IndexNumber,SortName", + sortOrder: "Ascending", + limit: 200); } - _androidAutoHelperLogger.info("Playing album: ${album.name} (${items?.length} songs)"); + _androidAutoHelperLogger + .info("Playing album: ${album.name} (${items?.length} songs)"); - await queueService.startPlayback(items: items ?? [], source: QueueItemSource( + await queueService.startPlayback( + items: items ?? [], + source: QueueItemSource( type: QueueItemSourceType.album, name: QueueItemSourceName( type: QueueItemSourceNameType.preTranslated, @@ -390,11 +490,16 @@ class AndroidAutoHelper { id: album.id, item: album, ), - order: FinampPlaybackOrder.linear, //TODO add a setting that sets the default (because Android Auto doesn't give use the prompt as an extra), or use the current order? + order: FinampPlaybackOrder + .linear, //TODO add a setting that sets the default (because Android Auto doesn't give use the prompt as an extra), or use the current order? ); } else if (itemType == TabContentType.artists.itemType) { if (FinampSettingsHelper.finampSettings.isOffline) { - final parentBaseItems = await getBaseItems(MediaItemId(contentType: TabContentType.artists, parentType: MediaItemParentType.collection, parentId: selectedResult.id, itemId: selectedResult.id)); + final parentBaseItems = await getBaseItems(MediaItemId( + contentType: TabContentType.artists, + parentType: MediaItemParentType.collection, + parentId: selectedResult.id, + itemId: selectedResult.id)); await queueService.startPlayback( items: parentBaseItems, @@ -409,7 +514,8 @@ class AndroidAutoHelper { order: FinampPlaybackOrder.linear, ); } else { - await audioServiceHelper.startInstantMixForArtists([selectedResult]).then((value) => 1); + await audioServiceHelper + .startInstantMixForArtists([selectedResult]).then((value) => 1); } } else { if (FinampSettingsHelper.finampSettings.isOffline) { @@ -418,38 +524,41 @@ class AndroidAutoHelper { offlineItems = await _downloadsService.getAllSongs( // nameFilter: widget.searchTerm, viewFilter: finampUserHelper.currentUser?.currentView?.id, - nullableViewFilters: - FinampSettingsHelper.finampSettings.showDownloadsWithUnknownLibrary); + nullableViewFilters: FinampSettingsHelper + .finampSettings.showDownloadsWithUnknownLibrary); - var items = offlineItems - .map((e) => e.baseItem) - .whereNotNull() - .toList(); + var items = + offlineItems.map((e) => e.baseItem).whereNotNull().toList(); items = sortItems( items, - FinampSettingsHelper.finampSettings.tabSortBy[TabContentType.songs]!, - FinampSettingsHelper.finampSettings.tabSortOrder[TabContentType.songs]!); + FinampSettingsHelper + .finampSettings.tabSortBy[TabContentType.songs]!, + FinampSettingsHelper + .finampSettings.tabSortOrder[TabContentType.songs]!); - final indexOfSelected = items.indexWhere((element) => element.id == selectedResult.id); + final indexOfSelected = + items.indexWhere((element) => element.id == selectedResult.id); return await queueService.startPlayback( items: items, startingIndex: indexOfSelected, source: QueueItemSource( - name: const QueueItemSourceName( - type: QueueItemSourceNameType.mix), + name: + const QueueItemSourceName(type: QueueItemSourceNameType.mix), type: QueueItemSourceType.allSongs, id: selectedResult.id, ), ); - } else { - await audioServiceHelper.startInstantMixForItem(selectedResult).then((value) => 1); + await audioServiceHelper + .startInstantMixForItem(selectedResult) + .then((value) => 1); } } } catch (err) { - _androidAutoHelperLogger.severe("Error while playing from search query: $err"); + _androidAutoHelperLogger + .severe("Error while playing from search query: $err"); } } @@ -457,7 +566,8 @@ class AndroidAutoHelper { final audioServiceHelper = GetIt.instance(); try { - await audioServiceHelper.shuffleAll(FinampSettingsHelper.finampSettings.onlyShowFavourite); + await audioServiceHelper + .shuffleAll(FinampSettingsHelper.finampSettings.onlyShowFavourite); } catch (err) { _androidAutoHelperLogger.severe("Error while shuffling all songs", err); } @@ -469,7 +579,10 @@ class AndroidAutoHelper { final List mediaItems = []; for (final item in items) { - final mediaItem = await queueService.generateMediaItem(item, parentType: MediaItemParentType.collection, parentId: item.parentId, isPlayable: _isPlayable); + final mediaItem = await queueService.generateMediaItem(item, + parentType: MediaItemParentType.collection, + parentId: item.parentId, + isPlayable: _isPlayable); mediaItems.add(mediaItem); } return mediaItems; @@ -483,7 +596,8 @@ class AndroidAutoHelper { // shouldn't happen, but just in case if (!_isPlayable(contentType: itemId.contentType)) { - _androidAutoHelperLogger.warning("Tried to play from media id with non-playable item type ${itemId.parentType.name}"); + _androidAutoHelperLogger.warning( + "Tried to play from media id with non-playable item type ${itemId.parentType.name}"); return; } @@ -494,38 +608,40 @@ class AndroidAutoHelper { offlineItems = await _downloadsService.getAllSongs( // nameFilter: widget.searchTerm, viewFilter: finampUserHelper.currentUser?.currentView?.id, - nullableViewFilters: - FinampSettingsHelper.finampSettings.showDownloadsWithUnknownLibrary); + nullableViewFilters: FinampSettingsHelper + .finampSettings.showDownloadsWithUnknownLibrary); - var items = offlineItems - .map((e) => e.baseItem) - .whereNotNull() - .toList(); + var items = offlineItems.map((e) => e.baseItem).whereNotNull().toList(); items = sortItems( items, - FinampSettingsHelper.finampSettings.tabSortBy[TabContentType.songs]!, - FinampSettingsHelper.finampSettings.tabSortOrder[TabContentType.songs]!); + FinampSettingsHelper + .finampSettings.tabSortBy[TabContentType.songs]!, + FinampSettingsHelper + .finampSettings.tabSortOrder[TabContentType.songs]!); - final indexOfSelected = items.indexWhere((element) => element.id == itemId.itemId); + final indexOfSelected = + items.indexWhere((element) => element.id == itemId.itemId); return await queueService.startPlayback( items: items, startingIndex: indexOfSelected, source: QueueItemSource( - name: const QueueItemSourceName( - type: QueueItemSourceNameType.mix), + name: const QueueItemSourceName(type: QueueItemSourceNameType.mix), type: QueueItemSourceType.allSongs, id: itemId.itemId!, ), ); } else { - return await audioServiceHelper.startInstantMixForItem(await _jellyfinApiHelper.getItemById(itemId.itemId!)); + return await audioServiceHelper.startInstantMixForItem( + await _jellyfinApiHelper.getItemById(itemId.itemId!)); } } - if (itemId.parentType != MediaItemParentType.collection || itemId.itemId == null) { - _androidAutoHelperLogger.warning("Tried to play from media id with invalid parent type '${itemId.parentType.name}' or null id"); + if (itemId.parentType != MediaItemParentType.collection || + itemId.itemId == null) { + _androidAutoHelperLogger.warning( + "Tried to play from media id with invalid parent type '${itemId.parentType.name}' or null id"); return; } // get all songs of current parent @@ -555,19 +671,22 @@ class AndroidAutoHelper { final parentBaseItems = await getBaseItems(itemId); - await queueService.startPlayback(items: parentBaseItems, source: QueueItemSource( - type: itemId.contentType == TabContentType.playlists - ? QueueItemSourceType.playlist - : QueueItemSourceType.album, - name: QueueItemSourceName( - type: QueueItemSourceNameType.preTranslated, - pretranslatedName: parentItem?.name), - id: parentItem?.id ?? itemId.parentId!, - item: parentItem, - )); + await queueService.startPlayback( + items: parentBaseItems, + source: QueueItemSource( + type: itemId.contentType == TabContentType.playlists + ? QueueItemSourceType.playlist + : QueueItemSourceType.album, + name: QueueItemSourceName( + type: QueueItemSourceNameType.preTranslated, + pretranslatedName: parentItem?.name), + id: parentItem?.id ?? itemId.parentId!, + item: parentItem, + )); } - Future> _searchTracks(AndroidAutoSearchQuery searchQuery, { + Future> _searchTracks( + AndroidAutoSearchQuery searchQuery, { int limit = 20, }) async { List? searchResult; @@ -580,10 +699,13 @@ class AndroidAutoHelper { searchResultExactQuery = await _getResults( searchTerm: searchQuery.rawQuery.trim(), itemTypes: [TabContentType.songs.itemType], - limit: searchQuery.extras?["android.intent.extra.title"] != null ? (limit/2).round() : limit, + limit: searchQuery.extras?["android.intent.extra.title"] != null + ? (limit / 2).round() + : limit, ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for exact query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for exact query:", e); } if (searchQuery.extras?["android.intent.extra.title"] != null) { try { @@ -593,13 +715,16 @@ class AndroidAutoHelper { limit: limit - (searchResultExactQuery?.length ?? 0), ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for adjusted query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for adjusted query:", e); } - } - searchResult = searchResultExactQuery?.followedBy(searchResultAdjustedQuery ?? []).toList() ?? []; - + searchResult = searchResultExactQuery + ?.followedBy(searchResultAdjustedQuery ?? []) + .toList() ?? + []; + final List filteredSearchResults = []; // filter out duplicates for (final item in searchResult) { @@ -609,20 +734,29 @@ class AndroidAutoHelper { } if (searchResult.isEmpty) { - _androidAutoHelperLogger.warning("No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); + _androidAutoHelperLogger.warning( + "No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); } - int calculateMatchQuality(BaseItemDto item, AndroidAutoSearchQuery searchQuery) { + int calculateMatchQuality( + BaseItemDto item, AndroidAutoSearchQuery searchQuery) { final title = item.name ?? ""; - final wantedTitle = searchQuery.extras?["android.intent.extra.title"]?.toString().trim(); - final wantedArtist = searchQuery.extras?["android.intent.extra.artist"]?.toString().trim(); - - if ( - title.toLowerCase() == searchQuery.rawQuery.toLowerCase() || - wantedArtist != null && - (item.albumArtists?.any((artist) => (artist.name?.isNotEmpty ?? false) && (wantedArtist?.toString().toLowerCase().contains(artist.name?.toLowerCase() ?? "") ?? false)) ?? false) - ) { + final wantedTitle = + searchQuery.extras?["android.intent.extra.title"]?.toString().trim(); + final wantedArtist = + searchQuery.extras?["android.intent.extra.artist"]?.toString().trim(); + + if (title.toLowerCase() == searchQuery.rawQuery.toLowerCase() || + wantedArtist != null && + (item.albumArtists?.any((artist) => + (artist.name?.isNotEmpty ?? false) && + (wantedArtist + ?.toString() + .toLowerCase() + .contains(artist.name?.toLowerCase() ?? "") ?? + false)) ?? + false)) { // Title matches exactly or artist matches, highest priority return 1; } else if (title == wantedTitle) { @@ -633,7 +767,7 @@ class AndroidAutoHelper { return -1; } } - + // sort items based on match quality with extras filteredSearchResults.sort((a, b) { final aMatchQuality = calculateMatchQuality(a, searchQuery); @@ -644,12 +778,14 @@ class AndroidAutoHelper { return filteredSearchResults; } - Future> _searchAlbums(AndroidAutoSearchQuery searchQuery, { + Future> _searchAlbums( + AndroidAutoSearchQuery searchQuery, { int limit = 20, }) async { List? searchResult; - bool hasAlbumMetadata = searchQuery.extras?["android.intent.extra.album"] != null; + bool hasAlbumMetadata = + searchQuery.extras?["android.intent.extra.album"] != null; // search for exact query first, then search for adjusted query // sometimes Google's adjustment might not be what we want, but sometimes it actually helps @@ -659,10 +795,11 @@ class AndroidAutoHelper { searchResultExactQuery = await _getResults( searchTerm: searchQuery.rawQuery.trim(), itemTypes: [TabContentType.albums.itemType], - limit: hasAlbumMetadata ? (limit/2).round() : limit, + limit: hasAlbumMetadata ? (limit / 2).round() : limit, ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for exact query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for exact query:", e); } if (hasAlbumMetadata) { try { @@ -672,13 +809,16 @@ class AndroidAutoHelper { limit: limit - (searchResultExactQuery?.length ?? 0), ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for adjusted query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for adjusted query:", e); } - } - searchResult = searchResultExactQuery?.followedBy(searchResultAdjustedQuery ?? []).toList() ?? []; - + searchResult = searchResultExactQuery + ?.followedBy(searchResultAdjustedQuery ?? []) + .toList() ?? + []; + final List filteredSearchResults = []; // filter out duplicates for (final item in searchResult) { @@ -688,20 +828,29 @@ class AndroidAutoHelper { } if (searchResult.isEmpty) { - _androidAutoHelperLogger.warning("No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); + _androidAutoHelperLogger.warning( + "No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); } - int calculateMatchQuality(BaseItemDto item, AndroidAutoSearchQuery searchQuery) { + int calculateMatchQuality( + BaseItemDto item, AndroidAutoSearchQuery searchQuery) { final title = item.name ?? ""; - final wantedAlbum = searchQuery.extras?["android.intent.extra.album"]?.toString().trim(); - final wantedArtist = searchQuery.extras?["android.intent.extra.artist"]?.toString().trim(); - - if ( - title.toLowerCase() == searchQuery.rawQuery.toLowerCase() || - wantedArtist != null && - (item.albumArtists?.any((artist) => (artist.name?.isNotEmpty ?? false) && (wantedArtist?.toString().toLowerCase().contains(artist.name?.toLowerCase() ?? "") ?? false)) ?? false) - ) { + final wantedAlbum = + searchQuery.extras?["android.intent.extra.album"]?.toString().trim(); + final wantedArtist = + searchQuery.extras?["android.intent.extra.artist"]?.toString().trim(); + + if (title.toLowerCase() == searchQuery.rawQuery.toLowerCase() || + wantedArtist != null && + (item.albumArtists?.any((artist) => + (artist.name?.isNotEmpty ?? false) && + (wantedArtist + ?.toString() + .toLowerCase() + .contains(artist.name?.toLowerCase() ?? "") ?? + false)) ?? + false)) { // Title matches exactly or artist matches, highest priority return 1; } else if (title == wantedAlbum) { @@ -712,7 +861,7 @@ class AndroidAutoHelper { return -1; } } - + // sort items based on match quality with extras filteredSearchResults.sort((a, b) { final aMatchQuality = calculateMatchQuality(a, searchQuery); @@ -723,12 +872,14 @@ class AndroidAutoHelper { return filteredSearchResults; } - Future> _searchPlaylists(AndroidAutoSearchQuery searchQuery, { + Future> _searchPlaylists( + AndroidAutoSearchQuery searchQuery, { int limit = 20, }) async { List? searchResult; - bool hasPlaylistMetadata = searchQuery.extras?["android.intent.extra.playlist"] != null; + bool hasPlaylistMetadata = + searchQuery.extras?["android.intent.extra.playlist"] != null; // search for exact query first, then search for adjusted query // sometimes Google's adjustment might not be what we want, but sometimes it actually helps @@ -738,26 +889,31 @@ class AndroidAutoHelper { searchResultExactQuery = await _getResults( searchTerm: searchQuery.rawQuery.trim(), itemTypes: [TabContentType.playlists.itemType], - limit: hasPlaylistMetadata ? (limit/2).round() : limit, + limit: hasPlaylistMetadata ? (limit / 2).round() : limit, ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for exact query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for exact query:", e); } if (hasPlaylistMetadata) { try { searchResultAdjustedQuery = await _getResults( - searchTerm: searchQuery.extras!["android.intent.extra.playlist"].trim(), + searchTerm: + searchQuery.extras!["android.intent.extra.playlist"].trim(), itemTypes: [TabContentType.playlists.itemType], limit: limit - (searchResultExactQuery?.length ?? 0), ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for adjusted query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for adjusted query:", e); } - } - searchResult = searchResultExactQuery?.followedBy(searchResultAdjustedQuery ?? []).toList() ?? []; - + searchResult = searchResultExactQuery + ?.followedBy(searchResultAdjustedQuery ?? []) + .toList() ?? + []; + final List filteredSearchResults = []; // filter out duplicates for (final item in searchResult) { @@ -767,13 +923,18 @@ class AndroidAutoHelper { } if (searchResult.isEmpty) { - _androidAutoHelperLogger.warning("No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); + _androidAutoHelperLogger.warning( + "No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); } - int calculateMatchQuality(BaseItemDto item, AndroidAutoSearchQuery searchQuery) { + int calculateMatchQuality( + BaseItemDto item, AndroidAutoSearchQuery searchQuery) { final title = item.name ?? ""; - final wantedPlaylist = searchQuery.extras?["android.intent.extra.playlist"]?.toString().trim(); + final wantedPlaylist = searchQuery + .extras?["android.intent.extra.playlist"] + ?.toString() + .trim(); if (title.toLowerCase() == searchQuery.rawQuery.toLowerCase()) { // Title matches exactly, highest priority @@ -786,7 +947,7 @@ class AndroidAutoHelper { return -1; } } - + // sort items based on match quality with extras filteredSearchResults.sort((a, b) { final aMatchQuality = calculateMatchQuality(a, searchQuery); @@ -797,12 +958,14 @@ class AndroidAutoHelper { return filteredSearchResults; } - Future> _searchArtists(AndroidAutoSearchQuery searchQuery, { + Future> _searchArtists( + AndroidAutoSearchQuery searchQuery, { int limit = 20, }) async { List? searchResult; - bool hasArtistMetadata = searchQuery.extras?["android.intent.extra.artist"] != null; + bool hasArtistMetadata = + searchQuery.extras?["android.intent.extra.artist"] != null; // search for exact query first, then search for adjusted query // sometimes Google's adjustment might not be what we want, but sometimes it actually helps @@ -812,10 +975,11 @@ class AndroidAutoHelper { searchResultExactQuery = await _getResults( searchTerm: searchQuery.rawQuery.trim(), itemTypes: [TabContentType.artists.itemType], - limit: hasArtistMetadata ? (limit/2).round() : limit, + limit: hasArtistMetadata ? (limit / 2).round() : limit, ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for exact query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for exact query:", e); } if (hasArtistMetadata) { try { @@ -825,13 +989,16 @@ class AndroidAutoHelper { limit: limit - (searchResultExactQuery?.length ?? 0), ); } catch (e) { - _androidAutoHelperLogger.severe("Error while searching for adjusted query:", e); + _androidAutoHelperLogger.severe( + "Error while searching for adjusted query:", e); } - } - searchResult = searchResultExactQuery?.followedBy(searchResultAdjustedQuery ?? []).toList() ?? []; - + searchResult = searchResultExactQuery + ?.followedBy(searchResultAdjustedQuery ?? []) + .toList() ?? + []; + final List filteredSearchResults = []; // filter out duplicates for (final item in searchResult) { @@ -841,19 +1008,21 @@ class AndroidAutoHelper { } if (searchResult.isEmpty) { - _androidAutoHelperLogger.warning("No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); + _androidAutoHelperLogger.warning( + "No search results found for query: ${searchQuery.rawQuery} (extras: ${searchQuery.extras})"); } - int calculateMatchQuality(BaseItemDto item, AndroidAutoSearchQuery searchQuery) { + int calculateMatchQuality( + BaseItemDto item, AndroidAutoSearchQuery searchQuery) { final title = item.name ?? ""; - final wantedArtist = searchQuery.extras?["android.intent.extra.artist"]?.toString().trim(); + final wantedArtist = + searchQuery.extras?["android.intent.extra.artist"]?.toString().trim(); if (title.toLowerCase() == searchQuery.rawQuery.toLowerCase()) { // Title matches exactly, highest priority return 1; - } else - if (title == wantedArtist) { + } else if (title == wantedArtist) { // Title matches, normal priority return 0; } else { @@ -861,7 +1030,7 @@ class AndroidAutoHelper { return -1; } } - + // sort items based on match quality with extras filteredSearchResults.sort((a, b) { final aMatchQuality = calculateMatchQuality(a, searchQuery); @@ -880,9 +1049,8 @@ class AndroidAutoHelper { final jellyfinApiHelper = GetIt.instance(); final finampUserHelper = GetIt.instance(); List? searchResult; - - if (FinampSettingsHelper.finampSettings.isOffline) { + if (FinampSettingsHelper.finampSettings.isOffline) { List offlineItems; if (itemTypes.first == TabContentType.songs.itemType) { @@ -891,7 +1059,8 @@ class AndroidAutoHelper { offlineItems = await _downloadsService.getAllSongs( nameFilter: searchTerm, viewFilter: finampUserHelper.currentUser?.currentView?.id, - nullableViewFilters: FinampSettingsHelper.finampSettings.showDownloadsWithUnknownLibrary, + nullableViewFilters: FinampSettingsHelper + .finampSettings.showDownloadsWithUnknownLibrary, onlyFavorites: false); } else { offlineItems = await _downloadsService.getAllCollections( @@ -901,16 +1070,19 @@ class AndroidAutoHelper { viewFilter: itemTypes.first == TabContentType.albums.itemType ? finampUserHelper.currentUser?.currentView?.id : null, - childViewFilter: (itemTypes.first != TabContentType.albums.itemType && - itemTypes.first != TabContentType.playlists.itemType) - ? finampUserHelper.currentUser?.currentView?.id - : null, - nullableViewFilters: itemTypes.first == TabContentType.albums.itemType && - FinampSettingsHelper.finampSettings.showDownloadsWithUnknownLibrary, + childViewFilter: + (itemTypes.first != TabContentType.albums.itemType && + itemTypes.first != TabContentType.playlists.itemType) + ? finampUserHelper.currentUser?.currentView?.id + : null, + nullableViewFilters: + itemTypes.first == TabContentType.albums.itemType && + FinampSettingsHelper + .finampSettings.showDownloadsWithUnknownLibrary, onlyFavorites: false); } - searchResult = offlineItems.map((e) => e.baseItem).whereNotNull().toList(); - + searchResult = + offlineItems.map((e) => e.baseItem).whereNotNull().toList(); } else { if (itemTypes.first == BaseItemDtoType.artist) { searchResult = await jellyfinApiHelper.getArtists( @@ -921,11 +1093,14 @@ class AndroidAutoHelper { ); } else { searchResult = await jellyfinApiHelper.getItems( - parentItem: itemTypes.contains(BaseItemDtoType.playlist) ? null : finampUserHelper.currentUser?.currentView, + parentItem: itemTypes.contains(BaseItemDtoType.playlist) + ? null + : finampUserHelper.currentUser?.currentView, includeItemTypes: itemTypes.map((type) => type.idString).join(","), searchTerm: searchTerm, startIndex: 0, - limit: limit, // get more than the first result so we can filter using additional metadata + limit: + limit, // get more than the first result so we can filter using additional metadata ); } } @@ -940,9 +1115,11 @@ class AndroidAutoHelper { BaseItemDto? item, TabContentType? contentType, }) { - final tabContentType = TabContentType.fromItemType(item?.type ?? contentType?.itemType.idString ?? "Audio"); - return tabContentType == TabContentType.albums || tabContentType == TabContentType.playlists - || tabContentType == TabContentType.artists || tabContentType == TabContentType.songs; + final tabContentType = TabContentType.fromItemType( + item?.type ?? contentType?.itemType.idString ?? "Audio"); + return tabContentType == TabContentType.albums || + tabContentType == TabContentType.playlists || + tabContentType == TabContentType.artists || + tabContentType == TabContentType.songs; } - } diff --git a/lib/services/audio_service_helper.dart b/lib/services/audio_service_helper.dart index e212912c9..d00b50c9d 100644 --- a/lib/services/audio_service_helper.dart +++ b/lib/services/audio_service_helper.dart @@ -28,11 +28,11 @@ class AudioServiceHelper { // shuffle them before making a sublist, but I couldn't think of a better // way. items = (await _isarDownloader.getAllSongs( - viewFilter: _finampUserHelper.currentUser?.currentView?.id, - nullableViewFilters: - FinampSettingsHelper.finampSettings.showDownloadsWithUnknownLibrary - )).map((e) => e.baseItem!) - .toList(); + viewFilter: _finampUserHelper.currentUser?.currentView?.id, + nullableViewFilters: FinampSettingsHelper + .finampSettings.showDownloadsWithUnknownLibrary)) + .map((e) => e.baseItem!) + .toList(); items.shuffle(); if (items.length - 1 > FinampSettingsHelper.finampSettings.songShuffleItemCount) { diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index 7a3f5fdf2..4d1d6e440 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -325,7 +325,8 @@ class FinampSettingsHelper { static void resetCustomizationSettings() { FinampSettings finampSettingsTemp = finampSettings; //TODO refactor this so default settings are available here - finampSettingsTemp.playbackSpeedVisibility = PlaybackSpeedVisibility.automatic; + finampSettingsTemp.playbackSpeedVisibility = + PlaybackSpeedVisibility.automatic; finampSettingsTemp.showStopButtonOnMediaNotification = false; finampSettingsTemp.showSeekControlsOnMediaNotification = true; Hive.box("FinampSettings") @@ -370,7 +371,7 @@ class FinampSettingsHelper { } static void setKeepScreenOnWhileCharging(bool keepScreenOnWhileCharging) { - FinampSettings finampSettingsTemp = finampSettings; + FinampSettings finampSettingsTemp = finampSettings; finampSettingsTemp.keepScreenOnWhilePluggedIn = keepScreenOnWhileCharging; Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); diff --git a/lib/services/jellyfin_api_helper.dart b/lib/services/jellyfin_api_helper.dart index d92648a94..ef5d03b8f 100644 --- a/lib/services/jellyfin_api_helper.dart +++ b/lib/services/jellyfin_api_helper.dart @@ -237,6 +237,7 @@ class JellyfinApiHelper { String? searchTerm, String? filters, String? fields, + /// The record index to start at. All items with a lower index will be /// dropped from the results. int? startIndex, @@ -258,7 +259,8 @@ class JellyfinApiHelper { defaultFields; // explicitly set the default fields, if we pass `null` to [JellyfinAPI.getItems] it will **not** apply the default fields, since the argument *is* provided. if (parentItem != null) { - _jellyfinApiHelperLogger.fine("Getting artists which are children of ${parentItem.name}"); + _jellyfinApiHelperLogger + .fine("Getting artists which are children of ${parentItem.name}"); } else { _jellyfinApiHelperLogger.fine("Getting artists."); } @@ -566,14 +568,15 @@ class JellyfinApiHelper { entryIds: entryIds?.join(","), ); if (response.statusCode == 403) { - _jellyfinApiHelperLogger.warning("Failed to remove items from playlist due to insufficient permissions. Status code: ${response.statusCode}"); + _jellyfinApiHelperLogger.warning( + "Failed to remove items from playlist due to insufficient permissions. Status code: ${response.statusCode}"); throw "You do not have permission to remove items from this playlist. Status code: ${response.statusCode}"; } else if (response.error != null) { if (response.error == "") { throw "An unknown error occurred while removing items from the playlist. Status code: ${response.statusCode}"; } throw "${response.error}. Status code: ${response.statusCode}"; - } + } } /// Updates an item. diff --git a/lib/services/playback_history_service.dart b/lib/services/playback_history_service.dart index 30c108def..e8ad884fa 100644 --- a/lib/services/playback_history_service.dart +++ b/lib/services/playback_history_service.dart @@ -78,7 +78,6 @@ class PlaybackHistoryService { final currentItem = _queueService.getCurrentTrack(); if (currentIndex != null && currentItem != null) { - // differences in queue index or item id are considered track changes if (currentItem.id != prevItem?.id) { if (currentState.playing != prevState?.playing) { @@ -462,7 +461,8 @@ class PlaybackHistoryService { Future _reportPlaybackStopped() async { if (FinampSettingsHelper.finampSettings.isOffline) { if (_currentTrack != null) { - await _offlineListenLogHelper.logOfflineListen(_currentTrack!.item.item); + await _offlineListenLogHelper + .logOfflineListen(_currentTrack!.item.item); } return; } @@ -476,7 +476,8 @@ class PlaybackHistoryService { } } catch (e) { _playbackHistoryServiceLogger.warning(e); - await _offlineListenLogHelper.logOfflineListen(_currentTrack!.item.item); + await _offlineListenLogHelper + .logOfflineListen(_currentTrack!.item.item); } } } diff --git a/lib/services/queue_service.dart b/lib/services/queue_service.dart index 80b8488f0..98a3b65f5 100644 --- a/lib/services/queue_service.dart +++ b/lib/services/queue_service.dart @@ -27,10 +27,9 @@ import 'music_player_background_task.dart'; /// A track queueing service for Finamp. class QueueService { - /// Used to build content:// URIs that are handled by Finamp's built-in content provider. static final contentProviderPackageName = "com.unicornsonlsd.finamp"; - + final _jellyfinApiHelper = GetIt.instance(); final _audioHandler = GetIt.instance(); final _finampUserHelper = GetIt.instance(); @@ -488,8 +487,8 @@ class QueueService { for (int i = 0; i < itemList.length; i++) { jellyfin_models.BaseItemDto item = itemList[i]; try { - MediaItem mediaItem = - await generateMediaItem(item, contextNormalizationGain: source.contextNormalizationGain); + MediaItem mediaItem = await generateMediaItem(item, + contextNormalizationGain: source.contextNormalizationGain); newItems.add(FinampQueueItem( item: mediaItem, source: source, @@ -583,21 +582,22 @@ class QueueService { required List items, QueueItemSource? source, }) async { - if (_queueAudioSource.length == 0) { return _replaceWholeQueue( itemList: items, - source: source ?? QueueItemSource( - type: QueueItemSourceType.queue, - name: const QueueItemSourceName(type: QueueItemSourceNameType.queue), - id: "queue", - item: null, - ), + source: source ?? + QueueItemSource( + type: QueueItemSourceType.queue, + name: const QueueItemSourceName( + type: QueueItemSourceNameType.queue), + id: "queue", + item: null, + ), initialIndex: 0, beginPlaying: false, ); } - + try { if (_savedQueueState == SavedQueueState.pendingSave) { _savedQueueState = SavedQueueState.saving; @@ -605,8 +605,8 @@ class QueueService { List queueItems = []; for (final item in items) { queueItems.add(FinampQueueItem( - item: - await generateMediaItem(item, contextNormalizationGain: source?.contextNormalizationGain), + item: await generateMediaItem(item, + contextNormalizationGain: source?.contextNormalizationGain), source: source ?? _order.originalSource, type: QueueItemQueueType.queue, )); @@ -631,16 +631,17 @@ class QueueService { required List items, QueueItemSource? source, }) async { - if (_queueAudioSource.length == 0) { return _replaceWholeQueue( itemList: items, - source: source ?? QueueItemSource( - type: QueueItemSourceType.queue, - name: const QueueItemSourceName(type: QueueItemSourceNameType.queue), - id: "queue", - item: null, - ), + source: source ?? + QueueItemSource( + type: QueueItemSourceType.queue, + name: const QueueItemSourceName( + type: QueueItemSourceNameType.queue), + id: "queue", + item: null, + ), initialIndex: 0, beginPlaying: false, ); @@ -653,8 +654,8 @@ class QueueService { List queueItems = []; for (final item in items) { queueItems.add(FinampQueueItem( - item: - await generateMediaItem(item, contextNormalizationGain: source?.contextNormalizationGain), + item: await generateMediaItem(item, + contextNormalizationGain: source?.contextNormalizationGain), source: source ?? QueueItemSource( id: "next-up", @@ -687,21 +688,22 @@ class QueueService { required List items, QueueItemSource? source, }) async { - if (_queueAudioSource.length == 0) { return _replaceWholeQueue( itemList: items, - source: source ?? QueueItemSource( - type: QueueItemSourceType.queue, - name: const QueueItemSourceName(type: QueueItemSourceNameType.queue), - id: "queue", - item: null, - ), + source: source ?? + QueueItemSource( + type: QueueItemSourceType.queue, + name: const QueueItemSourceName( + type: QueueItemSourceNameType.queue), + id: "queue", + item: null, + ), initialIndex: 0, beginPlaying: false, ); } - + try { if (_savedQueueState == SavedQueueState.pendingSave) { _savedQueueState = SavedQueueState.saving; @@ -709,8 +711,8 @@ class QueueService { List queueItems = []; for (final item in items) { queueItems.add(FinampQueueItem( - item: - await generateMediaItem(item, contextNormalizationGain: source?.contextNormalizationGain), + item: await generateMediaItem(item, + contextNormalizationGain: source?.contextNormalizationGain), source: source ?? QueueItemSource( id: "next-up", @@ -979,7 +981,9 @@ class QueueService { double? contextNormalizationGain, MediaItemParentType? parentType, String? parentId, - bool Function({ jellyfin_models.BaseItemDto? item, TabContentType? contentType })? isPlayable, + bool Function( + {jellyfin_models.BaseItemDto? item, TabContentType? contentType})? + isPlayable, }) async { const uuid = Uuid(); @@ -1005,9 +1009,11 @@ class QueueService { downloadedSong = downloadsService.getSongDownload(item: item); isDownloaded = downloadedSong != null; } else { - downloadedCollection = await downloadsService.getCollectionInfo(item: item); + downloadedCollection = + await downloadsService.getCollectionInfo(item: item); if (downloadedCollection != null) { - final downloadStatus = downloadsService.getStatus(downloadedCollection, null); + final downloadStatus = + downloadsService.getStatus(downloadedCollection, null); isDownloaded = downloadStatus != DownloadItemStatus.notNeeded; } } @@ -1015,7 +1021,8 @@ class QueueService { try { downloadedImage = downloadsService.getImageDownload(item: item); } catch (e) { - _queueServiceLogger.warning("Couldn't get the offline image for track '${item.name}' because it's not downloaded or missing a blurhash"); + _queueServiceLogger.warning( + "Couldn't get the offline image for track '${item.name}' because it's not downloaded or missing a blurhash"); } Uri? artUri; @@ -1028,12 +1035,15 @@ class QueueService { // try to get image file (Android Automotive needs this) if (artUri != null) { try { - final fileInfo = await AudioService.cacheManager.getFileFromCache(item.id); + final fileInfo = + await AudioService.cacheManager.getFileFromCache(item.id); if (fileInfo != null) { artUri = fileInfo.file.uri; } } catch (e) { - _queueServiceLogger.severe("Error setting new media artwork uri for item: ${item.id} name: ${item.name}", e); + _queueServiceLogger.severe( + "Error setting new media artwork uri for item: ${item.id} name: ${item.name}", + e); } } } @@ -1042,17 +1052,29 @@ class QueueService { if (Platform.isAndroid) { // replace with placeholder art if (artUri == null) { - final applicationSupportDirectory = await getApplicationSupportDirectory(); - artUri = Uri(scheme: "content", host: contentProviderPackageName, path: path_helper.join(applicationSupportDirectory.absolute.path, Assets.images.albumWhite.path)); + final applicationSupportDirectory = + await getApplicationSupportDirectory(); + artUri = Uri( + scheme: "content", + host: contentProviderPackageName, + path: path_helper.join(applicationSupportDirectory.absolute.path, + Assets.images.albumWhite.path)); } else { // store the origin in fragment since it should be unused - artUri = Uri(scheme: "content", host: contentProviderPackageName, path: artUri.path, fragment: ["http", "https"].contains(artUri.scheme) ? artUri.origin : null); + artUri = Uri( + scheme: "content", + host: contentProviderPackageName, + path: artUri.path, + fragment: ["http", "https"].contains(artUri.scheme) + ? artUri.origin + : null); } } return MediaItem( id: itemId?.toString() ?? uuid.v4(), - playable: isItemPlayable, // this dictates whether clicking on an item will try to play it or browse it in media browsers like Android Auto + playable: + isItemPlayable, // this dictates whether clicking on an item will try to play it or browse it in media browsers like Android Auto album: item.album, artist: item.artists?.join(", ") ?? item.albumArtist, artUri: artUri,