From 56fc7f905ae57b3df90d5b3cf8646ba0e3dd97aa Mon Sep 17 00:00:00 2001 From: Schnitzel5 Date: Fri, 15 Aug 2025 21:33:54 +0200 Subject: [PATCH] added downloaded only mode --- lib/l10n/app_en.arb | 2 + lib/models/settings.dart | 5 ++ lib/modules/library/library_screen.dart | 25 +++++++++- lib/modules/main_view/main_screen.dart | 46 +++++++++++++++++++ lib/modules/more/more_screen.dart | 2 + .../downloaded_only_state_provider.dart | 24 ++++++++++ .../downloaded_only_state_provider.g.dart | 27 +++++++++++ .../incognito_mode_state_provider.dart | 2 +- .../incognito_mode_state_provider.g.dart | 2 +- .../more/widgets/downloaded_only_widget.dart | 29 ++++++++++++ 10 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 lib/modules/more/providers/downloaded_only_state_provider.dart create mode 100644 lib/modules/more/providers/downloaded_only_state_provider.g.dart create mode 100644 lib/modules/more/widgets/downloaded_only_widget.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b169ef6f..96934e8a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -77,6 +77,8 @@ "clean_database_desc": "This will remove all items that are not added to the library!", "incognito_mode": "Incognito Mode", "incognito_mode_description": "Pauses reading history", + "downloaded_only": "Downloaded only", + "downloaded_only_description": "Only show downloaded entries in your library", "download_queue": "Download Queue", "categories": "Categories", "statistics": "Statistics", diff --git a/lib/models/settings.dart b/lib/models/settings.dart index bfd4a2ac..d8f67526 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -258,6 +258,8 @@ class Settings { bool? rpcShowCoverImage; + bool? downloadedOnlyMode; + Settings({ this.id = 227, this.updatedAt = 0, @@ -373,6 +375,7 @@ class Settings { this.rpcShowReadingWatchingProgress = true, this.rpcShowTitle = true, this.rpcShowCoverImage = true, + this.downloadedOnlyMode = false, }); Settings.fromJson(Map json) { @@ -594,6 +597,7 @@ class Settings { rpcShowReadingWatchingProgress = json['rpcShowReadingWatchingProgress']; rpcShowTitle = json['rpcShowTitle']; rpcShowCoverImage = json['rpcShowCoverImage']; + downloadedOnlyMode = json['downloadedOnlyMode']; } Map toJson() => { @@ -732,6 +736,7 @@ class Settings { 'rpcShowReadingWatchingProgress': rpcShowReadingWatchingProgress, 'rpcShowTitle': rpcShowTitle, 'rpcShowCoverImage': rpcShowCoverImage, + 'downloadedOnlyMode': downloadedOnlyMode, }; } diff --git a/lib/modules/library/library_screen.dart b/lib/modules/library/library_screen.dart index 971a3c12..9fa6a8e2 100644 --- a/lib/modules/library/library_screen.dart +++ b/lib/modules/library/library_screen.dart @@ -20,6 +20,7 @@ import 'package:mangayomi/modules/library/providers/add_torrent.dart'; import 'package:mangayomi/modules/library/providers/local_archive.dart'; import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart'; import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart'; +import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.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/category_selection_dialog.dart'; @@ -134,6 +135,7 @@ class _LibraryScreenState extends ConsumerState final withoutCategories = ref.watch( getAllMangaWithoutCategoriesStreamProvider(itemType: widget.itemType), ); + final downloadedOnly = ref.watch(downloadedOnlyStateProvider); final mangaAll = ref.watch( getAllMangaStreamProvider( categoryId: null, @@ -247,6 +249,7 @@ class _LibraryScreenState extends ConsumerState ref: ref, localSource: localSource, settings: settings, + downloadedOnly: downloadedOnly, ); } if (tabCount > 0 && @@ -279,6 +282,7 @@ class _LibraryScreenState extends ConsumerState startedFilterType: startedFilterType, bookmarkedFilterType: bookmarkedFilterType, sortType: sortType, + downloadedOnly: downloadedOnly, ); final withoutCategoryNumberOfItemsList = _filterAndSortManga( @@ -288,6 +292,7 @@ class _LibraryScreenState extends ConsumerState startedFilterType: startedFilterType, bookmarkedFilterType: bookmarkedFilterType, sortType: sortType, + downloadedOnly: downloadedOnly, ); return DefaultTabController( @@ -370,6 +375,8 @@ class _LibraryScreenState extends ConsumerState categoryId: entr[i - 1].id!, settings: settings, + downloadedOnly: + downloadedOnly, ), ], ), @@ -396,6 +403,8 @@ class _LibraryScreenState extends ConsumerState continueReaderBtn, categoryId: entr[i].id!, settings: settings, + downloadedOnly: + downloadedOnly, ), ], ), @@ -432,6 +441,8 @@ class _LibraryScreenState extends ConsumerState ref: ref, localSource: localSource, settings: settings, + downloadedOnly: + downloadedOnly, ) : _bodyWithCatories( categoryId: @@ -454,6 +465,8 @@ class _LibraryScreenState extends ConsumerState ref: ref, localSource: localSource, settings: settings, + downloadedOnly: + downloadedOnly, ), if (withoutCategory.isEmpty) for ( @@ -481,6 +494,7 @@ class _LibraryScreenState extends ConsumerState ref: ref, localSource: localSource, settings: settings, + downloadedOnly: downloadedOnly, ), ], ), @@ -501,6 +515,7 @@ class _LibraryScreenState extends ConsumerState startedFilterType: startedFilterType, bookmarkedFilterType: bookmarkedFilterType, sortType: sortType, + downloadedOnly: downloadedOnly, ); return Scaffold( appBar: _appBar( @@ -526,6 +541,7 @@ class _LibraryScreenState extends ConsumerState ref: ref, localSource: localSource, settings: settings, + downloadedOnly: downloadedOnly, ), ); }, @@ -731,6 +747,7 @@ class _LibraryScreenState extends ConsumerState required bool continueReaderBtn, required int categoryId, required Settings settings, + required bool downloadedOnly, }) { final mangas = ref.watch( getAllMangaStreamProvider( @@ -755,6 +772,7 @@ class _LibraryScreenState extends ConsumerState startedFilterType: startedFilterType, bookmarkedFilterType: bookmarkedFilterType, sortType: sortType!, + downloadedOnly: downloadedOnly, ); return CircleAvatar( backgroundColor: Theme.of(context).focusColor, @@ -791,6 +809,7 @@ class _LibraryScreenState extends ConsumerState required WidgetRef ref, required DisplayType displayType, required Settings settings, + required bool downloadedOnly, }) { final l10n = l10nLocalizations(context)!; final mangas = ref.watch( @@ -818,6 +837,7 @@ class _LibraryScreenState extends ConsumerState startedFilterType: startedFilterType, bookmarkedFilterType: bookmarkedFilterType, sortType: sortType!, + downloadedOnly: downloadedOnly, ); if (entries.isNotEmpty) { final entriesManga = reverse ? entries.reversed.toList() : entries; @@ -875,6 +895,7 @@ class _LibraryScreenState extends ConsumerState required WidgetRef ref, bool withoutCategories = false, required Settings settings, + required bool downloadedOnly, }) { final sortType = ref .watch( @@ -907,6 +928,7 @@ class _LibraryScreenState extends ConsumerState startedFilterType: startedFilterType, bookmarkedFilterType: bookmarkedFilterType, sortType: sortType ?? 0, + downloadedOnly: downloadedOnly, ); if (entries.isNotEmpty) { final entriesManga = reverse ? entries.reversed.toList() : entries; @@ -970,6 +992,7 @@ class _LibraryScreenState extends ConsumerState required int startedFilterType, required int bookmarkedFilterType, required int sortType, + bool downloadedOnly = false, }) { List? mangas; final searchQuery = _textEditingController.text; @@ -984,7 +1007,7 @@ class _LibraryScreenState extends ConsumerState .where((element) { // Filter by download List list = []; - if (downloadFilterType == 1) { + if (downloadFilterType == 1 || downloadedOnly) { for (var chap in element.chapters) { final modelChapDownload = isar.downloads .filter() diff --git a/lib/modules/main_view/main_screen.dart b/lib/modules/main_view/main_screen.dart index add15f8d..cf0c7f65 100644 --- a/lib/modules/main_view/main_screen.dart +++ b/lib/modules/main_view/main_screen.dart @@ -11,6 +11,7 @@ import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/update.dart'; import 'package:mangayomi/models/source.dart'; +import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart'; import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart'; import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart'; import 'package:mangayomi/modules/widgets/loading_icon.dart'; @@ -239,10 +240,16 @@ class _MainScreenState extends ConsumerState { } final incognitoMode = ref.watch(incognitoModeStateProvider); + final downloadedOnly = ref.watch(downloadedOnlyStateProvider); final isLongPressed = ref.watch(isLongPressedMangaStateProvider); return Column( children: [ + if (!isReadingScreen) + _DownloadedOnlyBar( + downloadedOnly: downloadedOnly, + l10n: l10n, + ), if (!isReadingScreen) _IncognitoModeBar(incognitoMode: incognitoMode, l10n: l10n), Flexible( @@ -526,6 +533,45 @@ class _MainScreenState extends ConsumerState { } } +class _DownloadedOnlyBar extends StatelessWidget { + const _DownloadedOnlyBar({required this.downloadedOnly, required this.l10n}); + + final bool downloadedOnly; + final dynamic l10n; + + @override + Widget build(BuildContext context) { + return Material( + child: AnimatedContainer( + height: downloadedOnly + ? Platform.isAndroid || Platform.isIOS + ? MediaQuery.of(context).padding.top * 2 + : 50 + : 0, + curve: Curves.easeIn, + duration: const Duration(milliseconds: 150), + color: context.secondaryColor, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + l10n.downloaded_only, + style: TextStyle( + color: Colors.white, + fontFamily: GoogleFonts.aBeeZee().fontFamily, + ), + ), + ), + ], + ), + ), + ); + } +} + class _IncognitoModeBar extends StatelessWidget { const _IncognitoModeBar({required this.incognitoMode, required this.l10n}); diff --git a/lib/modules/more/more_screen.dart b/lib/modules/more/more_screen.dart index 34227e00..4799b63f 100644 --- a/lib/modules/more/more_screen.dart +++ b/lib/modules/more/more_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:mangayomi/modules/more/widgets/downloaded_only_widget.dart'; import 'package:mangayomi/modules/more/widgets/incognito_mode_widget.dart'; import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; @@ -37,6 +38,7 @@ class MoreScreen extends StatelessWidget { // onChanged: (value) {}, // ), // ), + const DownloadedOnlyWidget(), const IncognitoModeWidget(), const Divider(), ListTileWidget( diff --git a/lib/modules/more/providers/downloaded_only_state_provider.dart b/lib/modules/more/providers/downloaded_only_state_provider.dart new file mode 100644 index 00000000..92760ca4 --- /dev/null +++ b/lib/modules/more/providers/downloaded_only_state_provider.dart @@ -0,0 +1,24 @@ +import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/settings.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'downloaded_only_state_provider.g.dart'; + +@riverpod +class DownloadedOnlyState extends _$DownloadedOnlyState { + @override + bool build() { + return isar.settings.getSync(227)!.downloadedOnlyMode ?? false; + } + + void setDownloadedOnly(bool value) { + final settings = isar.settings.getSync(227)!; + state = value; + isar.writeTxnSync( + () => isar.settings.putSync( + settings + ..downloadedOnlyMode = state + ..updatedAt = DateTime.now().millisecondsSinceEpoch, + ), + ); + } +} diff --git a/lib/modules/more/providers/downloaded_only_state_provider.g.dart b/lib/modules/more/providers/downloaded_only_state_provider.g.dart new file mode 100644 index 00000000..1d1d7ddb --- /dev/null +++ b/lib/modules/more/providers/downloaded_only_state_provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'downloaded_only_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$downloadedOnlyStateHash() => + r'09c451617c435ca59554546f5d3090d20c961bfe'; + +/// See also [DownloadedOnlyState]. +@ProviderFor(DownloadedOnlyState) +final downloadedOnlyStateProvider = + AutoDisposeNotifierProvider.internal( + DownloadedOnlyState.new, + name: r'downloadedOnlyStateProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$downloadedOnlyStateHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$DownloadedOnlyState = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/modules/more/providers/incognito_mode_state_provider.dart b/lib/modules/more/providers/incognito_mode_state_provider.dart index 87cbdd80..3bf04479 100644 --- a/lib/modules/more/providers/incognito_mode_state_provider.dart +++ b/lib/modules/more/providers/incognito_mode_state_provider.dart @@ -7,7 +7,7 @@ part 'incognito_mode_state_provider.g.dart'; class IncognitoModeState extends _$IncognitoModeState { @override bool build() { - return isar.settings.getSync(227)!.incognitoMode!; + return isar.settings.getSync(227)!.incognitoMode ?? false; } void setIncognitoMode(bool value) { diff --git a/lib/modules/more/providers/incognito_mode_state_provider.g.dart b/lib/modules/more/providers/incognito_mode_state_provider.g.dart index c5e77e99..b70a5e8a 100644 --- a/lib/modules/more/providers/incognito_mode_state_provider.g.dart +++ b/lib/modules/more/providers/incognito_mode_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'incognito_mode_state_provider.dart'; // ************************************************************************** String _$incognitoModeStateHash() => - r'149c4dcbc434fb6efc883e196392320bdc7c0821'; + r'3858256a820eef632d3df57533f2aad14f555b22'; /// See also [IncognitoModeState]. @ProviderFor(IncognitoModeState) diff --git a/lib/modules/more/widgets/downloaded_only_widget.dart b/lib/modules/more/widgets/downloaded_only_widget.dart new file mode 100644 index 00000000..6a0a1276 --- /dev/null +++ b/lib/modules/more/widgets/downloaded_only_widget.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart'; +import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart'; +import 'package:mangayomi/providers/l10n_providers.dart'; + +class DownloadedOnlyWidget extends ConsumerWidget { + const DownloadedOnlyWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = l10nLocalizations(context); + final downloadedOnly = ref.watch(downloadedOnlyStateProvider); + return ListTileWidget( + onTap: () => ref + .read(downloadedOnlyStateProvider.notifier) + .setDownloadedOnly(!downloadedOnly), + icon: Icons.cloud_off_outlined, + subtitle: l10n!.downloaded_only_description, + title: l10n.downloaded_only, + trailing: Switch( + value: downloadedOnly, + onChanged: (value) => ref + .read(downloadedOnlyStateProvider.notifier) + .setDownloadedOnly(value), + ), + ); + } +}