From 96aebceab629f9074632dde51c8544d77e3051a5 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Sun, 21 Dec 2025 16:16:40 +0330 Subject: [PATCH 1/6] ref: move some hardcoded string to localization fils and minor improvement in playlist item --- .../presentation/pages/playlists_page.dart | 2 +- .../widgets/pinned_playlist_view.dart | 3 ++- .../widgets/recently_playlist_item.dart | 16 ++++++++++------ lib/localization/app_en.arb | 2 ++ lib/localization/app_fa.arb | 2 ++ lib/localization/app_localizations.dart | 12 ++++++++++++ lib/localization/app_localizations_en.dart | 6 ++++++ lib/localization/app_localizations_fa.dart | 6 ++++++ 8 files changed, 41 insertions(+), 8 deletions(-) diff --git a/lib/features/playlist/presentation/pages/playlists_page.dart b/lib/features/playlist/presentation/pages/playlists_page.dart index 7cdba7a..3db29b6 100644 --- a/lib/features/playlist/presentation/pages/playlists_page.dart +++ b/lib/features/playlist/presentation/pages/playlists_page.dart @@ -125,7 +125,7 @@ class _PlaylistContentViewState extends State { children: [ const SizedBox.shrink(), Text( - 'Add Song to Playlist', + context.localization.addSongsToPlaylist, style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w600, ), diff --git a/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart b/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart index 5313245..59440bb 100644 --- a/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart +++ b/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:music_player/extensions/extensions.dart'; import 'package:music_player/features/playlist/domain/entities/playlist.dart'; import 'package:music_player/features/playlist/presentation/pages/playlist_details_page.dart'; import 'package:music_player/features/playlist/presentation/widgets/recently_playlist_item.dart'; @@ -35,7 +36,7 @@ class PinnedPlaylistsView extends StatelessWidget { itemBuilder: (context, index) { final recently = Playlist( id: 0, - name: 'Recently', + name: context.localization.recentlyPlayed, numOfSongs: 0, createdAt: DateTime.now(), updatedAt: DateTime.now(), diff --git a/lib/features/playlist/presentation/widgets/recently_playlist_item.dart b/lib/features/playlist/presentation/widgets/recently_playlist_item.dart index 5b2e137..7af864b 100644 --- a/lib/features/playlist/presentation/widgets/recently_playlist_item.dart +++ b/lib/features/playlist/presentation/widgets/recently_playlist_item.dart @@ -49,12 +49,16 @@ class PinnedPlaylistItem extends StatelessWidget { const SizedBox( height: 6, ), - Text( - playlist.name.length > 8 - ? '${playlist.name.substring(0, 6)}...' - : playlist.name, - style: theme.textTheme.labelLarge?.copyWith( - fontSize: 12, + SizedBox( + width: 74, + child: Text( + playlist.name, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelLarge?.copyWith( + fontSize: 12, + ), ), ), ], diff --git a/lib/localization/app_en.arb b/lib/localization/app_en.arb index 8a79834..9f6280c 100644 --- a/lib/localization/app_en.arb +++ b/lib/localization/app_en.arb @@ -125,6 +125,8 @@ "renamePlaylist": "Rename Playlist", "addSongs": "Add Songs", "noSongInThePlaylist": "No song in the playlist. Add songs to see them here.", + "addSongsToPlaylist": "Add Songs to Playlist", + "recentlyPlayed": "Recently Played", "musicPlayerPage": "", "@musicPlayerPage": { diff --git a/lib/localization/app_fa.arb b/lib/localization/app_fa.arb index 9c7763d..22e9851 100644 --- a/lib/localization/app_fa.arb +++ b/lib/localization/app_fa.arb @@ -124,6 +124,8 @@ "renamePlaylist": "تغییر نام لیست پخش", "addSongs": "اضافه کردن موزیک ها", "noSongInThePlaylist": "هیچ موزیکی در این لیست پخش وجود ندارد. برای دیدن موزیک ها، موزیک اضافه کنید.", + "addSongsToPlaylist": "اضافه کردن موزیک ها به لیست پخش", + "recentlyPlayed": "اخیراً پخش شده", "musicPlayerPage": "", "@musicPlayerPage": { diff --git a/lib/localization/app_localizations.dart b/lib/localization/app_localizations.dart index 5e6711a..f2d985b 100644 --- a/lib/localization/app_localizations.dart +++ b/lib/localization/app_localizations.dart @@ -614,6 +614,18 @@ abstract class AppLocalizations { /// **'No song in the playlist. Add songs to see them here.'** String get noSongInThePlaylist; + /// No description provided for @addSongsToPlaylist. + /// + /// In en, this message translates to: + /// **'Add Songs to Playlist'** + String get addSongsToPlaylist; + + /// No description provided for @recentlyPlayed. + /// + /// In en, this message translates to: + /// **'Recently Played'** + String get recentlyPlayed; + /// Music Player page localization /// /// In en, this message translates to: diff --git a/lib/localization/app_localizations_en.dart b/lib/localization/app_localizations_en.dart index 1d56df2..330bde3 100644 --- a/lib/localization/app_localizations_en.dart +++ b/lib/localization/app_localizations_en.dart @@ -275,6 +275,12 @@ class AppLocalizationsEn extends AppLocalizations { String get noSongInThePlaylist => 'No song in the playlist. Add songs to see them here.'; + @override + String get addSongsToPlaylist => 'Add Songs to Playlist'; + + @override + String get recentlyPlayed => 'Recently Played'; + @override String get musicPlayerPage => ''; diff --git a/lib/localization/app_localizations_fa.dart b/lib/localization/app_localizations_fa.dart index e2a8ef1..25a417f 100644 --- a/lib/localization/app_localizations_fa.dart +++ b/lib/localization/app_localizations_fa.dart @@ -275,6 +275,12 @@ class AppLocalizationsFa extends AppLocalizations { String get noSongInThePlaylist => 'هیچ موزیکی در این لیست پخش وجود ندارد. برای دیدن موزیک ها، موزیک اضافه کنید.'; + @override + String get addSongsToPlaylist => 'اضافه کردن موزیک ها به لیست پخش'; + + @override + String get recentlyPlayed => 'اخیراً پخش شده'; + @override String get musicPlayerPage => ''; From f8371562b11db831372683c0045abace9774b507 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Sun, 21 Dec 2025 16:58:42 +0330 Subject: [PATCH 2/6] feat: save song ids in recently played when it plays --- lib/app.dart | 1 + lib/core/constants/preferences_keys.dart | 1 + .../datasources/audio_handler_datasource.dart | 37 ++++++++++++++++++- .../repositories/music_player_repo_impl.dart | 10 +++++ .../repositories/music_player_repository.dart | 7 ++++ .../usecases/add_to_recently_played.dart | 18 +++++++++ .../music_plyer/domain/usecases/usecases.dart | 1 + .../presentation/bloc/music_player_bloc.dart | 3 ++ lib/injection/service_locator.dart | 2 + 9 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 lib/features/music_plyer/domain/usecases/add_to_recently_played.dart diff --git a/lib/app.dart b/lib/app.dart index 51a6b81..3ec7e37 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -104,6 +104,7 @@ class _MusicPlayerAppState extends State { getIt(), getIt(), getIt(), + getIt(), ), ), BlocProvider( diff --git a/lib/core/constants/preferences_keys.dart b/lib/core/constants/preferences_keys.dart index 6ab0f43..3c4b0dc 100644 --- a/lib/core/constants/preferences_keys.dart +++ b/lib/core/constants/preferences_keys.dart @@ -6,4 +6,5 @@ sealed class PreferencesKeys { static const String pinnedPlaylists = 'pinned_playlists'; static const String playlistCoverSongsId = 'playlist_cover_songs_id'; static const String songSortType = 'song_sort_type'; + static const String recentlyPlayedSongIds = 'recently_played_song_ids'; } diff --git a/lib/features/music_plyer/data/datasources/audio_handler_datasource.dart b/lib/features/music_plyer/data/datasources/audio_handler_datasource.dart index 2e82b8b..b7888d7 100644 --- a/lib/features/music_plyer/data/datasources/audio_handler_datasource.dart +++ b/lib/features/music_plyer/data/datasources/audio_handler_datasource.dart @@ -1,7 +1,9 @@ +import 'package:music_player/core/constants/preferences_keys.dart'; import 'package:music_player/core/services/services.dart'; import 'package:music_player/features/music_plyer/data/mapppers/mappers.dart'; import 'package:music_player/features/music_plyer/domain/entities/entities.dart'; import 'package:on_audio_query_pluse/on_audio_query.dart'; +import 'package:shared_preferences/shared_preferences.dart'; /// Datasource interface for audio playback operations. /// @@ -43,6 +45,12 @@ abstract interface class AudioHandlerDatasource { /// Sets the loop/repeat mode. PlayerLoopMode setPlayerLoopMode(PlayerLoopMode loopMode); + + /// Adds a song to the recently played list. + /// Parameters: + /// - [songId]: ID of the song to add + /// Returns a [bool] indicating success or failure. + Future addToRecentlyPlayed(int songId); } /// Implementation of [AudioHandlerDatasource] using MAudioHandler. @@ -53,9 +61,12 @@ class AudioHandlerDatasourceImpl implements AudioHandlerDatasource { /// Creates an [AudioHandlerDatasourceImpl]. AudioHandlerDatasourceImpl({ required MAudioHandler audioHandler, - }) : _audioHandler = audioHandler; + required SharedPreferences preferences, + }) : _audioHandler = audioHandler, + _preferences = preferences; final MAudioHandler _audioHandler; + final SharedPreferences _preferences; @override Future pause() { @@ -122,4 +133,28 @@ class AudioHandlerDatasourceImpl implements AudioHandlerDatasource { _audioHandler.setLoopMode(PlayerLoopModeMapper.mapToLoopMode(loopMode)); return loopMode; } + + @override + Future addToRecentlyPlayed(int songId) { + final recentList = + _preferences.getStringList(PreferencesKeys.recentlyPlayedSongIds) ?? []; + + final songIdStr = songId.toString(); + + // Remove if already exists to avoid duplicates + recentList + ..remove(songIdStr) + // Add to the start of the list + ..insert(0, songIdStr); + + // Keep only the latest 50 entries + if (recentList.length > 50) { + recentList.removeRange(50, recentList.length); + } + + return _preferences.setStringList( + PreferencesKeys.recentlyPlayedSongIds, + recentList, + ); + } } diff --git a/lib/features/music_plyer/data/repositories/music_player_repo_impl.dart b/lib/features/music_plyer/data/repositories/music_player_repo_impl.dart index 8d7eb89..6d168a3 100644 --- a/lib/features/music_plyer/data/repositories/music_player_repo_impl.dart +++ b/lib/features/music_plyer/data/repositories/music_player_repo_impl.dart @@ -130,4 +130,14 @@ class MusicPlayerRepoImpl implements MusicPlayerRepository { return Result.failure('failed to set loop mode: $e'); } } + + @override + Future> addToRecentlyPlayed(int songId) async { + try { + final result = await _audioHandlerDatasource.addToRecentlyPlayed(songId); + return Result.success(result); + } on Exception catch (e) { + return Result.failure('failed to add to recently played: $e'); + } + } } diff --git a/lib/features/music_plyer/domain/repositories/music_player_repository.dart b/lib/features/music_plyer/domain/repositories/music_player_repository.dart index 8fafd4c..e3ecc7b 100644 --- a/lib/features/music_plyer/domain/repositories/music_player_repository.dart +++ b/lib/features/music_plyer/domain/repositories/music_player_repository.dart @@ -81,4 +81,11 @@ abstract class MusicPlayerRepository { /// /// Returns a [Result] containing the set loop mode or an error. Result setLoopMode(PlayerLoopMode loopMode); + + /// Adds a song to the recently played list. + /// + /// Parameters: + /// - [songId]: ID of the song to add + /// Returns a [Result] indicating success or failure. + Future> addToRecentlyPlayed(int songId); } diff --git a/lib/features/music_plyer/domain/usecases/add_to_recently_played.dart b/lib/features/music_plyer/domain/usecases/add_to_recently_played.dart new file mode 100644 index 0000000..21d17cc --- /dev/null +++ b/lib/features/music_plyer/domain/usecases/add_to_recently_played.dart @@ -0,0 +1,18 @@ +import 'package:music_player/core/result.dart'; +import 'package:music_player/features/music_plyer/domain/domain.dart'; + +class AddToRecentlyPlayed { + /// Creates an [AddToRecentlyPlayed] use case. + const AddToRecentlyPlayed(this._repository); + + final MusicPlayerRepository _repository; + + /// Adds a song to the recently played list. + //// + /// Parameters: + /// - [songId]: ID of the song to add + /// Returns a [Result] indicating success or failure. + Future> call(int songId) { + return _repository.addToRecentlyPlayed(songId); + } +} diff --git a/lib/features/music_plyer/domain/usecases/usecases.dart b/lib/features/music_plyer/domain/usecases/usecases.dart index 0282f9a..96502e2 100644 --- a/lib/features/music_plyer/domain/usecases/usecases.dart +++ b/lib/features/music_plyer/domain/usecases/usecases.dart @@ -1,3 +1,4 @@ +export 'add_to_recently_played.dart'; export 'has_next_song.dart'; export 'has_previous_song.dart'; export 'pause_song.dart'; diff --git a/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart b/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart index 495ac75..65ff115 100644 --- a/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart +++ b/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart @@ -29,6 +29,7 @@ class MusicPlayerBloc extends Bloc { this.watchSongDuration, this.watchSongPosition, this.setLoopMode, + this.addToRecentlyPlayed, ) : super(const MusicPlayerState()) { // Listen to player index changes _playerIndexSubscription = watchPlayerIndex().distinct().listen( @@ -59,6 +60,7 @@ class MusicPlayerBloc extends Bloc { final WatchPlayerIndex watchPlayerIndex; final WatchSongDuration watchSongDuration; final WatchSongPosition watchSongPosition; + final AddToRecentlyPlayed addToRecentlyPlayed; late final StreamSubscription _playerIndexSubscription; @@ -230,6 +232,7 @@ class MusicPlayerBloc extends Bloc { // Start playback final result = await playSong(event.playList, event.index); + unawaited(addToRecentlyPlayed(event.playList[event.index].id)); if (result.isFailure) { emit( diff --git a/lib/injection/service_locator.dart b/lib/injection/service_locator.dart index 0a2a3b3..dd57ec0 100644 --- a/lib/injection/service_locator.dart +++ b/lib/injection/service_locator.dart @@ -141,6 +141,7 @@ void _setupMusicPlayerFeature() { ..registerLazySingleton(() => WatchPlayerIndex(getIt.get())) ..registerLazySingleton(() => WatchSongDuration(getIt.get())) ..registerLazySingleton(() => WatchSongPosition(getIt.get())) + ..registerLazySingleton(() => AddToRecentlyPlayed(getIt.get())) // Repositories ..registerLazySingleton( () => MusicPlayerRepoImpl(audioHandlerDatasource: getIt.get()), @@ -149,6 +150,7 @@ void _setupMusicPlayerFeature() { ..registerLazySingleton( () => AudioHandlerDatasourceImpl( audioHandler: getIt.get(), + preferences: getIt.get(), ), ); } From 2f442db636493f4a5bbd1c9835d254f99ef7d3c9 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Sun, 21 Dec 2025 18:08:49 +0330 Subject: [PATCH 3/6] fix: improve some sizes in the playlist card and songitem card --- lib/core/widgets/song_image_widget.dart | 2 +- lib/core/widgets/song_item.dart | 2 +- .../playlist/presentation/widgets/playlist_item.dart | 5 +++-- lib/features/songs/presentation/widgets/album_item.dart | 2 +- lib/features/songs/presentation/widgets/artist_item.dart | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/core/widgets/song_image_widget.dart b/lib/core/widgets/song_image_widget.dart index 778f512..89431c0 100644 --- a/lib/core/widgets/song_image_widget.dart +++ b/lib/core/widgets/song_image_widget.dart @@ -7,7 +7,7 @@ class ArtImageWidget extends StatelessWidget { const ArtImageWidget({ required this.id, this.type = ArtworkType.AUDIO, - this.size = 50, + this.size = 54, this.quality = 70, this.qualitySize = 200, this.defaultCoverBg, diff --git a/lib/core/widgets/song_item.dart b/lib/core/widgets/song_item.dart index 5d4d392..78725af 100644 --- a/lib/core/widgets/song_item.dart +++ b/lib/core/widgets/song_item.dart @@ -109,6 +109,7 @@ class SongItem extends StatelessWidget { Widget _buildTitleAndArtist(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ Text( track.title, @@ -119,7 +120,6 @@ class SongItem extends StatelessWidget { color: isCurrentTrack ? context.theme.colorScheme.primary : null, ), ), - const SizedBox(height: 4), ArtistWidget( artist: track.artist, isCurrentTrack: isCurrentTrack, diff --git a/lib/features/playlist/presentation/widgets/playlist_item.dart b/lib/features/playlist/presentation/widgets/playlist_item.dart index 834d508..e98c24f 100644 --- a/lib/features/playlist/presentation/widgets/playlist_item.dart +++ b/lib/features/playlist/presentation/widgets/playlist_item.dart @@ -9,8 +9,8 @@ import 'package:music_player/features/playlist/presentation/widgets/playlist_ite class PlaylistItem extends StatelessWidget { const PlaylistItem({ required this.playlist, - this.margin = const EdgeInsets.symmetric(horizontal: 4, vertical: 8), - this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + this.margin = const EdgeInsets.symmetric(horizontal: 4, vertical: 4), + this.padding = const EdgeInsets.symmetric(vertical: 4, horizontal: 12), this.borderRadius = 16, this.onTap, this.blurBackground = true, @@ -95,6 +95,7 @@ class PlaylistItem extends StatelessWidget { Widget _buildTitle(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ Text( playlist.name, diff --git a/lib/features/songs/presentation/widgets/album_item.dart b/lib/features/songs/presentation/widgets/album_item.dart index 37e0b39..4bf6e1b 100644 --- a/lib/features/songs/presentation/widgets/album_item.dart +++ b/lib/features/songs/presentation/widgets/album_item.dart @@ -17,7 +17,7 @@ class AlbumItem extends StatelessWidget { ? context.localization.songs : context.localization.song; return GlassCard( - margin: const EdgeInsets.all(12), + margin: const EdgeInsets.only(left: 12, right: 12, top: 8), borderRadius: const BorderRadius.all(Radius.circular(12)), onTap: onTap, child: ListTile( diff --git a/lib/features/songs/presentation/widgets/artist_item.dart b/lib/features/songs/presentation/widgets/artist_item.dart index 6ec887a..1ae35a5 100644 --- a/lib/features/songs/presentation/widgets/artist_item.dart +++ b/lib/features/songs/presentation/widgets/artist_item.dart @@ -17,7 +17,7 @@ class ArtistItem extends StatelessWidget { ? context.localization.songs : context.localization.song; return GlassCard( - margin: const EdgeInsets.all(12), + margin: const EdgeInsets.only(left: 12, right: 12, top: 8), borderRadius: const BorderRadius.all(Radius.circular(12)), onTap: onTap, child: ListTile( From 04c7685c2b11451a3e9ad031411a4652041886fc Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Sun, 21 Dec 2025 20:16:10 +0330 Subject: [PATCH 4/6] chore: show recently played songs in the playlist --- .../presentation/bloc/music_player_bloc.dart | 2 +- .../data/datasources/playlist_datasource.dart | 31 +++++++++++ .../playlist_repository_impl.dart | 11 ++++ .../repositories/playlist_repository.dart | 5 ++ .../usecases/get_recently_played_songs.dart | 16 ++++++ .../playlist/domain/usecases/usecases.dart | 1 + .../bloc/playlist_details_bloc.dart | 55 +++++++++++++------ .../pages/playlist_details_page.dart | 1 + .../widgets/all_playlist_view.dart | 6 +- .../widgets/pinned_playlist_view.dart | 2 +- .../widgets/playlist_details_appbar.dart | 43 ++++++++------- lib/injection/service_locator.dart | 1 + lib/localization/app_en.arb | 1 + lib/localization/app_fa.arb | 1 + lib/localization/app_localizations.dart | 6 ++ lib/localization/app_localizations_en.dart | 3 + lib/localization/app_localizations_fa.dart | 3 + 17 files changed, 148 insertions(+), 40 deletions(-) create mode 100644 lib/features/playlist/domain/usecases/get_recently_played_songs.dart diff --git a/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart b/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart index 65ff115..0df445a 100644 --- a/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart +++ b/lib/features/music_plyer/presentation/bloc/music_player_bloc.dart @@ -232,7 +232,7 @@ class MusicPlayerBloc extends Bloc { // Start playback final result = await playSong(event.playList, event.index); - unawaited(addToRecentlyPlayed(event.playList[event.index].id)); + await addToRecentlyPlayed(event.playList[event.index].id); if (result.isFailure) { emit( diff --git a/lib/features/playlist/data/datasources/playlist_datasource.dart b/lib/features/playlist/data/datasources/playlist_datasource.dart index 2919eac..34a70a1 100644 --- a/lib/features/playlist/data/datasources/playlist_datasource.dart +++ b/lib/features/playlist/data/datasources/playlist_datasource.dart @@ -12,6 +12,7 @@ abstract class PlaylistDatasource { Future createPlaylist(String name); Future deletePlaylist(int id); Future> getPlaylistSongs(int playlistId); + Future> getRecentPlayedSongs(); Future addSongsToPlaylist(int playlistId, List songIds); Future removeSongsFromPlaylist(int playlistId, List songIds); Future renamePlaylist(int id, String newName); @@ -187,4 +188,34 @@ class PlaylistDatasourceImpl implements PlaylistDatasource { rawList, ); } + + @override + Future> getRecentPlayedSongs() async { + final recentSongIds = + preferences.getStringList( + PreferencesKeys.recentlyPlayedSongIds, + ) ?? + []; + if (recentSongIds.isEmpty) { + return []; + } else { + final songIdInts = recentSongIds.map(int.parse).toList(); + final result = await audioQuery.querySongs(); + final filteredSongs = + result + .where( + (song) => songIdInts.contains(song.id), + ) + .toList() + // Sort songs to match the order in recentSongIds + ..sort( + (a, b) => songIdInts + .indexOf(a.id) + .compareTo( + songIdInts.indexOf(b.id), + ), + ); + return filteredSongs; + } + } } diff --git a/lib/features/playlist/data/repositories/playlist_repository_impl.dart b/lib/features/playlist/data/repositories/playlist_repository_impl.dart index cc5322e..d374eb5 100644 --- a/lib/features/playlist/data/repositories/playlist_repository_impl.dart +++ b/lib/features/playlist/data/repositories/playlist_repository_impl.dart @@ -160,4 +160,15 @@ class PlaylistRepositoryImpl implements PlaylistRepository { return Result.failure('Failed to save as pinned playlist: $e'); } } + + @override + Future>> getRecentlyPlayedSongs() async { + try { + final songModels = await datasource.getRecentPlayedSongs(); + final songs = songModels.map(SongModelMapper.toDomain).toList(); + return Result.success(songs); + } on Exception catch (e) { + return Result.failure('Failed to get recently played songs: $e'); + } + } } diff --git a/lib/features/playlist/domain/repositories/playlist_repository.dart b/lib/features/playlist/domain/repositories/playlist_repository.dart index 77b5593..c0ec17b 100644 --- a/lib/features/playlist/domain/repositories/playlist_repository.dart +++ b/lib/features/playlist/domain/repositories/playlist_repository.dart @@ -55,6 +55,11 @@ abstract class PlaylistRepository { /// Returns a [Result] containing the list of songs or an error. Future>> getPlaylistSongs(int playlistId); + /// Retrieves the list of recently played songs. + //// + /// Returns a [Result] containing the list of songs or an error. + Future>> getRecentlyPlayedSongs(); + /// Adds songs to a playlist. /// /// Parameters: diff --git a/lib/features/playlist/domain/usecases/get_recently_played_songs.dart b/lib/features/playlist/domain/usecases/get_recently_played_songs.dart new file mode 100644 index 0000000..9979071 --- /dev/null +++ b/lib/features/playlist/domain/usecases/get_recently_played_songs.dart @@ -0,0 +1,16 @@ +import 'package:music_player/core/domain/entities/song.dart'; +import 'package:music_player/core/result.dart'; +import 'package:music_player/features/playlist/domain/repositories/playlist_repository.dart'; + +class GetRecentlyPlayedSongs { + /// Creates a [GetRecentlyPlayedSongs] use case. + const GetRecentlyPlayedSongs(this._repository); + + final PlaylistRepository _repository; + + /// Retrieves the list of recently played songs. + //// /// Returns a [Result] containing the list of songs or an error. + Future>> call() { + return _repository.getRecentlyPlayedSongs(); + } +} diff --git a/lib/features/playlist/domain/usecases/usecases.dart b/lib/features/playlist/domain/usecases/usecases.dart index 3bdb9a7..3f7c142 100644 --- a/lib/features/playlist/domain/usecases/usecases.dart +++ b/lib/features/playlist/domain/usecases/usecases.dart @@ -5,6 +5,7 @@ export 'get_all_playlists.dart'; export 'get_playlist_by_id.dart'; export 'get_playlist_cover_song_id.dart'; export 'get_playlist_songs.dart'; +export 'get_recently_played_songs.dart'; export 'initialize_playlist_covers.dart'; export 'remove_songs_from_playlist.dart'; export 'rename_playlist.dart'; diff --git a/lib/features/playlist/presentation/bloc/playlist_details_bloc.dart b/lib/features/playlist/presentation/bloc/playlist_details_bloc.dart index 8fca179..8578a69 100644 --- a/lib/features/playlist/presentation/bloc/playlist_details_bloc.dart +++ b/lib/features/playlist/presentation/bloc/playlist_details_bloc.dart @@ -11,34 +11,57 @@ class PlaylistDetailsBloc PlaylistDetailsBloc({ required Playlist playlist, required this.getPlaylistSongs, + required this.getRecentlyPlayedSongs, }) : super(PlaylistDetailsState(playlist: playlist)) { on(_onGetPlaylistSongs); } final GetPlaylistSongs getPlaylistSongs; + final GetRecentlyPlayedSongs getRecentlyPlayedSongs; Future _onGetPlaylistSongs( GetPlaylistSongsEvent event, Emitter emit, ) async { emit(state.copyWith(status: PlaylistDetailsStatus.loading)); - - final result = await getPlaylistSongs(state.playlist.id); - - if (result.isFailure) { - emit( - state.copyWith( - status: PlaylistDetailsStatus.failure, - errorMessage: result.error, - ), - ); + if (state.playlist.id == -1) { + // return recently played songs + final result = await getRecentlyPlayedSongs(); + if (result.isFailure) { + emit( + state.copyWith( + status: PlaylistDetailsStatus.failure, + errorMessage: result.error, + ), + ); + return; + } else { + emit( + state.copyWith( + status: PlaylistDetailsStatus.success, + songs: result.value, + ), + ); + return; + } } else { - emit( - state.copyWith( - status: PlaylistDetailsStatus.success, - songs: result.value, - ), - ); + final result = await getPlaylistSongs(state.playlist.id); + + if (result.isFailure) { + emit( + state.copyWith( + status: PlaylistDetailsStatus.failure, + errorMessage: result.error, + ), + ); + } else { + emit( + state.copyWith( + status: PlaylistDetailsStatus.success, + songs: result.value, + ), + ); + } } } } diff --git a/lib/features/playlist/presentation/pages/playlist_details_page.dart b/lib/features/playlist/presentation/pages/playlist_details_page.dart index 16df9de..fd40284 100644 --- a/lib/features/playlist/presentation/pages/playlist_details_page.dart +++ b/lib/features/playlist/presentation/pages/playlist_details_page.dart @@ -23,6 +23,7 @@ class PlaylistDetailsPage extends StatelessWidget { create: (_) => PlaylistDetailsBloc( playlist: playlistModel, getPlaylistSongs: getIt.get(), + getRecentlyPlayedSongs: getIt.get(), ), child: const _PlaylistDetailsView(), ); diff --git a/lib/features/playlist/presentation/widgets/all_playlist_view.dart b/lib/features/playlist/presentation/widgets/all_playlist_view.dart index 55a1a76..fbbca12 100644 --- a/lib/features/playlist/presentation/widgets/all_playlist_view.dart +++ b/lib/features/playlist/presentation/widgets/all_playlist_view.dart @@ -51,9 +51,11 @@ class _AllPlaylistViewState extends State } Future _handleAddMusicToPlaylist(Playlist playlist) async { + // TODO(Taleb): Optimize this by reusing the existing bloc if possible. final detailsBloc = PlaylistDetailsBloc( playlist: playlist, getPlaylistSongs: getIt.get(), + getRecentlyPlayedSongs: getIt.get(), )..add(const GetPlaylistSongsEvent()); try { @@ -165,7 +167,7 @@ class _AllPlaylistViewState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'All Playlist', + context.localization.allPlaylists, style: Theme.of(context).textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), @@ -174,7 +176,7 @@ class _AllPlaylistViewState extends State GestureDetector( onTap: _showCreatePlaylistSheet, child: Text( - 'Create Playlist', + context.localization.createPlaylist, style: Theme.of(context).textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, color: Theme.of( diff --git a/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart b/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart index 59440bb..fe40108 100644 --- a/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart +++ b/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart @@ -35,7 +35,7 @@ class PinnedPlaylistsView extends StatelessWidget { scrollDirection: Axis.horizontal, itemBuilder: (context, index) { final recently = Playlist( - id: 0, + id: -1, name: context.localization.recentlyPlayed, numOfSongs: 0, createdAt: DateTime.now(), diff --git a/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart b/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart index 26d6327..e4b3ec7 100644 --- a/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart +++ b/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart @@ -22,35 +22,38 @@ class PlaylistDetailsAppbar extends StatelessWidget @override Widget build(BuildContext context) { return AppBar( - leading: SongsCount(songCount: songCount).paddingOnly(left: 12), - leadingWidth: 200, - flexibleSpace: Center( - child: Text( - playlist.name, - style: Theme.of(context).textTheme.titleLarge, - ), + centerTitle: true, + title: Text( + playlist.name, + style: Theme.of(context).textTheme.titleLarge, ), actions: [ PlaylistItemMoreAction( playlist: playlist, - onDeleted: () { - Navigator.of(context).pop(); - }, + onDeleted: () => Navigator.of(context).pop(), onRenamed: onPlaylistRenamed, ), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), - child: Container( - width: double.infinity, - padding: const EdgeInsets.all( - 6, - ), - child: ElevatedButton.icon( - onPressed: onAddSongs, - icon: const Icon(Icons.add), - label: Text(context.localization.addSongs), - ), + child: Row( + spacing: 8, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (playlist.id != -1) + Expanded( + child: Container( + padding: const EdgeInsets.all(6), + child: ElevatedButton.icon( + onPressed: onAddSongs, + icon: const Icon(Icons.add), + label: Text(context.localization.addSongs), + ), + ), + ), + SongsCount(songCount: songCount).padding(value: 12), + const SizedBox(width: 12), + ], ), ), ); diff --git a/lib/injection/service_locator.dart b/lib/injection/service_locator.dart index dd57ec0..203d5b4 100644 --- a/lib/injection/service_locator.dart +++ b/lib/injection/service_locator.dart @@ -67,6 +67,7 @@ void _setupPlaylistFeature() { ..registerLazySingleton(() => AddSongsToPlaylist(getIt.get())) ..registerLazySingleton(() => RemoveSongsFromPlaylist(getIt.get())) ..registerLazySingleton(() => GetPlaylistSongs(getIt.get())) + ..registerLazySingleton(() => GetRecentlyPlayedSongs(getIt.get())) ..registerLazySingleton(() => GetPlaylistCoverSongId(getIt.get())) ..registerLazySingleton(() => InitializePlaylistCovers(getIt.get())) ..registerLazySingleton(() => PinPlaylistById(getIt.get())) diff --git a/lib/localization/app_en.arb b/lib/localization/app_en.arb index 9f6280c..375275f 100644 --- a/lib/localization/app_en.arb +++ b/lib/localization/app_en.arb @@ -127,6 +127,7 @@ "noSongInThePlaylist": "No song in the playlist. Add songs to see them here.", "addSongsToPlaylist": "Add Songs to Playlist", "recentlyPlayed": "Recently Played", + "allPlaylists": "All Playlists", "musicPlayerPage": "", "@musicPlayerPage": { diff --git a/lib/localization/app_fa.arb b/lib/localization/app_fa.arb index 22e9851..a17d23a 100644 --- a/lib/localization/app_fa.arb +++ b/lib/localization/app_fa.arb @@ -126,6 +126,7 @@ "noSongInThePlaylist": "هیچ موزیکی در این لیست پخش وجود ندارد. برای دیدن موزیک ها، موزیک اضافه کنید.", "addSongsToPlaylist": "اضافه کردن موزیک ها به لیست پخش", "recentlyPlayed": "اخیراً پخش شده", + "allPlaylists": "همه لیست های پخش", "musicPlayerPage": "", "@musicPlayerPage": { diff --git a/lib/localization/app_localizations.dart b/lib/localization/app_localizations.dart index f2d985b..3ad2f72 100644 --- a/lib/localization/app_localizations.dart +++ b/lib/localization/app_localizations.dart @@ -626,6 +626,12 @@ abstract class AppLocalizations { /// **'Recently Played'** String get recentlyPlayed; + /// No description provided for @allPlaylists. + /// + /// In en, this message translates to: + /// **'All Playlists'** + String get allPlaylists; + /// Music Player page localization /// /// In en, this message translates to: diff --git a/lib/localization/app_localizations_en.dart b/lib/localization/app_localizations_en.dart index 330bde3..4839519 100644 --- a/lib/localization/app_localizations_en.dart +++ b/lib/localization/app_localizations_en.dart @@ -281,6 +281,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get recentlyPlayed => 'Recently Played'; + @override + String get allPlaylists => 'All Playlists'; + @override String get musicPlayerPage => ''; diff --git a/lib/localization/app_localizations_fa.dart b/lib/localization/app_localizations_fa.dart index 25a417f..5295b5b 100644 --- a/lib/localization/app_localizations_fa.dart +++ b/lib/localization/app_localizations_fa.dart @@ -281,6 +281,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get recentlyPlayed => 'اخیراً پخش شده'; + @override + String get allPlaylists => 'همه لیست های پخش'; + @override String get musicPlayerPage => ''; From 189dadb919b2697cd8468fbbe74a7b61238a381c Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Sun, 21 Dec 2025 20:19:11 +0330 Subject: [PATCH 5/6] chore: hide more action for recently playlist --- .../widgets/playlist_details_appbar.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart b/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart index e4b3ec7..823b4a7 100644 --- a/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart +++ b/lib/features/playlist/presentation/widgets/playlist_details_appbar.dart @@ -27,13 +27,16 @@ class PlaylistDetailsAppbar extends StatelessWidget playlist.name, style: Theme.of(context).textTheme.titleLarge, ), - actions: [ - PlaylistItemMoreAction( - playlist: playlist, - onDeleted: () => Navigator.of(context).pop(), - onRenamed: onPlaylistRenamed, - ), - ], + // more actions not shown for recently played pseudo-playlist + actions: playlist.id == -1 + ? null + : [ + PlaylistItemMoreAction( + playlist: playlist, + onDeleted: () => Navigator.of(context).pop(), + onRenamed: onPlaylistRenamed, + ), + ], bottom: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), child: Row( From b0b33e64acf402b235c29278c90e44cddf6246d4 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Sun, 21 Dec 2025 20:34:52 +0330 Subject: [PATCH 6/6] fix: resolve some issue ui when rtl mode --- .../presentation/pages/mini_player_page.dart | 1 - .../presentation/widgets/create_playlist_sheet.dart | 12 ++++++------ .../presentation/widgets/pinned_playlist_view.dart | 4 ++-- .../presentation/widgets/playlist_appbar.dart | 2 +- .../songs/presentation/pages/add_songs_page.dart | 1 - .../presentation/widgets/selection_song_card.dart | 2 +- lib/localization/app_en.arb | 1 + lib/localization/app_fa.arb | 1 + lib/localization/app_localizations.dart | 6 ++++++ lib/localization/app_localizations_en.dart | 3 +++ lib/localization/app_localizations_fa.dart | 3 +++ 11 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/features/music_plyer/presentation/pages/mini_player_page.dart b/lib/features/music_plyer/presentation/pages/mini_player_page.dart index 55cab59..e58e410 100644 --- a/lib/features/music_plyer/presentation/pages/mini_player_page.dart +++ b/lib/features/music_plyer/presentation/pages/mini_player_page.dart @@ -155,7 +155,6 @@ class _MiniPlayerPageState extends State tag: 'song_cover_${state.currentSong?.id ?? 0}', child: ArtImageWidget( id: state.currentSong?.id ?? 0, - size: 54, ), ), title: SongTitle( diff --git a/lib/features/playlist/presentation/widgets/create_playlist_sheet.dart b/lib/features/playlist/presentation/widgets/create_playlist_sheet.dart index 578bea6..abb3278 100644 --- a/lib/features/playlist/presentation/widgets/create_playlist_sheet.dart +++ b/lib/features/playlist/presentation/widgets/create_playlist_sheet.dart @@ -153,9 +153,9 @@ class _CreatePlaylistSheetState extends State { color: isDark ? Colors.white.withValues(alpha: 0.1) : Colors.black.withValues(alpha: 0.1), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - bottomLeft: Radius.circular(12), + borderRadius: const BorderRadiusDirectional.only( + topStart: Radius.circular(12), + bottomStart: Radius.circular(12), ), ), child: TextField( @@ -192,9 +192,9 @@ class _CreatePlaylistSheetState extends State { width: 56, decoration: const BoxDecoration( color: Colors.black, - borderRadius: BorderRadius.only( - topRight: Radius.circular(12), - bottomRight: Radius.circular(12), + borderRadius: BorderRadiusDirectional.only( + topEnd: Radius.circular(12), + bottomEnd: Radius.circular(12), ), ), child: Icon(actionIcon, color: Colors.white), diff --git a/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart b/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart index fe40108..b85776b 100644 --- a/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart +++ b/lib/features/playlist/presentation/widgets/pinned_playlist_view.dart @@ -20,7 +20,7 @@ class PinnedPlaylistsView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Favorite Playlist', + context.localization.favoritePlaylists, style: theme.textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), @@ -57,7 +57,7 @@ class PinnedPlaylistsView extends StatelessWidget { final playlist = pinnedPlaylists[index - 1]; return Padding( - padding: const EdgeInsets.only(left: 16), + padding: const EdgeInsetsDirectional.only(start: 16), child: PinnedPlaylistItem( playlist: playlist, onTap: () => Navigator.of(context).push( diff --git a/lib/features/playlist/presentation/widgets/playlist_appbar.dart b/lib/features/playlist/presentation/widgets/playlist_appbar.dart index 17055f8..1fb34f7 100644 --- a/lib/features/playlist/presentation/widgets/playlist_appbar.dart +++ b/lib/features/playlist/presentation/widgets/playlist_appbar.dart @@ -27,7 +27,7 @@ class PlaylistAppbar extends StatelessWidget implements PreferredSizeWidget { centerTitle: true, actions: [ Padding( - padding: const EdgeInsets.only(right: 24, top: 2), + padding: const EdgeInsets.symmetric(horizontal: 24), child: GestureDetector( onTap: onActionPressed, child: DecoratedBox( diff --git a/lib/features/songs/presentation/pages/add_songs_page.dart b/lib/features/songs/presentation/pages/add_songs_page.dart index 61823c2..fa15943 100644 --- a/lib/features/songs/presentation/pages/add_songs_page.dart +++ b/lib/features/songs/presentation/pages/add_songs_page.dart @@ -84,7 +84,6 @@ class _AddSongsPageState extends State { height: 54, child: ArtImageWidget( id: song.id, - size: 54, borderRadius: 8, ), ), diff --git a/lib/features/songs/presentation/widgets/selection_song_card.dart b/lib/features/songs/presentation/widgets/selection_song_card.dart index 249e62c..3750816 100644 --- a/lib/features/songs/presentation/widgets/selection_song_card.dart +++ b/lib/features/songs/presentation/widgets/selection_song_card.dart @@ -22,7 +22,7 @@ class SelectionSongCard extends StatelessWidget { ), child: Row( children: [ - ArtImageWidget(id: song.id, size: 54), + ArtImageWidget(id: song.id), const SizedBox(width: 8), Expanded( child: Column( diff --git a/lib/localization/app_en.arb b/lib/localization/app_en.arb index 375275f..91d8d76 100644 --- a/lib/localization/app_en.arb +++ b/lib/localization/app_en.arb @@ -128,6 +128,7 @@ "addSongsToPlaylist": "Add Songs to Playlist", "recentlyPlayed": "Recently Played", "allPlaylists": "All Playlists", + "favoritePlaylists": "Favorite Playlists", "musicPlayerPage": "", "@musicPlayerPage": { diff --git a/lib/localization/app_fa.arb b/lib/localization/app_fa.arb index a17d23a..b503f7e 100644 --- a/lib/localization/app_fa.arb +++ b/lib/localization/app_fa.arb @@ -127,6 +127,7 @@ "addSongsToPlaylist": "اضافه کردن موزیک ها به لیست پخش", "recentlyPlayed": "اخیراً پخش شده", "allPlaylists": "همه لیست های پخش", + "favoritePlaylists": "لیست های پخش محبوب", "musicPlayerPage": "", "@musicPlayerPage": { diff --git a/lib/localization/app_localizations.dart b/lib/localization/app_localizations.dart index 3ad2f72..69ce19b 100644 --- a/lib/localization/app_localizations.dart +++ b/lib/localization/app_localizations.dart @@ -632,6 +632,12 @@ abstract class AppLocalizations { /// **'All Playlists'** String get allPlaylists; + /// No description provided for @favoritePlaylists. + /// + /// In en, this message translates to: + /// **'Favorite Playlists'** + String get favoritePlaylists; + /// Music Player page localization /// /// In en, this message translates to: diff --git a/lib/localization/app_localizations_en.dart b/lib/localization/app_localizations_en.dart index 4839519..ced2395 100644 --- a/lib/localization/app_localizations_en.dart +++ b/lib/localization/app_localizations_en.dart @@ -284,6 +284,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get allPlaylists => 'All Playlists'; + @override + String get favoritePlaylists => 'Favorite Playlists'; + @override String get musicPlayerPage => ''; diff --git a/lib/localization/app_localizations_fa.dart b/lib/localization/app_localizations_fa.dart index 5295b5b..e57d0bc 100644 --- a/lib/localization/app_localizations_fa.dart +++ b/lib/localization/app_localizations_fa.dart @@ -284,6 +284,9 @@ class AppLocalizationsFa extends AppLocalizations { @override String get allPlaylists => 'همه لیست های پخش'; + @override + String get favoritePlaylists => 'لیست های پخش محبوب'; + @override String get musicPlayerPage => '';