diff --git a/lib/modules/library/library_screen.dart b/lib/modules/library/library_screen.dart index bb8f4b80..c42a7871 100644 --- a/lib/modules/library/library_screen.dart +++ b/lib/modules/library/library_screen.dart @@ -22,6 +22,7 @@ import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_pro import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart'; import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart'; import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart'; +import 'package:mangayomi/modules/widgets/bottom_select_bar.dart'; import 'package:mangayomi/modules/widgets/category_selection_dialog.dart'; import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart'; import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart'; @@ -554,161 +555,85 @@ class _LibraryScreenState extends ConsumerState return const ProgressCenter(); }, ), - bottomNavigationBar: Consumer( - builder: (context, ref, child) { - final isLongPressed = ref.watch(isLongPressedMangaStateProvider); - final color = Theme.of(context).textTheme.bodyLarge!.color!; + bottomNavigationBar: Builder( + builder: (context) { final mangaIds = ref.watch(mangasListStateProvider); - return AnimatedContainer( - curve: Curves.easeIn, - decoration: BoxDecoration( - color: context.primaryColor.withValues(alpha: 0.2), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + final color = Theme.of(context).textTheme.bodyLarge!.color!; + return BottomSelectBar( + isVisible: ref.watch(isLongPressedMangaStateProvider), + actions: [ + BottomSelectButton( + icon: Icon(Icons.label_outline_rounded, color: color), + onPressed: () { + final mangaIdsList = ref.watch(mangasListStateProvider); + final List bulkMangas = mangaIdsList + .map((id) => isar.mangas.getSync(id)!) + .toList(); + showCategorySelectionDialog( + context: context, + ref: ref, + itemType: widget.itemType, + bulkMangas: bulkMangas, + ); + }, ), - ), - duration: const Duration(milliseconds: 100), - height: isLongPressed ? 70 : 0, - width: context.width(1), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shadowColor: Colors.transparent, - elevation: 0, - backgroundColor: Colors.transparent, - ), - onPressed: () { - final mangaIdsList = ref.watch( - mangasListStateProvider, - ); - final List bulkMangas = mangaIdsList - .map((id) => isar.mangas.getSync(id)!) - .toList(); - showCategorySelectionDialog( - context: context, - ref: ref, - itemType: widget.itemType, - bulkMangas: bulkMangas, - ); - }, - child: Icon( - Icons.label_outline_rounded, - color: color, - ), + BottomSelectButton( + icon: Icon(Icons.done_all_sharp, color: color), + onPressed: () { + ref + .read( + mangasSetIsReadStateProvider( + mangaIds: mangaIds, + markAsRead: true, + ).notifier, + ) + .set(); + ref.invalidate( + getAllMangaWithoutCategoriesStreamProvider( + itemType: widget.itemType, ), - ), - ), - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - ref - .read( - mangasSetIsReadStateProvider( - mangaIds: mangaIds, - markAsRead: true, - ).notifier, - ) - .set(); - ref.invalidate( - getAllMangaWithoutCategoriesStreamProvider( - itemType: widget.itemType, - ), - ); - ref.invalidate( - getAllMangaStreamProvider( - categoryId: null, - itemType: widget.itemType, - ), - ); - }, - child: Icon(Icons.done_all_sharp, color: color), + ); + ref.invalidate( + getAllMangaStreamProvider( + categoryId: null, + itemType: widget.itemType, ), - ), - ), - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - ref - .read( - mangasSetIsReadStateProvider( - mangaIds: mangaIds, - markAsRead: false, - ).notifier, - ) - .set(); - ref.invalidate( - getAllMangaWithoutCategoriesStreamProvider( - itemType: widget.itemType, - ), - ); - ref.invalidate( - getAllMangaStreamProvider( - categoryId: null, - itemType: widget.itemType, - ), - ); - }, - child: Icon(Icons.remove_done_sharp, color: color), + ); + }, + ), + BottomSelectButton( + icon: Icon(Icons.remove_done_sharp, color: color), + onPressed: () { + ref + .read( + mangasSetIsReadStateProvider( + mangaIds: mangaIds, + markAsRead: false, + ).notifier, + ) + .set(); + ref.invalidate( + getAllMangaWithoutCategoriesStreamProvider( + itemType: widget.itemType, ), - ), - ), - // Expanded( - // child: SizedBox( - // height: 70, - // child: ElevatedButton( - // style: ElevatedButton.styleFrom( - // elevation: 0, - // backgroundColor: Colors.transparent, - // shadowColor: Colors.transparent, - // ), - // onPressed: () {}, - // child: Icon( - // Icons.download_outlined, - // color: color, - // )), - // ), - // ), - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - _deleteManga(); - }, - child: Icon( - Icons.delete_outline_outlined, - color: color, - ), + ); + ref.invalidate( + getAllMangaStreamProvider( + categoryId: null, + itemType: widget.itemType, ), - ), - ), - ], - ), + ); + }, + ), + // BottomBarAction( + // icon: Icon(Icons.download_outlined, color: color), + // onPressed: () {} + // ), + BottomSelectButton( + icon: Icon(Icons.delete_outline_outlined, color: color), + onPressed: () => _deleteManga(), + ), + ], ); }, ), diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 622c9570..6da41c1b 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -24,6 +24,7 @@ import 'package:mangayomi/modules/manga/detail/widgets/tracker_widget.dart'; import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart'; import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart'; import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart'; +import 'package:mangayomi/modules/widgets/bottom_select_bar.dart'; import 'package:mangayomi/modules/widgets/category_selection_dialog.dart'; import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart'; import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; @@ -811,8 +812,8 @@ class _MangaDetailViewState extends ConsumerState ], ), ), - bottomNavigationBar: Consumer( - builder: (context, ref, child) { + bottomNavigationBar: Builder( + builder: (context) { final chap = ref.watch(chaptersListStateProvider); bool getLength1 = chap.length == 1; bool checkFirstBookmarked = @@ -821,340 +822,241 @@ class _MangaDetailViewState extends ConsumerState chap.isNotEmpty && chap.first.isRead! && getLength1; final l10n = l10nLocalizations(context)!; final color = Theme.of(context).textTheme.bodyLarge!.color!; - return AnimatedContainer( - curve: Curves.easeIn, - decoration: BoxDecoration( - color: context.primaryColor.withValues(alpha: 0.2), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + return BottomSelectBar( + isVisible: isLongPressed, + actions: [ + BottomSelectButton( + icon: Icon( + checkFirstBookmarked + ? Icons.bookmark_remove_outlined + : Icons.bookmark_add_outlined, + color: color, + ), + onPressed: () { + final chapters = ref.watch(chaptersListStateProvider); + final List updatedChapters = []; + final now = DateTime.now().millisecondsSinceEpoch; + for (var chapter in chapters) { + chapter.isBookmarked = !chapter.isBookmarked!; + chapter.updatedAt = now; + chapter.manga.value = widget.manga; + updatedChapters.add(chapter); + } + isar.writeTxnSync(() { + isar.chapters.putAllSync(updatedChapters); + }); + ref + .read(isLongPressedStateProvider.notifier) + .update(false); + ref.read(chaptersListStateProvider.notifier).clear(); + }, ), - ), - duration: const Duration(milliseconds: 100), - height: isLongPressed ? 70 : 0, - width: context.width(1), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - final chapters = ref.watch( - chaptersListStateProvider, - ); - final List updatedChapters = []; - final now = DateTime.now().millisecondsSinceEpoch; - for (var chapter in chapters) { - chapter.isBookmarked = !chapter.isBookmarked!; - chapter.updatedAt = now; - chapter.manga.value = widget.manga; - updatedChapters.add(chapter); - } - isar.writeTxnSync(() { - isar.chapters.putAllSync(updatedChapters); - }); - ref - .read(isLongPressedStateProvider.notifier) - .update(false); - ref - .read(chaptersListStateProvider.notifier) - .clear(); - }, - child: Icon( - checkFirstBookmarked - ? Icons.bookmark_remove_outlined - : Icons.bookmark_add_outlined, - color: color, - ), - ), - ), + BottomSelectButton( + icon: Icon( + checkReadBookmarked + ? Icons.remove_done_sharp + : Icons.done_all_sharp, + color: color, ), - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - final chapters = ref.watch( - chaptersListStateProvider, - ); - final List updatedChapters = []; - final now = DateTime.now().millisecondsSinceEpoch; - for (var chapter in chapters) { - chapter.isRead = !chapter.isRead!; - if (!chapter.isRead!) { - chapter.lastPageRead = "1"; - } - chapter.updatedAt = now; - chapter.manga.value = widget.manga; - updatedChapters.add(chapter); - if (chapter.isRead!) { - chapter.updateTrackChapterRead(ref); - } - } - isar.writeTxnSync(() { - isar.chapters.putAllSync(updatedChapters); - isar.mangas.putSync(widget.manga!); - }); - ref - .read(isLongPressedStateProvider.notifier) - .update(false); - ref - .read(chaptersListStateProvider.notifier) - .clear(); - }, - child: Icon( - checkReadBookmarked - ? Icons.remove_done_sharp - : Icons.done_all_sharp, - color: color, - ), - ), - ), - ), - if (getLength1) - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - int index = chapters.indexOf(chap.first); - final List updatedChapters = []; - final now = DateTime.now().millisecondsSinceEpoch; - chapters[index + 1].updateTrackChapterRead(ref); - for ( - var i = index + 1; - i < chapters.length; - i++ - ) { - final chapter = chapters[i]; - if (!chapter.isRead!) { - chapter.isRead = true; - chapter.lastPageRead = "1"; - chapter.updatedAt = now; - chapter.manga.value = widget.manga; - updatedChapters.add(chapter); - } - } - isar.writeTxnSync(() { - isar.chapters.putAllSync(updatedChapters); - isar.mangas.putSync(widget.manga!); - }); - ref - .read(isLongPressedStateProvider.notifier) - .update(false); - ref - .read(chaptersListStateProvider.notifier) - .clear(); - }, - child: Stack( - children: [ - Icon(Icons.done_outlined, color: color), - Positioned( - bottom: 0, - right: 0, - child: Icon( - Icons.arrow_downward_outlined, - size: 11, - color: color, - ), - ), - ], - ), - ), - ), - ), - if (!isLocalArchive) - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - for (var chapter in ref.watch( - chaptersListStateProvider, - )) { - final entries = isar.downloads - .filter() - .idEqualTo(chapter.id) - .findAllSync(); - if (entries.isEmpty || - !entries.first.isDownload!) { - ref.read( - addDownloadToQueueProvider( - chapter: chapter, - ), - ); - } - } - ref.watch(processDownloadsProvider()); - - ref - .read(isLongPressedStateProvider.notifier) - .update(false); - ref - .read(chaptersListStateProvider.notifier) - .clear(); - }, - child: Icon(Icons.download_outlined, color: color), - ), - ), - ), - if (isLocalArchive) - Expanded( - child: SizedBox( - height: 70, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - elevation: 0, - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - ), - onPressed: () { - final selectedChapters = ref.watch( - chaptersListStateProvider, - ); - final totalChapters = - widget.manga!.chapters.length; - final isLastChapters = - selectedChapters.length == totalChapters; - final isAnime = widget.itemType == ItemType.anime; - final entryType = isAnime - ? l10n.episode - : l10n.chapter; - final pluralEntryType = isAnime - ? l10n.episodes - : l10n.chapters; - final mediaType = isAnime - ? l10n.anime - : l10n.manga; - final warningMessage = l10n - .last_entry_delete_warning( - totalChapters, - entryType, - pluralEntryType, - mediaType, - ); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(l10n.delete_chapters), - content: isLastChapters - ? Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Icon( - Icons.warning_amber_rounded, - color: Colors.orange, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - warningMessage, - style: TextStyle( - color: Colors.red, - ), - ), - ), - ], - ) - : null, - actions: [ - Row( - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text(l10n.cancel), - ), - const SizedBox(width: 15), - TextButton( - onPressed: () async { - final navigator = Navigator.of( - context, - ); - await isar.writeTxn(() async { - final idsToDelete = - selectedChapters - .map((c) => c.id!) - .toList(); - await isar.chapters.deleteAll( - idsToDelete, - ); - }); - if (!mounted) return; - ref - .read( - isLongPressedStateProvider - .notifier, - ) - .update(false); - ref - .read( - chaptersListStateProvider - .notifier, - ) - .clear(); - navigator.pop(); - if (isLastChapters) { - navigator.pop(); - Future.delayed( - const Duration( - milliseconds: 350, - ), - () { - isar.writeTxn( - () => isar.mangas.delete( - widget.manga!.id!, - ), - ); - }, - ); - } - }, - child: Text(l10n.delete), - ), - ], - ), - ], - ); - }, - ); - }, + onPressed: () { + final chapters = ref.watch(chaptersListStateProvider); + final List updatedChapters = []; + final now = DateTime.now().millisecondsSinceEpoch; + for (var chapter in chapters) { + chapter.isRead = !chapter.isRead!; + if (!chapter.isRead!) { + chapter.lastPageRead = "1"; + } + chapter.updatedAt = now; + chapter.manga.value = widget.manga; + updatedChapters.add(chapter); + if (chapter.isRead!) { + chapter.updateTrackChapterRead(ref); + } + } + isar.writeTxnSync(() { + isar.chapters.putAllSync(updatedChapters); + isar.mangas.putSync(widget.manga!); + }); + ref + .read(isLongPressedStateProvider.notifier) + .update(false); + ref.read(chaptersListStateProvider.notifier).clear(); + }, + ), + if (getLength1) + BottomSelectButton( + icon: Stack( + children: [ + Icon(Icons.done_outlined, color: color), + Positioned( + bottom: 0, + right: 0, child: Icon( - Icons.delete_outline_outlined, + Icons.arrow_downward_outlined, + size: 11, color: color, ), ), - ), + ], ), - ], - ), + onPressed: () { + int index = chapters.indexOf(chap.first); + final List updatedChapters = []; + final now = DateTime.now().millisecondsSinceEpoch; + chapters[index + 1].updateTrackChapterRead(ref); + for (var i = index + 1; i < chapters.length; i++) { + final chapter = chapters[i]; + if (!chapter.isRead!) { + chapter.isRead = true; + chapter.lastPageRead = "1"; + chapter.updatedAt = now; + chapter.manga.value = widget.manga; + updatedChapters.add(chapter); + } + } + isar.writeTxnSync(() { + isar.chapters.putAllSync(updatedChapters); + isar.mangas.putSync(widget.manga!); + }); + ref + .read(isLongPressedStateProvider.notifier) + .update(false); + ref.read(chaptersListStateProvider.notifier).clear(); + }, + ), + if (!isLocalArchive) + BottomSelectButton( + icon: Icon(Icons.download_outlined, color: color), + onPressed: () { + for (var chapter in ref.watch( + chaptersListStateProvider, + )) { + final entries = isar.downloads + .filter() + .idEqualTo(chapter.id) + .findAllSync(); + if (entries.isEmpty || !entries.first.isDownload!) { + ref.read( + addDownloadToQueueProvider(chapter: chapter), + ); + } + } + ref.watch(processDownloadsProvider()); + + ref + .read(isLongPressedStateProvider.notifier) + .update(false); + ref.read(chaptersListStateProvider.notifier).clear(); + }, + ), + if (isLocalArchive) + BottomSelectButton( + icon: Icon(Icons.delete_outline_outlined, color: color), + onPressed: () { + final selectedChapters = ref.watch( + chaptersListStateProvider, + ); + final totalChapters = widget.manga!.chapters.length; + final isLastChapters = + selectedChapters.length == totalChapters; + final isAnime = widget.itemType == ItemType.anime; + final entryType = isAnime ? l10n.episode : l10n.chapter; + final pluralEntryType = isAnime + ? l10n.episodes + : l10n.chapters; + final mediaType = isAnime ? l10n.anime : l10n.manga; + final warningMessage = l10n.last_entry_delete_warning( + totalChapters, + entryType, + pluralEntryType, + mediaType, + ); + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(l10n.delete_chapters), + content: isLastChapters + ? Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const Icon( + Icons.warning_amber_rounded, + color: Colors.orange, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + warningMessage, + style: TextStyle(color: Colors.red), + ), + ), + ], + ) + : null, + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text(l10n.cancel), + ), + const SizedBox(width: 15), + TextButton( + onPressed: () async { + final navigator = Navigator.of(context); + await isar.writeTxn(() async { + final idsToDelete = selectedChapters + .map((c) => c.id!) + .toList(); + await isar.chapters.deleteAll( + idsToDelete, + ); + }); + if (!mounted) return; + ref + .read( + isLongPressedStateProvider + .notifier, + ) + .update(false); + ref + .read( + chaptersListStateProvider + .notifier, + ) + .clear(); + navigator.pop(); + if (isLastChapters) { + navigator.pop(); + Future.delayed( + const Duration(milliseconds: 350), + () { + isar.writeTxn( + () => isar.mangas.delete( + widget.manga!.id!, + ), + ); + }, + ); + } + }, + child: Text(l10n.delete), + ), + ], + ), + ], + ); + }, + ); + }, + ), + ], ); }, ), diff --git a/lib/modules/widgets/bottom_select_bar.dart b/lib/modules/widgets/bottom_select_bar.dart new file mode 100644 index 00000000..0f2cc56b --- /dev/null +++ b/lib/modules/widgets/bottom_select_bar.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; + +/// Bar, that appears at the bottom of the screen when long-pressing (selecting) +/// a Manga/Anime/Novel or Chapter/Episode +class BottomSelectBar extends StatelessWidget { + final bool isVisible; + final List actions; + + const BottomSelectBar({ + super.key, + required this.isVisible, + required this.actions, + }); + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + curve: Curves.easeIn, + decoration: BoxDecoration( + color: context.primaryColor.withValues(alpha: 0.2), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + duration: const Duration(milliseconds: 100), + height: isVisible ? 70 : 0, + width: context.width(1), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: actions, + ), + ); + } +} + +/// Button for the BottomSelectBar +class BottomSelectButton extends StatelessWidget { + final Widget icon; + final VoidCallback onPressed; + + const BottomSelectButton({ + super.key, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: SizedBox( + height: 70, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 0, + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + ), + onPressed: onPressed, + child: icon, + ), + ), + ); + } +}