diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index 6d92cbd1..ddd74294 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -154,7 +154,8 @@ class _AnimeStreamPageState extends riv.ConsumerState { late final GlobalKey _key = GlobalKey(); late final Player _player = Player(); late final VideoController _controller = VideoController(_player); - late final _streamController = AnimeStreamController(episode: widget.episode); + late final _streamController = + ref.read(animeStreamControllerProvider(episode: widget.episode).notifier); late final _firstVid = widget.videos.first; late final ValueNotifier _video = ValueNotifier(VideoPrefs( @@ -176,6 +177,7 @@ class _AnimeStreamPageState extends riv.ConsumerState { bool _seekToCurrentPosition = true; bool _initSubtitle = true; late Duration _currentPosition = _streamController.geTCurrentPosition(); + Duration? _currentTotalDuration; final _showFitLabel = StateProvider((ref) => false); final _showSeekTo = StateProvider((ref) => false); final ValueNotifier _isCompleted = ValueNotifier(false); @@ -208,10 +210,18 @@ class _AnimeStreamPageState extends riv.ConsumerState { }, ); + late final StreamSubscription _currentTotalDurationSub = + _player.stream.duration.listen( + (position) { + _currentTotalDuration = position; + }, + ); + @override void initState() { - _setCurrentPosition(); + _setCurrentPosition(true); _currentPositionSub; + _currentTotalDurationSub; _player.open(Media(_video.value!.videoTrack!.id, httpHeaders: _video.value!.headers)); super.initState(); @@ -219,14 +229,17 @@ class _AnimeStreamPageState extends riv.ConsumerState { @override void dispose() { - _setCurrentPosition(); + _setCurrentPosition(true); _player.dispose(); _currentPositionSub.cancel(); + _currentTotalDurationSub.cancel(); super.dispose(); } - void _setCurrentPosition() { - _streamController.setCurrentPosition(_currentPosition.inMilliseconds); + void _setCurrentPosition(bool save) { + _streamController.setCurrentPosition( + _currentPosition, _currentTotalDuration, + save: save); _streamController.setAnimeHistoryUpdate(); } diff --git a/lib/modules/anime/providers/anime_player_controller_provider.dart b/lib/modules/anime/providers/anime_player_controller_provider.dart index 13709c9b..de0f7a4c 100644 --- a/lib/modules/anime/providers/anime_player_controller_provider.dart +++ b/lib/modules/anime/providers/anime_player_controller_provider.dart @@ -1,14 +1,17 @@ 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/modules/manga/reader/providers/reader_controller_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'anime_player_controller_provider.g.dart'; -class AnimeStreamController { - final Chapter episode; - AnimeStreamController({required this.episode}); +@riverpod +class AnimeStreamController extends _$AnimeStreamController { + @override + void build({required Chapter episode}) {} Manga getAnime() { return episode.manga.value!; @@ -21,7 +24,7 @@ class AnimeStreamController { } (int, bool) getEpisodeIndex() { - final episodes = _filterAndSortEpisodes(); + final episodes = getAnime().getFilteredChapterList(); int? index; for (var i = 0; i < episodes.length; i++) { if (episodes[i].id == episode.id) { @@ -41,7 +44,7 @@ class AnimeStreamController { } (int, bool) getPrevEpisodeIndex() { - final episodes = _filterAndSortEpisodes(); + final episodes = getAnime().getFilteredChapterList(); int? index; for (var i = 0; i < episodes.length; i++) { if (episodes[i].id == episode.id) { @@ -61,7 +64,7 @@ class AnimeStreamController { } (int, bool) getNextEpisodeIndex() { - final episodes = _filterAndSortEpisodes(); + final episodes = getAnime().getFilteredChapterList(); int? index; for (var i = 0; i < episodes.length; i++) { if (episodes[i].id == episode.id) { @@ -83,179 +86,82 @@ class AnimeStreamController { Chapter getPrevEpisode() { final prevEpIdx = getPrevEpisodeIndex(); return prevEpIdx.$2 - ? _filterAndSortEpisodes()[prevEpIdx.$1] + ? getAnime().getFilteredChapterList()[prevEpIdx.$1] : getAnime().chapters.toList().reversed.toList()[prevEpIdx.$1]; } Chapter getNextEpisode() { final nextEpIdx = getNextEpisodeIndex(); return nextEpIdx.$2 - ? _filterAndSortEpisodes()[nextEpIdx.$1] + ? getAnime().getFilteredChapterList()[nextEpIdx.$1] : getAnime().chapters.toList().reversed.toList()[nextEpIdx.$1]; } int getEpisodesLength(bool isInFilterList) { return isInFilterList - ? _filterAndSortEpisodes().length + ? getAnime().getFilteredChapterList().length : getAnime().chapters.length; } Duration geTCurrentPosition() { - if (!incognitoMode) { - String position = episode.lastPageRead ?? "0"; - return Duration( - milliseconds: episode.isRead! - ? 0 - : int.parse(position.isEmpty ? "0" : position)); - } - return Duration.zero; + if (incognitoMode) return Duration.zero; + String position = episode.lastPageRead ?? "0"; + return Duration( + milliseconds: + episode.isRead! ? 0 : int.parse(position.isEmpty ? "0" : position)); } void setAnimeHistoryUpdate() { - if (!incognitoMode) { - isar.writeTxnSync(() { - Manga? anime = episode.manga.value; - anime!.lastRead = DateTime.now().millisecondsSinceEpoch; - isar.mangas.putSync(anime); - }); - History? history; + if (incognitoMode) return; + isar.writeTxnSync(() { + Manga? anime = episode.manga.value; + anime!.lastRead = DateTime.now().millisecondsSinceEpoch; + isar.mangas.putSync(anime); + }); + History? history; - final empty = - isar.historys.filter().mangaIdEqualTo(getAnime().id).isEmptySync(); + final empty = + isar.historys.filter().mangaIdEqualTo(getAnime().id).isEmptySync(); - if (empty) { - history = History( - mangaId: getAnime().id, - date: DateTime.now().millisecondsSinceEpoch.toString(), - isManga: getAnime().isManga, - chapterId: episode.id) - ..chapter.value = episode; - } else { - history = (isar.historys - .filter() - .mangaIdEqualTo(getAnime().id) - .findFirstSync())! - ..chapter.value = episode - ..date = DateTime.now().millisecondsSinceEpoch.toString(); - } - isar.writeTxnSync(() { - isar.historys.putSync(history!); - history.chapter.saveSync(); - }); + if (empty) { + history = History( + mangaId: getAnime().id, + date: DateTime.now().millisecondsSinceEpoch.toString(), + isManga: getAnime().isManga, + chapterId: episode.id) + ..chapter.value = episode; + } else { + history = (isar.historys + .filter() + .mangaIdEqualTo(getAnime().id) + .findFirstSync())! + ..chapter.value = episode + ..date = DateTime.now().millisecondsSinceEpoch.toString(); } + isar.writeTxnSync(() { + isar.historys.putSync(history!); + history.chapter.saveSync(); + }); } - void setCurrentPosition(int duration) { - if (!episode.isRead!) { - if (!incognitoMode) { - final ep = episode; - isar.writeTxnSync(() { - ep.lastPageRead = (duration).toString(); - isar.chapters.putSync(ep); - }); + void setCurrentPosition(Duration duration, Duration? totalDuration, + {bool save = false}) { + if (episode.isRead!) return; + if (incognitoMode) return; + + final isWatch = totalDuration != null + ? duration.inSeconds >= (totalDuration.inSeconds - 120) + : false; + if (isWatch || save) { + final ep = episode; + isar.writeTxnSync(() { + ep.isRead = isWatch; + ep.lastPageRead = (duration.inMilliseconds).toString(); + isar.chapters.putSync(ep); + }); + if (isWatch) { + episode.updateTrackChapterRead(ref); } } } - - List? _getFilterScanlator() { - final scanlators = isar.settings.getSync(227)!.filterScanlatorList ?? []; - final filter = scanlators - .where((element) => element.mangaId == getAnime().id) - .toList(); - return filter.isEmpty ? null : filter.first.scanlators; - } - - List _filterAndSortEpisodes() { - final data = getAnime().chapters.toList().reversed.toList(); - final filterUnread = isar.settings - .getSync(227)! - .chapterFilterUnreadList! - .where((element) => element.mangaId == getAnime().id) - .toList() - .first - .type!; - final filterBookmarked = isar.settings - .getSync(227)! - .chapterFilterBookmarkedList! - .where((element) => element.mangaId == getAnime().id) - .toList() - .first - .type!; - final filterDownloaded = isar.settings - .getSync(227)! - .chapterFilterDownloadedList! - .where((element) => element.mangaId == getAnime().id) - .toList() - .first - .type!; - - final sortChapter = isar.settings - .getSync(227)! - .sortChapterList! - .where((element) => element.mangaId == getAnime().id) - .toList() - .first - .index; - final filterScanlator = _getFilterScanlator() ?? []; - 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; - } } diff --git a/lib/modules/anime/providers/anime_player_controller_provider.g.dart b/lib/modules/anime/providers/anime_player_controller_provider.g.dart new file mode 100644 index 00000000..ecd9b36a --- /dev/null +++ b/lib/modules/anime/providers/anime_player_controller_provider.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'anime_player_controller_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$animeStreamControllerHash() => + r'017ad7ee068f16f2b43b2c47692987cd1d1f70d7'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$AnimeStreamController + extends BuildlessAutoDisposeNotifier { + late final Chapter episode; + + void build({ + required Chapter episode, + }); +} + +/// See also [AnimeStreamController]. +@ProviderFor(AnimeStreamController) +const animeStreamControllerProvider = AnimeStreamControllerFamily(); + +/// See also [AnimeStreamController]. +class AnimeStreamControllerFamily extends Family { + /// See also [AnimeStreamController]. + const AnimeStreamControllerFamily(); + + /// See also [AnimeStreamController]. + AnimeStreamControllerProvider call({ + required Chapter episode, + }) { + return AnimeStreamControllerProvider( + episode: episode, + ); + } + + @override + AnimeStreamControllerProvider getProviderOverride( + covariant AnimeStreamControllerProvider provider, + ) { + return call( + episode: provider.episode, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'animeStreamControllerProvider'; +} + +/// See also [AnimeStreamController]. +class AnimeStreamControllerProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [AnimeStreamController]. + AnimeStreamControllerProvider({ + required Chapter episode, + }) : this._internal( + () => AnimeStreamController()..episode = episode, + from: animeStreamControllerProvider, + name: r'animeStreamControllerProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$animeStreamControllerHash, + dependencies: AnimeStreamControllerFamily._dependencies, + allTransitiveDependencies: + AnimeStreamControllerFamily._allTransitiveDependencies, + episode: episode, + ); + + AnimeStreamControllerProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.episode, + }) : super.internal(); + + final Chapter episode; + + @override + void runNotifierBuild( + covariant AnimeStreamController notifier, + ) { + return notifier.build( + episode: episode, + ); + } + + @override + Override overrideWith(AnimeStreamController Function() create) { + return ProviderOverride( + origin: this, + override: AnimeStreamControllerProvider._internal( + () => create()..episode = episode, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + episode: episode, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement + createElement() { + return _AnimeStreamControllerProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is AnimeStreamControllerProvider && other.episode == episode; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, episode.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin AnimeStreamControllerRef on AutoDisposeNotifierProviderRef { + /// The parameter `episode` of this provider. + Chapter get episode; +} + +class _AnimeStreamControllerProviderElement + extends AutoDisposeNotifierProviderElement + with AnimeStreamControllerRef { + _AnimeStreamControllerProviderElement(super.provider); + + @override + Chapter get episode => (origin as AnimeStreamControllerProvider).episode; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/modules/manga/detail/manga_detail_main.dart b/lib/modules/manga/detail/manga_detail_main.dart index 19ab8fc3..eb4c109b 100644 --- a/lib/modules/manga/detail/manga_detail_main.dart +++ b/lib/modules/manga/detail/manga_detail_main.dart @@ -40,7 +40,6 @@ class _MangaReaderDetailState extends ConsumerState { bool _isLoading = true; @override Widget build(BuildContext context) { - print(widget.mangaId); final manga = ref.watch(getMangaDetailStreamProvider(mangaId: widget.mangaId)); return Scaffold( diff --git a/lib/modules/manga/home/manga_home_screen.dart b/lib/modules/manga/home/manga_home_screen.dart index da49631e..9d9611f6 100644 --- a/lib/modules/manga/home/manga_home_screen.dart +++ b/lib/modules/manga/home/manga_home_screen.dart @@ -50,6 +50,7 @@ class _MangaHomeScreenState extends ConsumerState { int _page = 1; late int _selectedIndex = widget.isSearch ? 2 : 0; List filters = []; + final List _mangaList = []; List _types(BuildContext context) { final l10n = l10nLocalizations(context)!; return [ @@ -140,6 +141,7 @@ class _MangaHomeScreenState extends ConsumerState { onChanged: (value) {}, onSuffixPressed: () { _textEditingController.clear(); + _mangaList.clear(); _query = ""; setState(() {}); }, @@ -152,6 +154,7 @@ class _MangaHomeScreenState extends ConsumerState { _selectedIndex = 0; _page = 1; _textEditingController.clear(); + _mangaList.clear(); } else { Navigator.pop(context); } @@ -207,6 +210,7 @@ class _MangaHomeScreenState extends ConsumerState { selected: _selectedIndex == index, text: _types(context)[index].title, onPressed: () async { + _mangaList.clear(); if (filters.isEmpty) { filters = filterList; } @@ -307,6 +311,9 @@ class _MangaHomeScreenState extends ConsumerState { }, child: _getManga!.when( data: (data) { + if (_mangaList.isEmpty && data!.list.isNotEmpty) { + _mangaList.addAll(data.list); + } if (_getManga!.isLoading) { return const ProgressCenter(); } @@ -338,9 +345,9 @@ class _MangaHomeScreenState extends ConsumerState { }); } _loadMore().then((value) { - if (mounted) { + if (mounted && value != null) { setState(() { - data.list.addAll(value!.list); + _mangaList.addAll(value.list); _isLoading = false; }); } @@ -368,7 +375,7 @@ class _MangaHomeScreenState extends ConsumerState { _scrollController.addListener(() { if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { - if (data.list.isNotEmpty && + if (_mangaList.isNotEmpty && (data.hasNextPage) && !_isLoading) { if (mounted) { @@ -377,9 +384,9 @@ class _MangaHomeScreenState extends ConsumerState { }); } _loadMore().then((value) { - if (mounted) { + if (mounted && value != null) { setState(() { - data.list.addAll(value!.list); + _mangaList.addAll(value.list); _isLoading = false; }); } @@ -390,9 +397,9 @@ class _MangaHomeScreenState extends ConsumerState { _length = widget.source.isFullData! ? _fullDataLength - : data.list.length; + : _mangaList.length; _length = - (data.list.length < _length ? data.list.length : _length); + (_mangaList.length < _length ? _mangaList.length : _length); return Padding( padding: const EdgeInsets.only(top: 10), child: Column( @@ -407,7 +414,7 @@ class _MangaHomeScreenState extends ConsumerState { } return MangaHomeImageCard( isManga: widget.source.isManga ?? true, - manga: data.list[index], + manga: _mangaList[index], source: widget.source, ); }, diff --git a/lib/modules/manga/reader/providers/push_router.dart b/lib/modules/manga/reader/providers/push_router.dart index 5c6e104a..db1e5d67 100644 --- a/lib/modules/manga/reader/providers/push_router.dart +++ b/lib/modules/manga/reader/providers/push_router.dart @@ -23,7 +23,6 @@ pushMangaReaderView({ .isAddedEqualTo(true) .findAllSync() .isNotEmpty; - print(sourceExist); if (sourceExist || useTestSourceCode || chapter.manga.value!.isLocalArchive!) { diff --git a/lib/modules/manga/reader/providers/reader_controller_provider.dart b/lib/modules/manga/reader/providers/reader_controller_provider.dart index 3d8d62d5..2e097990 100644 --- a/lib/modules/manga/reader/providers/reader_controller_provider.dart +++ b/lib/modules/manga/reader/providers/reader_controller_provider.dart @@ -6,7 +6,11 @@ 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/utils/chapter_recognition.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'reader_controller_provider.g.dart'; @@ -15,15 +19,13 @@ class CurrentIndex extends _$CurrentIndex { @override int build(Chapter chapter) { final incognitoMode = ref.watch(incognitoModeStateProvider); - if (!incognitoMode) { - return ReaderController(chapter: chapter).getPageIndex(); - } - return 0; + if (incognitoMode) return 0; + return ref + .read(readerControllerProvider(chapter: chapter).notifier) + .getPageIndex(); } - setCurrentIndex( - int currentIndex, - ) { + setCurrentIndex(int currentIndex) { state = currentIndex; } } @@ -39,9 +41,10 @@ BoxFit getBoxFit(ScaleType scaleType) { }; } -class ReaderController { - final Chapter chapter; - ReaderController({required this.chapter}); +@riverpod +class ReaderController extends _$ReaderController { + @override + void build({required Chapter chapter}) {} Manga getManga() { return chapter.manga.value!; @@ -142,55 +145,53 @@ class ReaderController { } bool getShowPageNumber() { - if (!incognitoMode) { + if (incognitoMode) { return getIsarSetting().showPagesNumber!; } return true; } void setMangaHistoryUpdate() { - if (!incognitoMode) { - isar.writeTxnSync(() { - Manga? manga = chapter.manga.value; - manga!.lastRead = DateTime.now().millisecondsSinceEpoch; - isar.mangas.putSync(manga); - }); - History? history; + 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(); + 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())! - ..chapter.value = chapter - ..date = DateTime.now().millisecondsSinceEpoch.toString(); - } - isar.writeTxnSync(() { - isar.historys.putSync(history!); - history.chapter.saveSync(); - }); + 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())! + ..chapter.value = chapter + ..date = DateTime.now().millisecondsSinceEpoch.toString(); } + isar.writeTxnSync(() { + isar.historys.putSync(history!); + history.chapter.saveSync(); + }); } void setChapterBookmarked() { - if (!incognitoMode) { - final isBookmarked = getChapterBookmarked(); - final chap = chapter; - isar.writeTxnSync(() { - chap.isBookmarked = !isBookmarked; - isar.chapters.putSync(chap); - }); - } + if (incognitoMode) return; + final isBookmarked = getChapterBookmarked(); + final chap = chapter; + isar.writeTxnSync(() { + chap.isBookmarked = !isBookmarked; + isar.chapters.putSync(chap); + }); } bool getChapterBookmarked() { @@ -198,7 +199,7 @@ class ReaderController { } (int, bool) getPrevChapterIndex() { - final chapters = _filterAndSortChapters(); + final chapters = getManga().getFilteredChapterList(); int? index; for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { @@ -218,7 +219,7 @@ class ReaderController { } (int, bool) getNextChapterIndex() { - final chapters = _filterAndSortChapters(); + final chapters = getManga().getFilteredChapterList(); int? index; for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { @@ -238,7 +239,7 @@ class ReaderController { } (int, bool) getChapterIndex() { - final chapters = _filterAndSortChapters(); + final chapters = getManga().getFilteredChapterList(); int? index; for (var i = 0; i < chapters.length; i++) { if (chapters[i].id == chapter.id) { @@ -260,89 +261,144 @@ class ReaderController { Chapter getPrevChapter() { final prevChapIdx = getPrevChapterIndex(); return prevChapIdx.$2 - ? _filterAndSortChapters()[prevChapIdx.$1] + ? getManga().getFilteredChapterList()[prevChapIdx.$1] : getManga().chapters.toList().reversed.toList()[prevChapIdx.$1]; } Chapter getNextChapter() { final nextChapIdx = getNextChapterIndex(); return nextChapIdx.$2 - ? _filterAndSortChapters()[nextChapIdx.$1] + ? getManga().getFilteredChapterList()[nextChapIdx.$1] : getManga().chapters.toList().reversed.toList()[nextChapIdx.$1]; } int getChaptersLength(bool isInFilterList) { return isInFilterList - ? _filterAndSortChapters().length + ? 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); - if (!incognitoMode) { - return chapter.isRead! - ? 0 - : index.isNotEmpty - ? index.first.index! - : 0; - } - return 0; + return chapter.isRead! + ? 0 + : index.isNotEmpty + ? index.first.index! + : 0; } int getPageLength(List incognitoPageLength) { - if (!incognitoMode) { - return getIsarSetting() - .chapterPageUrlsList! - .where((element) => element.chapterId == chapter.id) - .first - .urls! - .length; - } - return incognitoPageLength.length; + if (incognitoMode) return incognitoPageLength.length; + return getIsarSetting() + .chapterPageUrlsList! + .where((element) => element.chapterId == chapter.id) + .first + .urls! + .length; } - void setPageIndex(int newIndex) { - if (!chapter.isRead!) { - if (!incognitoMode) { - List? chapterPageIndexs = []; - for (var chapterPageIndex - in getIsarSetting().chapterPageIndexList ?? []) { - if (chapterPageIndex.chapterId != chapter.id) { - chapterPageIndexs.add(chapterPageIndex); - } + 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 = newIndex); - final chap = chapter; - final isRead = (newIndex + 1) == getPageLength([]); - isar.writeTxnSync(() { - isar.settings.putSync( - getIsarSetting()..chapterPageIndexList = chapterPageIndexs); - chap.isRead = isRead; - chap.lastPageRead = isRead ? '1' : (newIndex + 1).toString(); - isar.chapters.putSync(chap); - }); + } + chapterPageIndexs.add(ChapterPageIndex() + ..chapterId = chapter.id + ..index = newIndex); + final chap = chapter; + isar.writeTxnSync(() { + isar.settings.putSync( + getIsarSetting()..chapterPageIndexList = chapterPageIndexs); + chap.isRead = isRead; + chap.lastPageRead = isRead ? '1' : (newIndex + 1).toString(); + isar.chapters.putSync(chap); + }); + if (isRead) { + chapter.updateTrackChapterRead(ref); } } } - List? _getFilterScanlator() { - final scanlators = isar.settings.getSync(227)!.filterScanlatorList ?? []; - final filter = scanlators - .where((element) => element.mangaId == getManga().id) - .toList(); - return filter.isEmpty ? null : filter.first.scanlators; + String getMangaName() { + return getManga().name!; } - List _filterAndSortChapters() { - final data = getManga().chapters.toList().reversed.toList(); + String getSourceName() { + return getManga().source!; + } + + String getChapterTitle() { + return chapter.name!; + } +} + +extension ChapterExtensions on Chapter { + void updateTrackChapterRead(AutoDisposeNotifierProviderRef ref) { + 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 == getManga().id) + .where((element) => element.mangaId == id) .toList() .first .type!; @@ -350,14 +406,14 @@ class ReaderController { final filterBookmarked = isar.settings .getSync(227)! .chapterFilterBookmarkedList! - .where((element) => element.mangaId == getManga().id) + .where((element) => element.mangaId == id) .toList() .first .type!; final filterDownloaded = isar.settings .getSync(227)! .chapterFilterDownloadedList! - .where((element) => element.mangaId == getManga().id) + .where((element) => element.mangaId == id) .toList() .first .type!; @@ -365,11 +421,11 @@ class ReaderController { final sortChapter = isar.settings .getSync(227)! .sortChapterList! - .where((element) => element.mangaId == getManga().id) + .where((element) => element.mangaId == id) .toList() .first .index; - final filterScanlator = _getFilterScanlator() ?? []; + final filterScanlator = _getFilterScanlator(this) ?? []; List? chapterList; chapterList = data .where((element) => filterUnread == 1 @@ -431,16 +487,11 @@ class ReaderController { } return chapterList; } - - String getMangaName() { - return getManga().name!; - } - - String getSourceName() { - return getManga().source!; - } - - String getChapterTitle() { - return chapter.name!; - } +} + +List? _getFilterScanlator(Manga manga) { + final scanlators = isar.settings.getSync(227)!.filterScanlatorList ?? []; + final filter = + scanlators.where((element) => element.mangaId == manga.id).toList(); + return filter.isEmpty ? null : filter.first.scanlators; } 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 48b2eadd..e39b2917 100644 --- a/lib/modules/manga/reader/providers/reader_controller_provider.g.dart +++ b/lib/modules/manga/reader/providers/reader_controller_provider.g.dart @@ -6,7 +6,7 @@ part of 'reader_controller_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$currentIndexHash() => r'a5103d732efaeec89b719a14e060ac7aab16c8f4'; +String _$currentIndexHash() => r'c2b912af925d9efd3e36e7a810914ef11393c1da'; /// Copied from Dart SDK class _SystemHash { @@ -168,5 +168,147 @@ class _CurrentIndexProviderElement @override Chapter get chapter => (origin as CurrentIndexProvider).chapter; } + +String _$readerControllerHash() => r'611b6eca40a398fe9c71911db2ca9714d6cc05a0'; + +abstract class _$ReaderController extends BuildlessAutoDisposeNotifier { + late final Chapter chapter; + + void build({ + required Chapter chapter, + }); +} + +/// See also [ReaderController]. +@ProviderFor(ReaderController) +const readerControllerProvider = ReaderControllerFamily(); + +/// See also [ReaderController]. +class ReaderControllerFamily extends Family { + /// See also [ReaderController]. + const ReaderControllerFamily(); + + /// See also [ReaderController]. + ReaderControllerProvider call({ + required Chapter chapter, + }) { + return ReaderControllerProvider( + chapter: chapter, + ); + } + + @override + ReaderControllerProvider getProviderOverride( + covariant ReaderControllerProvider provider, + ) { + return call( + chapter: provider.chapter, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'readerControllerProvider'; +} + +/// See also [ReaderController]. +class ReaderControllerProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [ReaderController]. + ReaderControllerProvider({ + required Chapter chapter, + }) : this._internal( + () => ReaderController()..chapter = chapter, + from: readerControllerProvider, + name: r'readerControllerProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$readerControllerHash, + dependencies: ReaderControllerFamily._dependencies, + allTransitiveDependencies: + ReaderControllerFamily._allTransitiveDependencies, + chapter: chapter, + ); + + ReaderControllerProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.chapter, + }) : super.internal(); + + final Chapter chapter; + + @override + void runNotifierBuild( + covariant ReaderController notifier, + ) { + return notifier.build( + chapter: chapter, + ); + } + + @override + Override overrideWith(ReaderController Function() create) { + return ProviderOverride( + origin: this, + override: ReaderControllerProvider._internal( + () => create()..chapter = chapter, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + chapter: chapter, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement createElement() { + return _ReaderControllerProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ReaderControllerProvider && other.chapter == chapter; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, chapter.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ReaderControllerRef on AutoDisposeNotifierProviderRef { + /// The parameter `chapter` of this provider. + Chapter get chapter; +} + +class _ReaderControllerProviderElement + extends AutoDisposeNotifierProviderElement + with ReaderControllerRef { + _ReaderControllerProviderElement(super.provider); + + @override + Chapter get chapter => (origin as ReaderControllerProvider).chapter; +} // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index 82b25575..78303a34 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -155,13 +155,14 @@ class _MangaChapterPageGalleryState with TickerProviderStateMixin { late AnimationController _scaleAnimationController; late Animation _animation; - late ReaderController _readerController = ReaderController(chapter: chapter); + late ReaderController _readerController = + ref.read(readerControllerProvider(chapter: chapter).notifier); @override void dispose() { _readerController.setMangaHistoryUpdate(); _readerController.setPageIndex( - _geCurrentIndex(_uChapDataPreload[_currentIndex!].index!)); + _geCurrentIndex(_uChapDataPreload[_currentIndex!].index!), true); _rebuildDetail.close(); _doubleClickAnimationController.dispose(); _autoScroll.value = false; @@ -714,7 +715,8 @@ class _MangaChapterPageGalleryState void _readProgressListener() { _currentIndex = _itemPositionsListener.itemPositions.value.first.index; - + _readerController.setPageIndex( + _geCurrentIndex(_uChapDataPreload[_currentIndex!].index!), false); int pagesLength = _pageMode == PageMode.doublePage ? (_uChapDataPreload.length / 2).ceil() + 1 : _uChapDataPreload.length; @@ -723,8 +725,10 @@ class _MangaChapterPageGalleryState _uChapDataPreload[_currentIndex!].chapter!.id) { if (mounted) { setState(() { - _readerController = ReaderController( - chapter: _uChapDataPreload[_currentIndex!].chapter!); + _readerController = ref.read(readerControllerProvider( + chapter: _uChapDataPreload[_currentIndex!].chapter!) + .notifier); + chapter = _uChapDataPreload[_currentIndex!].chapter!; _chapterUrlModel = _uChapDataPreload[_currentIndex!].chapterUrlModel!; @@ -740,7 +744,8 @@ class _MangaChapterPageGalleryState pagesLength - 1) { _isBookmarked = _readerController.getChapterBookmarked(); try { - bool hasNextChapter = _readerController.getChapterIndex() != 0; + bool hasNextChapter = _readerController.getChapterIndex().$1 != 0; + final chapter = hasNextChapter ? _readerController.getNextChapter() : null; if (chapter != null) { @@ -840,12 +845,14 @@ class _MangaChapterPageGalleryState _precacheImages(index + i); _precacheImages(index - i); } - + _readerController.setPageIndex( + _geCurrentIndex(_uChapDataPreload[_currentIndex!].index!), false); if (_readerController.chapter.id != _uChapDataPreload[index].chapter!.id) { if (mounted) { setState(() { - _readerController = - ReaderController(chapter: _uChapDataPreload[index].chapter!); + _readerController = ref.read(readerControllerProvider( + chapter: _uChapDataPreload[_currentIndex!].chapter!) + .notifier); chapter = _uChapDataPreload[_currentIndex!].chapter!; _chapterUrlModel = _uChapDataPreload[index].chapterUrlModel!; }); @@ -860,14 +867,12 @@ class _MangaChapterPageGalleryState if (_uChapDataPreload[index].pageIndex! == _uChapDataPreload.length - 1) { _isBookmarked = _readerController.getChapterBookmarked(); try { - bool hasNextChapter = _readerController.getChapterIndex() != 0; + bool hasNextChapter = _readerController.getChapterIndex().$1 != 0; final chapter = hasNextChapter ? _readerController.getNextChapter() : null; if (chapter != null) { ref - .watch(getChapterPagesProvider( - chapter: chapter, - ).future) + .watch(getChapterPagesProvider(chapter: chapter).future) .then((value) { _preloadNextChapter(value, chapter); }); diff --git a/lib/modules/more/about/providers/check_for_update.dart b/lib/modules/more/about/providers/check_for_update.dart index 26476fa2..9da23c21 100644 --- a/lib/modules/more/about/providers/check_for_update.dart +++ b/lib/modules/more/about/providers/check_for_update.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; @@ -19,7 +20,7 @@ checkForUpdate(CheckForUpdateRef ref, BotToast.showText(text: l10n.searching_for_updates); } final info = await PackageInfo.fromPlatform(); - print(info.data); + log(info.data.toString()); final updateAvailable = await _checkUpdate(); if (compareVersions(info.version, updateAvailable.$1) < 0) { if (manualUpdate) { diff --git a/lib/modules/more/about/providers/check_for_update.g.dart b/lib/modules/more/about/providers/check_for_update.g.dart index 8b2c9739..4a889b1a 100644 --- a/lib/modules/more/about/providers/check_for_update.g.dart +++ b/lib/modules/more/about/providers/check_for_update.g.dart @@ -6,7 +6,7 @@ part of 'check_for_update.dart'; // RiverpodGenerator // ************************************************************************** -String _$checkForUpdateHash() => r'2c53166482e6584a6004d6788beb7d27d89bc67c'; +String _$checkForUpdateHash() => r'07a3b5c85180261e8040064974c668e4fe5dbfcc'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/modules/more/backup_and_restore/providers/restore.dart b/lib/modules/more/backup_and_restore/providers/restore.dart index 56aa004b..f46162b2 100644 --- a/lib/modules/more/backup_and_restore/providers/restore.dart +++ b/lib/modules/more/backup_and_restore/providers/restore.dart @@ -122,7 +122,6 @@ void doRestore(DoRestoreRef ref, if (extensionsPref != null) { isar.sourcePreferences.putAllSync(extensionsPref); } - print("object"); isar.settings.clearSync(); if (settings != null) { isar.settings.putAllSync(settings); @@ -134,7 +133,6 @@ void doRestore(DoRestoreRef ref, ref.invalidate(l10nLocaleStateProvider); }); } catch (e) { - print(e); botToast(e.toString()); } BotToast.showNotification( diff --git a/lib/modules/more/backup_and_restore/providers/restore.g.dart b/lib/modules/more/backup_and_restore/providers/restore.g.dart index 70422f1b..27896837 100644 --- a/lib/modules/more/backup_and_restore/providers/restore.g.dart +++ b/lib/modules/more/backup_and_restore/providers/restore.g.dart @@ -6,7 +6,7 @@ part of 'restore.dart'; // RiverpodGenerator // ************************************************************************** -String _$doRestoreHash() => r'91d54be97447d51073f214b1f51deffa0045b1d0'; +String _$doRestoreHash() => r'3c88ad8ba80c245a4b511961111f7ab79c0d330f'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/router/router.dart b/lib/router/router.dart index 830ce016..9af60c94 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -1,7 +1,5 @@ import 'dart:io'; - import 'package:bot_toast/bot_toast.dart'; -import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/source.dart'; @@ -490,7 +488,7 @@ class RouterNotifier extends ChangeNotifier { ]; } -dynamic transitionPage({required LocalKey key, required child}) { +Page transitionPage({required LocalKey key, required child}) { return Platform.isIOS ? CupertinoPage(key: key, child: child) : CustomTransition(child: child, key: key); diff --git a/lib/services/supports_latest.g.dart b/lib/services/supports_latest.g.dart index a10cc661..9bd306a9 100644 --- a/lib/services/supports_latest.g.dart +++ b/lib/services/supports_latest.g.dart @@ -6,7 +6,7 @@ part of 'supports_latest.dart'; // RiverpodGenerator // ************************************************************************** -String _$supportsLatestHash() => r'14d8db1a09da5467ba96f3d62a80fdd44d303b9c'; +String _$supportsLatestHash() => r'affdd0558a86dcdf8c40a9dba0fe6f4d053e9709'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/services/trackers/kitsu.dart b/lib/services/trackers/kitsu.dart index 6acc19cf..cda7ac40 100644 --- a/lib/services/trackers/kitsu.dart +++ b/lib/services/trackers/kitsu.dart @@ -37,7 +37,7 @@ class Kitsu extends _$Kitsu { } @override - void build({required int syncId, bool? isManga}) {} + void build({required int syncId, bool? isManga}) {} Future<(bool, String)> login(String username, String password) async { try { @@ -115,7 +115,7 @@ class Kitsu extends _$Kitsu { 'data': { 'type': 'libraryEntries', 'attributes': { - 'status': toKitsuStatusManga(track.status), + 'status': tokitsuStatusAnime(track.status), 'progress': track.lastChapterRead, }, 'relationships': { @@ -179,7 +179,7 @@ class Kitsu extends _$Kitsu { "type": "libraryEntries", "id": track.mediaId, "attributes": { - "status": toKitsuStatusManga(track.status), + "status": tokitsuStatusAnime(track.status), "progress": track.lastChapterRead, "ratingTwenty": _toKitsuScore(track.score!), "startedAt": _convertDate(track.startedReadingDate!), @@ -305,7 +305,7 @@ class Kitsu extends _$Kitsu { track.libraryId = int.parse(obj["id"]); track.syncId = syncId; track.trackingUrl = _mangaUrl(int.parse(obj["id"])); - track.status = _getKitsuTrackStatus(obj["attributes"]["status"]); + track.status = _getKitsuTrackStatusManga(obj["attributes"]["status"]); track.title = jsonResponse['included'][0]["attributes"]["canonicalTitle"]; track.totalChapter = @@ -344,7 +344,7 @@ class Kitsu extends _$Kitsu { jsonResponse['included'][0]["attributes"]["canonicalTitle"]; track.totalChapter = jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0; - track.status = _getKitsuTrackStatus(obj["attributes"]["status"]); + track.status = _getKitsuTrackStatusManga(obj["attributes"]["status"]); track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); track.lastChapterRead = obj["attributes"]["progress"]; track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]); @@ -374,7 +374,8 @@ class Kitsu extends _$Kitsu { track.libraryId = int.parse(data[0]["id"]); track.syncId = syncId; track.trackingUrl = _mangaUrl(int.parse(data[0]["id"])); - track.status = _getKitsuTrackStatus(data[0]["attributes"]["status"]); + track.status = + _getKitsuTrsackStatusAnime(data[0]["attributes"]["status"]); track.title = jsonResponse['included'][0]["attributes"]["canonicalTitle"]; track.totalChapter = @@ -409,7 +410,8 @@ class Kitsu extends _$Kitsu { track.libraryId = int.parse(data[0]["id"]); track.syncId = syncId; track.trackingUrl = _mangaUrl(int.parse(data[0]["id"])); - track.status = _getKitsuTrackStatus(data[0]["attributes"]["status"]); + track.status = + _getKitsuTrsackStatusAnime(data[0]["attributes"]["status"]); track.score = ((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); track.title = @@ -460,11 +462,22 @@ class Kitsu extends _$Kitsu { return track!.username!; } - TrackStatus _getKitsuTrackStatus(String status) { + TrackStatus _getKitsuTrsackStatusAnime(String status) { + return switch (status) { + "current" => TrackStatus.watching, + "completed" => TrackStatus.completed, + "on_hold" => TrackStatus.onHold, + "dropped" => TrackStatus.dropped, + _ => TrackStatus.planToWatch, + }; + } + + TrackStatus _getKitsuTrackStatusManga(String status) { return switch (status) { "current" => TrackStatus.reading, "completed" => TrackStatus.completed, "on_hold" => TrackStatus.onHold, + "dropped" => TrackStatus.dropped, _ => TrackStatus.planToRead, }; } diff --git a/lib/utils/chapter_recognition.dart b/lib/utils/chapter_recognition.dart new file mode 100644 index 00000000..efffd4cc --- /dev/null +++ b/lib/utils/chapter_recognition.dart @@ -0,0 +1,71 @@ +class ChapterRecognition { + final _numberPattern = r"([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?"; + + final _unwanted = + RegExp(r"\b(?:v|ver|vol|version|volume|season|s)[^a-z]?[0-9]+"); + + final _unwantedWhiteSpace = RegExp(r"\s(?=extra|special|omake)"); + + int parseChapterNumber(String mangaTitle, String chapterName) { + var name = chapterName.toLowerCase(); + + name = name.replaceAll(mangaTitle.toLowerCase(), "").trim(); + + name = name.replaceAll(',', '.').replaceAll('-', '.'); + + name = name.replaceAll(_unwantedWhiteSpace, ""); + + name = name.replaceAll(_unwanted, ""); + final numberPat = "*$_numberPattern"; + const ch = r"(?<=ch\.)"; + var match = RegExp("$ch $numberPat").firstMatch(name); + if (match != null) { + return _getChapterNumberFromMatch(match).toInt(); + } + + match = RegExp(_numberPattern).firstMatch(name); + if (match != null) { + return _getChapterNumberFromMatch(match).toInt(); + } + + return 0; + } + + double _getChapterNumberFromMatch(Match match) { + final initial = double.parse(match.group(1)!); + final subChapterDecimal = match.group(2); + final subChapterAlpha = match.group(3); + final addition = _checkForDecimal(subChapterDecimal, subChapterAlpha); + return initial + addition; + } + + double _checkForDecimal(String? decimal, String? alpha) { + if (decimal != null && decimal.isNotEmpty) { + return double.parse(decimal); + } + + if (alpha != null && alpha.isNotEmpty) { + if (alpha.contains("extra")) { + return 0.99; + } + if (alpha.contains("omake")) { + return 0.98; + } + if (alpha.contains("special")) { + return 0.97; + } + final trimmedAlpha = alpha.replaceFirst('.', ''); + if (trimmedAlpha.length == 1) { + return _parseAlphaPostFix(trimmedAlpha[0]); + } + } + + return 0.0; + } + + double _parseAlphaPostFix(String alpha) { + final number = alpha.codeUnitAt(0) - ('a'.codeUnitAt(0) - 1); + if (number >= 10) return 0.0; + return number / 10.0; + } +}