From ea58d9b9be2b1b3b9829362e4cf2c9d6cd58f6bd Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 00:08:14 +0530 Subject: [PATCH 01/10] =?UTF-8?q?=E2=9C=A8=20New:=20show=20downloads=20ins?= =?UTF-8?q?ide=20library=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cubits/download/download_cubit.dart | 19 +++++++++ lib/features/library/cubit/library_cubit.dart | 7 +++- lib/features/library/cubit/library_state.dart | 7 +++- .../library/ui/library_widgets/page.dart | 14 ++++++- .../user-library/data/user_library.dart | 22 ++++++++-- .../user-library/data/user_library.g.dart | 5 +++ .../user-library/ui/user_library_page.dart | 40 ++++++++++++++++--- lib/widgets/add_to_library.dart | 10 +++-- lib/widgets/disable_child.dart | 22 ++++++++++ lib/widgets/download_button.dart | 6 --- 10 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 lib/widgets/disable_child.dart diff --git a/lib/cubits/download/download_cubit.dart b/lib/cubits/download/download_cubit.dart index 5d4138e..5daef68 100644 --- a/lib/cubits/download/download_cubit.dart +++ b/lib/cubits/download/download_cubit.dart @@ -5,9 +5,11 @@ import 'package:background_downloader/background_downloader.dart'; import 'package:equatable/equatable.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:varanasi_mobile_app/features/user-library/data/user_library.dart'; import 'package:varanasi_mobile_app/models/app_config.dart'; import 'package:varanasi_mobile_app/models/download.dart'; import 'package:varanasi_mobile_app/models/download_url.dart'; +import 'package:varanasi_mobile_app/models/image.dart'; import 'package:varanasi_mobile_app/models/media_playlist.dart'; import 'package:varanasi_mobile_app/models/playable_item.dart'; import 'package:varanasi_mobile_app/models/song.dart'; @@ -279,4 +281,21 @@ class DownloadCubit extends AppCubit { ); return AppConfig.effectiveDlQuality; } + + Stream get downloadLibraryStream { + return _downloadBox.watch().map((event) => toUserLibrary()); + } + + UserLibrary toUserLibrary() { + final List values = _downloadBox.values.toList(); + final library = UserLibrary( + id: "downloads", + title: "Downloads", + description: "Your downloaded songs", + mediaItems: values.map((e) => e.media).toList(), + images: const [Image.likedSongs], + type: UserLibraryType.download, + ); + return library; + } } diff --git a/lib/features/library/cubit/library_cubit.dart b/lib/features/library/cubit/library_cubit.dart index 023053d..4575b20 100644 --- a/lib/features/library/cubit/library_cubit.dart +++ b/lib/features/library/cubit/library_cubit.dart @@ -28,7 +28,12 @@ class LibraryCubit extends Cubit { final configCubit = appContext.read(); final colorPalette = await configCubit.generatePalleteGenerator(link); final image = configCubit.getProvider(link); - emit(LibraryLoaded(playlist.toMediaPlaylist(), colorPalette!, image)); + emit(LibraryLoaded( + playlist.toMediaPlaylist(), + colorPalette!, + image, + sourceLibrary: playlist, + )); } catch (e, s) { emit(LibraryError(e, stackTrace: s)); } diff --git a/lib/features/library/cubit/library_state.dart b/lib/features/library/cubit/library_state.dart index fddfa6b..717aaf3 100644 --- a/lib/features/library/cubit/library_state.dart +++ b/lib/features/library/cubit/library_state.dart @@ -27,16 +27,19 @@ class LibraryLoaded extends LibraryState { final PaletteGenerator colorPalette; final ImageProvider image; final bool showTitleInAppBar; + final UserLibrary? sourceLibrary; const LibraryLoaded( this.playlist, this.colorPalette, this.image, { this.showTitleInAppBar = false, + this.sourceLibrary, }); @override - List get props => [playlist, colorPalette, image, showTitleInAppBar]; + List get props => + [playlist, colorPalette, image, showTitleInAppBar, sourceLibrary]; PaletteColor? get baseColor => colorPalette.dominantColor ?? colorPalette.vibrantColor; @@ -75,12 +78,14 @@ class LibraryLoaded extends LibraryState { PaletteGenerator? colorPalette, ImageProvider? image, bool? showTitleInAppBar, + UserLibrary? sourceLibrary, }) { return LibraryLoaded( playlist ?? this.playlist, colorPalette ?? this.colorPalette, image ?? this.image, showTitleInAppBar: showTitleInAppBar ?? this.showTitleInAppBar, + sourceLibrary: sourceLibrary ?? this.sourceLibrary, ); } diff --git a/lib/features/library/ui/library_widgets/page.dart b/lib/features/library/ui/library_widgets/page.dart index 6e1b96c..bab1e5a 100644 --- a/lib/features/library/ui/library_widgets/page.dart +++ b/lib/features/library/ui/library_widgets/page.dart @@ -10,6 +10,7 @@ import 'package:varanasi_mobile_app/models/playable_item.dart'; import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; import 'package:varanasi_mobile_app/utils/extensions/media_query.dart'; import 'package:varanasi_mobile_app/widgets/add_to_library.dart'; +import 'package:varanasi_mobile_app/widgets/disable_child.dart'; import 'package:varanasi_mobile_app/widgets/download_button.dart'; import 'package:varanasi_mobile_app/widgets/media_list.dart'; import 'package:varanasi_mobile_app/widgets/play_pause_button.dart'; @@ -128,8 +129,17 @@ class _LibraryContentState extends State { Row( key: titleKey, children: [ - AddToLibrary(state.playlist), - DownloadPlaylist(playlist: state.playlist), + AddToLibrary( + state.playlist, + sourceLibrary: state.sourceLibrary, + ), + DisableChild( + disabled: + state.sourceLibrary?.isDownload == true, + child: DownloadPlaylist( + playlist: state.playlist, + ), + ), const Spacer(), const ShuffleModeToggle(iconSize: 24), ], diff --git a/lib/features/user-library/data/user_library.dart b/lib/features/user-library/data/user_library.dart index 570c1cd..d9b0fb2 100644 --- a/lib/features/user-library/data/user_library.dart +++ b/lib/features/user-library/data/user_library.dart @@ -14,7 +14,9 @@ enum UserLibraryType { @HiveField(1) album('album'), @HiveField(2) - playlist('playlist'); + playlist('playlist'), + @HiveField(3) + download('download'); // TODO: Add Artist final String type; @@ -24,10 +26,11 @@ enum UserLibraryType { bool get isFavorite => this == UserLibraryType.favorite; bool get isAlbum => this == UserLibraryType.album; bool get isPlaylist => this == UserLibraryType.playlist; + bool get isDownload => this == UserLibraryType.download; } @HiveType(typeId: 18) -class UserLibrary extends Equatable { +class UserLibrary with EquatableMixin implements Comparable { @HiveField(0) final UserLibraryType type; @HiveField(1) @@ -57,9 +60,10 @@ class UserLibrary extends Equatable { bool get isNotEmpty => mediaItems.isNotEmpty; - bool get isFavorite => type.isFavorite; + bool get isFavorite => type.isFavorite || id == "favorite"; bool get isAlbum => type.isAlbum; bool get isPlaylist => type.isPlaylist; + bool get isDownload => type.isDownload || id == "downloads"; const UserLibrary.empty(this.type) : id = "", @@ -97,6 +101,18 @@ class UserLibrary extends Equatable { images: images ?? this.images, ); } + + @override + int compareTo(UserLibrary other) { + // if id download then it should be first + if (isDownload) return -1; + if (other.isDownload) return 1; + // if id favorite then it should be first + if (isFavorite) return -1; + if (other.isFavorite) return 1; + // else sort by title + return (title ?? "").compareTo(other.title ?? ""); + } } @HiveType(typeId: 19) diff --git a/lib/features/user-library/data/user_library.g.dart b/lib/features/user-library/data/user_library.g.dart index a188d3a..cc10218 100644 --- a/lib/features/user-library/data/user_library.g.dart +++ b/lib/features/user-library/data/user_library.g.dart @@ -208,6 +208,8 @@ class UserLibraryTypeAdapter extends TypeAdapter { return UserLibraryType.album; case 2: return UserLibraryType.playlist; + case 3: + return UserLibraryType.download; default: return UserLibraryType.favorite; } @@ -225,6 +227,9 @@ class UserLibraryTypeAdapter extends TypeAdapter { case UserLibraryType.playlist: writer.writeByte(2); break; + case UserLibraryType.download: + writer.writeByte(3); + break; } } diff --git a/lib/features/user-library/ui/user_library_page.dart b/lib/features/user-library/ui/user_library_page.dart index cf3d084..94d962a 100644 --- a/lib/features/user-library/ui/user_library_page.dart +++ b/lib/features/user-library/ui/user_library_page.dart @@ -1,21 +1,33 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; +import 'package:varanasi_mobile_app/cubits/download/download_cubit.dart'; import 'package:varanasi_mobile_app/features/user-library/cubit/user_library_cubit.dart'; +import 'package:varanasi_mobile_app/features/user-library/data/user_library.dart'; import 'package:varanasi_mobile_app/features/user-library/ui/widgets/add_playlist_button.dart'; import 'package:varanasi_mobile_app/flavors.dart'; import 'package:varanasi_mobile_app/gen/assets.gen.dart'; import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; +import 'package:varanasi_mobile_app/utils/helpers/get_app_context.dart'; import 'package:varanasi_mobile_app/utils/routes.dart'; import 'widgets/empty.dart'; -class UserLibraryPage extends StatelessWidget { +class UserLibraryPage extends HookWidget { const UserLibraryPage({super.key}); + DownloadCubit get downloadCubit => appContext.read(); + Stream get downloadLibraryStream => + downloadCubit.downloadLibraryStream; + @override Widget build(BuildContext context) { + final downloadSnapshot = useStream( + downloadLibraryStream, + initialData: downloadCubit.toUserLibrary(), + ); return Scaffold( appBar: AppBar( title: Row( @@ -38,19 +50,35 @@ class UserLibraryPage extends StatelessWidget { if (state is! UserLibraryLoaded) { return const Center(child: CircularProgressIndicator()); } - final library = state.library; + final library = [...state.library, downloadSnapshot.data] + .whereType() + .toList() + ..sort(); if (library.isEmpty) return const EmptyUserLibrary(); return ListView.builder( itemBuilder: (context, index) { final item = library[index]; return ListTile( onTap: () => context.push(AppRoutes.library.path, extra: item), - leading: ClipRRect( - borderRadius: BorderRadius.circular(4), - child: CachedNetworkImage( - imageUrl: item.images.lastOrNull?.link ?? '', + leading: Visibility( + replacement: Container( height: 48, width: 48, + decoration: BoxDecoration( + color: context.colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(4), + ), + child: Icon(Icons.download_rounded, + color: context.theme.colorScheme.onSecondaryContainer), + ), + visible: !item.isDownload, + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: CachedNetworkImage( + imageUrl: item.images.lastOrNull?.link ?? '', + height: 48, + width: 48, + ), ), ), title: Text(item.title ?? ''), diff --git a/lib/widgets/add_to_library.dart b/lib/widgets/add_to_library.dart index ff7d8f6..f40884c 100644 --- a/lib/widgets/add_to_library.dart +++ b/lib/widgets/add_to_library.dart @@ -1,21 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:varanasi_mobile_app/features/user-library/cubit/user_library_cubit.dart'; +import 'package:varanasi_mobile_app/features/user-library/data/user_library.dart'; import 'package:varanasi_mobile_app/models/media_playlist.dart'; class AddToLibrary extends StatelessWidget { final MediaPlaylist playlist; - const AddToLibrary(this.playlist, {super.key}); + final UserLibrary? sourceLibrary; + const AddToLibrary(this.playlist, {super.key, this.sourceLibrary}); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { if (state is! UserLibraryLoaded) return const SizedBox.shrink(); - final isAdded = state.isAdded(playlist); - AnimatedIcons.add_event; + final isInternalLibrary = sourceLibrary?.isDownload == true || + sourceLibrary?.isFavorite == true; + final isAdded = state.isAdded(playlist) || isInternalLibrary; return IconButton( onPressed: () { + if (isInternalLibrary) return; final cubit = context.read(); if (isAdded) { cubit.removeFromLibrary(playlist); diff --git a/lib/widgets/disable_child.dart b/lib/widgets/disable_child.dart new file mode 100644 index 0000000..b637ece --- /dev/null +++ b/lib/widgets/disable_child.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class DisableChild extends StatelessWidget { + const DisableChild({ + super.key, + this.disabled = true, + required this.child, + }); + final bool disabled; + + final Widget child; + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: disabled, + child: Opacity( + opacity: disabled ? 0.5 : 1, + child: child, + ), + ); + } +} diff --git a/lib/widgets/download_button.dart b/lib/widgets/download_button.dart index 9fea6fd..5e8f1fb 100644 --- a/lib/widgets/download_button.dart +++ b/lib/widgets/download_button.dart @@ -7,7 +7,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; import 'package:varanasi_mobile_app/cubits/download/download_cubit.dart'; -import 'package:varanasi_mobile_app/features/library/cubit/library_cubit.dart'; import 'package:varanasi_mobile_app/features/user-library/cubit/user_library_cubit.dart'; import 'package:varanasi_mobile_app/models/download.dart'; import 'package:varanasi_mobile_app/models/media_playlist.dart'; @@ -38,11 +37,6 @@ class DownloadButton extends StatelessWidget { } else { cubit.downloadSong(media); } - final libraryState = context.read().state; - if (libraryState is LibraryLoaded) { - final playlist = libraryState.playlist; - context.read().addToLibrary(playlist); - } HapticFeedback.mediumImpact(); }, icon: DownloadStatus( From 5b09d34b28783f74f7a15a533c5bb9a401460615 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 01:18:50 +0530 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20downloaded=20files?= =?UTF-8?q?=20not=20playing=20on=20IOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cubits/download/download_cubit.dart | 31 ++++++++++++++++--- lib/cubits/download/download_state.dart | 25 +++++++++++---- .../library/ui/library_widgets/page.dart | 3 +- .../user-library/ui/user_library_page.dart | 1 + lib/utils/player/audio_handler_impl.dart | 16 +++++++--- lib/widgets/download_button.dart | 2 +- pubspec.lock | 8 ++--- pubspec.yaml | 2 ++ 8 files changed, 67 insertions(+), 21 deletions(-) diff --git a/lib/cubits/download/download_cubit.dart b/lib/cubits/download/download_cubit.dart index 5daef68..199a869 100644 --- a/lib/cubits/download/download_cubit.dart +++ b/lib/cubits/download/download_cubit.dart @@ -4,6 +4,8 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:equatable/equatable.dart'; import 'package:hive_flutter/hive_flutter.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; import 'package:rxdart/rxdart.dart'; import 'package:varanasi_mobile_app/features/user-library/data/user_library.dart'; import 'package:varanasi_mobile_app/models/app_config.dart'; @@ -21,7 +23,7 @@ import 'package:varanasi_mobile_app/utils/logger.dart'; part 'download_state.dart'; class DownloadCubit extends AppCubit { - DownloadCubit() : super(const DownloadState()); + DownloadCubit() : super(DownloadInitial()); late final Box _downloadBox; late final FileDownloader _downloader; @@ -32,11 +34,19 @@ class DownloadCubit extends AppCubit { Box get downloadBox => _downloadBox; + FileDownloader get downloader => _downloader; + + DownloadLoadedState get loadedState => state as DownloadLoadedState; + @override FutureOr init() async { + final baseDir = await getApplicationDocumentsDirectory(); + emit(DownloadLoadedState(downloadDirectory: baseDir)); _songMap = {}; _downloadBox = Hive.box(AppStrings.downloadBoxName); - _downloader = FileDownloader(); + _downloader = FileDownloader() + ..trackTasks() + ..resumeFromBackground(); _downloader.updates.listen((update) { if (update is TaskStatusUpdate) { _handleTaskStatusUpdate(update); @@ -144,12 +154,13 @@ class DownloadCubit extends AppCubit { for (final song in filteredsong) { _songMap[song.itemId] = song; } - emit(state.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(state.updateProgress(MapEntry(playlist.id!, percentComplete))); + emit(loadedState + .updateProgress(MapEntry(playlist.id!, percentComplete))); _logger.i('Batch progress: $succeeded, $failed'); }, taskStatusCallback: _handleTaskStatusUpdate, @@ -298,4 +309,16 @@ class DownloadCubit extends AppCubit { ); return library; } + + String getDownloadPath(String id) { + final item = _downloadBox.get(id); + final filename = _fileNameFromSong(item!.media); + return path.join(loadedState.downloadDirectory.path, '', filename); + } + + File? getCacheFile(String itemId, String itemUrl) { + final ext = itemUrl.split('.').last; + final fileName = '$itemId.$ext'; + return File(path.join(loadedState.downloadDirectory.path, '', fileName)); + } } diff --git a/lib/cubits/download/download_state.dart b/lib/cubits/download/download_state.dart index e68ec64..6ca732c 100644 --- a/lib/cubits/download/download_state.dart +++ b/lib/cubits/download/download_state.dart @@ -1,25 +1,38 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first part of 'download_cubit.dart'; -class DownloadState extends Equatable { +sealed class DownloadState extends Equatable { + const DownloadState(); + + @override + List get props => []; +} + +class DownloadInitial extends DownloadState {} + +class DownloadLoadedState extends DownloadState { final Map playlistProgressMap; + final Directory downloadDirectory; - const DownloadState({ + const DownloadLoadedState({ this.playlistProgressMap = const {}, + required this.downloadDirectory, }); @override - List get props => [playlistProgressMap]; + List get props => [playlistProgressMap, downloadDirectory]; - DownloadState copyWith({ + DownloadLoadedState copyWith({ Map? playlistProgressMap, + Directory? downloadDirectory, }) { - return DownloadState( + return DownloadLoadedState( playlistProgressMap: playlistProgressMap ?? this.playlistProgressMap, + downloadDirectory: downloadDirectory ?? this.downloadDirectory, ); } - DownloadState updateProgress(MapEntry entry) { + DownloadLoadedState updateProgress(MapEntry entry) { final Map oldProgress = Map.from(playlistProgressMap) ..addEntries([entry]); return copyWith( diff --git a/lib/features/library/ui/library_widgets/page.dart b/lib/features/library/ui/library_widgets/page.dart index bab1e5a..4939ab0 100644 --- a/lib/features/library/ui/library_widgets/page.dart +++ b/lib/features/library/ui/library_widgets/page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide Typography; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -134,7 +135,7 @@ class _LibraryContentState extends State { sourceLibrary: state.sourceLibrary, ), DisableChild( - disabled: + disabled: !kDebugMode && state.sourceLibrary?.isDownload == true, child: DownloadPlaylist( playlist: state.playlist, diff --git a/lib/features/user-library/ui/user_library_page.dart b/lib/features/user-library/ui/user_library_page.dart index 94d962a..78206e3 100644 --- a/lib/features/user-library/ui/user_library_page.dart +++ b/lib/features/user-library/ui/user_library_page.dart @@ -52,6 +52,7 @@ class UserLibraryPage extends HookWidget { } final library = [...state.library, downloadSnapshot.data] .whereType() + .where((element) => element.isNotEmpty) .toList() ..sort(); if (library.isEmpty) return const EmptyUserLibrary(); diff --git a/lib/utils/player/audio_handler_impl.dart b/lib/utils/player/audio_handler_impl.dart index b304dbf..5e7bc0c 100644 --- a/lib/utils/player/audio_handler_impl.dart +++ b/lib/utils/player/audio_handler_impl.dart @@ -2,13 +2,16 @@ import 'dart:async'; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive/hive.dart'; import 'package:just_audio/just_audio.dart'; import 'package:rxdart/rxdart.dart'; import 'package:varanasi_mobile_app/cubits/config/config_cubit.dart'; +import 'package:varanasi_mobile_app/cubits/download/download_cubit.dart'; import 'package:varanasi_mobile_app/models/app_config.dart'; import 'package:varanasi_mobile_app/models/download.dart'; import 'package:varanasi_mobile_app/utils/constants/strings.dart'; +import 'package:varanasi_mobile_app/utils/helpers/get_app_context.dart'; import 'typings.dart'; @@ -183,13 +186,14 @@ final class AudioHandlerImpl extends BaseAudioHandler final downloaded = _isSongDownloaded(itemId); if (!downloaded) { final uri = Uri.parse(mediaItem.id); + // final cacheFile = + // appContext.read().getCacheFile(itemId, mediaItem.id); final audioSource = LockCachingAudioSource(uri); _mediaItemExpando[audioSource] = mediaItem; return audioSource; } - final box = Hive.box(AppStrings.downloadBoxName); - final downloadedMedia = box.get(itemId); - final audioSource = AudioSource.file(downloadedMedia!.path); + final path = appContext.read().getDownloadPath(itemId); + final audioSource = AudioSource.file(path); _mediaItemExpando[audioSource] = mediaItem; return audioSource; } @@ -199,7 +203,8 @@ final class AudioHandlerImpl extends BaseAudioHandler @override Future addQueueItem(MediaItem mediaItem) async { - await _playlist.add(_itemToSource(mediaItem)); + final source = _itemToSource(mediaItem); + await _playlist.add(source); } @override @@ -209,7 +214,8 @@ final class AudioHandlerImpl extends BaseAudioHandler @override Future insertQueueItem(int index, MediaItem mediaItem) async { - await _playlist.insert(index, _itemToSource(mediaItem)); + final source = _itemToSource(mediaItem); + await _playlist.insert(index, source); } @override diff --git a/lib/widgets/download_button.dart b/lib/widgets/download_button.dart index 5e8f1fb..29a525a 100644 --- a/lib/widgets/download_button.dart +++ b/lib/widgets/download_button.dart @@ -61,7 +61,7 @@ class DownloadPlaylist extends StatelessWidget { @override Widget build(BuildContext context) { final progress = context.select((DownloadCubit value) => - value.state.playlistProgressMap[playlist.id] ?? 0); + value.loadedState.playlistProgressMap[playlist.id] ?? 0); final downloadBox = context.read().downloadBox; final keys = playlist.mediaItems?.map((e) => e.itemId).toList(); return ValueListenableBuilder( diff --git a/pubspec.lock b/pubspec.lock index d65156d..eace605 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -897,7 +897,7 @@ packages: source: hosted version: "0.3.3+3" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" @@ -913,13 +913,13 @@ packages: source: hosted version: "1.0.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.1" path_provider_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 450aa9c..5531d43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,8 @@ dependencies: background_downloader: ^7.10.1 lottie: ^2.7.0 percent_indicator: ^4.2.3 + path_provider: ^2.1.1 + path: ^1.8.3 dev_dependencies: flutter_test: From 37dd776c79c452d8b0e27d68804c815aa3a07fbe Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 01:59:12 +0530 Subject: [PATCH 03/10] =?UTF-8?q?=E2=9C=A8=20Add:=20custom=20cache=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cubits/download/download_cubit.dart | 3 ++- lib/utils/player/audio_handler_impl.dart | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/cubits/download/download_cubit.dart b/lib/cubits/download/download_cubit.dart index 199a869..efc5c6d 100644 --- a/lib/cubits/download/download_cubit.dart +++ b/lib/cubits/download/download_cubit.dart @@ -319,6 +319,7 @@ class DownloadCubit extends AppCubit { File? getCacheFile(String itemId, String itemUrl) { final ext = itemUrl.split('.').last; final fileName = '$itemId.$ext'; - return File(path.join(loadedState.downloadDirectory.path, '', fileName)); + return File( + path.join(loadedState.downloadDirectory.path, 'cache', fileName)); } } diff --git a/lib/utils/player/audio_handler_impl.dart b/lib/utils/player/audio_handler_impl.dart index 5e7bc0c..21ce1e1 100644 --- a/lib/utils/player/audio_handler_impl.dart +++ b/lib/utils/player/audio_handler_impl.dart @@ -186,9 +186,9 @@ final class AudioHandlerImpl extends BaseAudioHandler final downloaded = _isSongDownloaded(itemId); if (!downloaded) { final uri = Uri.parse(mediaItem.id); - // final cacheFile = - // appContext.read().getCacheFile(itemId, mediaItem.id); - final audioSource = LockCachingAudioSource(uri); + final cacheFile = + appContext.read().getCacheFile(itemId, mediaItem.id); + final audioSource = LockCachingAudioSource(uri, cacheFile: cacheFile); _mediaItemExpando[audioSource] = mediaItem; return audioSource; } From 2cf9f0ecd888e30e5070a7550cf93b94ab57c5fe Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 01:59:51 +0530 Subject: [PATCH 04/10] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20index=20of=20item?= =?UTF-8?q?=20when=20shuffle=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cubits/player/player_cubit.dart | 12 +++++++++++- lib/features/library/ui/library_widgets/page.dart | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/cubits/player/player_cubit.dart b/lib/cubits/player/player_cubit.dart index 31e5f83..efce673 100644 --- a/lib/cubits/player/player_cubit.dart +++ b/lib/cubits/player/player_cubit.dart @@ -62,10 +62,13 @@ class MediaPlayerCubit extends AppCubit Future playFromMediaPlaylist( MediaPlaylist playlist, [ - int? startIndex, + PlayableMedia? initialMedia, bool autoPlay = true, ]) async { if (playlist.id == state.currentPlaylist && !audioHandler.player.playing) { + final startIndex = initialMedia == null + ? null + : state.queueState.queue.indexOf(initialMedia.toMediaItem()); if (startIndex != null) { await skipToIndex(startIndex, autoPlay); } else if (autoPlay) { @@ -76,6 +79,13 @@ class MediaPlayerCubit extends AppCubit unawaited(_configCubit.saveCurrentPlaylist(playlist)); emit(state.copyWith(currentPlaylist: playlist.id)); await audioHandler.updateQueue(playlist.mediaItemsAsMediaItems); + final shuffleModeEnabled = audioHandler.player.shuffleModeEnabled; + if (shuffleModeEnabled) { + await audioHandler.setShuffleMode(AudioServiceShuffleMode.all); + } + final startIndex = initialMedia == null + ? null + : state.queueState.queue.indexOf(initialMedia.toMediaItem()); if (startIndex != null) { await skipToIndex(startIndex, autoPlay); } else if (autoPlay) { diff --git a/lib/features/library/ui/library_widgets/page.dart b/lib/features/library/ui/library_widgets/page.dart index 4939ab0..fc79c11 100644 --- a/lib/features/library/ui/library_widgets/page.dart +++ b/lib/features/library/ui/library_widgets/page.dart @@ -162,7 +162,7 @@ class _LibraryContentState extends State { } else { context.readMediaPlayerCubit.playFromMediaPlaylist( state.playlist.copyWith(mediaItems: sortedMediaItems), - index, + mediaItem, ); } }, From 13aca4d647f77e064878b61e552de2a5be01ed05 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 02:30:11 +0530 Subject: [PATCH 05/10] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20Internal=20librarie?= =?UTF-8?q?s=20now=20hide=20images?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/library/cubit/library_cubit.dart | 7 ++++- .../ui/library_widgets/library_app_bar.dart | 27 ++++++++++++------- .../user-library/ui/user_library_page.dart | 12 ++------- lib/widgets/downloads_icon.dart | 25 +++++++++++++++++ 4 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 lib/widgets/downloads_icon.dart diff --git a/lib/features/library/cubit/library_cubit.dart b/lib/features/library/cubit/library_cubit.dart index 4575b20..d128512 100644 --- a/lib/features/library/cubit/library_cubit.dart +++ b/lib/features/library/cubit/library_cubit.dart @@ -26,7 +26,12 @@ class LibraryCubit extends Cubit { String link = playlist.images.last.link!; if (!appContext.mounted) return; final configCubit = appContext.read(); - final colorPalette = await configCubit.generatePalleteGenerator(link); + PaletteGenerator.fromColors([]); + if (!appContext.mounted) return; + final colorPalette = playlist.isDownload && appContext.mounted + ? PaletteGenerator.fromColors( + [PaletteColor(appContext.colorScheme.secondaryContainer, 1)]) + : await configCubit.generatePalleteGenerator(link); final image = configCubit.getProvider(link); emit(LibraryLoaded( playlist.toMediaPlaylist(), diff --git a/lib/features/library/ui/library_widgets/library_app_bar.dart b/lib/features/library/ui/library_widgets/library_app_bar.dart index bb39891..754693a 100644 --- a/lib/features/library/ui/library_widgets/library_app_bar.dart +++ b/lib/features/library/ui/library_widgets/library_app_bar.dart @@ -17,6 +17,11 @@ class LibraryAppBar extends StatelessWidget { final LibraryLoaded state; final EdgeInsets padding; + bool get isFromUserLibrary => + state.sourceLibrary != null && + (state.sourceLibrary!.isDownload == true || + state.sourceLibrary!.isFavorite == true); + @override Widget build(BuildContext context) { return SliverAppBar( @@ -24,7 +29,7 @@ class LibraryAppBar extends StatelessWidget { elevation: 0, scrolledUnderElevation: 10, stretch: true, - expandedHeight: kSliverExpandedHeight, + expandedHeight: isFromUserLibrary ? 130 : kSliverExpandedHeight, pinned: kSliverAppBarPinned, collapsedHeight: kToolbarHeight, flexibleSpace: LayoutBuilder(builder: (context, constraints) { @@ -64,7 +69,7 @@ class LibraryAppBar extends StatelessWidget { children: [ Container( height: 64, - width: context.width * 0.85, + width: context.width * 0.8, alignment: Alignment.topCenter, child: SizedBox( height: 32, @@ -79,17 +84,19 @@ class LibraryAppBar extends StatelessWidget { ), ), ), - AnimatedContainer( - duration: kThemeAnimationDuration, - height: imageHeight, - width: imageHeight, - decoration: BoxDecoration(boxShadow: state.boxShadow), - child: Image( + if (!isFromUserLibrary) + AnimatedContainer( + duration: kThemeAnimationDuration, height: imageHeight, width: imageHeight, - image: state.image, + decoration: BoxDecoration(boxShadow: state.boxShadow), + child: Image( + height: imageHeight, + width: imageHeight, + image: state.image, + fit: BoxFit.cover, + ), ), - ), ], ), ), diff --git a/lib/features/user-library/ui/user_library_page.dart b/lib/features/user-library/ui/user_library_page.dart index 78206e3..0b2a444 100644 --- a/lib/features/user-library/ui/user_library_page.dart +++ b/lib/features/user-library/ui/user_library_page.dart @@ -12,6 +12,7 @@ import 'package:varanasi_mobile_app/gen/assets.gen.dart'; import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; import 'package:varanasi_mobile_app/utils/helpers/get_app_context.dart'; import 'package:varanasi_mobile_app/utils/routes.dart'; +import 'package:varanasi_mobile_app/widgets/downloads_icon.dart'; import 'widgets/empty.dart'; @@ -62,16 +63,7 @@ class UserLibraryPage extends HookWidget { return ListTile( onTap: () => context.push(AppRoutes.library.path, extra: item), leading: Visibility( - replacement: Container( - height: 48, - width: 48, - decoration: BoxDecoration( - color: context.colorScheme.secondaryContainer, - borderRadius: BorderRadius.circular(4), - ), - child: Icon(Icons.download_rounded, - color: context.theme.colorScheme.onSecondaryContainer), - ), + replacement: const DownloadsIcon(), visible: !item.isDownload, child: ClipRRect( borderRadius: BorderRadius.circular(4), diff --git a/lib/widgets/downloads_icon.dart b/lib/widgets/downloads_icon.dart new file mode 100644 index 0000000..06fafad --- /dev/null +++ b/lib/widgets/downloads_icon.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; + +class DownloadsIcon extends StatelessWidget { + const DownloadsIcon({ + super.key, + this.dimension = 48, + }); + + final double dimension; + + @override + Widget build(BuildContext context) { + return Container( + height: dimension, + width: dimension, + decoration: BoxDecoration( + color: context.colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(4), + ), + child: Icon(Icons.download_rounded, + color: context.theme.colorScheme.onSecondaryContainer), + ); + } +} From 2dd76071255a2245776e9a22a1430557d8ddf743 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 02:35:21 +0530 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20option=20picker=20s?= =?UTF-8?q?howing=20up=20behind=20app=20bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/library_widgets/sort_by_toggle.dart | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/lib/features/library/ui/library_widgets/sort_by_toggle.dart b/lib/features/library/ui/library_widgets/sort_by_toggle.dart index 84eaa2d..29f6151 100644 --- a/lib/features/library/ui/library_widgets/sort_by_toggle.dart +++ b/lib/features/library/ui/library_widgets/sort_by_toggle.dart @@ -1,11 +1,9 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; import 'package:varanasi_mobile_app/cubits/config/config_cubit.dart'; import 'package:varanasi_mobile_app/models/sort_type.dart'; +import 'package:varanasi_mobile_app/utils/dialogs/app_dialog.dart'; import 'package:varanasi_mobile_app/utils/extensions/extensions.dart'; -import 'package:varanasi_mobile_app/utils/helpers/show_bottom_sheet.dart'; class SortByToggle extends StatelessWidget { const SortByToggle({super.key}); @@ -31,30 +29,12 @@ class SortByToggle extends StatelessWidget { ), child: const Text('Sort'), onPressed: () async { - final padding = MediaQuery.paddingOf(context); - // show dialog - final value = await showAppBottomSheet( + final value = await AppDialog.showOptionsPicker( context, - builder: (context) => ListView( - padding: padding.copyWith(left: 8, right: 8, top: 16), - children: [ - ListTile( - title: const Text('Sort by'), - titleTextStyle: context.textTheme.bodyLarge - ?.copyWith(fontWeight: FontWeight.bold), - ), - ...SortBy.values.map( - (e) => RadioListTile( - controlAffinity: ListTileControlAffinity.trailing, - groupValue: sortBy, - value: e, - onChanged: (value) => context.pop(value), - title: Text(describeEnum(e).capitalize), - selected: sortBy == e, - ), - ), - ], - ), + sortBy, + SortBy.values, + (e) => e.name.capitalize, + title: "Sort by", ); if (context.mounted && value != null) { context.read().setSortType(value); From b8a3b63a39f5496aca14e4fa2e892d04845febe5 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 02:46:54 +0530 Subject: [PATCH 07/10] Added pinned icon --- lib/features/user-library/ui/user_library_page.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/features/user-library/ui/user_library_page.dart b/lib/features/user-library/ui/user_library_page.dart index 0b2a444..7980408 100644 --- a/lib/features/user-library/ui/user_library_page.dart +++ b/lib/features/user-library/ui/user_library_page.dart @@ -75,7 +75,17 @@ class UserLibraryPage extends HookWidget { ), ), title: Text(item.title ?? ''), - subtitle: Text(item.description ?? ''), + subtitle: Row( + children: [ + if (item.isDownload || item.isFavorite) + Icon( + Icons.push_pin_rounded, + size: 12, + color: context.colorScheme.primary, + ), + Expanded(child: Text(item.description ?? '')), + ], + ), ); }, itemCount: library.length, From e64f785d02d86fcc52bdf7ee38823cdd7ebcf152 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 02:51:02 +0530 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20updated=20placehold?= =?UTF-8?q?er=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/configs.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/configs.dart b/lib/utils/configs.dart index 19b1b03..2862ad9 100644 --- a/lib/utils/configs.dart +++ b/lib/utils/configs.dart @@ -114,9 +114,9 @@ Config get appConfig { const server = kReleaseMode ? Server('https://saavn.aryak.dev') : Server('https://saavn.aryak.dev'); - return Config( + return const Config( env: kReleaseMode ? 'production' : 'development', - endpoint: const Endpoint( + endpoint: Endpoint( modules: '/modules', playlists: Playlists(id: 'playlists'), albums: Albums(link: 'albums'), @@ -131,6 +131,6 @@ Config get appConfig { ), ), server: server, - placeholderImageLink: '${server.baseUrl}/audio.jpg', + placeholderImageLink: 'http://192.168.31.130:3000/audio.jpg', ); } From ee282a401057ea2386d0469e5d7a407713ddbf52 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 03:20:12 +0530 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=9A=A7=20Add:=20shimmer=20loader=20?= =?UTF-8?q?for=20top=20searches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/data/search_repository.dart | 25 +-------- .../widgets/trending_searches_carousel.dart | 56 ++++++++++++++++++- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/lib/features/search/data/search_repository.dart b/lib/features/search/data/search_repository.dart index aba75c8..1c4ee2a 100644 --- a/lib/features/search/data/search_repository.dart +++ b/lib/features/search/data/search_repository.dart @@ -2,7 +2,6 @@ import 'package:hive/hive.dart'; import 'package:varanasi_mobile_app/features/search/data/search_data_provider.dart'; import 'package:varanasi_mobile_app/features/search/data/top_search_result/top_search_result.dart'; import 'package:varanasi_mobile_app/utils/constants/strings.dart'; -import 'package:varanasi_mobile_app/utils/convert_nested_map.dart'; import 'package:varanasi_mobile_app/utils/logger.dart'; import 'package:varanasi_mobile_app/utils/mixins/cachable_mixin.dart'; @@ -28,30 +27,8 @@ class SearchRepository with CacheableService { final Map _searchResultsCache = {}; Future fetchTopSearchResults() async { - await initcache().then((value) { - if (value == null) return; - _box = value; - }); - final cached = maybeGetCached(AppStrings.topSearchesCacheKey); - if (cached != null) { - try { - final cachemap = convertNestedMap(cached); - final library = - SearchDataProvider.instance.parseTopSearchResult(cachemap); - return library; - } catch (e) { - /// If the cached data is corrupted, delete it - _logger.e(e); - deleteCache(AppStrings.topSearchesCacheKey); - } - } - final (response, searchResults) = + final (_, searchResults) = await SearchDataProvider.instance.fetchTopSearchResults(); - if (searchResults != null) { - cache(AppStrings.topSearchesCacheKey, response, const Duration(hours: 1)); - } else { - throw Exception('Failed to fetch top searches'); - } return searchResults; } diff --git a/lib/features/search/ui/widgets/trending_searches_carousel.dart b/lib/features/search/ui/widgets/trending_searches_carousel.dart index 29bc1c7..ee5345a 100644 --- a/lib/features/search/ui/widgets/trending_searches_carousel.dart +++ b/lib/features/search/ui/widgets/trending_searches_carousel.dart @@ -3,14 +3,66 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:varanasi_mobile_app/features/home/ui/home_widgets/media_carousel/media_carousel.dart'; import 'package:varanasi_mobile_app/features/search/cubit/search_cubit.dart'; import 'package:varanasi_mobile_app/models/media_playlist.dart'; +import 'package:varanasi_mobile_app/utils/extensions/theme.dart'; +import 'package:varanasi_mobile_app/widgets/shimmer_loader.dart'; class TrendingSearchesCarousel extends StatelessWidget { const TrendingSearchesCarousel({super.key}); @override Widget build(BuildContext context) { - final trendingSearches = context - .select((SearchCubit cubit) => cubit.state.topSearchResult?.data ?? []); + final (trendingSearches, isFetchingTopSearchResults) = context.select( + (SearchCubit cubit) => ( + cubit.state.topSearchResult?.data ?? [], + cubit.state.isFetchingTopSearchResults + )); + if (isFetchingTopSearchResults) { + return SliverToBoxAdapter( + child: SizedBox( + height: 220, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Trending Searches', + style: context.textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ], + ), + ), + const SizedBox(height: 16), + SizedBox( + height: 140, + child: ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: 10, + itemBuilder: (context, index) => ShimmerLoader( + height: 120, + width: 140, + margin: index == 0 + ? const EdgeInsets.only(left: 8) + : index == 19 + ? const EdgeInsets.only(right: 8) + : EdgeInsets.zero, + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(8)), + ), + separatorBuilder: (context, index) => + const SizedBox(width: 16), + scrollDirection: Axis.horizontal, + ), + ), + ], + ), + ), + ); + } return SliverToBoxAdapter( child: MediaCarousel( playlist: MediaPlaylist( From b66d9e8e3c39580b94e08e160f13ce5b4aede4b8 Mon Sep 17 00:00:00 2001 From: devaryakjha Date: Tue, 24 Oct 2023 03:21:06 +0530 Subject: [PATCH 10/10] updates version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 5531d43..d1bbea3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.1.3+17 +version: 0.1.4+18 environment: sdk: ">=3.0.6 <4.0.0"