added Hive for caching

This commit is contained in:
Schnitzel5 2025-06-24 22:59:58 +02:00
parent 0801b2b88f
commit b01ae9d909
12 changed files with 258 additions and 30 deletions

View file

@ -9,12 +9,15 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/track_search.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -57,6 +60,8 @@ void main(List<String> args) async {
}
}
isar = await StorageProvider().initDB(null, inspector: kDebugMode);
await Hive.initFlutter();
Hive.registerAdapter(TrackSearchAdapter());
runApp(const ProviderScope(child: MyApp()));
unawaited(_postLaunchInit()); // Defer non-essential async operations

View file

@ -1,36 +1,57 @@
import 'package:hive/hive.dart';
part 'track_search.g.dart';
@HiveType(typeId: 0, adapterName: "TrackSearchAdapter")
class TrackSearch {
@HiveField(0)
int? id;
@HiveField(1)
int? libraryId;
@HiveField(2)
int? mediaId;
@HiveField(3)
int? syncId;
@HiveField(4)
String? title;
@HiveField(5)
int? lastChapterRead;
@HiveField(6)
int? totalChapter;
@HiveField(7)
double? score;
@HiveField(8)
String? status;
@HiveField(9)
int? startedReadingDate;
@HiveField(10)
int? finishedReadingDate;
@HiveField(11)
String? trackingUrl;
@HiveField(12)
String? coverUrl;
@HiveField(13)
String? summary;
@HiveField(14)
String? publishingStatus;
@HiveField(15)
String? publishingType;
@HiveField(16)
String? startDate;
TrackSearch({

View file

@ -0,0 +1,89 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'track_search.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class TrackSearchAdapter extends TypeAdapter<TrackSearch> {
@override
final int typeId = 0;
@override
TrackSearch read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return TrackSearch(
id: fields[0] as int?,
libraryId: fields[1] as int?,
mediaId: fields[2] as int?,
syncId: fields[3] as int?,
title: fields[4] as String?,
lastChapterRead: fields[5] as int?,
totalChapter: fields[6] as int?,
score: fields[7] as double?,
status: fields[8] as String?,
startedReadingDate: fields[9] as int?,
finishedReadingDate: fields[10] as int?,
trackingUrl: fields[11] as String?,
coverUrl: fields[12] as String?,
publishingStatus: fields[14] as String?,
publishingType: fields[15] as String?,
startDate: fields[16] as String?,
summary: fields[13] as String?,
);
}
@override
void write(BinaryWriter writer, TrackSearch obj) {
writer
..writeByte(17)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.libraryId)
..writeByte(2)
..write(obj.mediaId)
..writeByte(3)
..write(obj.syncId)
..writeByte(4)
..write(obj.title)
..writeByte(5)
..write(obj.lastChapterRead)
..writeByte(6)
..write(obj.totalChapter)
..writeByte(7)
..write(obj.score)
..writeByte(8)
..write(obj.status)
..writeByte(9)
..write(obj.startedReadingDate)
..writeByte(10)
..write(obj.finishedReadingDate)
..writeByte(11)
..write(obj.trackingUrl)
..writeByte(12)
..write(obj.coverUrl)
..writeByte(13)
..write(obj.summary)
..writeByte(14)
..write(obj.publishingStatus)
..writeByte(15)
..write(obj.publishingType)
..writeByte(16)
..write(obj.startDate);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TrackSearchAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -28,6 +28,7 @@ import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_pr
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/router/router.dart';
import 'package:protobuf/protobuf.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'restore.g.dart';
@ -382,7 +383,10 @@ void restoreTachiBkBackup(Ref ref, String path, BackupType bkType) {
final content = GZipDecoder().decodeBytes(
InputFileStream(path).toUint8List(),
);
final backup = BackupMihon.fromBuffer(content);
final backup = BackupMihon.create();
backup.mergeFromCodedBufferReader(
CodedBufferReader(content, sizeLimit: 250 << 20),
);
List<Category> cats = [];
isar.writeTxnSync(() {
isar.categorys.clearSync();

View file

@ -452,7 +452,7 @@ class _RestoreKotatsuBackupProviderElement
}
String _$restoreTachiBkBackupHash() =>
r'76021dbcf0d576b50379f19f17e6a3ee8434942c';
r'd46c9a68ea7227857abd1d5ea8dce62d1ea374ca';
/// See also [restoreTachiBkBackup].
@ProviderFor(restoreTachiBkBackup)

View file

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_qjs/quickjs/ffi.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/l10n/generated/app_localizations.dart';
import 'package:mangayomi/main.dart';
@ -38,10 +39,12 @@ class TrackLibrarySection {
String name;
Future<List<TrackSearch>?> Function() func;
ItemType itemType;
int syncId;
TrackLibrarySection({
required this.name,
required this.func,
required this.syncId,
this.itemType = ItemType.manga,
});
}
@ -60,6 +63,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
late String _query = "";
late bool _isSearch = false;
List<TrackLibrarySection> _sections = [];
List<TrackPreference> _preferences = [];
@override
Widget build(BuildContext context) {
@ -82,6 +86,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
0,
TrackLibrarySection(
name: "Search results",
syncId: trackerProvider.syncId,
func: _fetchSearch(trackerProvider.syncId, _query, itemType),
itemType: itemType,
),
@ -131,7 +136,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
IconButton(
splashRadius: 20,
onPressed: () {
_openSwitchDialog(l10n);
_openSwitchProviderDialog(l10n);
},
icon: CircleAvatar(
radius: 14,
@ -154,15 +159,12 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
child: StreamBuilder(
stream: isar.trackPreferences
.filter()
.syncIdEqualTo(trackerProvider.syncId)
.watch(fireImmediately: true),
stream: isar.trackPreferences.filter().syncIdIsNotNull().watch(
fireImmediately: true,
),
builder: (context, snapshot) {
List<TrackPreference> entries = snapshot.hasData
? snapshot.data ?? []
: [];
return entries.isNotEmpty
_preferences = snapshot.hasData ? snapshot.data ?? [] : [];
return _preferences.any((p) => p.syncId == trackerProvider.syncId)
? SuperListView.builder(
itemCount: _sections.length,
extentPrecalculationPolicy: SuperPrecalculationPolicy(),
@ -197,11 +199,13 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
? [
TrackLibrarySection(
name: "Airing Anime",
syncId: syncId,
func: _fetchGeneralData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Popular Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -211,6 +215,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Upcoming Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -220,6 +225,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Continue watching",
syncId: syncId,
func: _fetchUserData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
@ -227,6 +233,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
: [
TrackLibrarySection(
name: "Popular Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -235,6 +242,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Top Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -243,6 +251,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Top Manhwa",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -251,6 +260,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Top Manhua",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -259,6 +269,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Continue reading",
syncId: syncId,
func: _fetchUserData(syncId, ItemType.manga),
),
];
@ -269,11 +280,13 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
? [
TrackLibrarySection(
name: "Popular Anime",
syncId: syncId,
func: _fetchGeneralData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Latest Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -283,6 +296,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Best Rated Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -292,6 +306,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Continue watching",
syncId: syncId,
func: _fetchUserData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
@ -299,10 +314,12 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
: [
TrackLibrarySection(
name: "Popular Manga",
syncId: syncId,
func: _fetchGeneralData(syncId, ItemType.manga),
),
TrackLibrarySection(
name: "Latest Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -311,6 +328,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Best Rated Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -319,6 +337,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Continue reading",
syncId: syncId,
func: _fetchUserData(syncId, ItemType.manga),
),
];
@ -329,11 +348,13 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
? [
TrackLibrarySection(
name: "Upcoming Anime",
syncId: syncId,
func: _fetchGeneralData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Popular Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -343,6 +364,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Trending Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -352,6 +374,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Latest Anime",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.anime,
@ -362,6 +385,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Continue watching",
syncId: syncId,
func: _fetchUserData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
@ -369,10 +393,12 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
: [
TrackLibrarySection(
name: "Upcoming Manga",
syncId: syncId,
func: _fetchGeneralData(syncId, ItemType.manga),
),
TrackLibrarySection(
name: "Popular Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -381,6 +407,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Trending Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -389,6 +416,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Latest Manga",
syncId: syncId,
func: _fetchGeneralData(
syncId,
ItemType.manga,
@ -398,6 +426,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
TrackLibrarySection(
name: "Continue reading",
syncId: syncId,
func: _fetchUserData(syncId, ItemType.manga),
),
];
@ -447,7 +476,7 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
.fetchUserData();
}
void _openSwitchDialog(AppLocalizations l10n) {
void _openSwitchProviderDialog(AppLocalizations l10n) {
showDialog(
context: context,
builder: (ctx) {
@ -456,12 +485,9 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
content: SingleChildScrollView(
child: Column(
children: [
_getListile(l10n, TrackerProviders.myAnimeList.syncId, true),
_getListile(l10n, TrackerProviders.myAnimeList.syncId, false),
_getListile(l10n, TrackerProviders.anilist.syncId, true),
_getListile(l10n, TrackerProviders.anilist.syncId, false),
_getListile(l10n, TrackerProviders.kitsu.syncId, true),
_getListile(l10n, TrackerProviders.kitsu.syncId, false),
_getListile(l10n, TrackerProviders.myAnimeList.syncId),
_getListile(l10n, TrackerProviders.anilist.syncId),
_getListile(l10n, TrackerProviders.kitsu.syncId),
],
),
),
@ -470,7 +496,27 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
);
}
Widget _getListile(AppLocalizations l10n, int syncId, bool isManga) {
void _openSwitchTypeDialog(AppLocalizations l10n, int syncId) {
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
title: Text(l10n.track_library_switch),
content: SingleChildScrollView(
child: Column(
children: [
_getListile(l10n, syncId, isManga: true),
_getListile(l10n, syncId, isManga: false),
],
),
),
);
},
);
}
Widget _getListile(AppLocalizations l10n, int syncId, {bool? isManga}) {
final isLoggedIn = _preferences.any((p) => p.syncId == syncId);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: ListTile(
@ -482,17 +528,32 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
),
width: 60,
height: 70,
child: Image.asset(trackInfos(syncId).$1, height: 30),
child: isManga == null
? Image.asset(trackInfos(syncId).$1, height: 30)
: Icon(
isManga ? Icons.collections_bookmark : Icons.video_collection,
size: 30,
),
),
title: Text(
"${trackInfos(syncId).$2} | ${isManga ? l10n.manga : l10n.anime}",
isManga == null
? trackInfos(syncId).$2
: isManga
? l10n.manga
: l10n.anime,
),
enabled: isLoggedIn,
onTap: () {
ref.read(lastTrackerLibraryLocationStateProvider.notifier).set((
syncId,
isManga,
));
context.pop();
if (isManga == null) {
context.pop();
_openSwitchTypeDialog(l10n, syncId);
} else {
ref.read(lastTrackerLibraryLocationStateProvider.notifier).set((
syncId,
isManga,
));
context.pop();
}
},
),
);
@ -562,9 +623,22 @@ class _TrackerSectionScreenState extends State<TrackerSectionScreen> {
}
_fetchData() async {
final box = await Hive.openBox("tracker_library");
final key = "${widget.section.syncId}-${widget.section.name}";
if (box.containsKey(key)) {
_errorMessage = "";
_tracks = box.get(key);
if (mounted) {
setState(() {
_isLoading = false;
});
}
return;
}
try {
_errorMessage = "";
_tracks = await widget.section.func() ?? [];
box.put(key, _tracks);
if (mounted) {
setState(() {
_isLoading = false;

View file

@ -6,7 +6,7 @@ part of 'aniskip.dart';
// RiverpodGenerator
// **************************************************************************
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
/// See also [AniSkip].
@ProviderFor(AniSkip)

View file

@ -6,7 +6,7 @@ part of 'anilist.dart';
// RiverpodGenerator
// **************************************************************************
String _$anilistHash() => r'6f84817b91a4d9cbea7beacbaf1a77cbdeac9290';
String _$anilistHash() => r'83369d0c8ab5de9ed850b9318f005f6bc9981293';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -6,7 +6,7 @@ part of 'kitsu.dart';
// RiverpodGenerator
// **************************************************************************
String _$kitsuHash() => r'bcedbbc9460d79b4a026210261f63f77ec9958fd';
String _$kitsuHash() => r'065624a456dee23e149b8296e04c060b1c3afb1e';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -6,7 +6,7 @@ part of 'myanimelist.dart';
// RiverpodGenerator
// **************************************************************************
String _$myAnimeListHash() => r'5e1144313509b9556bd0da4246f9fb22d00722a5';
String _$myAnimeListHash() => r'885e6c50439c699f12c4f17c9e26b4a53b1d2036';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -846,6 +846,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
hive:
dependency: "direct main"
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
url: "https://pub.dev"
source: hosted
version: "1.1.0"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
html:
dependency: "direct main"
description:
@ -1716,6 +1740,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev"
source: hosted
version: "1.3.5"
source_span:
dependency: transitive
description:

View file

@ -92,6 +92,8 @@ dependencies:
url: https://github.com/kodjodevf/epubx.dart.git
ref: dev
d4rt: 0.0.8
hive: ^2.2.3
hive_flutter: ^1.1.0
dependency_overrides:
ffi: ^2.1.3
@ -115,6 +117,7 @@ dev_dependencies:
freezed: ^2.0.0
inno_bundle: ^0.9.0
protoc_plugin: ^22.0.1
hive_generator: ^2.0.1
flutter:
uses-material-design: true