diff --git a/lib/src/data/api/jellyfin/jellyfin_api.dart b/lib/src/data/api/jellyfin/jellyfin_api.dart index 8bf3b8d..fe89819 100644 --- a/lib/src/data/api/jellyfin/jellyfin_api.dart +++ b/lib/src/data/api/jellyfin/jellyfin_api.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:jplayer/src/data/dto/dto.dart'; +import 'package:jplayer/src/data/dto/item/item_dto.dart'; import 'package:jplayer/src/data/dto/songs/songs_dto.dart'; import 'package:jplayer/src/data/dto/wrappers/wrappers_dto.dart'; import 'package:retrofit/retrofit.dart'; @@ -95,6 +96,11 @@ abstract class JellyfinApi { @Query('IncludeItemTypes') String includeType = 'music', }); + @GET('/Items/{itemId}') + Future> getItem({ + @Path('itemId') required String itemId, + }); + @POST('/Playlists') Future createPlaylist( @Body() Map arguments, diff --git a/lib/src/data/api/jellyfin/jellyfin_api.g.dart b/lib/src/data/api/jellyfin/jellyfin_api.g.dart index cc0225f..395a294 100644 --- a/lib/src/data/api/jellyfin/jellyfin_api.g.dart +++ b/lib/src/data/api/jellyfin/jellyfin_api.g.dart @@ -360,6 +360,34 @@ class _JellyfinApi implements JellyfinApi { return httpResponse; } + @override + Future> getItem({required String itemId}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _result = await _dio.fetch>( + _setStreamType>(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/Items/${itemId}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final _value = ItemDTO.fromJson(_result.data!); + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + @override Future createPlaylist(Map arguments) async { final _extra = {}; diff --git a/lib/src/data/dto/item/item_dto.dart b/lib/src/data/dto/item/item_dto.dart index f45fbc2..4ee502d 100644 --- a/lib/src/data/dto/item/item_dto.dart +++ b/lib/src/data/dto/item/item_dto.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:jplayer/src/data/dto/dto.dart'; part 'item_dto.freezed.dart'; part 'item_dto.g.dart'; @@ -15,6 +16,7 @@ class ItemDTO with _$ItemDTO { @JsonKey(name: 'RunTimeTicks') required int? durationInTicks, @JsonKey(name: 'ProductionYear') int? productionYear, @JsonKey(name: 'AlbumArtist') String? albumArtist, + @Default([]) @JsonKey(name: 'AlbumArtists') List albumArtists, @Default([]) @JsonKey(name: 'BackdropImageTags') List backgropImageTags, @Default({}) @JsonKey(name: 'ImageTags') Map imageTags, }) = _ItemDTO; diff --git a/lib/src/data/dto/item/item_dto.freezed.dart b/lib/src/data/dto/item/item_dto.freezed.dart index 49b0bc1..3f1f13a 100644 --- a/lib/src/data/dto/item/item_dto.freezed.dart +++ b/lib/src/data/dto/item/item_dto.freezed.dart @@ -36,6 +36,8 @@ mixin _$ItemDTO { int? get productionYear => throw _privateConstructorUsedError; @JsonKey(name: 'AlbumArtist') String? get albumArtist => throw _privateConstructorUsedError; + @JsonKey(name: 'AlbumArtists') + List get albumArtists => throw _privateConstructorUsedError; @JsonKey(name: 'BackdropImageTags') List get backgropImageTags => throw _privateConstructorUsedError; @JsonKey(name: 'ImageTags') @@ -60,6 +62,7 @@ abstract class $ItemDTOCopyWith<$Res> { @JsonKey(name: 'RunTimeTicks') int? durationInTicks, @JsonKey(name: 'ProductionYear') int? productionYear, @JsonKey(name: 'AlbumArtist') String? albumArtist, + @JsonKey(name: 'AlbumArtists') List albumArtists, @JsonKey(name: 'BackdropImageTags') List backgropImageTags, @JsonKey(name: 'ImageTags') Map imageTags}); } @@ -85,6 +88,7 @@ class _$ItemDTOCopyWithImpl<$Res, $Val extends ItemDTO> Object? durationInTicks = freezed, Object? productionYear = freezed, Object? albumArtist = freezed, + Object? albumArtists = null, Object? backgropImageTags = null, Object? imageTags = null, }) { @@ -121,6 +125,10 @@ class _$ItemDTOCopyWithImpl<$Res, $Val extends ItemDTO> ? _value.albumArtist : albumArtist // ignore: cast_nullable_to_non_nullable as String?, + albumArtists: null == albumArtists + ? _value.albumArtists + : albumArtists // ignore: cast_nullable_to_non_nullable + as List, backgropImageTags: null == backgropImageTags ? _value.backgropImageTags : backgropImageTags // ignore: cast_nullable_to_non_nullable @@ -149,6 +157,7 @@ abstract class _$$ItemDTOImplCopyWith<$Res> implements $ItemDTOCopyWith<$Res> { @JsonKey(name: 'RunTimeTicks') int? durationInTicks, @JsonKey(name: 'ProductionYear') int? productionYear, @JsonKey(name: 'AlbumArtist') String? albumArtist, + @JsonKey(name: 'AlbumArtists') List albumArtists, @JsonKey(name: 'BackdropImageTags') List backgropImageTags, @JsonKey(name: 'ImageTags') Map imageTags}); } @@ -172,6 +181,7 @@ class __$$ItemDTOImplCopyWithImpl<$Res> Object? durationInTicks = freezed, Object? productionYear = freezed, Object? albumArtist = freezed, + Object? albumArtists = null, Object? backgropImageTags = null, Object? imageTags = null, }) { @@ -208,6 +218,10 @@ class __$$ItemDTOImplCopyWithImpl<$Res> ? _value.albumArtist : albumArtist // ignore: cast_nullable_to_non_nullable as String?, + albumArtists: null == albumArtists + ? _value._albumArtists + : albumArtists // ignore: cast_nullable_to_non_nullable + as List, backgropImageTags: null == backgropImageTags ? _value._backgropImageTags : backgropImageTags // ignore: cast_nullable_to_non_nullable @@ -232,11 +246,14 @@ class _$ItemDTOImpl extends _ItemDTO { @JsonKey(name: 'RunTimeTicks') required this.durationInTicks, @JsonKey(name: 'ProductionYear') this.productionYear, @JsonKey(name: 'AlbumArtist') this.albumArtist, + @JsonKey(name: 'AlbumArtists') + final List albumArtists = const [], @JsonKey(name: 'BackdropImageTags') final List backgropImageTags = const [], @JsonKey(name: 'ImageTags') final Map imageTags = const {}}) - : _backgropImageTags = backgropImageTags, + : _albumArtists = albumArtists, + _backgropImageTags = backgropImageTags, _imageTags = imageTags, super._(); @@ -267,6 +284,15 @@ class _$ItemDTOImpl extends _ItemDTO { @override @JsonKey(name: 'AlbumArtist') final String? albumArtist; + final List _albumArtists; + @override + @JsonKey(name: 'AlbumArtists') + List get albumArtists { + if (_albumArtists is EqualUnmodifiableListView) return _albumArtists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_albumArtists); + } + final List _backgropImageTags; @override @JsonKey(name: 'BackdropImageTags') @@ -288,7 +314,7 @@ class _$ItemDTOImpl extends _ItemDTO { @override String toString() { - return 'ItemDTO(id: $id, name: $name, serverId: $serverId, type: $type, overview: $overview, durationInTicks: $durationInTicks, productionYear: $productionYear, albumArtist: $albumArtist, backgropImageTags: $backgropImageTags, imageTags: $imageTags)'; + return 'ItemDTO(id: $id, name: $name, serverId: $serverId, type: $type, overview: $overview, durationInTicks: $durationInTicks, productionYear: $productionYear, albumArtist: $albumArtist, albumArtists: $albumArtists, backgropImageTags: $backgropImageTags, imageTags: $imageTags)'; } @override @@ -309,6 +335,8 @@ class _$ItemDTOImpl extends _ItemDTO { other.productionYear == productionYear) && (identical(other.albumArtist, albumArtist) || other.albumArtist == albumArtist) && + const DeepCollectionEquality() + .equals(other._albumArtists, _albumArtists) && const DeepCollectionEquality() .equals(other._backgropImageTags, _backgropImageTags) && const DeepCollectionEquality() @@ -327,6 +355,7 @@ class _$ItemDTOImpl extends _ItemDTO { durationInTicks, productionYear, albumArtist, + const DeepCollectionEquality().hash(_albumArtists), const DeepCollectionEquality().hash(_backgropImageTags), const DeepCollectionEquality().hash(_imageTags)); @@ -354,6 +383,7 @@ abstract class _ItemDTO extends ItemDTO { @JsonKey(name: 'RunTimeTicks') required final int? durationInTicks, @JsonKey(name: 'ProductionYear') final int? productionYear, @JsonKey(name: 'AlbumArtist') final String? albumArtist, + @JsonKey(name: 'AlbumArtists') final List albumArtists, @JsonKey(name: 'BackdropImageTags') final List backgropImageTags, @JsonKey(name: 'ImageTags') final Map imageTags}) = _$ItemDTOImpl; @@ -386,6 +416,9 @@ abstract class _ItemDTO extends ItemDTO { @JsonKey(name: 'AlbumArtist') String? get albumArtist; @override + @JsonKey(name: 'AlbumArtists') + List get albumArtists; + @override @JsonKey(name: 'BackdropImageTags') List get backgropImageTags; @override diff --git a/lib/src/data/dto/item/item_dto.g.dart b/lib/src/data/dto/item/item_dto.g.dart index 9c54fcd..f7d9062 100644 --- a/lib/src/data/dto/item/item_dto.g.dart +++ b/lib/src/data/dto/item/item_dto.g.dart @@ -16,6 +16,10 @@ _$ItemDTOImpl _$$ItemDTOImplFromJson(Map json) => durationInTicks: (json['RunTimeTicks'] as num?)?.toInt(), productionYear: (json['ProductionYear'] as num?)?.toInt(), albumArtist: json['AlbumArtist'] as String?, + albumArtists: (json['AlbumArtists'] as List?) + ?.map((e) => ArtistDTO.fromJson(e as Map)) + .toList() ?? + const [], backgropImageTags: (json['BackdropImageTags'] as List?) ?.map((e) => e as String) .toList() ?? @@ -36,6 +40,7 @@ Map _$$ItemDTOImplToJson(_$ItemDTOImpl instance) => 'RunTimeTicks': instance.durationInTicks, 'ProductionYear': instance.productionYear, 'AlbumArtist': instance.albumArtist, + 'AlbumArtists': instance.albumArtists, 'BackdropImageTags': instance.backgropImageTags, 'ImageTags': instance.imageTags, }; diff --git a/lib/src/domain/providers/playback_provider.dart b/lib/src/domain/providers/playback_provider.dart index 9d16c54..7415c4b 100644 --- a/lib/src/domain/providers/playback_provider.dart +++ b/lib/src/domain/providers/playback_provider.dart @@ -32,10 +32,7 @@ class PlaybackNotifier extends StateNotifier { PlaybackNotifier( this._ref, this._audioPlayer, - ) : super(PlaybackState( - status: PlaybackStatus.stopped, - position: Duration.zero, - cacheProgress: Duration.zero)) { + ) : super(PlaybackState(status: PlaybackStatus.stopped, position: Duration.zero, cacheProgress: Duration.zero)) { // Listen for song completion _audioPlayer.positionStream.listen((position) { state = PlaybackState( @@ -48,15 +45,10 @@ class PlaybackNotifier extends StateNotifier { _audioPlayer.playerStateStream.listen((playerState) { if (playerState.processingState == ProcessingState.completed) { - state = PlaybackState( - status: PlaybackStatus.stopped, - position: Duration.zero, - cacheProgress: Duration.zero, - totalDuration: Duration.zero); - print(_audioPlayer.sequenceState?.currentIndex); - print('PlayerState: Completed'); - _audioPlayer.stop(); - _audioPlayer.setAudioSource(_audioPlayer.audioSource!, initialIndex: 0); + state = PlaybackState(status: PlaybackStatus.stopped, position: Duration.zero, cacheProgress: Duration.zero, totalDuration: Duration.zero); + _audioPlayer + ..stop() + ..setAudioSource(_audioPlayer.audioSource!, initialIndex: 0); } // Handle other player states as needed }); @@ -64,8 +56,7 @@ class PlaybackNotifier extends StateNotifier { final AudioPlayer _audioPlayer; final StateNotifierProviderRef _ref; - Future play( - SongDTO playSong, List songs, ItemDTO album) async { + Future play(SongDTO playSong, List songs, ItemDTO album) async { try { final domainUri = Uri.parse(_ref.read(baseUrlProvider)!); @@ -85,8 +76,7 @@ class PlaybackNotifier extends StateNotifier { 'TranscodingProtocol': 'http', 'TranscodingContainer': 'm4a', 'AudioCodec': 'm4a', - 'Container': - 'mp3,aac,m4a|aac,m4b|aac,flac,alac,m4a|alac,m4b|alac,wav,m4a,aiff,aif', + 'Container': 'mp3,aac,m4a|aac,m4b|aac,flac,alac,m4a|alac,m4b|alac,wav,m4a,aiff,aif', }, ), tag: MediaItem( @@ -101,19 +91,16 @@ class PlaybackNotifier extends StateNotifier { displaySubtitle: song.albumName, displayDescription: song.albumArtist, artUri: song.imageTags['Primary'] != null - ? Uri.parse(_ref.read(imageProvider).imagePath( - tagId: song.imageTags['Primary']!, id: song.id)) + ? Uri.parse(_ref.read(imageProvider).imagePath(tagId: song.imageTags['Primary']!, id: song.id)) : album.imageTags['Primary'] != null - ? Uri.parse(_ref.read(imageProvider).imagePath( - tagId: album.imageTags['Primary']!, id: album.id)) + ? Uri.parse(_ref.read(imageProvider).imagePath(tagId: album.imageTags['Primary']!, id: album.id)) : null, ), ), ], ); - await _audioPlayer.setAudioSource(playlist, - initialIndex: songs.indexOf(playSong), preload: false); + await _audioPlayer.setAudioSource(playlist, initialIndex: songs.indexOf(playSong), preload: false); unawaited(_audioPlayer.play()); state = PlaybackState( status: PlaybackStatus.playing, @@ -165,13 +152,11 @@ class PlaybackNotifier extends StateNotifier { } Future resume() async { - if ((state.status == PlaybackStatus.stopped) && - state.totalDuration?.inSeconds == 0) { + if ((state.status == PlaybackStatus.stopped) && state.totalDuration?.inSeconds == 0) { final queue = _ref.read(audioQueueProvider.notifier); // Case when song has finished but user clicks on play(resume) button. In this case we want to restart playback from first song. if (queue.state.songs.isNotEmpty) { - await play( - queue.state.songs.first, queue.state.songs, queue.state.album!); + await play(queue.state.songs.first, queue.state.songs, queue.state.album!); } return; @@ -229,14 +214,12 @@ class PlaybackNotifier extends StateNotifier { if (currentSong == null) return; - final currentIndex = - songs.indexOf(songs.firstWhere((element) => element == currentSong)); + final currentIndex = songs.indexOf(songs.firstWhere((element) => element == currentSong)); if (currentIndex != -1 && currentIndex + 1 < songs.length) { // There's a next song in the queue final nextSong = songs[currentIndex + 1]; _ref.read(audioQueueProvider.notifier).setCurrentSong(nextSong); - unawaited(play( - nextSong, songs, queueState.album!)); // Start playing the next song + unawaited(play(nextSong, songs, queueState.album!)); // Start playing the next song } else { await _ref.read(playerProvider).stop(); state = PlaybackState( @@ -257,5 +240,4 @@ class PlaybackNotifier extends StateNotifier { } } -final playbackProvider = StateNotifierProvider( - (ref) => PlaybackNotifier(ref, ref.read(playerProvider))); +final playbackProvider = StateNotifierProvider((ref) => PlaybackNotifier(ref, ref.read(playerProvider))); diff --git a/lib/src/presentation/pages/album_page.dart b/lib/src/presentation/pages/album_page.dart index a7782a6..9afe84c 100644 --- a/lib/src/presentation/pages/album_page.dart +++ b/lib/src/presentation/pages/album_page.dart @@ -3,7 +3,9 @@ import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:jplayer/resources/j_player_icons.dart'; +import 'package:jplayer/src/config/routes.dart'; import 'package:jplayer/src/data/dto/item/item_dto.dart'; import 'package:jplayer/src/data/dto/songs/songs_dto.dart'; import 'package:jplayer/src/data/providers/jellyfin_api_provider.dart'; @@ -12,6 +14,7 @@ import 'package:jplayer/src/domain/providers/current_user_provider.dart'; import 'package:jplayer/src/domain/providers/playback_provider.dart'; import 'package:jplayer/src/domain/providers/playlists_provider.dart'; import 'package:jplayer/src/presentation/utils/utils.dart'; +import 'package:jplayer/src/presentation/widgets/clickable_widget.dart'; import 'package:jplayer/src/presentation/widgets/random_queue_button.dart'; import 'package:jplayer/src/presentation/widgets/widgets.dart'; import 'package:jplayer/src/providers/base_url_provider.dart'; @@ -357,7 +360,26 @@ class _AlbumPageState extends ConsumerState { ), ], ), - Text(widget.album.albumArtist ?? ''), + // Text(widget.album.albumArtist ?? ''), + Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Row( + children: widget.album.albumArtists.map((a) { + return ClickableWidget( + onPressed: () async { + final item = await ref.read(jellyfinApiProvider).getItem(itemId: a.id); + if (!context.mounted) return; + context.go( + '${Routes.listen}${Routes.artist}', + extra: {'artist': item.data}, + ); + }, + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w300), + child: Text(a.name), + ); + }).toList(), + ), + ), Row( children: [ _albumDetails( diff --git a/lib/src/presentation/widgets/bottom_player.dart b/lib/src/presentation/widgets/bottom_player.dart index 98e8bdb..571218f 100644 --- a/lib/src/presentation/widgets/bottom_player.dart +++ b/lib/src/presentation/widgets/bottom_player.dart @@ -9,6 +9,7 @@ import 'package:jplayer/resources/j_player_icons.dart'; import 'package:jplayer/resources/resources.dart'; import 'package:jplayer/src/config/routes.dart'; import 'package:jplayer/src/data/dto/item/item_dto.dart'; +import 'package:jplayer/src/data/providers/jellyfin_api_provider.dart'; import 'package:jplayer/src/domain/providers/artists_provider.dart'; import 'package:jplayer/src/domain/providers/playback_provider.dart'; import 'package:jplayer/src/presentation/widgets/clickable_widget.dart'; @@ -29,8 +30,7 @@ class BottomPlayer extends ConsumerStatefulWidget { ConsumerState createState() => _BottomPlayerState(); } -class _BottomPlayerState extends ConsumerState - with SingleTickerProviderStateMixin { +class _BottomPlayerState extends ConsumerState with SingleTickerProviderStateMixin { late final AnimationController _animationController; final _imageProvider = ValueNotifier(null); final _dynamicColors = ValueNotifier(null); @@ -46,8 +46,7 @@ class _BottomPlayerState extends ConsumerState late bool _isMobile; late bool _isDesktop; - Future _onExpand(MediaItem? currentSong) => - Navigator.of(context, rootNavigator: true).push( + Future _onExpand(MediaItem? currentSong) => Navigator.of(context, rootNavigator: true).push( ModalSheetRoute( builder: (context) => SafeArea( top: false, @@ -76,15 +75,12 @@ class _BottomPlayerState extends ConsumerState aspectRatio: 1, child: ValueListenableBuilder( valueListenable: _imageProvider, - builder: (context, image, child) => (image == - null) + builder: (context, image, child) => (image == null) ? const SizedBox.shrink() : Image( image: currentSong?.artUri != null - ? NetworkImage(currentSong!.artUri - .toString()) - : const AssetImage(Images.album) - as ImageProvider, + ? NetworkImage(currentSong!.artUri.toString()) + : const AssetImage(Images.album) as ImageProvider, fit: BoxFit.cover, ), ), @@ -96,8 +92,7 @@ class _BottomPlayerState extends ConsumerState size: _isMobile ? 28 : 24, ), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // _openListButton(), _randomQueueButton(), @@ -123,26 +118,12 @@ class _BottomPlayerState extends ConsumerState ClickableWidget( onPressed: (currentSong?.artist != null) ? () async { - ItemDTO? artist; - while (artist == null) { - artist = ref - .watch(artistsProvider) - .value - .items - .firstWhereOrNull( - (e) => e.id == currentSong!.artist!, - ); - if (artist == null) { - await ref - .read(artistsProvider.notifier) - .loadMore(); - } - } + final item = await ref.read(jellyfinApiProvider).getItem(itemId: currentSong!.artist!); if (!context.mounted) return; Navigator.of(context).pop(); context.go( '${Routes.listen}${Routes.artist}', - extra: {'artist': artist}, + extra: {'artist': item.data}, ); } : null, @@ -259,11 +240,8 @@ class _BottomPlayerState extends ConsumerState stream: ref.read(playerProvider).sequenceStateStream, builder: (context, snapshot) { if (snapshot.data?.sequence.isEmpty ?? true) return Container(); - final currentSong = snapshot - .data?.sequence[snapshot.data!.currentIndex].tag as MediaItem?; - final image = currentSong?.artUri != null - ? NetworkImage(currentSong!.artUri.toString()) - : const Svg(Images.emptyItem) as ImageProvider; + final currentSong = snapshot.data?.sequence[snapshot.data!.currentIndex].tag as MediaItem?; + final image = currentSong?.artUri != null ? NetworkImage(currentSong!.artUri.toString()) : const Svg(Images.emptyItem) as ImageProvider; _imageProvider.value = image; return Container( height: (_isMobile ? 69 : 92) + _viewPadding.bottom, @@ -289,13 +267,30 @@ class _BottomPlayerState extends ConsumerState ), maxLines: 1, ), - subtitle: Text( - currentSong?.displayDescription ?? '', - style: TextStyle( + subtitle: ClickableWidget( + onPressed: (currentSong?.artist != null) + ? () async { + final item = await ref.read(jellyfinApiProvider).getItem(itemId: currentSong!.artist!); + if (!context.mounted) return; + context.go( + '${Routes.listen}${Routes.artist}', + extra: {'artist': item.data}, + ); + } + : null, + textStyle: TextStyle( fontSize: _isMobile ? 12 : 18, height: 1.2, ), + child: Text(currentSong?.displayDescription ?? ''), ), + // Text( + // currentSong?.displayDescription ?? '', + // style: TextStyle( + // fontSize: _isMobile ? 12 : 18, + // height: 1.2, + // ), + // ), trailing: Wrap( spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, @@ -322,8 +317,7 @@ class _BottomPlayerState extends ConsumerState data: Theme.of(context).copyWith( colorScheme: colorScheme, ), - child: const Positioned( - left: -25, top: -22, right: -25, child: PositionSlider()), + child: const Positioned(left: -25, top: -22, right: -25, child: PositionSlider()), ), ), ], @@ -344,9 +338,7 @@ class _BottomPlayerState extends ConsumerState } Widget _playPauseButton() => PlayPauseButton( - onPressed: () => _isPlaying.value - ? ref.read(playbackProvider.notifier).pause() - : ref.read(playbackProvider.notifier).resume(), + onPressed: () => _isPlaying.value ? ref.read(playbackProvider.notifier).pause() : ref.read(playbackProvider.notifier).resume(), background: _theme.colorScheme.onPrimary, foreground: _theme.scaffoldBackgroundColor, stateNotifier: _isPlaying, @@ -374,8 +366,7 @@ class _BottomPlayerState extends ConsumerState stream: ref.read(playerProvider).shuffleModeEnabledStream, builder: (context, snapshot) { return IconButton( - onPressed: () => ref.read(playerProvider).setShuffleModeEnabled( - snapshot.data == null ? !snapshot.data! : true), + onPressed: () => ref.read(playerProvider).setShuffleModeEnabled(snapshot.data == null ? !snapshot.data! : true), icon: Icon( JPlayer.mix, color: _theme.colorScheme.onPrimary, @@ -393,8 +384,7 @@ class _BottomPlayerState extends ConsumerState stream: ref.read(playerProvider).loopModeStream, builder: (context, snapshot) { return IconButton( - onPressed: () => ref.read(playerProvider).setLoopMode( - snapshot.data == LoopMode.all ? LoopMode.off : LoopMode.all), + onPressed: () => ref.read(playerProvider).setLoopMode(snapshot.data == LoopMode.all ? LoopMode.off : LoopMode.all), icon: Icon( JPlayer.repeat, color: _theme.colorScheme.onPrimary,