Skip to content

Commit

Permalink
🐛 Fix: Added support for multiple media items in Library cubit (#77)
Browse files Browse the repository at this point in the history
* Refactor navigation routes in home and library
screens

* Refactor LibraryCubit and LibraryState classes

* made library cubit handle multiple media items

* Refactor AddPlaylistButton and update
create-library route

* Fix AddToPlaylist widget bug and update onPressed
action

* Fix toJson() method in PlayableMedia subclasses

* Refactor media fetching and library operations

* Added suggestions from playlist name
  • Loading branch information
devaryakjha authored Nov 21, 2023
1 parent f9d81fe commit 2bcd6bd
Show file tree
Hide file tree
Showing 41 changed files with 362 additions and 218 deletions.
6 changes: 3 additions & 3 deletions lib/cubits/download/download_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,13 @@ class DownloadCubit extends AppCubit<DownloadState> {
for (final song in filteredsong) {
_songMap[song.itemId] = song;
}
emit(loadedState.updateProgress(MapEntry(playlist.id!, 0)));
emit(loadedState.updateProgress(MapEntry(playlist.id, 0)));
await _downloader.downloadBatch(
tasks,
batchProgressCallback: (succeeded, failed) {
final percentComplete = (succeeded + failed) / tasks.length;
emit(loadedState
.updateProgress(MapEntry(playlist.id!, percentComplete)));
emit(
loadedState.updateProgress(MapEntry(playlist.id, percentComplete)));
_logger.i('Batch progress: $succeeded, $failed');
},
taskStatusCallback: _handleTaskStatusUpdate,
Expand Down
21 changes: 11 additions & 10 deletions lib/cubits/player/player_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,18 @@ class MediaPlayerCubit extends AppCubit<MediaPlayerState>

Future<void> playFromSong(PlayableMedia media) async {
assert(media.itemType == PlayableMediaType.song, 'Media must be a song');
final song = (await fetchSong(media))!;
cache(media.cacheKey, song);
await playFromMediaPlaylist(song.toMediaPlaylist());
}

Future<Song?> fetchSong(PlayableMedia media) async {
final cached = maybeGetCached<Song>(media.cacheKey);
return cached ?? await fetchSongFromNetwork(media);
}

if (cached != null) {
await playFromMediaPlaylist(cached.toMediaPlaylist());
return;
}

final response = await fetchUri(
Future<Song?> fetchSongFromNetwork(media) async {
return (await fetchUri(
media.moreInfoUrl,
options: CommonOptions(
transformer: (response) {
Expand All @@ -123,10 +126,8 @@ class MediaPlayerCubit extends AppCubit<MediaPlayerState>
return song;
},
),
);
final song = response.$2!;
cache(media.cacheKey, song);
await playFromMediaPlaylist(song.toMediaPlaylist());
))
.$2;
}

Future<void> play() async {
Expand Down
1 change: 1 addition & 0 deletions lib/features/home/data/models/chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Chart extends PlayableMedia {

factory Chart.fromJson(Map<String, dynamic> json) => _$ChartFromJson(json);

@override
Map<String, dynamic> toJson() => _$ChartToJson(this);

Chart copyWith({
Expand Down
6 changes: 5 additions & 1 deletion lib/features/home/ui/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ class _RecentlyPlayedState extends State<RecentlyPlayed> {
.read<MediaPlayerCubit>()
.playFromSong(PlayableMediaImpl.fromMediaPlaylist(item));
} else {
context.push(AppRoutes.library.path, extra: item);
context.pushNamed(
AppRoutes.library.name,
extra: item,
pathParameters: {'id': item.id},
);
}
},
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ class MediaCard extends StatelessWidget {
if (item.itemType.isSong) {
context.read<MediaPlayerCubit>().playFromSong(item);
} else {
context.push(AppRoutes.library.path, extra: item);
context.pushNamed(
AppRoutes.library.name,
extra: item,
pathParameters: {'id': item.itemId},
);
}
},
child: Padding(
Expand Down
6 changes: 5 additions & 1 deletion lib/features/home/ui/home_widgets/trending/item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ class TrendingItem extends StatelessWidget {
PlayableMediaType.song => () {
context.readMediaPlayerCubit.playFromSong(media);
},
_ => () => context.push(AppRoutes.library.path, extra: media),
_ => () => context.pushNamed(
AppRoutes.library.name,
extra: media,
pathParameters: {'id': media.itemId},
),
},
child: Container(
height: 56,
Expand Down
32 changes: 22 additions & 10 deletions lib/features/library/cubit/library_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import 'package:varanasi_mobile_app/utils/logger.dart';
part 'library_state.dart';

class LibraryCubit extends Cubit<LibraryState> {
LibraryCubit() : super(const LibraryInitial());
LibraryCubit() : super(const LibraryState());

static LibraryCubit of(BuildContext context) => context.read<LibraryCubit>();

StreamSubscription? _subscription;

Future<void> loadUserLibrary(MediaPlaylist playlist) async {
try {
emit(const LibraryLoading());
emit(state + MediaLoadingState(playlist.id));
String link = playlist.images.last.link!;
if (!appContext.mounted) return;
final configCubit = appContext.read<ConfigCubit>();
Expand All @@ -41,7 +41,8 @@ class LibraryCubit extends Cubit<LibraryState> {
if (!appContext.mounted) return;
if (playlist.isDownload) {
final newPlaylist = appContext.read<DownloadCubit>().toUserLibrary();
emit(LibraryLoaded(newPlaylist, colorPalette!, image));
emit(state +
MediaLoadedState(playlist.id, newPlaylist, colorPalette!, image));
} else if (playlist.isCustomPlaylist) {
_subscription =
UserLibraryRepository.instance.librariesStream.where((event) {
Expand All @@ -51,11 +52,21 @@ class LibraryCubit extends Cubit<LibraryState> {
final newPlaylist =
event.firstWhereOrNull((element) => element.id == playlist.id);
if (newPlaylist != null) {
emit(LibraryLoaded(newPlaylist, colorPalette!, image));
emit(
state +
MediaLoadedState(
newPlaylist.id,
newPlaylist,
colorPalette!,
image,
),
);
}
});
} else {
emit(LibraryLoaded(playlist, colorPalette!, image));
emit(
state + MediaLoadedState(playlist.id, playlist, colorPalette!, image),
);
}
} catch (e, s) {
Logger.instance.e(e);
Expand All @@ -65,7 +76,7 @@ class LibraryCubit extends Cubit<LibraryState> {

Future<void> fetchLibrary(PlayableMedia media) async {
try {
emit(const LibraryLoading());
emit(state + MediaLoadingState(media.itemId));
final playlist = await LibraryRepository.instance.fetchLibrary(media);
String link = playlist.images.last.link!;
if (link == appConfig.placeholderImageLink) {
Expand All @@ -75,16 +86,17 @@ class LibraryCubit extends Cubit<LibraryState> {
final configCubit = appContext.read<ConfigCubit>();
final colorPalette = await configCubit.generatePalleteGenerator(link);
final image = configCubit.getProvider(link);
emit(LibraryLoaded(playlist, colorPalette!, image, media: media));
emit(state +
MediaLoadedState(playlist.id, playlist, colorPalette!, image));
} on Exception catch (e, s) {
LibraryRepository.instance.deleteCache(media.cacheKey);
emit(LibraryError(e, stackTrace: s));
}
}

void toggleAppbarTitle([bool? expanded]) {
if (state is LibraryLoaded) {
emit((state as LibraryLoaded).toggleAppbarTitle(expanded));
void toggleAppbarTitle(String id, [bool? expanded]) {
if (state[id] != null) {
emit(state + (state[id] as MediaLoadedState).toggleAppbarTitle(expanded));
}
}

Expand Down
82 changes: 52 additions & 30 deletions lib/features/library/cubit/library_state.dart
Original file line number Diff line number Diff line change
@@ -1,59 +1,82 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'library_cubit.dart';

abstract class LibraryState extends Equatable {
const LibraryState();
class LibraryState extends Equatable {
final Map<String, MediaState> data;

const LibraryState({
this.data = const {},
});

MediaState? operator [](String key) => data[key];

@override
List<Object?> get props => [];
List<Object?> get props => [data];

LibraryState copyWith({
Map<String, MediaState>? data,
}) {
return LibraryState(
data: data ?? this.data,
);
}

LibraryState operator +(MediaState other) {
return copyWith(data: Map.from(data)..addAll({other.id: other}));
}
}

class LibraryInitial extends LibraryState {
const LibraryInitial();
sealed class MediaState extends Equatable {
final String id;
const MediaState(this.id);

@override
List<Object?> get props => [];
List<Object?> get props => [id];

bool get isLoading => this is MediaLoadingState;
bool get isError => this is MediaErrorState;
bool get isLoaded => this is MediaLoadedState;
}

class MediaLoadingState extends MediaState {
const MediaLoadingState(super.id);
}

class LibraryLoading extends LibraryState {
const LibraryLoading();
class MediaErrorState extends MediaState {
final dynamic error;
final StackTrace? stackTrace;

const MediaErrorState(
super.id,
this.error, {
this.stackTrace,
});

@override
List<Object?> get props => [];
List<Object?> get props => [...super.props, error, stackTrace];
}

class LibraryLoaded<T extends PlayableMedia> extends LibraryState {
class MediaLoadedState<T extends PlayableMedia> extends MediaState {
final MediaPlaylist<T> playlist;
final PaletteGenerator colorPalette;
final ImageProvider image;
final bool showTitleInAppBar;
final PlayableMedia? media;

const LibraryLoaded(
const MediaLoadedState(
super.id,
this.playlist,
this.colorPalette,
this.image, {
this.showTitleInAppBar = false,
this.media,
});

@override
List<Object?> get props => [
playlist,
colorPalette,
image,
showTitleInAppBar,
media,
];
List<Object?> get props =>
[playlist, colorPalette, image, showTitleInAppBar, id];

PaletteColor? get baseColor =>
colorPalette.dominantColor ?? colorPalette.vibrantColor;

/// - create a list of color which will be used as gradient color
/// - for the background of the playlist card's thumbnail
/// - there should be exactly 3 colors in the list
/// - the first two colors are the muted and vibrant color from the image
/// - the last color has to be black to blend with the rest of the screen
List<Color> get gradientColors {
return [
baseColor?.color ?? Colors.white,
Expand All @@ -78,19 +101,18 @@ class LibraryLoaded<T extends PlayableMedia> extends LibraryState {

bool get needSearchBar => length > 10;

LibraryLoaded<T> copyWith({
MediaLoadedState<T> copyWith({
MediaPlaylist<T>? playlist,
PaletteGenerator? colorPalette,
ImageProvider? image,
bool? showTitleInAppBar,
PlayableMedia? media,
}) {
return LibraryLoaded<T>(
return MediaLoadedState<T>(
id,
playlist ?? this.playlist,
colorPalette ?? this.colorPalette,
image ?? this.image,
showTitleInAppBar: showTitleInAppBar ?? this.showTitleInAppBar,
media: media ?? this.media,
);
}

Expand All @@ -109,7 +131,7 @@ class LibraryLoaded<T extends PlayableMedia> extends LibraryState {
return playlist.copyWith(mediaItems: sortedMediaItems(sortBy));
}

LibraryLoaded<T> toggleAppbarTitle([bool? expanded]) {
MediaLoadedState<T> toggleAppbarTitle([bool? expanded]) {
return copyWith(showTitleInAppBar: expanded ?? !showTitleInAppBar);
}
}
Expand Down
8 changes: 5 additions & 3 deletions lib/features/library/ui/library_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import 'library_widgets/library_loader.dart';
import 'library_widgets/page.dart';

class LibraryPage extends StatefulWidget {
final String id;
final Object? source;

const LibraryPage({super.key, this.source});
const LibraryPage(this.id, {super.key, this.source});

@override
State<LibraryPage> createState() => _LibraryPageState();
Expand Down Expand Up @@ -46,8 +47,8 @@ class _LibraryPageState extends State<LibraryPage> {

@override
Widget build(BuildContext context) {
final isLoading =
context.select((LibraryCubit source) => source.state is LibraryLoading);
final isLoading = context.select(
(LibraryCubit source) => (source.state[widget.id]?.isLoading ?? false));
if (isLoading) {
return const LibraryLoader();
} else {
Expand All @@ -58,6 +59,7 @@ class _LibraryPageState extends State<LibraryPage> {
}
},
child: LibraryContent(
id: widget.id,
source: switch (widget.source) {
(PlayableMedia media) => media,
(_) => null,
Expand Down
2 changes: 1 addition & 1 deletion lib/features/library/ui/library_search_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class LibrarySearchPage extends HookWidget {

List<PlayableMedia> get media => playlist.mediaItems ?? [];

String get playlistId => playlist.id!;
String get playlistId => playlist.id;

@override
Widget build(BuildContext context) {
Expand Down
Loading

0 comments on commit 2bcd6bd

Please sign in to comment.