mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-21 07:41:58 +00:00
Merge pull request #497 from Schnitzel5/enhance/two-way-tracker
improved two-way track screen
This commit is contained in:
commit
73e029aad0
37 changed files with 1299 additions and 541 deletions
|
|
@ -477,5 +477,6 @@
|
|||
"hwdec": "Hardware Decoder",
|
||||
"track_library_add": "Add to local library",
|
||||
"track_library_add_confirm": "Add tracked item to local library",
|
||||
"track_library_not_logged": "Login to the corresponding tracker to use this feature!"
|
||||
"track_library_not_logged": "Login to the corresponding tracker to use this feature!",
|
||||
"track_library_switch": "Switch to another tracker"
|
||||
}
|
||||
|
|
@ -2987,6 +2987,12 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Login to the corresponding tracker to use this feature!'**
|
||||
String get track_library_not_logged;
|
||||
|
||||
/// No description provided for @track_library_switch.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Switch to another tracker'**
|
||||
String get track_library_switch;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1527,4 +1527,7 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1538,4 +1538,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1527,4 +1527,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1544,6 +1544,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
||||
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).
|
||||
|
|
|
|||
|
|
@ -1549,4 +1549,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1533,4 +1533,7 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1543,4 +1543,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1541,6 +1541,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
||||
/// The translations for Portuguese, as used in Brazil (`pt_BR`).
|
||||
|
|
|
|||
|
|
@ -1543,4 +1543,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1528,4 +1528,7 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1534,4 +1534,7 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1496,4 +1496,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get track_library_not_logged =>
|
||||
'Login to the corresponding tracker to use this feature!';
|
||||
|
||||
@override
|
||||
String get track_library_switch => 'Switch to another tracker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -242,6 +242,8 @@ class Settings {
|
|||
|
||||
bool? clearChapterCacheOnAppLaunch;
|
||||
|
||||
String? lastTrackerLibraryLocation;
|
||||
|
||||
Settings({
|
||||
this.id = 227,
|
||||
this.displayType = DisplayType.compactGrid,
|
||||
|
|
@ -349,6 +351,7 @@ class Settings {
|
|||
this.mangaExtensionsRepo,
|
||||
this.animeExtensionsRepo,
|
||||
this.novelExtensionsRepo,
|
||||
this.lastTrackerLibraryLocation,
|
||||
});
|
||||
|
||||
Settings.fromJson(Map<String, dynamic> json) {
|
||||
|
|
@ -562,6 +565,7 @@ class Settings {
|
|||
.map((e) => Repo.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
lastTrackerLibraryLocation = json['lastTrackerLibraryLocation'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
@ -692,6 +696,7 @@ class Settings {
|
|||
'mangaExtensionsRepo': mangaExtensionsRepo?.map((e) => e.toJson()).toList(),
|
||||
'animeExtensionsRepo': animeExtensionsRepo?.map((e) => e.toJson()).toList(),
|
||||
'novelExtensionsRepo': novelExtensionsRepo?.map((e) => e.toJson()).toList(),
|
||||
'lastTrackerLibraryLocation': lastTrackerLibraryLocation,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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({
|
||||
|
|
@ -52,4 +73,38 @@ class TrackSearch {
|
|||
this.startDate = '',
|
||||
this.summary = '',
|
||||
});
|
||||
|
||||
TrackSearch.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
libraryId = json['libraryId'];
|
||||
mediaId = json['mediaId'];
|
||||
syncId = json['syncId'];
|
||||
title = json['title'];
|
||||
lastChapterRead = json['lastChapterRead'];
|
||||
totalChapter = json['totalChapter'];
|
||||
score = json['score'];
|
||||
status = json['status'];
|
||||
startedReadingDate = json['startedReadingDate'];
|
||||
finishedReadingDate = json['finishedReadingDate'];
|
||||
trackingUrl = json['trackingUrl'];
|
||||
coverUrl = json['coverUrl'];
|
||||
publishingStatus = json['publishingStatus'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'libraryId': libraryId,
|
||||
'mediaId': mediaId,
|
||||
'syncId': syncId,
|
||||
'title': title,
|
||||
'lastChapterRead': lastChapterRead,
|
||||
'totalChapter': totalChapter,
|
||||
'score': score,
|
||||
'status': status,
|
||||
'startedReadingDate': startedReadingDate,
|
||||
'finishedReadingDate': finishedReadingDate,
|
||||
'trackingUrl': trackingUrl,
|
||||
'coverUrl': coverUrl,
|
||||
'publishingStatus': publishingStatus,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
89
lib/models/track_search.g.dart
Normal file
89
lib/models/track_search.g.dart
Normal 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;
|
||||
}
|
||||
|
|
@ -329,39 +329,13 @@ class _MainScreenState extends ConsumerState<MainScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
if (dest.contains("/trackerLibrary/anilist")) {
|
||||
destinations[dest.indexOf(
|
||||
"/trackerLibrary/anilist",
|
||||
)] = NavigationRailDestination(
|
||||
if (dest.contains("/trackerLibrary")) {
|
||||
destinations[dest.indexOf("/trackerLibrary")] = NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.account_tree),
|
||||
icon: const Icon(Icons.account_tree_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text("AL"),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (dest.contains("/trackerLibrary/kitsu")) {
|
||||
destinations[dest.indexOf(
|
||||
"/trackerLibrary/kitsu",
|
||||
)] = NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.account_tree),
|
||||
icon: const Icon(Icons.account_tree_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text("Kitsu"),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (dest.contains("/trackerLibrary/mal")) {
|
||||
destinations[dest.indexOf(
|
||||
"/trackerLibrary/mal",
|
||||
)] = NavigationRailDestination(
|
||||
selectedIcon: const Icon(Icons.account_tree),
|
||||
icon: const Icon(Icons.account_tree_outlined),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Text("MAL"),
|
||||
child: Text(l10n.tracking),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -448,29 +422,11 @@ class _MainScreenState extends ConsumerState<MainScreen> {
|
|||
label: l10n.more,
|
||||
);
|
||||
}
|
||||
if (dest.contains("/trackerLibrary/anilist")) {
|
||||
destinations[dest.indexOf(
|
||||
"/trackerLibrary/anilist",
|
||||
)] = NavigationDestination(
|
||||
if (dest.contains("/trackerLibrary")) {
|
||||
destinations[dest.indexOf("/trackerLibrary")] = NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.account_tree),
|
||||
icon: const Icon(Icons.account_tree_outlined),
|
||||
label: "AL",
|
||||
);
|
||||
}
|
||||
if (dest.contains("/trackerLibrary/kitsu")) {
|
||||
destinations[dest.indexOf(
|
||||
"/trackerLibrary/kitsu",
|
||||
)] = NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.account_tree),
|
||||
icon: const Icon(Icons.account_tree_outlined),
|
||||
label: "Kitsu",
|
||||
);
|
||||
}
|
||||
if (dest.contains("/trackerLibrary/mal")) {
|
||||
destinations[dest.indexOf("/trackerLibrary/mal")] = NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.account_tree),
|
||||
icon: const Icon(Icons.account_tree_outlined),
|
||||
label: "MAL",
|
||||
label: l10n.tracking,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -592,9 +548,7 @@ class _TabletLayout extends StatelessWidget {
|
|||
'/updates',
|
||||
'/browse',
|
||||
'/more',
|
||||
'/trackerLibrary/anilist',
|
||||
'/trackerLibrary/kitsu',
|
||||
'/trackerLibrary/mal',
|
||||
'/trackerLibrary',
|
||||
};
|
||||
|
||||
return (location == null || validLocations.contains(location)) ? 100 : 0;
|
||||
|
|
@ -662,9 +616,7 @@ class _MobileBottomNavigation extends StatelessWidget {
|
|||
'/updates',
|
||||
'/browse',
|
||||
'/more',
|
||||
'/trackerLibrary/anilist',
|
||||
'/trackerLibrary/kitsu',
|
||||
'/trackerLibrary/mal',
|
||||
'/trackerLibrary',
|
||||
};
|
||||
|
||||
return (location == null || validLocations.contains(location)) ? null : 0;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/models/track.dart';
|
||||
import 'package:mangayomi/models/track_search.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
|
||||
import 'package:mangayomi/modules/tracker_library/tracker_library_screen.dart';
|
||||
import 'package:mangayomi/services/trackers/anilist.dart';
|
||||
import 'package:mangayomi/services/trackers/kitsu.dart';
|
||||
import 'package:mangayomi/services/trackers/myanimelist.dart';
|
||||
|
|
@ -156,3 +159,26 @@ class TrackState extends _$TrackState {
|
|||
return await tracker.fetchUserData(isManga: _isManga);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class LastTrackerLibraryLocationState
|
||||
extends _$LastTrackerLibraryLocationState {
|
||||
@override
|
||||
(int, bool) build() {
|
||||
final value = isar.settings.getSync(227)!.lastTrackerLibraryLocation;
|
||||
if (value != null) {
|
||||
final data = value.split(",");
|
||||
return (int.parse(data[0]), bool.parse(data[1]));
|
||||
}
|
||||
return (TrackerProviders.myAnimeList.syncId, false);
|
||||
}
|
||||
|
||||
void set((int, bool) value) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
final val = "${value.$1},${value.$2}";
|
||||
state = value;
|
||||
isar.writeTxnSync(
|
||||
() => isar.settings.putSync(settings!..lastTrackerLibraryLocation = val),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,5 +193,23 @@ class _TrackStateProviderElement
|
|||
@override
|
||||
ItemType? get itemType => (origin as TrackStateProvider).itemType;
|
||||
}
|
||||
|
||||
String _$lastTrackerLibraryLocationStateHash() =>
|
||||
r'f1e13ed88277e26123c2384fbfd4992678ad498a';
|
||||
|
||||
/// See also [LastTrackerLibraryLocationState].
|
||||
@ProviderFor(LastTrackerLibraryLocationState)
|
||||
final lastTrackerLibraryLocationStateProvider = AutoDisposeNotifierProvider<
|
||||
LastTrackerLibraryLocationState, (int, bool)>.internal(
|
||||
LastTrackerLibraryLocationState.new,
|
||||
name: r'lastTrackerLibraryLocationStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$lastTrackerLibraryLocationStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$LastTrackerLibraryLocationState = AutoDisposeNotifier<(int, bool)>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ class _RestoreKotatsuBackupProviderElement
|
|||
}
|
||||
|
||||
String _$restoreTachiBkBackupHash() =>
|
||||
r'76021dbcf0d576b50379f19f17e6a3ee8434942c';
|
||||
r'd46c9a68ea7227857abd1d5ea8dce62d1ea374ca';
|
||||
|
||||
/// See also [restoreTachiBkBackup].
|
||||
@ProviderFor(restoreTachiBkBackup)
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ final navigationItems = {
|
|||
"/history": "History",
|
||||
"/browse": "Browse",
|
||||
"/more": "More",
|
||||
"/trackerLibrary/anilist": "AL",
|
||||
"/trackerLibrary/kitsu": "Kitsu",
|
||||
"/trackerLibrary/mal": "MAL",
|
||||
"/trackerLibrary": "Tracking",
|
||||
};
|
||||
|
||||
class SettingsSection extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -157,9 +157,7 @@ class NavigationOrderState extends _$NavigationOrderState {
|
|||
'/history',
|
||||
'/browse',
|
||||
'/more',
|
||||
'/trackerLibrary/anilist',
|
||||
'/trackerLibrary/kitsu',
|
||||
'/trackerLibrary/mal',
|
||||
'/trackerLibrary',
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
@ -189,12 +187,7 @@ class NavigationOrderState extends _$NavigationOrderState {
|
|||
class HideItemsState extends _$HideItemsState {
|
||||
@override
|
||||
List<String> build() {
|
||||
return isar.settings.getSync(227)!.hideItems ??
|
||||
[
|
||||
'/trackerLibrary/anilist',
|
||||
'/trackerLibrary/kitsu',
|
||||
'/trackerLibrary/mal',
|
||||
];
|
||||
return isar.settings.getSync(227)!.hideItems ?? ['/trackerLibrary'];
|
||||
}
|
||||
|
||||
void set(List<String> values) {
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ final fullScreenReaderStateProvider =
|
|||
|
||||
typedef _$FullScreenReaderState = AutoDisposeNotifier<bool>;
|
||||
String _$navigationOrderStateHash() =>
|
||||
r'f300869743afaccfd47210115f341d25fec522bb';
|
||||
r'15ad6f31d8c5f3acd8cd6f4e4131e7536914cfc6';
|
||||
|
||||
/// See also [NavigationOrderState].
|
||||
@ProviderFor(NavigationOrderState)
|
||||
|
|
@ -174,7 +174,7 @@ final navigationOrderStateProvider =
|
|||
);
|
||||
|
||||
typedef _$NavigationOrderState = AutoDisposeNotifier<List<String>>;
|
||||
String _$hideItemsStateHash() => r'6844a05786f6c547a7cba261f742e82d871b6cb1';
|
||||
String _$hideItemsStateHash() => r'312605bf45a83d7628d9c1da0597bb151362052b';
|
||||
|
||||
/// See also [HideItemsState].
|
||||
@ProviderFor(HideItemsState)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
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';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/track.dart';
|
||||
import 'package:mangayomi/models/track_preference.dart';
|
||||
import 'package:mangayomi/models/track_search.dart';
|
||||
import 'package:mangayomi/modules/library/widgets/search_text_form_field.dart';
|
||||
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
|
||||
import 'package:mangayomi/modules/tracker_library/tracker_item_card.dart';
|
||||
import 'package:mangayomi/modules/widgets/bottom_text_widget.dart';
|
||||
|
|
@ -34,22 +39,21 @@ class TrackLibrarySection {
|
|||
String name;
|
||||
Future<List<TrackSearch>?> Function() func;
|
||||
ItemType itemType;
|
||||
int syncId;
|
||||
bool isSearch;
|
||||
|
||||
TrackLibrarySection({
|
||||
required this.name,
|
||||
required this.func,
|
||||
required this.syncId,
|
||||
this.itemType = ItemType.manga,
|
||||
this.isSearch = false,
|
||||
});
|
||||
}
|
||||
|
||||
class TrackerLibraryScreen extends ConsumerStatefulWidget {
|
||||
final TrackerProviders trackerProvider;
|
||||
final String? presetInput;
|
||||
const TrackerLibraryScreen({
|
||||
required this.trackerProvider,
|
||||
required this.presetInput,
|
||||
super.key,
|
||||
});
|
||||
const TrackerLibraryScreen({required this.presetInput, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<TrackerLibraryScreen> createState() =>
|
||||
|
|
@ -57,40 +61,139 @@ class TrackerLibraryScreen extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
|
||||
late final _textEditingController = TextEditingController();
|
||||
late String _query = "";
|
||||
late bool _isSearch = false;
|
||||
List<TrackLibrarySection> _sections = [];
|
||||
List<TrackPreference> _preferences = [];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final sections = switch (widget.trackerProvider.syncId) {
|
||||
1 => _sectionsMAL(),
|
||||
2 => _sectionsAL(),
|
||||
3 => _sectionsKitsu(),
|
||||
final lastLocation = ref.watch(lastTrackerLibraryLocationStateProvider);
|
||||
final trackerProvider =
|
||||
TrackerProviders.values.firstWhereOrNull(
|
||||
(t) => t.syncId == lastLocation.$1,
|
||||
) ??
|
||||
TrackerProviders.myAnimeList;
|
||||
final itemType = lastLocation.$2 ? ItemType.manga : ItemType.anime;
|
||||
_sections = switch (trackerProvider.syncId) {
|
||||
1 => _sectionsMAL(trackerProvider.syncId, itemType),
|
||||
2 => _sectionsAL(trackerProvider.syncId, itemType),
|
||||
3 => _sectionsKitsu(trackerProvider.syncId, itemType),
|
||||
_ => [],
|
||||
};
|
||||
if (_isSearch && _query.isNotEmpty) {
|
||||
_sections.insert(
|
||||
0,
|
||||
TrackLibrarySection(
|
||||
name: "Search results",
|
||||
syncId: trackerProvider.syncId,
|
||||
func: _fetchSearch(trackerProvider.syncId, _query, itemType),
|
||||
itemType: itemType,
|
||||
isSearch: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(widget.trackerProvider.name)),
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
"${trackerProvider.name} | ${itemType == ItemType.anime ? l10n.anime : l10n.manga}",
|
||||
),
|
||||
leading: !_isSearch ? null : Container(),
|
||||
actions: [
|
||||
_isSearch
|
||||
? SeachFormTextField(
|
||||
onFieldSubmitted: (submit) {
|
||||
setState(() {
|
||||
if (submit.isNotEmpty) {
|
||||
_query = submit;
|
||||
}
|
||||
});
|
||||
},
|
||||
onChanged: (value) {},
|
||||
onSuffixPressed: () {
|
||||
_query = "";
|
||||
_textEditingController.clear();
|
||||
setState(() {});
|
||||
},
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isSearch = false;
|
||||
_query = "";
|
||||
_textEditingController.clear();
|
||||
});
|
||||
},
|
||||
controller: _textEditingController,
|
||||
)
|
||||
: IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isSearch = true;
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.search, color: Theme.of(context).hintColor),
|
||||
),
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () async => await _resetData(trackerProvider, itemType),
|
||||
icon: Icon(
|
||||
Icons.refresh_outlined,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
_openSwitchProviderDialog(l10n);
|
||||
},
|
||||
icon: CircleAvatar(
|
||||
radius: 14,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: trackInfos(trackerProvider.syncId).$3,
|
||||
),
|
||||
width: 60,
|
||||
height: 70,
|
||||
child: Image.asset(
|
||||
trackInfos(trackerProvider.syncId).$1,
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
||||
child: StreamBuilder(
|
||||
stream: isar.trackPreferences
|
||||
.filter()
|
||||
.syncIdEqualTo(widget.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
|
||||
? SuperListView.builder(
|
||||
itemCount: sections.length,
|
||||
extentPrecalculationPolicy: SuperPrecalculationPolicy(),
|
||||
itemBuilder: (context, index) {
|
||||
final section = sections[index];
|
||||
return SizedBox(
|
||||
height: 260,
|
||||
child: TrackerSectionScreen(section: section),
|
||||
);
|
||||
_preferences = snapshot.hasData ? snapshot.data ?? [] : [];
|
||||
return _preferences.any((p) => p.syncId == trackerProvider.syncId)
|
||||
? RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _resetData(trackerProvider, itemType);
|
||||
},
|
||||
child: SuperListView.builder(
|
||||
itemCount: _sections.length,
|
||||
extentPrecalculationPolicy: SuperPrecalculationPolicy(),
|
||||
itemBuilder: (context, index) {
|
||||
final section = _sections[index];
|
||||
return SizedBox(
|
||||
height: 260,
|
||||
child: TrackerSectionScreen(
|
||||
key: ValueKey(section.name),
|
||||
section: section,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
@ -107,192 +210,384 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
List<TrackLibrarySection> _sectionsMAL() {
|
||||
return [
|
||||
TrackLibrarySection(
|
||||
name: "Airing Anime",
|
||||
func: _fetchGeneralData(ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
Future<void> _resetData(
|
||||
TrackerProviders trackerProvider,
|
||||
ItemType itemType,
|
||||
) async {
|
||||
final box = await Hive.openBox("tracker_library");
|
||||
final keys = box.keys.where(
|
||||
(e) => (e as String).startsWith(
|
||||
"${trackerProvider.syncId}-${itemType.name}-",
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Popular Anime",
|
||||
func: _fetchGeneralData(ItemType.anime, rankingType: "bypopularity"),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Upcoming Anime",
|
||||
func: _fetchGeneralData(ItemType.anime, rankingType: "upcoming"),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue watching",
|
||||
func: _fetchUserData(ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Popular Manga",
|
||||
func: _fetchGeneralData(ItemType.manga, rankingType: "bypopularity"),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Top Manga",
|
||||
func: _fetchGeneralData(ItemType.manga, rankingType: "manga"),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Top Manhwa",
|
||||
func: _fetchGeneralData(ItemType.manga, rankingType: "manhwa"),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Top Manhua",
|
||||
func: _fetchGeneralData(ItemType.manga, rankingType: "manhua"),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue reading",
|
||||
func: _fetchUserData(ItemType.manga),
|
||||
),
|
||||
];
|
||||
);
|
||||
await box.deleteAll(keys);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
List<TrackLibrarySection> _sectionsKitsu() {
|
||||
return [
|
||||
TrackLibrarySection(
|
||||
name: "Popular Anime",
|
||||
func: _fetchGeneralData(ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Anime",
|
||||
func: _fetchGeneralData(ItemType.anime, rankingType: "-updatedAt"),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Best Rated Anime",
|
||||
func: _fetchGeneralData(ItemType.anime, rankingType: "-averageRating"),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue watching",
|
||||
func: _fetchUserData(ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Popular Manga",
|
||||
func: _fetchGeneralData(ItemType.manga),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Manga",
|
||||
func: _fetchGeneralData(ItemType.manga, rankingType: "-updatedAt"),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Best Rated Manga",
|
||||
func: _fetchGeneralData(ItemType.manga, rankingType: "-averageRating"),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue reading",
|
||||
func: _fetchUserData(ItemType.manga),
|
||||
),
|
||||
];
|
||||
List<TrackLibrarySection> _sectionsMAL(int syncId, ItemType itemType) {
|
||||
return itemType == ItemType.anime
|
||||
? [
|
||||
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,
|
||||
rankingType: "bypopularity",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Upcoming Anime",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.anime,
|
||||
rankingType: "upcoming",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue watching",
|
||||
syncId: syncId,
|
||||
func: _fetchUserData(syncId, ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
]
|
||||
: [
|
||||
TrackLibrarySection(
|
||||
name: "Popular Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "bypopularity",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Top Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "manga",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Top Manhwa",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "manhwa",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Top Manhua",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "manhua",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue reading",
|
||||
syncId: syncId,
|
||||
func: _fetchUserData(syncId, ItemType.manga),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<TrackLibrarySection> _sectionsAL() {
|
||||
return [
|
||||
TrackLibrarySection(
|
||||
name: "Upcoming Anime",
|
||||
func: _fetchGeneralData(ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Popular Anime",
|
||||
func: _fetchGeneralData(
|
||||
ItemType.anime,
|
||||
rankingType: "sort: POPULARITY_DESC",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Trending Anime",
|
||||
func: _fetchGeneralData(
|
||||
ItemType.anime,
|
||||
rankingType: "sort: TRENDING_DESC",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Anime",
|
||||
func: _fetchGeneralData(
|
||||
ItemType.anime,
|
||||
rankingType:
|
||||
"sort: [UPDATED_AT_DESC, POPULARITY_DESC], status: RELEASING",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue watching",
|
||||
func: _fetchUserData(ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Upcoming Manga",
|
||||
func: _fetchGeneralData(ItemType.manga),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Popular Manga",
|
||||
func: _fetchGeneralData(
|
||||
ItemType.manga,
|
||||
rankingType: "sort: POPULARITY_DESC",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Trending Manga",
|
||||
func: _fetchGeneralData(
|
||||
ItemType.manga,
|
||||
rankingType: "sort: TRENDING_DESC",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Manga",
|
||||
func: _fetchGeneralData(
|
||||
ItemType.manga,
|
||||
rankingType:
|
||||
"sort: [UPDATED_AT_DESC, POPULARITY_DESC], status: RELEASING",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue reading",
|
||||
func: _fetchUserData(ItemType.manga),
|
||||
),
|
||||
];
|
||||
List<TrackLibrarySection> _sectionsKitsu(int syncId, ItemType itemType) {
|
||||
return itemType == ItemType.anime
|
||||
? [
|
||||
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,
|
||||
rankingType: "-updatedAt",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Best Rated Anime",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.anime,
|
||||
rankingType: "-averageRating",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue watching",
|
||||
syncId: syncId,
|
||||
func: _fetchUserData(syncId, ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
]
|
||||
: [
|
||||
TrackLibrarySection(
|
||||
name: "Popular Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(syncId, ItemType.manga),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "-updatedAt",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Best Rated Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "-averageRating",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue reading",
|
||||
syncId: syncId,
|
||||
func: _fetchUserData(syncId, ItemType.manga),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<TrackLibrarySection> _sectionsAL(int syncId, ItemType itemType) {
|
||||
return itemType == ItemType.anime
|
||||
? [
|
||||
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,
|
||||
rankingType: "sort: POPULARITY_DESC",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Trending Anime",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.anime,
|
||||
rankingType: "sort: TRENDING_DESC",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Anime",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.anime,
|
||||
rankingType:
|
||||
"sort: [UPDATED_AT_DESC, POPULARITY_DESC], status: RELEASING",
|
||||
),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue watching",
|
||||
syncId: syncId,
|
||||
func: _fetchUserData(syncId, ItemType.anime),
|
||||
itemType: ItemType.anime,
|
||||
),
|
||||
]
|
||||
: [
|
||||
TrackLibrarySection(
|
||||
name: "Upcoming Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(syncId, ItemType.manga),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Popular Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "sort: POPULARITY_DESC",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Trending Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType: "sort: TRENDING_DESC",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Latest Manga",
|
||||
syncId: syncId,
|
||||
func: _fetchGeneralData(
|
||||
syncId,
|
||||
ItemType.manga,
|
||||
rankingType:
|
||||
"sort: [UPDATED_AT_DESC, POPULARITY_DESC], status: RELEASING",
|
||||
),
|
||||
),
|
||||
TrackLibrarySection(
|
||||
name: "Continue reading",
|
||||
syncId: syncId,
|
||||
func: _fetchUserData(syncId, ItemType.manga),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>?> Function() _fetchSearch(
|
||||
int syncId,
|
||||
String query,
|
||||
ItemType itemType,
|
||||
) {
|
||||
return () async => await ref
|
||||
.read(
|
||||
trackStateProvider(
|
||||
track: Track(syncId: syncId, status: TrackStatus.completed),
|
||||
itemType: itemType,
|
||||
).notifier,
|
||||
)
|
||||
.search(query);
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>?> Function() _fetchGeneralData(
|
||||
int syncId,
|
||||
ItemType itemType, {
|
||||
String? rankingType,
|
||||
}) {
|
||||
return () async => await ref
|
||||
.read(
|
||||
trackStateProvider(
|
||||
track: Track(
|
||||
syncId: widget.trackerProvider.syncId,
|
||||
status: TrackStatus.completed,
|
||||
),
|
||||
track: Track(syncId: syncId, status: TrackStatus.completed),
|
||||
itemType: itemType,
|
||||
).notifier,
|
||||
)
|
||||
.fetchGeneralData(rankingType: rankingType);
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>?> Function() _fetchUserData(ItemType itemType) {
|
||||
Future<List<TrackSearch>?> Function() _fetchUserData(
|
||||
int syncId,
|
||||
ItemType itemType,
|
||||
) {
|
||||
return () async => await ref
|
||||
.read(
|
||||
trackStateProvider(
|
||||
track: Track(
|
||||
syncId: widget.trackerProvider.syncId,
|
||||
status: TrackStatus.completed,
|
||||
),
|
||||
track: Track(syncId: syncId, status: TrackStatus.completed),
|
||||
itemType: itemType,
|
||||
).notifier,
|
||||
)
|
||||
.fetchUserData();
|
||||
}
|
||||
|
||||
void _openSwitchProviderDialog(AppLocalizations l10n) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.track_library_switch),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_getListile(l10n, TrackerProviders.myAnimeList.syncId),
|
||||
_getListile(l10n, TrackerProviders.anilist.syncId),
|
||||
_getListile(l10n, TrackerProviders.kitsu.syncId),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
leading: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: trackInfos(syncId).$3,
|
||||
),
|
||||
width: 60,
|
||||
height: 70,
|
||||
child: isManga == null
|
||||
? Image.asset(trackInfos(syncId).$1, height: 30)
|
||||
: Icon(
|
||||
isManga ? Icons.collections_bookmark : Icons.video_collection,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
isManga == null
|
||||
? trackInfos(syncId).$2
|
||||
: isManga
|
||||
? l10n.manga
|
||||
: l10n.anime,
|
||||
),
|
||||
enabled: isLoggedIn,
|
||||
onTap: () {
|
||||
if (isManga == null) {
|
||||
context.pop();
|
||||
_openSwitchTypeDialog(l10n, syncId);
|
||||
} else {
|
||||
ref.read(lastTrackerLibraryLocationStateProvider.notifier).set((
|
||||
syncId,
|
||||
isManga,
|
||||
));
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TrackerSectionScreen extends StatefulWidget {
|
||||
|
|
@ -305,33 +600,20 @@ class TrackerSectionScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TrackerSectionScreenState extends State<TrackerSectionScreen> {
|
||||
String _errorMessage = "";
|
||||
bool _isLoading = true;
|
||||
List<TrackSearch> _tracks = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
_fetchData();
|
||||
}
|
||||
|
||||
String _errorMessage = "";
|
||||
bool _isLoading = true;
|
||||
List<TrackSearch> tracks = [];
|
||||
_init() async {
|
||||
try {
|
||||
_errorMessage = "";
|
||||
tracks = await widget.section.func() ?? [];
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_errorMessage = e.toString();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
@override
|
||||
void didUpdateWidget(covariant TrackerSectionScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_fetchData();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -352,15 +634,15 @@ class _TrackerSectionScreenState extends State<TrackerSectionScreen> {
|
|||
if (_errorMessage.isNotEmpty) {
|
||||
return Center(child: Text(_errorMessage));
|
||||
}
|
||||
if (tracks.isNotEmpty) {
|
||||
if (_tracks.isNotEmpty) {
|
||||
return SuperListView.builder(
|
||||
extentPrecalculationPolicy:
|
||||
SuperPrecalculationPolicy(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: tracks.length,
|
||||
itemCount: _tracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
return TrackerLibraryImageCard(
|
||||
track: tracks[index],
|
||||
track: _tracks[index],
|
||||
itemType: widget.section.itemType,
|
||||
);
|
||||
},
|
||||
|
|
@ -375,6 +657,39 @@ class _TrackerSectionScreenState extends State<TrackerSectionScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
_fetchData() async {
|
||||
final box = await Hive.openBox("tracker_library");
|
||||
final key =
|
||||
"${widget.section.syncId}-${widget.section.itemType.name}-${widget.section.name}";
|
||||
if (!widget.section.isSearch && 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;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_errorMessage = e.toString();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TrackerLibraryImageCard extends ConsumerStatefulWidget {
|
||||
|
|
@ -428,20 +743,52 @@ class _TrackerLibraryImageCardState
|
|||
}
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: cachedNetworkImage(
|
||||
imageUrl: toImgUrl(
|
||||
hasData
|
||||
? snapshot
|
||||
.data!
|
||||
.first
|
||||
.customCoverFromTracker ??
|
||||
snapshot.data!.first.imageUrl ??
|
||||
""
|
||||
: trackData.coverUrl ?? "",
|
||||
),
|
||||
width: 110,
|
||||
height: 150,
|
||||
fit: BoxFit.cover,
|
||||
child: Stack(
|
||||
children: [
|
||||
cachedNetworkImage(
|
||||
imageUrl: toImgUrl(
|
||||
hasData
|
||||
? snapshot
|
||||
.data!
|
||||
.first
|
||||
.customCoverFromTracker ??
|
||||
snapshot.data!.first.imageUrl ??
|
||||
""
|
||||
: trackData.coverUrl ?? "",
|
||||
),
|
||||
width: 110,
|
||||
height: 150,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
background: Paint()
|
||||
..color = Theme.of(context)
|
||||
.scaffoldBackgroundColor
|
||||
.withValues(alpha: 0.75)
|
||||
..strokeWidth = 20.0
|
||||
..strokeJoin = StrokeJoin.round
|
||||
..style = PaintingStyle.stroke,
|
||||
),
|
||||
children: [
|
||||
WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.star,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " ${trackData.score ?? "?"}",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -475,20 +822,6 @@ class _TrackerLibraryImageCardState
|
|||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.star, color: context.primaryColor),
|
||||
),
|
||||
TextSpan(text: " ${trackData.score ?? "?"}"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -125,25 +125,8 @@ class RouterNotifier extends ChangeNotifier {
|
|||
LibraryScreen(itemType: ItemType.novel, presetInput: id),
|
||||
),
|
||||
_genericRoute<String?>(
|
||||
name: "trackerLibrary/anilist",
|
||||
builder: (id) => TrackerLibraryScreen(
|
||||
trackerProvider: TrackerProviders.anilist,
|
||||
presetInput: id,
|
||||
),
|
||||
),
|
||||
_genericRoute<String?>(
|
||||
name: "trackerLibrary/kitsu",
|
||||
builder: (id) => TrackerLibraryScreen(
|
||||
trackerProvider: TrackerProviders.kitsu,
|
||||
presetInput: id,
|
||||
),
|
||||
),
|
||||
_genericRoute<String?>(
|
||||
name: "trackerLibrary/mal",
|
||||
builder: (id) => TrackerLibraryScreen(
|
||||
trackerProvider: TrackerProviders.myAnimeList,
|
||||
presetInput: id,
|
||||
),
|
||||
name: "trackerLibrary",
|
||||
builder: (id) => TrackerLibraryScreen(presetInput: id),
|
||||
),
|
||||
_genericRoute(name: "history", child: const HistoryScreen()),
|
||||
_genericRoute(name: "updates", child: const UpdatesScreen()),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'aniskip.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
|
||||
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
|
||||
|
||||
/// See also [AniSkip].
|
||||
@ProviderFor(AniSkip)
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ class Anilist extends _$Anilist {
|
|||
$contentUnit
|
||||
description
|
||||
startDate { year month day }
|
||||
averageScore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -160,6 +161,9 @@ class Anilist extends _$Anilist {
|
|||
).toString(),
|
||||
publishingType: "",
|
||||
publishingStatus: jsonRes['status'],
|
||||
score: jsonRes["averageScore"] != null
|
||||
? jsonRes["averageScore"] * 1.0
|
||||
: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
|
@ -291,7 +295,7 @@ class Anilist extends _$Anilist {
|
|||
'''
|
||||
query(\$id: Int!) {
|
||||
Page {
|
||||
mediaList(userId: \$id, type: $type) {
|
||||
mediaList(userId: \$id, type: $type, status: CURRENT) {
|
||||
id
|
||||
status
|
||||
scoreRaw: score(format: POINT_100)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'anilist.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$anilistHash() => r'6f84817b91a4d9cbea7beacbaf1a77cbdeac9290';
|
||||
String _$anilistHash() => r'83369d0c8ab5de9ed850b9318f005f6bc9981293';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -167,6 +167,9 @@ class Kitsu extends _$Kitsu {
|
|||
publishingStatus: jsonRes['endDate'] == null
|
||||
? "Publishing"
|
||||
: "Finished",
|
||||
score: jsonRes['averageRating'] is String
|
||||
? double.parse(jsonRes['averageRating'])
|
||||
: jsonRes['averageRating'],
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'kitsu.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$kitsuHash() => r'bcedbbc9460d79b4a026210261f63f77ec9958fd';
|
||||
String _$kitsuHash() => r'065624a456dee23e149b8296e04c060b1c3afb1e';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
final url = Uri.parse('$baseApiUrl/$item/$id').replace(
|
||||
queryParameters: {
|
||||
'fields':
|
||||
'id,title,synopsis,$contentUnit,main_picture,status,media_type,start_date',
|
||||
'id,title,synopsis,$contentUnit,main_picture,status,media_type,start_date,mean',
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -146,13 +146,13 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
publishingType: res["media_type"].toString().replaceAll("_", " "),
|
||||
publishingStatus: res["status"].toString().replaceAll("_", " "),
|
||||
trackingUrl: "https://myanimelist.net/$item/${res["id"]}",
|
||||
score: res["mean"],
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>> fetchGeneralData({
|
||||
bool isManga = true,
|
||||
String rankingType =
|
||||
"airing",
|
||||
String rankingType = "airing",
|
||||
}) async {
|
||||
final accessToken = await _getAccessToken();
|
||||
final item = isManga ? "manga" : "anime";
|
||||
|
|
@ -178,7 +178,9 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
totalChapter: e["node"][contentUnit],
|
||||
coverUrl: e["node"]["main_picture"]["large"] ?? "",
|
||||
title: e["node"]["title"],
|
||||
score: e["node"]["mean"],
|
||||
score: e["node"]["mean"] is double
|
||||
? e["node"]["mean"]
|
||||
: ((e["node"]["mean"] ?? 0) as int).toDouble(),
|
||||
startDate: e["node"]["start_date"] ?? "",
|
||||
publishingType: e["node"]["media_type"].toString().replaceAll(
|
||||
"_",
|
||||
|
|
@ -223,7 +225,7 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
title: e["node"]["title"],
|
||||
score: e["node"]["mean"] is double
|
||||
? e["node"]["mean"]
|
||||
: (e["node"]["mean"] as int).toDouble(),
|
||||
: ((e["node"]["mean"] ?? 0) as int).toDouble(),
|
||||
startDate: e["node"]["start_date"] ?? "",
|
||||
publishingType: e["node"]["media_type"].toString().replaceAll(
|
||||
"_",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'myanimelist.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$myAnimeListHash() => r'5e1144313509b9556bd0da4246f9fb22d00722a5';
|
||||
String _$myAnimeListHash() => r'885e6c50439c699f12c4f17c9e26b4a53b1d2036';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
32
pubspec.lock
32
pubspec.lock
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue