diff --git a/lib/modules/anime/widgets/search_subtitles.dart b/lib/modules/anime/widgets/search_subtitles.dart index f1c00820..112a6db7 100644 --- a/lib/modules/anime/widgets/search_subtitles.dart +++ b/lib/modules/anime/widgets/search_subtitles.dart @@ -76,7 +76,7 @@ class _SubtitlesWidgetSearchState extends ConsumerState { bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: _isLoading ? SizedBox( height: context.height(0.3), @@ -229,7 +229,7 @@ class _SubtitlesWidgetSearchState extends ConsumerState { Material( borderRadius: BorderRadius.circular(5), color: Colors.transparent, - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: Ink.image( height: 120, width: 80, diff --git a/lib/modules/library/library_screen.dart b/lib/modules/library/library_screen.dart index 09689416..758542fb 100644 --- a/lib/modules/library/library_screen.dart +++ b/lib/modules/library/library_screen.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mangayomi/main.dart'; @@ -54,6 +55,7 @@ class _LibraryScreenState extends ConsumerState final _textEditingController = TextEditingController(); TabController? tabBarController; int _tabIndex = 0; + Timer? _searchDebounce; @override void initState() { @@ -68,6 +70,7 @@ class _LibraryScreenState extends ConsumerState void dispose() { _textEditingController.dispose(); tabBarController?.dispose(); + _searchDebounce?.cancel(); super.dispose(); } @@ -266,7 +269,15 @@ class _LibraryScreenState extends ConsumerState textEditingController: _textEditingController, onSearchToggle: () => setState(() => _isSearch = !_isSearch), - onSearchClear: () => setState(() {}), + onSearchClear: () { + _searchDebounce?.cancel(); + _searchDebounce = Timer( + const Duration(milliseconds: 300), + () { + if (mounted) setState(() {}); + }, + ); + }, onIgnoreFiltersChanged: (val) => setState(() => _ignoreFiltersOnSearch = val), vsync: this, @@ -346,7 +357,12 @@ class _LibraryScreenState extends ConsumerState ignoreFiltersOnSearch: _ignoreFiltersOnSearch, textEditingController: _textEditingController, onSearchToggle: () => setState(() => _isSearch = !_isSearch), - onSearchClear: () => setState(() {}), + onSearchClear: () { + _searchDebounce?.cancel(); + _searchDebounce = Timer(const Duration(milliseconds: 300), () { + if (mounted) setState(() {}); + }); + }, onIgnoreFiltersChanged: (val) => setState(() => _ignoreFiltersOnSearch = val), vsync: this, @@ -436,8 +452,7 @@ class _LibraryScreenState extends ConsumerState BottomSelectButton( icon: Icon(Icons.label_outline_rounded, color: color), onPressed: () { - final mangaIdsList = ref.watch(mangasListStateProvider); - final List bulkMangas = mangaIdsList + final List bulkMangas = mangaIds .map((id) => isar.mangas.getSync(id)!) .toList(); showCategorySelectionDialog( diff --git a/lib/modules/library/providers/library_state_provider.dart b/lib/modules/library/providers/library_state_provider.dart index a2ec69e7..8f739452 100644 --- a/lib/modules/library/providers/library_state_provider.dart +++ b/lib/modules/library/providers/library_state_provider.dart @@ -902,51 +902,40 @@ class SortLibraryMangaState extends _$SortLibraryMangaState { @riverpod class MangasListState extends _$MangasListState { @override - List build() { - return []; - } + Set build() => {}; void update(Manga value) { - var newList = state.reversed.toList(); - if (newList.contains(value.id)) { - newList.remove(value.id); + var newSet = Set.from(state); + if (newSet.contains(value.id)) { + newSet.remove(value.id); } else { - newList.add(value.id!); + newSet.add(value.id!); } - if (newList.isEmpty) { + if (newSet.isEmpty) { ref.read(isLongPressedStateProvider.notifier).update(false); } - state = newList; + state = newSet; } - void selectAll(Manga value) { - var newList = state.reversed.toList(); - if (!newList.contains(value.id)) { - newList.add(value.id!); - } - - state = newList; - } + void selectAll(Manga value) => state = {...state, value.id!}; void selectSome(Manga value) { - var newList = state.reversed.toList(); - if (newList.contains(value.id)) { - newList.remove(value.id); + final newSet = Set.from(state); + if (newSet.contains(value.id)) { + newSet.remove(value.id); } else { - newList.add(value.id!); + newSet.add(value.id!); } - state = newList; + state = newSet; } - void clear() { - state = []; - } + void clear() => state = {}; } @riverpod class MangasSetIsReadState extends _$MangasSetIsReadState { @override - void build({required List mangaIds, required bool markAsRead}) {} + void build({required Set mangaIds, required bool markAsRead}) {} void set() { final allChapters = []; diff --git a/lib/modules/library/providers/library_state_provider.g.dart b/lib/modules/library/providers/library_state_provider.g.dart index e68428ae..0f942f9f 100644 --- a/lib/modules/library/providers/library_state_provider.g.dart +++ b/lib/modules/library/providers/library_state_provider.g.dart @@ -1823,7 +1823,7 @@ abstract class _$SortLibraryMangaState extends $Notifier { final mangasListStateProvider = MangasListStateProvider._(); final class MangasListStateProvider - extends $NotifierProvider> { + extends $NotifierProvider> { MangasListStateProvider._() : super( from: null, @@ -1843,27 +1843,27 @@ final class MangasListStateProvider MangasListState create() => MangasListState(); /// {@macro riverpod.override_with_value} - Override overrideWithValue(List value) { + Override overrideWithValue(Set value) { return $ProviderOverride( origin: this, - providerOverride: $SyncValueProvider>(value), + providerOverride: $SyncValueProvider>(value), ); } } -String _$mangasListStateHash() => r'bbd2e3600ec22a774b1774ae3c221815e52bfef6'; +String _$mangasListStateHash() => r'61c6477ea43c6113caa89ef13984cd4370d303ee'; -abstract class _$MangasListState extends $Notifier> { - List build(); +abstract class _$MangasListState extends $Notifier> { + Set build(); @$mustCallSuper @override void runBuild() { - final ref = this.ref as $Ref, List>; + final ref = this.ref as $Ref, Set>; final element = ref.element as $ClassProviderElement< - AnyNotifier, List>, - List, + AnyNotifier, Set>, + Set, Object?, Object? >; @@ -1878,7 +1878,7 @@ final class MangasSetIsReadStateProvider extends $NotifierProvider { MangasSetIsReadStateProvider._({ required MangasSetIsReadStateFamily super.from, - required ({List mangaIds, bool markAsRead}) super.argument, + required ({Set mangaIds, bool markAsRead}) super.argument, }) : super( retry: null, name: r'mangasSetIsReadStateProvider', @@ -1921,7 +1921,7 @@ final class MangasSetIsReadStateProvider } String _$mangasSetIsReadStateHash() => - r'2a1b1005e2ed5068d36188a3fb969d21b64bfef6'; + r'a2c64ecdf03b3d27282c63d8cadbc1cc44943e39'; final class MangasSetIsReadStateFamily extends $Family with @@ -1930,7 +1930,7 @@ final class MangasSetIsReadStateFamily extends $Family void, void, void, - ({List mangaIds, bool markAsRead}) + ({Set mangaIds, bool markAsRead}) > { MangasSetIsReadStateFamily._() : super( @@ -1942,7 +1942,7 @@ final class MangasSetIsReadStateFamily extends $Family ); MangasSetIsReadStateProvider call({ - required List mangaIds, + required Set mangaIds, required bool markAsRead, }) => MangasSetIsReadStateProvider._( argument: (mangaIds: mangaIds, markAsRead: markAsRead), @@ -1954,11 +1954,11 @@ final class MangasSetIsReadStateFamily extends $Family } abstract class _$MangasSetIsReadState extends $Notifier { - late final _$args = ref.$arg as ({List mangaIds, bool markAsRead}); - List get mangaIds => _$args.mangaIds; + late final _$args = ref.$arg as ({Set mangaIds, bool markAsRead}); + Set get mangaIds => _$args.mangaIds; bool get markAsRead => _$args.markAsRead; - void build({required List mangaIds, required bool markAsRead}); + void build({required Set mangaIds, required bool markAsRead}); @$mustCallSuper @override void runBuild() { diff --git a/lib/modules/library/widgets/continue_reader_button.dart b/lib/modules/library/widgets/continue_reader_button.dart new file mode 100644 index 00000000..df1945e2 --- /dev/null +++ b/lib/modules/library/widgets/continue_reader_button.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar_community/isar.dart'; +import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/history.dart'; +import 'package:mangayomi/models/manga.dart'; +import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart'; +import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; +import 'package:mangayomi/utils/extensions/chapter.dart'; + +class ContinueReaderButton extends ConsumerWidget { + final Manga entry; + + const ContinueReaderButton({super.key, required this.entry}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return StreamBuilder( + stream: isar.historys + .filter() + .mangaIdEqualTo(entry.id!) + .watch(fireImmediately: true), + builder: (context, snapshot) => GestureDetector( + onTap: () { + final incognitoMode = ref.read(incognitoModeStateProvider); + if (snapshot.hasData && snapshot.data!.isNotEmpty && !incognitoMode) { + snapshot.data!.first.chapter.value!.pushToReaderView(context); + } else { + entry.chapters.first.pushToReaderView(context); + } + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: context.primaryColor.withValues(alpha: 0.9), + ), + child: const Padding( + padding: EdgeInsets.all(7), + child: Icon(Icons.play_arrow, size: 19, color: Colors.white), + ), + ), + ), + ); + } +} diff --git a/lib/modules/library/widgets/library_app_bar.dart b/lib/modules/library/widgets/library_app_bar.dart index 181e9f1e..bc38b898 100644 --- a/lib/modules/library/widgets/library_app_bar.dart +++ b/lib/modules/library/widgets/library_app_bar.dart @@ -226,7 +226,7 @@ class LibraryAppBar extends ConsumerWidget implements PreferredSizeWidget { /// AppBar shown when items are long-pressed for bulk selection. class _SelectionAppBar extends ConsumerWidget { final ItemType itemType; - final List mangaIdsList; + final Set mangaIdsList; final List data; const _SelectionAppBar({ diff --git a/lib/modules/library/widgets/library_dialogs.dart b/lib/modules/library/widgets/library_dialogs.dart index 0009a579..cd4c3059 100644 --- a/lib/modules/library/widgets/library_dialogs.dart +++ b/lib/modules/library/widgets/library_dialogs.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar_community/isar.dart'; @@ -27,8 +28,8 @@ void showDeleteMangaDialog({ required WidgetRef ref, required ItemType itemType, }) { - List fromLibList = []; - List downloadedChapsList = []; + Set fromLibList = {}; + Set downloadedChapsList = {}; showDialog( context: context, builder: (context) { @@ -53,10 +54,10 @@ void showDeleteMangaDialog({ label: l10n.from_library, onTap: () { setState(() { - if (fromLibList == mangaIdsList) { - fromLibList = []; + if (setEquals(fromLibList, mangaIdsList)) { + fromLibList = {}; } else { - fromLibList = mangaIdsList; + fromLibList = {...mangaIdsList}; } }); }, @@ -68,10 +69,10 @@ void showDeleteMangaDialog({ : l10n.downloaded_episodes, onTap: () { setState(() { - if (downloadedChapsList == mangaIdsList) { - downloadedChapsList = []; + if (setEquals(downloadedChapsList, mangaIdsList)) { + downloadedChapsList = {}; } else { - downloadedChapsList = mangaIdsList; + downloadedChapsList = {...mangaIdsList}; } }); }, diff --git a/lib/modules/library/widgets/library_gridview_widget.dart b/lib/modules/library/widgets/library_gridview_widget.dart index 48335245..b6fff370 100644 --- a/lib/modules/library/widgets/library_gridview_widget.dart +++ b/lib/modules/library/widgets/library_gridview_widget.dart @@ -3,19 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar_community/isar.dart'; import 'package:mangayomi/main.dart'; -import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/download.dart'; -import 'package:mangayomi/models/history.dart'; import 'package:mangayomi/modules/library/providers/isar_providers.dart'; import 'package:mangayomi/modules/library/providers/library_state_provider.dart'; import 'package:mangayomi/models/manga.dart'; +import 'package:mangayomi/modules/library/widgets/continue_reader_button.dart'; import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart'; import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; import 'package:mangayomi/utils/constant.dart'; -import 'package:mangayomi/utils/extensions/chapter.dart'; import 'package:mangayomi/utils/headers.dart'; -import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart'; import 'package:mangayomi/modules/widgets/bottom_text_widget.dart'; import 'package:mangayomi/modules/widgets/cover_view_widget.dart'; import 'package:mangayomi/modules/widgets/gridview_widget.dart'; @@ -24,7 +21,7 @@ import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart'; class LibraryGridViewWidget extends StatefulWidget { final bool isCoverOnlyGrid; final bool isComfortableGrid; - final List mangaIdsList; + final Set mangaIdsList; final List entriesManga; final bool language; final bool downloadedChapter; @@ -178,29 +175,22 @@ class _LibraryGridViewWidgetState extends State { builder: (context, ref, child) { List nbrDown = []; if (widget.downloadedChapter) { - isar.txnSync(() { - for ( - var i = 0; - i < entry.chapters.length; - i++ - ) { - final entries = isar.downloads - .filter() - .idEqualTo( - entry.chapters - .toList()[i] - .id, - ) - .findAllSync(); - - if (entries.isNotEmpty && - entries.first.isDownload!) { - nbrDown.add(1); - } - } - }); + final chapterIds = entry.chapters + .toList() + .map((c) => c.id) + .whereType() + .toList(); + if (chapterIds.isNotEmpty) { + nbrDown = isar.downloads + .filter() + .anyOf( + chapterIds, + (q, id) => q.idEqualTo(id), + ) + .isDownloadEqualTo(true) + .findAllSync(); + } } - return Row( children: [ if (nbrDown.isNotEmpty && @@ -303,116 +293,7 @@ class _LibraryGridViewWidgetState extends State { right: 0, child: Padding( padding: const EdgeInsets.all(9), - child: Consumer( - builder: (context, ref, child) { - return StreamBuilder( - stream: isar.historys - .filter() - .idIsNotNull() - .and() - .chapter( - (q) => q.manga( - (q) => - q.itemTypeEqualTo(entry.itemType), - ), - ) - .watch(fireImmediately: true), - builder: (context, snapshot) { - if (snapshot.hasData && - snapshot.data!.isNotEmpty) { - final incognitoMode = ref.watch( - incognitoModeStateProvider, - ); - final entries = snapshot.data! - .where( - (element) => - element.mangaId == entry.id, - ) - .toList(); - if (entries.isNotEmpty && - !incognitoMode) { - return GestureDetector( - onTap: () { - entries.first.chapter.value! - .pushToReaderView(context); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(5), - color: context.primaryColor - .withValues(alpha: 0.9), - ), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon( - Icons.play_arrow, - size: 19, - color: Colors.white, - ), - ), - ), - ); - } - return GestureDetector( - onTap: () { - entry.chapters - .toList() - .reversed - .toList() - .last - .pushToReaderView(context); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 5, - ), - color: context.primaryColor - .withValues(alpha: 0.9), - ), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon( - Icons.play_arrow, - size: 19, - color: Colors.white, - ), - ), - ), - ); - } - return GestureDetector( - onTap: () { - entry.chapters - .toList() - .reversed - .toList() - .last - .pushToReaderView(context); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 5, - ), - color: context.primaryColor - .withValues(alpha: 0.9), - ), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon( - Icons.play_arrow, - size: 19, - color: Colors.white, - ), - ), - ), - ); - }, - ); - }, - ), + child: ContinueReaderButton(entry: entry), ), ), ], diff --git a/lib/modules/library/widgets/library_listview_widget.dart b/lib/modules/library/widgets/library_listview_widget.dart index 93e4d492..f7e557f7 100644 --- a/lib/modules/library/widgets/library_listview_widget.dart +++ b/lib/modules/library/widgets/library_listview_widget.dart @@ -3,19 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar_community/isar.dart'; import 'package:mangayomi/main.dart'; -import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/download.dart'; -import 'package:mangayomi/models/history.dart'; import 'package:mangayomi/modules/library/providers/isar_providers.dart'; import 'package:mangayomi/modules/library/providers/library_state_provider.dart'; import 'package:mangayomi/models/manga.dart'; +import 'package:mangayomi/modules/library/widgets/continue_reader_button.dart'; import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart'; import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; import 'package:mangayomi/utils/constant.dart'; -import 'package:mangayomi/utils/extensions/chapter.dart'; import 'package:mangayomi/utils/headers.dart'; -import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart'; import 'package:mangayomi/modules/widgets/listview_widget.dart'; import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart'; @@ -23,7 +20,7 @@ class LibraryListViewWidget extends StatelessWidget { final List entriesManga; final bool language; final bool downloadedChapter; - final List mangaIdsList; + final Set mangaIdsList; final bool continueReaderBtn; final bool localSource; const LibraryListViewWidget({ @@ -49,7 +46,7 @@ class LibraryListViewWidget extends StatelessWidget { return Material( borderRadius: BorderRadius.circular(5), color: Colors.transparent, - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: InkWell( onTap: () async { if (isLongPressed) { @@ -208,28 +205,22 @@ class LibraryListViewWidget extends StatelessWidget { ), child: Consumer( builder: (context, ref, child) { - List nbrDown = []; - isar.txnSync(() { - for ( - var i = 0; - i < entry.chapters.length; - i++ - ) { - final entries = isar.downloads - .filter() - .idEqualTo( - entry.chapters - .toList()[i] - .id, - ) - .findAllSync(); - - if (entries.isNotEmpty && - entries.first.isDownload!) { - nbrDown.add(entries.first); - } - } - }); + final chapterIds = entry.chapters + .toList() + .map((c) => c.id) + .whereType() + .toList(); + List nbrDown = chapterIds.isNotEmpty + ? isar.downloads + .filter() + .anyOf( + chapterIds, + (q, id) => + q.idEqualTo(id), + ) + .isDownloadEqualTo(true) + .findAllSync() + : []; if (nbrDown.isNotEmpty) { return Container( decoration: BoxDecoration( @@ -307,117 +298,7 @@ class LibraryListViewWidget extends StatelessWidget { ), ), if (continueReaderBtn) - Consumer( - builder: (context, ref, child) { - return StreamBuilder( - stream: isar.historys - .filter() - .idIsNotNull() - .and() - .chapter( - (q) => q.manga( - (q) => - q.itemTypeEqualTo(entry.itemType), - ), - ) - .watch(fireImmediately: true), - builder: (context, snapshot) { - if (snapshot.hasData && - snapshot.data!.isNotEmpty) { - final incognitoMode = ref.watch( - incognitoModeStateProvider, - ); - final entries = snapshot.data! - .where( - (element) => - element.mangaId == entry.id, - ) - .toList(); - if (entries.isNotEmpty && - !incognitoMode) { - final chap = - entries.first.chapter.value!; - return GestureDetector( - onTap: () { - chap.pushToReaderView(context); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(5), - color: context.primaryColor - .withValues(alpha: 0.9), - ), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon( - Icons.play_arrow, - size: 19, - color: Colors.white, - ), - ), - ), - ); - } - return GestureDetector( - onTap: () { - entry.chapters - .toList() - .reversed - .toList() - .last - .pushToReaderView(context); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 5, - ), - color: context.primaryColor - .withValues(alpha: 0.9), - ), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon( - Icons.play_arrow, - size: 19, - color: Colors.white, - ), - ), - ), - ); - } - return GestureDetector( - onTap: () { - entry.chapters - .toList() - .reversed - .toList() - .last - .pushToReaderView(context); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - 5, - ), - color: context.primaryColor - .withValues(alpha: 0.9), - ), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon( - Icons.play_arrow, - size: 19, - color: Colors.white, - ), - ), - ), - ); - }, - ); - }, - ), + ContinueReaderButton(entry: entry), ], ), ), diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 75455c6d..93df9b56 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -2509,7 +2509,7 @@ class _MangaDetailViewState extends ConsumerState child: Material( color: bgColor, borderRadius: BorderRadius.circular(20), - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(8.0), child: SuperListView.separated( diff --git a/lib/modules/manga/detail/widgets/tracker_search_widget.dart b/lib/modules/manga/detail/widgets/tracker_search_widget.dart index 96ad5229..6d3013ca 100644 --- a/lib/modules/manga/detail/widgets/tracker_search_widget.dart +++ b/lib/modules/manga/detail/widgets/tracker_search_widget.dart @@ -77,7 +77,7 @@ class _TrackerWidgetSearchState extends ConsumerState { bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: _isLoading ? SizedBox( height: context.height(0.3), @@ -123,8 +123,7 @@ class _TrackerWidgetSearchState extends ConsumerState { 5, ), color: Colors.transparent, - clipBehavior: - Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: Ink.image( height: 120, width: 80, diff --git a/lib/modules/manga/download/providers/download_provider.g.dart b/lib/modules/manga/download/providers/download_provider.g.dart index afc5b46d..4320e028 100644 --- a/lib/modules/manga/download/providers/download_provider.g.dart +++ b/lib/modules/manga/download/providers/download_provider.g.dart @@ -136,7 +136,7 @@ final class DownloadChapterProvider } } -String _$downloadChapterHash() => r'690619b8914877f3913ed1601818b6149752279b'; +String _$downloadChapterHash() => r'c0d7bc9cd975bb5f1abdf29f9aa6d9d8dc8ca441'; final class DownloadChapterFamily extends $Family with diff --git a/lib/modules/manga/reader/image_view_vertical.dart b/lib/modules/manga/reader/image_view_vertical.dart index f54e41cb..b87220aa 100644 --- a/lib/modules/manga/reader/image_view_vertical.dart +++ b/lib/modules/manga/reader/image_view_vertical.dart @@ -14,6 +14,7 @@ class ImageViewVertical extends ConsumerWidget { final UChapDataPreload data; final Function(UChapDataPreload data) onLongPressData; final bool isHorizontal; + final ValueNotifier isScrolling; final Function(bool) failedToLoadImage; @@ -23,94 +24,100 @@ class ImageViewVertical extends ConsumerWidget { required this.onLongPressData, required this.failedToLoadImage, required this.isHorizontal, + required this.isScrolling, }); @override Widget build(BuildContext context, WidgetRef ref) { final (colorBlendMode, color) = chapterColorFIlterValues(context, ref); - final imageWidget = ExtendedImage( - colorBlendMode: colorBlendMode, - color: color, - image: data.getImageProvider(ref, true), - filterQuality: FilterQuality.medium, - handleLoadingProgress: true, - fit: getBoxFit(ref.watch(scaleTypeStateProvider)), - enableLoadState: true, - loadStateChanged: (state) { - if (state.extendedImageLoadState == LoadState.completed) { - failedToLoadImage(false); - final rawSize = state.extendedImageInfo?.image; - if (rawSize != null && data.loadedHeight == null) { - final screenWidth = isHorizontal - ? context.width(0.8) - : MediaQuery.of(context).size.width; - final aspect = rawSize.width / rawSize.height; - data.loadedWidth = screenWidth; - data.loadedHeight = screenWidth / aspect; + final imageWidget = ValueListenableBuilder( + valueListenable: isScrolling, + builder: (context, scrolling, _) => ExtendedImage( + colorBlendMode: colorBlendMode, + color: color, + image: data.getImageProvider(ref, true), + filterQuality: scrolling ? FilterQuality.low : FilterQuality.medium, + handleLoadingProgress: true, + fit: getBoxFit(ref.watch(scaleTypeStateProvider)), + enableLoadState: true, + loadStateChanged: (state) { + if (state.extendedImageLoadState == LoadState.completed) { + failedToLoadImage(false); + final rawSize = state.extendedImageInfo?.image; + if (rawSize != null && data.loadedHeight == null) { + final screenWidth = isHorizontal + ? context.width(0.8) + : MediaQuery.of(context).size.width; + final aspect = rawSize.width / rawSize.height; + data.loadedWidth = screenWidth; + data.loadedHeight = screenWidth / aspect; + } } - } - final placeholderHeight = data.loadedHeight ?? context.height(0.8); - final placeholderWidth = isHorizontal - ? (data.loadedWidth ?? context.width(0.8)) - : null; - if (state.extendedImageLoadState == LoadState.loading) { - final ImageChunkEvent? loadingProgress = state.loadingProgress; - final double progress = loadingProgress?.expectedTotalBytes != null - ? loadingProgress!.cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : 0; - return Container( - color: Colors.black, - height: placeholderHeight, - width: placeholderWidth, - child: CircularProgressIndicatorAnimateRotate(progress: progress), - ); - } - if (state.extendedImageLoadState == LoadState.failed) { - failedToLoadImage(true); - return Container( - color: Colors.black, - height: placeholderHeight, - width: placeholderWidth, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - context.l10n.image_loading_error, - style: TextStyle(color: Colors.white.withValues(alpha: 0.7)), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onLongPress: () { - state.reLoadImage(); - failedToLoadImage(false); - }, - onTap: () { - state.reLoadImage(); - failedToLoadImage(false); - }, - child: Container( - decoration: BoxDecoration( - color: context.primaryColor, - borderRadius: BorderRadius.circular(30), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, + final placeholderHeight = data.loadedHeight ?? context.height(0.8); + final placeholderWidth = isHorizontal + ? (data.loadedWidth ?? context.width(0.8)) + : null; + if (state.extendedImageLoadState == LoadState.loading) { + final ImageChunkEvent? loadingProgress = state.loadingProgress; + final double progress = loadingProgress?.expectedTotalBytes != null + ? loadingProgress!.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : 0; + return Container( + color: Colors.black, + height: placeholderHeight, + width: placeholderWidth, + child: CircularProgressIndicatorAnimateRotate(progress: progress), + ); + } + if (state.extendedImageLoadState == LoadState.failed) { + failedToLoadImage(true); + return Container( + color: Colors.black, + height: placeholderHeight, + width: placeholderWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + context.l10n.image_loading_error, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.7), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: GestureDetector( + onLongPress: () { + state.reLoadImage(); + failedToLoadImage(false); + }, + onTap: () { + state.reLoadImage(); + failedToLoadImage(false); + }, + child: Container( + decoration: BoxDecoration( + color: context.primaryColor, + borderRadius: BorderRadius.circular(30), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: Text(context.l10n.retry), ), - child: Text(context.l10n.retry), ), ), ), - ), - ], - ), - ); - } - return null; - }, + ], + ), + ); + } + return null; + }, + ), ); return applyReaderColorFilter( GestureDetector( diff --git a/lib/modules/manga/reader/image_view_webtoon.dart b/lib/modules/manga/reader/image_view_webtoon.dart index 094e583a..54bf1353 100644 --- a/lib/modules/manga/reader/image_view_webtoon.dart +++ b/lib/modules/manga/reader/image_view_webtoon.dart @@ -34,6 +34,7 @@ class ImageViewWebtoon extends StatelessWidget { final int webtoonSidePadding; final bool showPageGaps; final bool reverse; + final ValueNotifier isScrolling; const ImageViewWebtoon({ super.key, @@ -57,6 +58,7 @@ class ImageViewWebtoon extends StatelessWidget { required this.onScaleEnd, required this.onDoubleTapDown, required this.onDoubleTap, + required this.isScrolling, this.webtoonSidePadding = 0, this.showPageGaps = true, this.reverse = false, @@ -89,11 +91,17 @@ class ImageViewWebtoon extends StatelessWidget { } Widget _buildItem(BuildContext context, int index) { - if (isDoublePageMode && !isHorizontalContinuous) { - return _buildDoublePageItem(context, index); - } else { - return _buildSinglePageItem(context, index); - } + final currentPage = pages[index]; + final uniqueKey = ValueKey( + '${currentPage.chapter?.id ?? "trans"}-${currentPage.index ?? index}', + ); + + return KeyedSubtree( + key: uniqueKey, + child: (isDoublePageMode && !isHorizontalContinuous) + ? _buildDoublePageItem(context, index) + : _buildSinglePageItem(context, index), + ); } Widget _buildSinglePageItem(BuildContext context, int index) { @@ -124,6 +132,7 @@ class ImageViewWebtoon extends StatelessWidget { failedToLoadImage: onFailedToLoadImage, onLongPressData: onLongPressData, isHorizontal: isHorizontalContinuous, + isScrolling: isScrolling, ), ), ); diff --git a/lib/modules/manga/reader/managers/chapter_preload_manager.dart b/lib/modules/manga/reader/managers/chapter_preload_manager.dart index 2be7343d..ca7a4d35 100644 --- a/lib/modules/manga/reader/managers/chapter_preload_manager.dart +++ b/lib/modules/manga/reader/managers/chapter_preload_manager.dart @@ -20,9 +20,6 @@ class ChapterPreloadManager { /// Queue of chapter IDs in order of loading (for LRU eviction) final Queue _chapterLoadOrder = Queue(); - /// Current reading index - int _currentIndex = 0; - /// Separate flags to allow concurrent prev/next preloading bool _isPreloadingNext = false; bool _isPreloadingPrev = false; @@ -36,9 +33,6 @@ class ChapterPreloadManager { /// Gets the current number of pages int get pageCount => _pages.length; - /// Gets the current index - int get currentIndex => _currentIndex; - /// Gets the loaded chapter count int get loadedChapterCount => _loadedChapterIds.length; @@ -48,13 +42,6 @@ class ChapterPreloadManager { /// Whether a next chapter preload is in progress. bool get isPreloadingNext => _isPreloadingNext; - /// Sets the current reading index - set currentIndex(int value) { - if (value >= 0 && value < _pages.length) { - _currentIndex = value; - } - } - /// Returns `true` if pages from [chapter] are already in memory. bool isChapterLoaded(Chapter? chapter) { final id = _getChapterIdentifier(chapter); @@ -62,13 +49,12 @@ class ChapterPreloadManager { } /// Initializes the manager with the first chapter's pages. - void initialize(List initialPages, int startIndex) { + void initialize(List initialPages) { _pages.clear(); _loadedChapterIds.clear(); _chapterLoadOrder.clear(); _pages.addAll(initialPages); - _currentIndex = startIndex; // Track the initial chapter if (initialPages.isNotEmpty) { @@ -263,9 +249,6 @@ class ChapterPreloadManager { // Prepend to pages list _pages.insertAll(0, prependList); - // Update current index to account for prepended pages - _currentIndex += prependCount; - // Track the new chapter if (chapterId != null) { _loadedChapterIds.add(chapterId); diff --git a/lib/modules/manga/reader/mixins/reader_memory_management.dart b/lib/modules/manga/reader/mixins/reader_memory_management.dart index fc332991..d4d3a140 100644 --- a/lib/modules/manga/reader/mixins/reader_memory_management.dart +++ b/lib/modules/manga/reader/mixins/reader_memory_management.dart @@ -20,23 +20,13 @@ mixin ReaderMemoryManagement { /// Gets the total page count. int get pageCount => _preloadManager.pageCount; - /// Gets the current page index. - int get currentPageIndex => _preloadManager.currentIndex; - - /// Sets the current page index. - set currentPageIndex(int value) { - _preloadManager.currentIndex = value; - } - /// Initializes the preload manager with initial chapter data. /// /// [chapterData] - The initial chapter pages to load. - /// [startIndex] - The initial page index (default: 0). /// [onPagesUpdated] - Callback when pages are added/removed. /// [onIndexAdjusted] - Callback when current index needs adjustment. void initializePreloadManager( GetChapterPagesModel chapterData, { - int startIndex = 0, VoidCallback? onPagesUpdated, }) { if (_isPreloadManagerInitialized) { @@ -48,7 +38,7 @@ mixin ReaderMemoryManagement { _preloadManager.onPagesUpdated = onPagesUpdated; - _preloadManager.initialize(chapterData.uChapDataPreload, startIndex); + _preloadManager.initialize(chapterData.uChapDataPreload); _isPreloadManagerInitialized = true; diff --git a/lib/modules/manga/reader/providers/reader_controller_provider.dart b/lib/modules/manga/reader/providers/reader_controller_provider.dart index 6e67aced..1869c9e8 100644 --- a/lib/modules/manga/reader/providers/reader_controller_provider.dart +++ b/lib/modules/manga/reader/providers/reader_controller_provider.dart @@ -67,6 +67,9 @@ class ReaderController extends _$ReaderController { } final incognitoMode = isar.settings.getSync(227)!.incognitoMode!; + Settings? _cachedSettings; + void _invalidateSettingsCache() => _cachedSettings = null; + ReaderMode getReaderMode() { final personalReaderModeList = getIsarSetting().personalReaderModeList ?? []; @@ -113,6 +116,7 @@ class ReaderController extends _$ReaderController { ..updatedAt = DateTime.now().millisecondsSinceEpoch, ), ); + _invalidateSettingsCache(); } PageMode getPageMode() { @@ -146,6 +150,7 @@ class ReaderController extends _$ReaderController { ..updatedAt = DateTime.now().millisecondsSinceEpoch, ), ); + _invalidateSettingsCache(); } void setPageMode(PageMode newPageMode) { @@ -167,6 +172,7 @@ class ReaderController extends _$ReaderController { ..updatedAt = DateTime.now().millisecondsSinceEpoch, ), ); + _invalidateSettingsCache(); } void setShowPageNumber(bool value) { @@ -178,17 +184,14 @@ class ReaderController extends _$ReaderController { ..updatedAt = DateTime.now().millisecondsSinceEpoch, ), ); + _invalidateSettingsCache(); } } - Settings getIsarSetting() { - return isar.settings.getSync(227)!; - } + Settings getIsarSetting() => _cachedSettings ??= isar.settings.getSync(227)!; bool getShowPageNumber() { - if (!incognitoMode) { - return getIsarSetting().showPagesNumber!; - } + if (!incognitoMode) return getIsarSetting().showPagesNumber!; return true; } @@ -353,9 +356,11 @@ class ReaderController extends _$ReaderController { return urls.length; } + int? _lastSavedIndex; void setPageIndex(int newIndex, bool save) { - if (chapter.isRead!) return; - if (incognitoMode) return; + if (chapter.isRead! || incognitoMode) return; + if (!save && newIndex == _lastSavedIndex) return; + _lastSavedIndex = newIndex; final isContinuousLike = getReaderMode() == ReaderMode.verticalContinuous || getReaderMode() == ReaderMode.webtoon; @@ -387,6 +392,7 @@ class ReaderController extends _$ReaderController { chap.updatedAt = DateTime.now().millisecondsSinceEpoch; isar.chapters.putSync(chap); }); + _invalidateSettingsCache(); if (isRead) { chapter.updateTrackChapterRead(ref); if (ref.read(deleteDownloadAfterReadingStateProvider)) { @@ -469,10 +475,9 @@ extension ChapterExtensions on Chapter { extension MangaExtensions on Manga { List getFilteredChapterList() { final data = this.chapters.toList().reversed.toList(); + final settings = isar.settings.getSync(227)!; final filterUnread = - (isar.settings - .getSync(227)! - .chapterFilterUnreadList! + (settings.chapterFilterUnreadList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? @@ -480,18 +485,14 @@ extension MangaExtensions on Manga { .type!; final filterBookmarked = - (isar.settings - .getSync(227)! - .chapterFilterBookmarkedList! + (settings.chapterFilterBookmarkedList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? ChapterFilterBookmarked(mangaId: id, type: 0)) .type!; final filterDownloaded = - (isar.settings - .getSync(227)! - .chapterFilterDownloadedList! + (settings.chapterFilterDownloadedList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? @@ -499,15 +500,23 @@ extension MangaExtensions on Manga { .type!; final sortChapter = - (isar.settings - .getSync(227)! - .sortChapterList! + (settings.sortChapterList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? SortChapter(mangaId: id, index: 1, reverse: false)) .index; final filterScanlator = _getFilterScanlator(this) ?? []; + final chapterIds = data.map((c) => c.id).whereType().toList(); + final downloadedIds = (filterDownloaded == 0 || chapterIds.isEmpty) + ? const {} + : isar.downloads + .filter() + .anyOf(chapterIds, (q, id) => q.idEqualTo(id)) + .isDownloadEqualTo(true) + .findAllSync() + .map((d) => d.id!) + .toSet(); List? chapterList; chapterList = data .where( @@ -525,21 +534,13 @@ extension MangaExtensions on Manga { : true, ) .where((element) { - final modelChapDownload = isar.downloads - .filter() - .idEqualTo(element.id) - .findAllSync(); - return filterDownloaded == 1 - ? modelChapDownload.isNotEmpty && - modelChapDownload.first.isDownload == true - : filterDownloaded == 2 - ? !(modelChapDownload.isNotEmpty && - modelChapDownload.first.isDownload == true) - : true; + if (filterDownloaded == 0) return true; + final isDownloaded = downloadedIds.contains(element.id); + return filterDownloaded == 1 ? isDownloaded : !isDownloaded; }) .where((element) => !filterScanlator.contains(element.scanlator)) .toList(); - List chapters = sortChapter == 1 + List chapters = sortChapter == 0 ? chapterList.reversed.toList() : chapterList; if (sortChapter == 0) { @@ -565,7 +566,7 @@ extension MangaExtensions on Manga { : a.name!.compareTo(b.name!); }); } - return chapterList; + return chapters; } } diff --git a/lib/modules/manga/reader/providers/reader_controller_provider.g.dart b/lib/modules/manga/reader/providers/reader_controller_provider.g.dart index 59bca3ce..22fa1e81 100644 --- a/lib/modules/manga/reader/providers/reader_controller_provider.g.dart +++ b/lib/modules/manga/reader/providers/reader_controller_provider.g.dart @@ -147,7 +147,7 @@ final class ReaderControllerProvider } } -String _$readerControllerHash() => r'fd5ce439786209d9e218fa4067f91f606bb8458a'; +String _$readerControllerHash() => r'adab728fa21939302d0f928b11be204e8e8a0527'; final class ReaderControllerFamily extends $Family with diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index a57de7e7..b4a0fc54 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -152,6 +152,8 @@ class _MangaChapterPageGalleryState ); bool isDesktop = Platform.isMacOS || Platform.isLinux || Platform.isWindows; + final ValueNotifier _isScrolling = ValueNotifier(false); + Timer? _scrollIdleTimer; final Stopwatch _readingStopwatch = Stopwatch(); @@ -174,6 +176,8 @@ class _MangaChapterPageGalleryState _autoScroll.value = false; _autoScroll.dispose(); _autoScrollPage.dispose(); + _scrollIdleTimer?.cancel(); + _isScrolling.dispose(); _itemPositionsListener.itemPositions.removeListener(_readProgressListener); _photoViewController.dispose(); _photoViewScaleStateController.dispose(); @@ -344,14 +348,10 @@ class _MangaChapterPageGalleryState Widget build(BuildContext context) { final backgroundColor = ref.watch(backgroundColorStateProvider); final fullScreenReader = ref.watch(fullScreenReaderStateProvider); - final cropBorders = ref.watch(cropBordersStateProvider); final readerMode = ref.watch(_currentReaderMode); - final bool isHorizontalContinuaous = + final bool isHorizontalContinuous = readerMode == ReaderMode.horizontalContinuous || readerMode == ReaderMode.horizontalContinuousRTL; - if (cropBorders) { - _processCropBorders(); - } final l10n = l10nLocalizations(context)!; return ReaderKeyboardHandler( @@ -419,7 +419,7 @@ class _MangaChapterPageGalleryState itemScrollController: _itemScrollController, scrollOffsetController: _pageOffsetController, itemPositionsListener: _itemPositionsListener, - scrollDirection: isHorizontalContinuaous + scrollDirection: isHorizontalContinuous ? Axis.horizontal : Axis.vertical, minCacheExtent: @@ -434,7 +434,7 @@ class _MangaChapterPageGalleryState chapterName: widget.chapter.name!, ), onFailedToLoadImage: (value) { - // // Handle failed image loading + // TODO: Handle failed image loading // if (_failedToLoadImage.value != value && // context.mounted) { // _failedToLoadImage.value = value; @@ -443,8 +443,8 @@ class _MangaChapterPageGalleryState backgroundColor: backgroundColor, isDoublePageMode: _pageMode == PageMode.doublePage && - !isHorizontalContinuaous, - isHorizontalContinuous: isHorizontalContinuaous, + !isHorizontalContinuous, + isHorizontalContinuous: isHorizontalContinuous, readerMode: ref.watch(_currentReaderMode)!, photoViewController: _photoViewController, photoViewScaleStateController: @@ -462,13 +462,14 @@ class _MangaChapterPageGalleryState ), showPageGaps: ref.watch(showPageGapsStateProvider), reverse: _isReverseHorizontal, + isScrolling: _isScrolling, ) : Material( color: getBackgroundColor(backgroundColor), shadowColor: getBackgroundColor(backgroundColor), child: (_pageMode == PageMode.doublePage && - !isHorizontalContinuaous) + !isHorizontalContinuous) ? ExtendedImageGesturePageView.builder( controller: _extendedController, scrollDirection: _scrollDirection, @@ -958,57 +959,60 @@ class _MangaChapterPageGalleryState void _readProgressListener() async { if (_isAdjustingScroll) return; final itemPositions = _itemPositionsListener.itemPositions.value; - if (itemPositions.isNotEmpty) { - _currentIndex = itemPositions.first.index; - int pagesLength = - (_pageMode == PageMode.doublePage && - !(ref.watch(_currentReaderMode) == - ReaderMode.horizontalContinuous || - ref.watch(_currentReaderMode) == - ReaderMode.horizontalContinuousRTL)) - ? (pages.length / 2).ceil() - : pages.length; - if (_currentIndex! >= 0 && _currentIndex! < pagesLength) { - if (_readerController.chapter.id != pages[_currentIndex!].chapter!.id) { - if (mounted) { - setState(() { - _readerController = ref.read( - readerControllerProvider( - chapter: pages[_currentIndex!].chapter!, - ).notifier, - ); + if (itemPositions.isEmpty) return; + _currentIndex = itemPositions.first.index; + if (!_isScrolling.value) _isScrolling.value = true; + _scrollIdleTimer?.cancel(); + _scrollIdleTimer = Timer(const Duration(milliseconds: 150), () { + if (mounted) _isScrolling.value = false; + }); + final currentReaderMode = ref.read(_currentReaderMode); + int pagesLength = + (_pageMode == PageMode.doublePage && + currentReaderMode != ReaderMode.horizontalContinuous && + currentReaderMode != ReaderMode.horizontalContinuousRTL) + ? (pages.length / 2).ceil() + : pages.length; + if (_currentIndex! >= 0 && _currentIndex! < pagesLength) { + if (_readerController.chapter.id != pages[_currentIndex!].chapter!.id) { + if (mounted) { + setState(() { + _readerController = ref.read( + readerControllerProvider( + chapter: pages[_currentIndex!].chapter!, + ).notifier, + ); - chapter = pages[_currentIndex!].chapter!; - final chapterUrlModel = pages[_currentIndex!].chapterUrlModel; + chapter = pages[_currentIndex!].chapter!; + final chapterUrlModel = pages[_currentIndex!].chapterUrlModel; - if (chapterUrlModel != null) { - _chapterUrlModel = chapterUrlModel; - } + if (chapterUrlModel != null) { + _chapterUrlModel = chapterUrlModel; + } - _isBookmarked = _readerController.getChapterBookmarked(); - }); - } + _isBookmarked = _readerController.getChapterBookmarked(); + }); } + } - // ── Next-chapter preloading: trigger when near the end ── - final distToEnd = pagesLength - 1 - itemPositions.last.index; - if (distToEnd <= pagePreloadAmount && !_isLastPageTransition) { - _triggerNextChapterPreload(); - } + // ── Next-chapter preloading: trigger when near the end ── + final distToEnd = pagesLength - 1 - itemPositions.last.index; + if (distToEnd <= pagePreloadAmount && !_isLastPageTransition) { + _triggerNextChapterPreload(); + } - // ── Previous-chapter preloading: trigger when near the start ── - if (itemPositions.first.index <= pagePreloadAmount) { - _triggerPrevChapterPreload(); - } + // ── Previous-chapter preloading: trigger when near the start ── + if (itemPositions.first.index <= pagePreloadAmount) { + _triggerPrevChapterPreload(); + } - final idx = pages[_currentIndex!].index; - if (idx != null) { - _readerController.setPageIndex( - _isDoublePageActive ? idx : _geCurrentIndex(idx), - false, - ); - ref.read(currentIndexProvider(chapter).notifier).setCurrentIndex(idx); - } + final idx = pages[_currentIndex!].index; + if (idx != null) { + _readerController.setPageIndex( + _isDoublePageActive ? idx : _geCurrentIndex(idx), + false, + ); + ref.read(currentIndexProvider(chapter).notifier).setCurrentIndex(idx); } } } @@ -1108,24 +1112,35 @@ class _MangaChapterPageGalleryState ) { try { if (chapterData.uChapDataPreload.isEmpty || !mounted) return; + + // Record the CURRENT visible top index BEFORE prepending + final currentVisibleItems = _itemPositionsListener.itemPositions.value; + final oldTopIndex = currentVisibleItems.isNotEmpty + ? currentVisibleItems.first.index + : _currentIndex ?? 0; + preloadPreviousChapter(chapterData, chap).then((prependCount) { if (prependCount > 0 && mounted) { _isAdjustingScroll = true; + + // New index = old visible index + how many items we just prepended + final newIndex = oldTopIndex + prependCount; + // In double page mode, _currentIndex stores the page view index, // so convert the prepended page count to page view units. if (_isDoublePageActive) { // Recompute the page view index from the new actual index. - final oldActual = _pageViewToActualIndex(_currentIndex!); + final oldActual = _pageViewToActualIndex(oldTopIndex); final newActual = oldActual + prependCount; _currentIndex = _actualToPageViewIndex(newActual); } else { - _currentIndex = _currentIndex! + prependCount; + _currentIndex = newIndex; } setState(() {}); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { if (_isContinuousMode()) { - _itemScrollController.jumpTo(index: _currentIndex!); + _itemScrollController.jumpTo(index: newIndex); } else if (_extendedController.hasClients) { _extendedController.jumpToPage(_currentIndex!); } @@ -1138,14 +1153,17 @@ class _MangaChapterPageGalleryState } void _initCurrentIndex() async { + if (ref.read(cropBordersStateProvider)) _processCropBorders(); final readerMode = _readerController.getReaderMode(); // Initialize the preload manager with bounded memory (from ReaderMemoryManagement mixin) initializePreloadManager( _chapterUrlModel, - startIndex: _currentIndex ?? 0, onPagesUpdated: () { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + if (ref.read(cropBordersStateProvider)) _processCropBorders(); + } }, ); @@ -1363,20 +1381,29 @@ class _MangaChapterPageGalleryState } } + bool _isCropBordersProcessing = false; void _processCropBorders() async { - if (_cropBorderCheckList.length == pages.length) return; + if (_isCropBordersProcessing || + _cropBorderCheckList.length == pages.length) { + return; + } + _isCropBordersProcessing = true; - for (var i = 0; i < pages.length; i++) { - if (!_cropBorderCheckList.contains(i)) { - _cropBorderCheckList.add(i); - if (!mounted) return; - final value = await ref.read( - cropBordersProvider(data: pages[i], cropBorder: true).future, - ); - if (mounted) { - updatePageCropImage(i, value); + try { + for (var i = 0; i < pages.length; i++) { + if (!_cropBorderCheckList.contains(i)) { + _cropBorderCheckList.add(i); + if (!mounted) return; + final value = await ref.read( + cropBordersProvider(data: pages[i], cropBorder: true).future, + ); + if (mounted) { + updatePageCropImage(i, value); + } } } + } finally { + _isCropBordersProcessing = false; } } @@ -1452,7 +1479,7 @@ class _MangaChapterPageGalleryState _isDoublePageActive ? (pages.length / 2).ceil() : pages.length; bool _isContinuousMode() { - final readerMode = ref.watch(_currentReaderMode); + final readerMode = ref.read(_currentReaderMode); return readerMode == ReaderMode.verticalContinuous || readerMode == ReaderMode.webtoon || readerMode == ReaderMode.horizontalContinuous || diff --git a/lib/modules/updates/widgets/update_chapter_list_tile_widget.dart b/lib/modules/updates/widgets/update_chapter_list_tile_widget.dart index 4ec472ae..fd73d74a 100644 --- a/lib/modules/updates/widgets/update_chapter_list_tile_widget.dart +++ b/lib/modules/updates/widgets/update_chapter_list_tile_widget.dart @@ -25,7 +25,7 @@ class UpdateChapterListTileWidget extends ConsumerWidget { return Material( borderRadius: BorderRadius.circular(5), color: Colors.transparent, - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: InkWell( onTap: () async { chapter.pushToReaderView(context, ignoreIsRead: true); diff --git a/lib/modules/widgets/cover_view_widget.dart b/lib/modules/widgets/cover_view_widget.dart index b93315fe..17375ec2 100644 --- a/lib/modules/widgets/cover_view_widget.dart +++ b/lib/modules/widgets/cover_view_widget.dart @@ -32,7 +32,7 @@ class CoverViewWidget extends StatelessWidget { child: Material( borderRadius: BorderRadius.circular(5), color: Colors.transparent, - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, onLongPress: onLongPress, diff --git a/lib/modules/widgets/manga_image_card_widget.dart b/lib/modules/widgets/manga_image_card_widget.dart index cfd11afe..99c098cc 100644 --- a/lib/modules/widgets/manga_image_card_widget.dart +++ b/lib/modules/widgets/manga_image_card_widget.dart @@ -212,7 +212,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget { child: Material( borderRadius: BorderRadius.circular(5), color: Colors.transparent, - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: InkWell( onTap: () { pushToMangaReaderDetail( @@ -258,7 +258,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget { Material( borderRadius: BorderRadius.circular(5), color: Colors.transparent, - clipBehavior: Clip.antiAliasWithSaveLayer, + clipBehavior: Clip.antiAlias, child: Image( height: 55, width: 40, diff --git a/lib/services/get_chapter_pages.g.dart b/lib/services/get_chapter_pages.g.dart index 2ae69ef7..106fff8a 100644 --- a/lib/services/get_chapter_pages.g.dart +++ b/lib/services/get_chapter_pages.g.dart @@ -66,7 +66,7 @@ final class GetChapterPagesProvider } } -String _$getChapterPagesHash() => r'544311ac02b1034b938bb5f85e97fe34683c26c7'; +String _$getChapterPagesHash() => r'593f5af68761ff44d50fb3667d6717edf58769d7'; final class GetChapterPagesFamily extends $Family with $FunctionalFamilyOverride, Chapter> {