Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/images/album_cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/artist_cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ class _MusicPlayerAppState extends State<MusicPlayerApp> {
FlutterNativeSplash.remove();
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => AlbumsBloc(getIt())
..add(
const LoadAlbumsEvent(),
),
),
BlocProvider(
create: (_) =>
ArtistsBloc(
getIt(),
)..add(
const LoadArtistsEvent(),
),
),
BlocProvider(
create: (_) => SongsBloc(
getIt(),
Expand Down
2 changes: 1 addition & 1 deletion lib/core/commands/delete_song_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class DeleteSongCommand implements BaseCommand<bool> {
await originalFile.copy(_backupFile!.path);

// Proceed with deletion
final result = await repository.deleteSong(song.data);
final result = await repository.deleteSong(songUri: song.data);
if (result.isSuccess && (result.value ?? false)) {
_wasDeleted = true;
return Result.success(true);
Expand Down
2 changes: 2 additions & 0 deletions lib/core/constants/image_assets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ sealed class ImageAssets {
static const String logo = 'assets/logo/app_logo.png';
static const String logoBranding = 'assets/logo/logo_branding.png';
static const String songCover = 'assets/images/song_cover.png';
static const String albumCover = 'assets/images/album_cover.png';
static const String artistCover = 'assets/images/artist_cover.png';
static const String emptySongs = 'assets/images/empty_songs.png';
static const String errorLoadSongs = 'assets/images/error_load_songs.png';
static const String emptyPlaylists = 'assets/images/empty_playlist.png';
Expand Down
1 change: 0 additions & 1 deletion lib/core/domain/enums/enums.dart

This file was deleted.

6 changes: 0 additions & 6 deletions lib/core/domain/enums/songs_sort_type.dart

This file was deleted.

119 changes: 119 additions & 0 deletions lib/core/views/songs_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:music_player/core/domain/entities/song.dart';
import 'package:music_player/core/mixins/mixins.dart';
import 'package:music_player/core/widgets/no_songs_widget.dart';
import 'package:music_player/core/widgets/song_item.dart';
import 'package:music_player/extensions/extensions.dart';
import 'package:music_player/features/favorite/presentation/bloc/bloc.dart';
import 'package:music_player/features/music_plyer/presentation/bloc/bloc.dart';
import 'package:music_player/features/music_plyer/presentation/pages/pages.dart';
import 'package:music_player/features/playlist/playlist.dart';
import 'package:music_player/features/songs/presentation/pages/pages.dart';

class SongsView extends StatefulWidget {
const SongsView({
required this.songs,
this.onRefresh,
super.key,
});

final List<Song> songs;
final Future<void> Function()? onRefresh;

@override
State<SongsView> createState() => _SongsViewState();
}

class _SongsViewState extends State<SongsView>
with
SongSharingMixin,
RingtoneMixin,
PlaylistManagementMixin,
SongDeletionMixin,
ToggleLikeMixin {
// Event handlers for song actions
Future<void> _handleSongTap(int songIndex, List<Song> songs) async {
context.read<MusicPlayerBloc>().add(PlayMusicEvent(songIndex, songs));
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const MusicPlayerPage(),
),
);
}

Future<void> onLongPress(Song song, List<Song> songs) async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => SongsSelectionPage(
title: context.localization.songs,
availableSongs: songs,
selectedSongIds: {song.id},
),
),
);
}

@override
Widget build(BuildContext context) {
if (widget.songs.isEmpty) {
return const NoSongsWidget();
}
return BlocBuilder<MusicPlayerBloc, MusicPlayerState>(
bloc: context.read<MusicPlayerBloc>(),
buildWhen: (previous, next) =>
previous.currentSongIndex != next.currentSongIndex ||
previous.playList != next.playList ||
previous.status != next.status,
builder: (context, musicPlayerState) {
return RefreshIndicator(
onRefresh: widget.onRefresh ?? () async {},
child: BlocSelector<FavoriteSongsBloc, FavoriteSongsState, Set<int>>(
selector: (state) {
return state.favoriteSongIds;
},
builder: (context, favoriteSongIds) {
return ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 8,
),
itemCount: widget.songs.length,
itemBuilder: (context, index) {
final song = widget.songs[index];
final isCurrent = musicPlayerState.currentSong?.id == song.id;
return SongItem(
track: song,
isCurrentTrack: isCurrent,
isPlayingNow:
musicPlayerState.status == MusicPlayerStatus.playing &&
isCurrent,
isFavorite: favoriteSongIds.contains(song.id),
onSetAsRingtone: () => setAsRingtone(song.data),
onDelete: () => showDeleteSongDialog(song),
onFavoriteToggle: () => onToggleLike(song.id),
onAddToPlaylist: () async {
await PlaylistsPage.showSheet(
context: context,
songIds: {song.id},
);
},
onShare: () => shareSong(song),
onLongPress: () => onLongPress(song, widget.songs),
onTap: () => _handleSongTap(index, widget.songs),
onPlayPause: () {
context.read<MusicPlayerBloc>().add(
const TogglePlayPauseEvent(),
);
},
);
},
);
},
),
);
},
);
}
}
1 change: 1 addition & 0 deletions lib/core/views/views.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'songs_view.dart';
81 changes: 19 additions & 62 deletions lib/core/widgets/no_songs_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import 'package:flutter/material.dart';
import 'package:music_player/core/constants/constants.dart';
import 'package:music_player/extensions/extensions.dart';

