import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar/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/models/manga.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/models/track.dart'; import 'package:mangayomi/models/track_preference.dart'; import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart'; import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart'; import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart'; import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart'; import 'package:mangayomi/services/sync_server.dart'; import 'package:mangayomi/utils/chapter_recognition.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'reader_controller_provider.g.dart'; @riverpod class CurrentIndex extends _$CurrentIndex { @override int build(Chapter chapter) { final incognitoMode = ref.watch(incognitoModeStateProvider); if (incognitoMode) return 0; return ref.read(readerControllerProvider(chapter: chapter).notifier).getPageIndex(); } setCurrentIndex(int currentIndex) { state = currentIndex; } } BoxFit getBoxFit(ScaleType scaleType) { return switch (scaleType) { ScaleType.fitHeight => BoxFit.fitHeight, ScaleType.fitWidth => BoxFit.fitWidth, ScaleType.fitScreen => BoxFit.contain, ScaleType.originalSize => BoxFit.cover, ScaleType.smartFit => BoxFit.contain, _ => BoxFit.cover }; } @riverpod class ReaderController extends _$ReaderController { @override void build({required Chapter chapter}) {} Manga getManga() { return chapter.manga.value!; } Chapter geChapter() { return chapter; } final incognitoMode = isar.settings.getSync(227)!.incognitoMode!; ReaderMode getReaderMode() { final personalReaderModeList = getIsarSetting().personalReaderModeList ?? []; final personalReaderMode = personalReaderModeList.where((element) => element.mangaId == getManga().id); if (personalReaderMode.isNotEmpty) { return personalReaderMode.first.readerMode; } return isar.settings.getSync(227)!.defaultReaderMode; } (bool, double) autoScrollValues() { final autoScrollPagesList = getIsarSetting().autoScrollPages ?? []; final autoScrollPages = autoScrollPagesList.where((element) => element.mangaId == getManga().id); if (autoScrollPages.isNotEmpty) { return (autoScrollPages.first.autoScroll ?? false, autoScrollPages.first.pageOffset ?? 10); } return (false, 10); } void setAutoScroll(bool value, double offset) { List? autoScrollPagesList = []; for (var autoScrollPages in getIsarSetting().autoScrollPages ?? []) { if (autoScrollPages.mangaId != getManga().id) { autoScrollPagesList.add(autoScrollPages); } } autoScrollPagesList.add(AutoScrollPages() ..mangaId = getManga().id ..pageOffset = offset ..autoScroll = value); isar.writeTxnSync(() => isar.settings.putSync(getIsarSetting()..autoScrollPages = autoScrollPagesList)); } PageMode getPageMode() { final personalPageModeList = getIsarSetting().personalPageModeList ?? []; final personalPageMode = personalPageModeList.where((element) => element.mangaId == getManga().id); if (personalPageMode.isNotEmpty) { return personalPageMode.first.pageMode; } return PageMode.onePage; } void setReaderMode(ReaderMode newReaderMode) { List? personalReaderModeLists = []; for (var personalReaderMode in getIsarSetting().personalReaderModeList ?? []) { if (personalReaderMode.mangaId != getManga().id) { personalReaderModeLists.add(personalReaderMode); } } personalReaderModeLists.add(PersonalReaderMode() ..mangaId = getManga().id ..readerMode = newReaderMode); isar.writeTxnSync(() => isar.settings.putSync(getIsarSetting()..personalReaderModeList = personalReaderModeLists)); } void setPageMode(PageMode newPageMode) { List? personalPageModeLists = []; for (var personalPageMode in getIsarSetting().personalPageModeList ?? []) { if (personalPageMode.mangaId != getManga().id) { personalPageModeLists.add(personalPageMode); } } personalPageModeLists.add(PersonalPageMode() ..mangaId = getManga().id ..pageMode = newPageMode); isar.writeTxnSync(() => isar.settings.putSync(getIsarSetting()..personalPageModeList = personalPageModeLists)); } void setShowPageNumber(bool value) { if (!incognitoMode) { isar.writeTxnSync(() => isar.settings.putSync(getIsarSetting()..showPagesNumber = value)); } } Settings getIsarSetting() { return isar.settings.getSync(227)!; } bool getShowPageNumber() { if (!incognitoMode) { return getIsarSetting().showPagesNumber!; } return true; } void setMangaHistoryUpdate() { if (incognitoMode) return; isar.writeTxnSync(() { Manga? manga = chapter.manga.value; manga!.lastRead = DateTime.now().millisecondsSinceEpoch; isar.mangas.putSync(manga); }); History? history; final empty = isar.historys.filter().mangaIdEqualTo(getManga().id).isEmptySync(); if (empty) { history = History( mangaId: getManga().id, date: DateTime.now().millisecondsSinceEpoch.toString(), isManga: getManga().isManga, chapterId: chapter.id) ..chapter.value = chapter; } else { history = (isar.historys.filter().mangaIdEqualTo(getManga().id).findFirstSync())! ..chapterId = chapter.id ..chapter.value = chapter ..date = DateTime.now().millisecondsSinceEpoch.toString(); } isar.writeTxnSync(() { isar.historys.putSync(history!); history.chapter.saveSync(); }); } void checkAndSyncProgress() { final syncAfterReading = ref.watch(syncAfterReadingStateProvider); if (syncAfterReading) { ref.read(syncServerProvider(syncId: 1).notifier).checkForSync(true); } } void setChapterBookmarked() { if (incognitoMode) return; final isBookmarked = getChapterBookmarked(); final chap = chapter; isar.writeTxnSync(() { chap.isBookmarked = !isBookmarked; ref.read(changedItemsManagerProvider(managerId: 1).notifier).addUpdatedChapter(chap, false, false); isar.chapters.putSync(chap); }); } bool getChapterBookmarked() { return isar.chapters.getSync(chapter.id!)!.isBookmarked!; } (int, bool) getPrevChapterIndex() { final chapters = getManga().getFilteredChapterList(); int? index; for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { index = i + 1; } } if (index == null) { final chapters = getManga().chapters.toList().reversed.toList(); for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { index = i + 1; } } return (index!, false); } return (index, true); } (int, bool) getNextChapterIndex() { final chapters = getManga().getFilteredChapterList(); int? index; for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { index = i - 1; } } if (index == null) { final chapters = getManga().chapters.toList().reversed.toList(); for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { index = i - 1; } } return (index!, false); } return (index, true); } (int, bool) getChapterIndex() { final chapters = getManga().getFilteredChapterList(); int? index; for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { index = i; } } if (index == null) { final chapters = getManga().chapters.toList().reversed.toList(); for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { index = i; } } return (index!, false); } return (index, true); } Chapter getPrevChapter() { final prevChapIdx = getPrevChapterIndex(); return prevChapIdx.$2 ? getManga().getFilteredChapterList()[prevChapIdx.$1] : getManga().chapters.toList().reversed.toList()[prevChapIdx.$1]; } Chapter getNextChapter() { final nextChapIdx = getNextChapterIndex(); return nextChapIdx.$2 ? getManga().getFilteredChapterList()[nextChapIdx.$1] : getManga().chapters.toList().reversed.toList()[nextChapIdx.$1]; } int getChaptersLength(bool isInFilterList) { return isInFilterList ? getManga().getFilteredChapterList().length : getManga().chapters.length; } int getPageIndex() { if (incognitoMode) return 0; final chapterPageIndexList = getIsarSetting().chapterPageIndexList ?? []; final index = chapterPageIndexList.where((element) => element.chapterId == chapter.id); return chapter.isRead! ? 0 : index.isNotEmpty ? index.first.index! : 0; } int getPageLength(List incognitoPageLength) { if (incognitoMode) return incognitoPageLength.length; return getIsarSetting().chapterPageUrlsList!.where((element) => element.chapterId == chapter.id).first.urls!.length; } void setPageIndex(int newIndex, bool save) { if (chapter.isRead!) return; if (incognitoMode) return; final isRead = (getReaderMode() == ReaderMode.verticalContinuous || getReaderMode() == ReaderMode.webtoon) ? ((newIndex + 1) == getPageLength([]) - 1) ? ((newIndex + 1) == getPageLength([]) - 1) : (newIndex + 1) == getPageLength([]) : (newIndex + 1) == getPageLength([]); if (isRead || save) { List? chapterPageIndexs = []; for (var chapterPageIndex in getIsarSetting().chapterPageIndexList ?? []) { if (chapterPageIndex.chapterId != chapter.id) { chapterPageIndexs.add(chapterPageIndex); } } chapterPageIndexs.add(ChapterPageIndex() ..chapterId = chapter.id ..index = isRead ? 0 : newIndex); final chap = chapter; isar.writeTxnSync(() { isar.settings.putSync(getIsarSetting()..chapterPageIndexList = chapterPageIndexs); chap.isRead = isRead; chap.lastPageRead = isRead ? '1' : (newIndex + 1).toString(); ref.read(changedItemsManagerProvider(managerId: 1).notifier).addUpdatedChapter(chap, false, false); isar.chapters.putSync(chap); }); if (isRead) { chapter.updateTrackChapterRead(ref); } } } String getMangaName() { return getManga().name!; } String getSourceName() { return getManga().source!; } String getChapterTitle() { return chapter.name!; } } extension ChapterExtensions on Chapter { void updateTrackChapterRead(dynamic ref) { if (!(ref is WidgetRef || ref is Ref)) return; final updateProgressAfterReading = ref.watch(updateProgressAfterReadingStateProvider); if (!updateProgressAfterReading) return; final manga = this.manga.value!; final chapterNumber = ChapterRecognition().parseChapterNumber(manga.name!, name!); final tracks = isar.tracks.filter().idIsNotNull().isMangaEqualTo(manga.isManga).mangaIdEqualTo(manga.id!).findAllSync(); if (tracks.isEmpty) return; for (var track in tracks) { final service = isar.trackPreferences.filter().syncIdIsNotNull().syncIdEqualTo(track.syncId).findFirstSync(); if (!(service == null || chapterNumber <= (track.lastChapterRead ?? 0))) { if (track.status != TrackStatus.completed) { track.lastChapterRead = chapterNumber; if (track.lastChapterRead == track.totalChapter && (track.totalChapter ?? 0) > 0) { track.status = TrackStatus.completed; track.finishedReadingDate = DateTime.now().millisecondsSinceEpoch; } else { track.status = manga.isManga! ? TrackStatus.reading : TrackStatus.watching; if (track.lastChapterRead == 1) { track.startedReadingDate = DateTime.now().millisecondsSinceEpoch; } } } ref.read(trackStateProvider(track: track, isManga: manga.isManga).notifier).updateManga(); } } } } extension MangaExtensions on Manga { List getFilteredChapterList() { final data = this.chapters.toList().reversed.toList(); final filterUnread = (isar.settings .getSync(227)! .chapterFilterUnreadList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? ChapterFilterUnread( mangaId: id, type: 0, )) .type!; final filterBookmarked = (isar.settings .getSync(227)! .chapterFilterBookmarkedList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? ChapterFilterBookmarked( mangaId: id, type: 0, )) .type!; final filterDownloaded = (isar.settings .getSync(227)! .chapterFilterDownloadedList! .where((element) => element.mangaId == id) .toList() .firstOrNull ?? ChapterFilterDownloaded( mangaId: id, type: 0, )) .type!; final sortChapter = (isar.settings.getSync(227)!.sortChapterList!.where((element) => element.mangaId == id).toList().firstOrNull ?? SortChapter( mangaId: id, index: 1, reverse: false, )) .index; final filterScanlator = _getFilterScanlator(this) ?? []; List? chapterList; chapterList = data .where((element) => filterUnread == 1 ? element.isRead == false : filterUnread == 2 ? element.isRead == true : true) .where((element) => filterBookmarked == 1 ? element.isBookmarked == true : filterBookmarked == 2 ? element.isBookmarked == false : true) .where((element) { final modelChapDownload = isar.downloads.filter().idIsNotNull().chapterIdEqualTo(element.id).findAllSync(); return filterDownloaded == 1 ? modelChapDownload.isNotEmpty && modelChapDownload.first.isDownload == true : filterDownloaded == 2 ? !(modelChapDownload.isNotEmpty && modelChapDownload.first.isDownload == true) : true; }) .where((element) => !filterScanlator.contains(element.scanlator)) .toList(); List chapters = sortChapter == 1 ? chapterList.reversed.toList() : chapterList; if (sortChapter == 0) { chapters.sort( (a, b) { return (a.scanlator == null || b.scanlator == null || a.dateUpload == null || b.dateUpload == null) ? 0 : a.scanlator!.compareTo(b.scanlator!) | a.dateUpload!.compareTo(b.dateUpload!); }, ); } else if (sortChapter == 2) { chapters.sort( (a, b) { return (a.dateUpload == null || b.dateUpload == null) ? 0 : int.parse(a.dateUpload!).compareTo(int.parse(b.dateUpload!)); }, ); } else if (sortChapter == 3) { chapters.sort( (a, b) { return (a.name == null || b.name == null) ? 0 : a.name!.compareTo(b.name!); }, ); } return chapterList; } } List? _getFilterScanlator(Manga manga) { final scanlators = isar.settings.getSync(227)!.filterScanlatorList ?? []; final filter = scanlators.where((element) => element.mangaId == manga.id).toList(); return filter.firstOrNull?.scanlators; }