Merge pull request #80 from kodjodevf/feat/auto-tracker

feat: Automatic Tracking #71
This commit is contained in:
Moustapha Kodjo Amadou 2023-12-21 17:23:46 +01:00 committed by GitHub
commit 3cba49e47e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 696 additions and 318 deletions

View file

@ -154,7 +154,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
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<VideoPrefs?> _video = ValueNotifier(VideoPrefs(
@ -176,6 +177,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
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<bool> _isCompleted = ValueNotifier(false);
@ -208,10 +210,18 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
},
);
late final StreamSubscription<Duration> _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<AnimeStreamPage> {
@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();
}

View file

@ -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<String>? _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<Chapter> _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<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()
.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<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;
}
}

View file

@ -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<void> {
late final Chapter episode;
void build({
required Chapter episode,
});
}
/// See also [AnimeStreamController].
@ProviderFor(AnimeStreamController)
const animeStreamControllerProvider = AnimeStreamControllerFamily();
/// See also [AnimeStreamController].
class AnimeStreamControllerFamily extends Family<void> {
/// 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<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'animeStreamControllerProvider';
}
/// See also [AnimeStreamController].
class AnimeStreamControllerProvider
extends AutoDisposeNotifierProviderImpl<AnimeStreamController, void> {
/// 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<AnimeStreamController, void>
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<void> {
/// The parameter `episode` of this provider.
Chapter get episode;
}
class _AnimeStreamControllerProviderElement
extends AutoDisposeNotifierProviderElement<AnimeStreamController, void>
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

View file

@ -40,7 +40,6 @@ class _MangaReaderDetailState extends ConsumerState<MangaReaderDetail> {
bool _isLoading = true;
@override
Widget build(BuildContext context) {
print(widget.mangaId);
final manga =
ref.watch(getMangaDetailStreamProvider(mangaId: widget.mangaId));
return Scaffold(

View file

@ -50,6 +50,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
int _page = 1;
late int _selectedIndex = widget.isSearch ? 2 : 0;
List<dynamic> filters = [];
final List<MManga> _mangaList = [];
List<TypeMangaSelector> _types(BuildContext context) {
final l10n = l10nLocalizations(context)!;
return [
@ -140,6 +141,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
onChanged: (value) {},
onSuffixPressed: () {
_textEditingController.clear();
_mangaList.clear();
_query = "";
setState(() {});
},
@ -152,6 +154,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
_selectedIndex = 0;
_page = 1;
_textEditingController.clear();
_mangaList.clear();
} else {
Navigator.pop(context);
}
@ -207,6 +210,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
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<MangaHomeScreen> {
},
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<MangaHomeScreen> {
});
}
_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<MangaHomeScreen> {
_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<MangaHomeScreen> {
});
}
_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<MangaHomeScreen> {
_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<MangaHomeScreen> {
}
return MangaHomeImageCard(
isManga: widget.source.isManga ?? true,
manga: data.list[index],
manga: _mangaList[index],
source: widget.source,
);
},

View file

@ -23,7 +23,6 @@ pushMangaReaderView({
.isAddedEqualTo(true)
.findAllSync()
.isNotEmpty;
print(sourceExist);
if (sourceExist ||
useTestSourceCode ||
chapter.manga.value!.isLocalArchive!) {

View file

@ -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<ChapterPageIndex>? 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<ChapterPageIndex>? 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<String>? _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<Chapter> _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<Chapter> 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<Chapter>? 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<String>? _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;
}

View file

@ -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<void> {
late final Chapter chapter;
void build({
required Chapter chapter,
});
}
/// See also [ReaderController].
@ProviderFor(ReaderController)
const readerControllerProvider = ReaderControllerFamily();
/// See also [ReaderController].
class ReaderControllerFamily extends Family<void> {
/// 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<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'readerControllerProvider';
}
/// See also [ReaderController].
class ReaderControllerProvider
extends AutoDisposeNotifierProviderImpl<ReaderController, void> {
/// 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<ReaderController, void> 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<void> {
/// The parameter `chapter` of this provider.
Chapter get chapter;
}
class _ReaderControllerProviderElement
extends AutoDisposeNotifierProviderElement<ReaderController, void>
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

View file

@ -155,13 +155,14 @@ class _MangaChapterPageGalleryState
with TickerProviderStateMixin {
late AnimationController _scaleAnimationController;
late Animation<double> _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);
});

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@ part of 'restore.dart';
// RiverpodGenerator
// **************************************************************************
String _$doRestoreHash() => r'91d54be97447d51073f214b1f51deffa0045b1d0';
String _$doRestoreHash() => r'3c88ad8ba80c245a4b511961111f7ab79c0d330f';
/// Copied from Dart SDK
class _SystemHash {

View file

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

View file

@ -6,7 +6,7 @@ part of 'supports_latest.dart';
// RiverpodGenerator
// **************************************************************************
String _$supportsLatestHash() => r'14d8db1a09da5467ba96f3d62a80fdd44d303b9c';
String _$supportsLatestHash() => r'affdd0558a86dcdf8c40a9dba0fe6f4d053e9709';
/// Copied from Dart SDK
class _SystemHash {

View file

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

View file

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