class NoSongsWidget2 extends StatelessWidget {
const NoSongsWidget2({
class NoSongsWidget extends StatelessWidget {
const NoSongsWidget({
super.key,
this.message = 'No Songs in Your Library',
this.onRefresh,
});

final String message;
final VoidCallback? onRefresh;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -43,69 +45,24 @@ class NoSongsWidget2 extends StatelessWidget {
),
),
),
],
),
);
}
}

class NoSongsWidget extends StatelessWidget {
const NoSongsWidget({
super.key,
this.onRefresh,
this.message = 'No songs available',
});

final String message;
final VoidCallback? onRefresh;

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return Center(
child: Card(
elevation: 4,
margin: const EdgeInsets.symmetric(horizontal: 32),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
ImageAssets.emptySongs,
width: 85,
),
const SizedBox(height: 20),
Text(
message,
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.onSurface.withValues(
alpha: 0.8,
),
fontWeight: FontWeight.w600,
if (onRefresh != null) ...[
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: onRefresh,
icon: const Icon(Icons.refresh),
label: Text(context.localization.refresh),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: onRefresh,
icon: const Icon(Icons.refresh),
label: Text(context.localization.refresh),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
),
],
],
),
);
}
Expand Down
21 changes: 13 additions & 8 deletions lib/core/widgets/song_image_widget.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
import 'package:flutter/material.dart';
import 'package:music_player/core/constants/constants.dart';
import 'package:music_player/extensions/extensions.dart';
import 'package:on_audio_query_pluse/on_audio_query.dart';

class SongImageWidget extends StatelessWidget {
const SongImageWidget({
required this.songId,
class ArtImageWidget extends StatelessWidget {
const ArtImageWidget({
required this.id,
this.type = ArtworkType.AUDIO,
this.size = 50,
this.quality = 70,
this.qualitySize = 200,
this.defaultCoverBg,
this.artworkQuality = FilterQuality.medium,
this.borderRadius,
this.artworkFit = BoxFit.cover,
this.defaultCover = ImageAssets.songCover,
super.key,
});

final int songId;
final int id;
final ArtworkType type;
final double size;
final double? borderRadius;
final int quality;
final int qualitySize;
final FilterQuality artworkQuality;
final BoxFit artworkFit;
final String defaultCover;
final Color? defaultCoverBg;

@override
Widget build(BuildContext context) {
return QueryArtworkWidget(
id: songId,
id: id,
quality: quality,
type: ArtworkType.AUDIO,
type: type,
size: qualitySize,
artworkWidth: size,
artworkHeight: size,
Expand All @@ -40,10 +45,10 @@ class SongImageWidget extends StatelessWidget {
nullArtworkWidget: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius ?? (size / 2)),
child: ColoredBox(
color: Colors.white,
color: defaultCoverBg ?? context.theme.scaffoldBackgroundColor,
child: Image.asset(
defaultCover,
fit: BoxFit.cover,
fit: artworkFit,
width: size,
height: size,
),
Expand Down
2 changes: 1 addition & 1 deletion lib/core/widgets/song_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class SongItem extends StatelessWidget {
// Common row content used by both blurred and plain variants
final content = Row(
children: [
SongImageWidget(songId: track.id, size: songImageSize),
ArtImageWidget(id: track.id, size: songImageSize),
const SizedBox(width: 12),
Expanded(child: _buildTitleAndArtist(context)),
const SizedBox(width: 8),
Expand Down
10 changes: 6 additions & 4 deletions lib/core/widgets/songs_count.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ class SongsCount extends StatelessWidget {

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Row(
spacing: 4,
children: [
Icon(Icons.library_music, size: 18, color: theme.primaryColor),
const SizedBox(width: 8),
const Icon(
Icons.music_note_rounded,
size: 18,
),
Text(
'${context.localization.songs}: $songCount',
style: theme.textTheme.labelSmall?.copyWith(
style: context.theme.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
Expand Down
Loading
Loading