mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
Merge pull request #80 from kodjodevf/feat/auto-tracker
feat: Automatic Tracking #71
This commit is contained in:
commit
3cba49e47e
17 changed files with 696 additions and 318 deletions
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ pushMangaReaderView({
|
|||
.isAddedEqualTo(true)
|
||||
.findAllSync()
|
||||
.isNotEmpty;
|
||||
print(sourceExist);
|
||||
if (sourceExist ||
|
||||
useTestSourceCode ||
|
||||
chapter.manga.value!.isLocalArchive!) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'restore.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$doRestoreHash() => r'91d54be97447d51073f214b1f51deffa0045b1d0';
|
||||
String _$doRestoreHash() => r'3c88ad8ba80c245a4b511961111f7ab79c0d330f';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'supports_latest.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$supportsLatestHash() => r'14d8db1a09da5467ba96f3d62a80fdd44d303b9c';
|
||||
String _$supportsLatestHash() => r'affdd0558a86dcdf8c40a9dba0fe6f4d053e9709';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
71
lib/utils/chapter_recognition.dart
Normal file
71
lib/utils/chapter_recognition.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue