mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-05-10 03:10:59 +00:00
- to prevent old entries (before sourceId was introduced) from accidentally using conflicting sources with the same name even if they are not installed
569 lines
17 KiB
Dart
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;
|
|
}
|