merged trackers together and added search

This commit is contained in:
Schnitzel5 2025-06-22 23:17:55 +02:00
parent 6088ac22f4
commit 92c167d585
28 changed files with 962 additions and 537 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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`).

View file

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

View file

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

View file

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

View file

@ -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`).

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,8 +2,11 @@
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: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';
@ -44,13 +47,8 @@ class TrackLibrarySection {
}
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() =>
@ -61,30 +59,40 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
late final _textEditingController = TextEditingController();
late String _query = "";
late bool _isSearch = false;
List<TrackLibrarySection> _sections = [];
@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(
_sections.insert(
0,
TrackLibrarySection(
name: "Search results",
func: _fetchGeneralData(ItemType.anime),
itemType: ItemType.anime,
func: _fetchSearch(trackerProvider.syncId, _query, itemType),
itemType: itemType,
),
);
}
return Scaffold(
appBar: AppBar(
title: Text(widget.trackerProvider.name),
title: Text(
"${trackerProvider.name} | ${itemType == ItemType.anime ? l10n.anime : l10n.manga}",
),
leading: !_isSearch ? null : Container(),
actions: [
_isSearch
@ -98,19 +106,15 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
},
onChanged: (value) {},
onSuffixPressed: () {
_textEditingController.clear();
_query = "";
_textEditingController.clear();
setState(() {});
},
onPressed: () {
setState(() {
if (_textEditingController.text.isEmpty) {
_isSearch = false;
_query = "";
_textEditingController.clear();
} else {
Navigator.pop(context);
}
_isSearch = false;
_query = "";
_textEditingController.clear();
});
},
controller: _textEditingController,
@ -127,25 +131,32 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
IconButton(
splashRadius: 20,
onPressed: () {
// open dialog to switch between trackers / item types
_openSwitchDialog(l10n);
},
icon: CircleAvatar(
radius: 14,
backgroundColor: Theme.of(context).primaryColorLight,
child: Image.asset(
"assets/trackers_icons/tracker_mal.webp",
height: 30,
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)
.syncIdEqualTo(trackerProvider.syncId)
.watch(fireImmediately: true),
builder: (context, snapshot) {
List<TrackPreference> entries = snapshot.hasData
@ -153,13 +164,16 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
: [];
return entries.isNotEmpty
? SuperListView.builder(
itemCount: sections.length,
itemCount: _sections.length,
extentPrecalculationPolicy: SuperPrecalculationPolicy(),
itemBuilder: (context, index) {
final section = sections[index];
final section = _sections[index];
return SizedBox(
height: 260,
child: TrackerSectionScreen(section: section),
child: TrackerSectionScreen(
key: Key(section.name),
section: section,
),
);
},
)
@ -178,192 +192,311 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
);
}
List<TrackLibrarySection> _sectionsMAL() {
return [
TrackLibrarySection(
name: "Airing Anime",
func: _fetchGeneralData(ItemType.anime),
itemType: ItemType.anime,
),
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),
),
];
List<TrackLibrarySection> _sectionsMAL(int syncId, ItemType itemType) {
return itemType == ItemType.anime
? [
TrackLibrarySection(
name: "Airing Anime",
func: _fetchGeneralData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Popular Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType: "bypopularity",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Upcoming Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType: "upcoming",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Continue watching",
func: _fetchUserData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
]
: [
TrackLibrarySection(
name: "Popular Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "bypopularity",
),
),
TrackLibrarySection(
name: "Top Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "manga",
),
),
TrackLibrarySection(
name: "Top Manhwa",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "manhwa",
),
),
TrackLibrarySection(
name: "Top Manhua",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "manhua",
),
),
TrackLibrarySection(
name: "Continue reading",
func: _fetchUserData(syncId, ItemType.manga),
),
];
}
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> _sectionsKitsu(int syncId, ItemType itemType) {
return itemType == ItemType.anime
? [
TrackLibrarySection(
name: "Popular Anime",
func: _fetchGeneralData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Latest Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType: "-updatedAt",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Best Rated Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType: "-averageRating",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Continue watching",
func: _fetchUserData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
]
: [
TrackLibrarySection(
name: "Popular Manga",
func: _fetchGeneralData(syncId, ItemType.manga),
),
TrackLibrarySection(
name: "Latest Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "-updatedAt",
),
),
TrackLibrarySection(
name: "Best Rated Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "-averageRating",
),
),
TrackLibrarySection(
name: "Continue reading",
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> _sectionsAL(int syncId, ItemType itemType) {
return itemType == ItemType.anime
? [
TrackLibrarySection(
name: "Upcoming Anime",
func: _fetchGeneralData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Popular Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType: "sort: POPULARITY_DESC",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Trending Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType: "sort: TRENDING_DESC",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Latest Anime",
func: _fetchGeneralData(
syncId,
ItemType.anime,
rankingType:
"sort: [UPDATED_AT_DESC, POPULARITY_DESC], status: RELEASING",
),
itemType: ItemType.anime,
),
TrackLibrarySection(
name: "Continue watching",
func: _fetchUserData(syncId, ItemType.anime),
itemType: ItemType.anime,
),
]
: [
TrackLibrarySection(
name: "Upcoming Manga",
func: _fetchGeneralData(syncId, ItemType.manga),
),
TrackLibrarySection(
name: "Popular Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "sort: POPULARITY_DESC",
),
),
TrackLibrarySection(
name: "Trending Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType: "sort: TRENDING_DESC",
),
),
TrackLibrarySection(
name: "Latest Manga",
func: _fetchGeneralData(
syncId,
ItemType.manga,
rankingType:
"sort: [UPDATED_AT_DESC, POPULARITY_DESC], status: RELEASING",
),
),
TrackLibrarySection(
name: "Continue reading",
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 _openSwitchDialog(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, 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),
],
),
),
);
},
);
}
Widget _getListile(AppLocalizations l10n, int syncId, bool isManga) {
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: Image.asset(trackInfos(syncId).$1, height: 30),
),
title: Text(
"${trackInfos(syncId).$2} | ${isManga ? l10n.manga : l10n.anime}",
),
onTap: () {
ref.read(lastTrackerLibraryLocationStateProvider.notifier).set((
syncId,
isManga,
));
context.pop();
},
),
);
}
}
class TrackerSectionScreen extends StatefulWidget {
@ -376,33 +509,14 @@ class TrackerSectionScreen extends StatefulWidget {
}
class _TrackerSectionScreenState extends State<TrackerSectionScreen> {
String _errorMessage = "";
bool _isLoading = true;
List<TrackSearch> _tracks = [];
@override
void initState() {
super.initState();
_init();
}
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;
}
_fetchData();
}
@override
@ -423,15 +537,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,
);
},
@ -446,6 +560,25 @@ class _TrackerSectionScreenState extends State<TrackerSectionScreen> {
),
);
}
_fetchData() async {
try {
_errorMessage = "";
_tracks = await widget.section.func() ?? [];
if (mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_errorMessage = e.toString();
_isLoading = false;
});
}
}
}
}
class TrackerLibraryImageCard extends ConsumerStatefulWidget {
@ -499,20 +632,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 ?? "?"}",
),
],
),
),
),
],
),
);
},
@ -546,20 +711,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 ?? "?"}"),
],
),
),
),
],
),
);

View file

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

View file

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

View file

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

View file

@ -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,6 +146,7 @@ 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"],
);
}