mangayomi-mirror/lib/modules/manga/reader/providers/reader_controller_provider.dart
2025-10-29 13:08:42 +01:00

569 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/misc.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/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/track/providers/track_providers.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();
}
void 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
KeepAliveLink build({required Chapter chapter}) {
_keepAliveLink = ref.keepAlive();
return _keepAliveLink!;
}
KeepAliveLink? _keepAliveLink;
KeepAliveLink? get keepAliveLink => _keepAliveLink;
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<AutoScrollPages>? 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
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
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<PersonalReaderMode>? 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
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
void setPageMode(PageMode newPageMode) {
List<PersonalPageMode>? 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
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
void setShowPageNumber(bool value) {
if (!incognitoMode) {
isar.writeTxnSync(
() => isar.settings.putSync(
getIsarSetting()
..showPagesNumber = value
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
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;
manga.updatedAt = 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(),
itemType: getManga().itemType,
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(() {
history!.updatedAt = DateTime.now().millisecondsSinceEpoch;
isar.historys.putSync(history);
history.chapter.saveSync();
});
}
void setChapterBookmarked() {
if (incognitoMode) return;
final isBookmarked = getChapterBookmarked();
final chap = chapter;
isar.writeTxnSync(() {
chap.isBookmarked = !isBookmarked;
chap.updatedAt = DateTime.now().millisecondsSinceEpoch;
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 + 2) == getPageLength([]) - 1)
? ((newIndex + 2) == getPageLength([]) - 1)
: (newIndex + 2) == getPageLength([])
: (newIndex + 2) == getPageLength([]);
if (isRead || save) {
List<ChapterPageIndex>? 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
..updatedAt = DateTime.now().millisecondsSinceEpoch,
);
chap.isRead = isRead;
chap.lastPageRead = isRead ? '1' : (newIndex + 1).toString();
chap.updatedAt = DateTime.now().millisecondsSinceEpoch;
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()
.itemTypeEqualTo(manga.itemType)
.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.itemType == ItemType.manga
? TrackStatus.reading
: TrackStatus.watching;
if (track.lastChapterRead == 1) {
track.startedReadingDate = DateTime.now().millisecondsSinceEpoch;
}
}
}
ref
.read(
trackStateProvider(
track: track,
itemType: manga.itemType,
widgetRef: ref,
).notifier,
)
.updateManga();
}
}
}
}
extension MangaExtensions on Manga {
List<Chapter> 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<Chapter>? 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()
.idEqualTo(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<Chapter> 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<String>? _getFilterScanlator(Manga manga) {
final scanlators = isar.settings.getSync(227)!.filterScanlatorList ?? [];
final filter = scanlators
.where((element) => element.mangaId == manga.id)
.toList();
return filter.firstOrNull?.scanlators;
}