Merge pull request #697 from NBA2K1/performance-improvements

fix page jumps in vertical/webtoon mode + performance improvements in library/reader
This commit is contained in:
Moustapha Kodjo Amadou 2026-04-13 10:55:25 +01:00 committed by GitHub
commit dacebb660a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 387 additions and 559 deletions

View file

@ -76,7 +76,7 @@ class _SubtitlesWidgetSearchState extends ConsumerState<SubtitlesWidgetSearch> {
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<SubtitlesWidgetSearch> {
Material(
borderRadius: BorderRadius.circular(5),
color: Colors.transparent,
clipBehavior: Clip.antiAliasWithSaveLayer,
clipBehavior: Clip.antiAlias,
child: Ink.image(
height: 120,
width: 80,

View file

@ -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<LibraryScreen>
final _textEditingController = TextEditingController();
TabController? tabBarController;
int _tabIndex = 0;
Timer? _searchDebounce;
@override
void initState() {
@ -68,6 +70,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
void dispose() {
_textEditingController.dispose();
tabBarController?.dispose();
_searchDebounce?.cancel();
super.dispose();
}
@ -266,7 +269,15 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
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<LibraryScreen>
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<LibraryScreen>
BottomSelectButton(
icon: Icon(Icons.label_outline_rounded, color: color),
onPressed: () {
final mangaIdsList = ref.watch(mangasListStateProvider);
final List<Manga> bulkMangas = mangaIdsList
final List<Manga> bulkMangas = mangaIds
.map((id) => isar.mangas.getSync(id)!)
.toList();
showCategorySelectionDialog(

View file

@ -902,51 +902,40 @@ class SortLibraryMangaState extends _$SortLibraryMangaState {
@riverpod
class MangasListState extends _$MangasListState {
@override
List<int> build() {
return [];
}
Set<int> build() => {};
void update(Manga value) {
var newList = state.reversed.toList();
if (newList.contains(value.id)) {
newList.remove(value.id);
var newSet = Set<int>.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<int>.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<int> mangaIds, required bool markAsRead}) {}
void build({required Set<int> mangaIds, required bool markAsRead}) {}
void set() {
final allChapters = <Chapter>[];

View file

@ -1823,7 +1823,7 @@ abstract class _$SortLibraryMangaState extends $Notifier<SortLibraryManga> {
final mangasListStateProvider = MangasListStateProvider._();
final class MangasListStateProvider
extends $NotifierProvider<MangasListState, List<int>> {
extends $NotifierProvider<MangasListState, Set<int>> {
MangasListStateProvider._()
: super(
from: null,
@ -1843,27 +1843,27 @@ final class MangasListStateProvider
MangasListState create() => MangasListState();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<int> value) {
Override overrideWithValue(Set<int> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<int>>(value),
providerOverride: $SyncValueProvider<Set<int>>(value),
);
}
}
String _$mangasListStateHash() => r'bbd2e3600ec22a774b1774ae3c221815e52bfef6';
String _$mangasListStateHash() => r'61c6477ea43c6113caa89ef13984cd4370d303ee';
abstract class _$MangasListState extends $Notifier<List<int>> {
List<int> build();
abstract class _$MangasListState extends $Notifier<Set<int>> {
Set<int> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<List<int>, List<int>>;
final ref = this.ref as $Ref<Set<int>, Set<int>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<List<int>, List<int>>,
List<int>,
AnyNotifier<Set<int>, Set<int>>,
Set<int>,
Object?,
Object?
>;
@ -1878,7 +1878,7 @@ final class MangasSetIsReadStateProvider
extends $NotifierProvider<MangasSetIsReadState, void> {
MangasSetIsReadStateProvider._({
required MangasSetIsReadStateFamily super.from,
required ({List<int> mangaIds, bool markAsRead}) super.argument,
required ({Set<int> 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<int> mangaIds, bool markAsRead})
({Set<int> mangaIds, bool markAsRead})
> {
MangasSetIsReadStateFamily._()
: super(
@ -1942,7 +1942,7 @@ final class MangasSetIsReadStateFamily extends $Family
);
MangasSetIsReadStateProvider call({
required List<int> mangaIds,
required Set<int> mangaIds,
required bool markAsRead,
}) => MangasSetIsReadStateProvider._(
argument: (mangaIds: mangaIds, markAsRead: markAsRead),
@ -1954,11 +1954,11 @@ final class MangasSetIsReadStateFamily extends $Family
}
abstract class _$MangasSetIsReadState extends $Notifier<void> {
late final _$args = ref.$arg as ({List<int> mangaIds, bool markAsRead});
List<int> get mangaIds => _$args.mangaIds;
late final _$args = ref.$arg as ({Set<int> mangaIds, bool markAsRead});
Set<int> get mangaIds => _$args.mangaIds;
bool get markAsRead => _$args.markAsRead;
void build({required List<int> mangaIds, required bool markAsRead});
void build({required Set<int> mangaIds, required bool markAsRead});
@$mustCallSuper
@override
void runBuild() {

View file

@ -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),
),
),
),
);
}
}

View file

@ -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<int> mangaIdsList;
final Set<int> mangaIdsList;
final List<Manga> data;
const _SelectionAppBar({

View file

@ -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<int> fromLibList = [];
List<int> downloadedChapsList = [];
Set<int> fromLibList = {};
Set<int> 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};
}
});
},

View file

@ -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<int> mangaIdsList;
final Set<int> mangaIdsList;
final List<Manga> entriesManga;
final bool language;
final bool downloadedChapter;
@ -178,29 +175,22 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
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<int>()
.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<LibraryGridViewWidget> {
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),
),
),
],

View file

@ -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<Manga> entriesManga;
final bool language;
final bool downloadedChapter;
final List<int> mangaIdsList;
final Set<int> 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<int>()
.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),
],
),
),

View file

@ -2509,7 +2509,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
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(

View file

@ -77,7 +77,7 @@ class _TrackerWidgetSearchState extends ConsumerState<TrackerWidgetSearch> {
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<TrackerWidgetSearch> {
5,
),
color: Colors.transparent,
clipBehavior:
Clip.antiAliasWithSaveLayer,
clipBehavior: Clip.antiAlias,
child: Ink.image(
height: 120,
width: 80,

View file

@ -136,7 +136,7 @@ final class DownloadChapterProvider
}
}
String _$downloadChapterHash() => r'690619b8914877f3913ed1601818b6149752279b';
String _$downloadChapterHash() => r'c0d7bc9cd975bb5f1abdf29f9aa6d9d8dc8ca441';
final class DownloadChapterFamily extends $Family
with

View file

@ -14,6 +14,7 @@ class ImageViewVertical extends ConsumerWidget {
final UChapDataPreload data;
final Function(UChapDataPreload data) onLongPressData;
final bool isHorizontal;
final ValueNotifier<bool> 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<bool>(
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(

View file

@ -34,6 +34,7 @@ class ImageViewWebtoon extends StatelessWidget {
final int webtoonSidePadding;
final bool showPageGaps;
final bool reverse;
final ValueNotifier<bool> 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,
),
),
);

View file

@ -20,9 +20,6 @@ class ChapterPreloadManager {
/// Queue of chapter IDs in order of loading (for LRU eviction)
final Queue<String> _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<UChapDataPreload> initialPages, int startIndex) {
void initialize(List<UChapDataPreload> 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);

View file

@ -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;

View file

@ -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<Chapter> 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<int>().toList();
final downloadedIds = (filterDownloaded == 0 || chapterIds.isEmpty)
? const <int>{}
: isar.downloads
.filter()
.anyOf(chapterIds, (q, id) => q.idEqualTo(id))
.isDownloadEqualTo(true)
.findAllSync()
.map((d) => d.id!)
.toSet();
List<Chapter>? 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<Chapter> chapters = sortChapter == 1
List<Chapter> 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;
}
}

View file

@ -147,7 +147,7 @@ final class ReaderControllerProvider
}
}
String _$readerControllerHash() => r'fd5ce439786209d9e218fa4067f91f606bb8458a';
String _$readerControllerHash() => r'adab728fa21939302d0f928b11be204e8e8a0527';
final class ReaderControllerFamily extends $Family
with

View file

@ -152,6 +152,8 @@ class _MangaChapterPageGalleryState
);
bool isDesktop = Platform.isMacOS || Platform.isLinux || Platform.isWindows;
final ValueNotifier<bool> _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 ||

View file

@ -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);

View file

@ -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,

View file

@ -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,

View file

@ -66,7 +66,7 @@ final class GetChapterPagesProvider
}
}
String _$getChapterPagesHash() => r'544311ac02b1034b938bb5f85e97fe34683c26c7';
String _$getChapterPagesHash() => r'593f5af68761ff44d50fb3667d6717edf58769d7';
final class GetChapterPagesFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<GetChapterPagesModel>, Chapter> {