feat : Kitsu tracker
This commit is contained in:
parent
7e534bd013
commit
48f8e319c8
16 changed files with 1002 additions and 71 deletions
BIN
assets/trackers_icons/tracker_kitsu.webp
Normal file
BIN
assets/trackers_icons/tracker_kitsu.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
|
|
@ -329,7 +329,8 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
false),
|
||||
BridgeParameter(
|
||||
'prefix',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string),
|
||||
nullable: true),
|
||||
false),
|
||||
]),
|
||||
),
|
||||
|
|
@ -721,6 +722,18 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
false),
|
||||
]),
|
||||
),
|
||||
'base64': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'string',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
false),
|
||||
BridgeParameter('type',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), false),
|
||||
]),
|
||||
),
|
||||
'regExp': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)),
|
||||
|
|
@ -774,6 +787,12 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
return $Future.wrap(MBridge.http(args[0]!.$reified, args[1]!.$reified)
|
||||
.then((value) => $String(value)));
|
||||
}),
|
||||
'base64' => $Function((_, __, List<$Value?> args) {
|
||||
final result = args[1]!.$reified == 0
|
||||
? utf8.decode(base64Url.decode(args[0]!.$reified))
|
||||
: base64Url.encode(utf8.encode(args[0]!.$reified));
|
||||
return $String(result);
|
||||
}),
|
||||
"cryptoHandler" => $Function((_, __, List<$Value?> args) {
|
||||
return $String(MBridge.cryptoHandler(args[0]!.$value, args[1]!.$value,
|
||||
args[2]!.$value, args[3]!.$value));
|
||||
|
|
@ -842,7 +861,7 @@ class $MProvider extends MProvider with $Bridge<MProvider> {
|
|||
$List.wrap(value.map((e) => _toMVideo(e)).toList())))),
|
||||
"yourUploadExtractor" => $Function((_, __, List<$Value?> args) =>
|
||||
$Future.wrap(MBridge.yourUploadExtractor(args[0]!.$value,
|
||||
args[1]?.$value, args[2]?.$value, args[3]?.$value)
|
||||
args[1]?.$value, args[2]?.$value, args[3]?.$value ?? "")
|
||||
.then((value) =>
|
||||
$List.wrap(value.map((e) => _toMVideo(e)).toList())))),
|
||||
"gogoCdnExtractor" => $Function((_, __, List<$Value?> args) =>
|
||||
|
|
|
|||
|
|
@ -500,6 +500,9 @@ class MBridge {
|
|||
//Get body
|
||||
final bodyMap = jsonDecode(datas)["body"] as Map?;
|
||||
|
||||
final useFormBuilder =
|
||||
(jsonDecode(datas)["useFormBuilder"] as bool?) ?? false;
|
||||
|
||||
final url = jsonDecode(datas)["url"] as String;
|
||||
//Convert body Map<dynamic,dynamic> to Map<String,String>
|
||||
Map<String, dynamic> body = {};
|
||||
|
|
@ -517,16 +520,28 @@ class MBridge {
|
|||
//Get the serie source
|
||||
final source = sourceId != null ? isar.sources.getSync(sourceId) : null;
|
||||
|
||||
var request = hp.Request(method, Uri.parse(url));
|
||||
if (useFormBuilder) {
|
||||
var request = hp.MultipartRequest(method, Uri.parse(url));
|
||||
if (bodyMap != null) {
|
||||
final fields = bodyMap
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
request.fields.addAll(fields);
|
||||
}
|
||||
request.headers.addAll(headers);
|
||||
|
||||
if (bodyMap != null) {
|
||||
request.body = json.encode(body);
|
||||
res = await request.send();
|
||||
} else {
|
||||
var request = hp.Request(method, Uri.parse(url));
|
||||
|
||||
if (bodyMap != null) {
|
||||
request.body = json.encode(body);
|
||||
}
|
||||
|
||||
request.headers.addAll(headers);
|
||||
|
||||
res = await request.send();
|
||||
}
|
||||
|
||||
request.headers.addAll(headers);
|
||||
|
||||
res = await request.send();
|
||||
|
||||
if (res.statusCode == 403 && (source?.hasCloudflare ?? false)) {
|
||||
return await cloudflareBypass(
|
||||
url: url, sourceId: source!.id.toString(), method: 0);
|
||||
|
|
|
|||
|
|
@ -228,5 +228,11 @@
|
|||
"automaic":"Automatic",
|
||||
"preferred_domain":"Preferred Domain",
|
||||
"load_more":"Load More",
|
||||
"cancel_all_for_this_series":"Cancel all for this series"
|
||||
"cancel_all_for_this_series":"Cancel all for this series",
|
||||
"login":"Login",
|
||||
"login_into":"Login into {tracker}",
|
||||
"email_adress":"Email Address",
|
||||
"password":"Password",
|
||||
"log_out_from":"Log out from {tracker}?",
|
||||
"log_out":"Log out"
|
||||
}
|
||||
|
|
@ -228,5 +228,11 @@
|
|||
"automaic":"Automatique",
|
||||
"preferred_domain":"Domaine préféré",
|
||||
"load_more":"Charger plus",
|
||||
"cancel_all_for_this_series":"Tout annuler pour cette serie"
|
||||
"cancel_all_for_this_series":"Tout annuler pour cette serie",
|
||||
"login":"Connexion",
|
||||
"login_into":"Connexion à {tracker}",
|
||||
"email_adress":"Adresse couriel",
|
||||
"password":"Mot de passe",
|
||||
"log_out_from":"Se déconnecter de {tracker} ?",
|
||||
"log_out":"Se déconnecter"
|
||||
}
|
||||
|
|
@ -15,4 +15,8 @@ class Track {
|
|||
String? label;
|
||||
|
||||
Track({this.file, this.label});
|
||||
Track.fromJson(Map<String, dynamic> json) {
|
||||
file = json['file'];
|
||||
label = json['label'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'extension_preferences_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getMirrorPrefHash() => r'b56f6cf8dcb17279b2945c9233711182380dd0c5';
|
||||
String _$getMirrorPrefHash() => r'9b39c2c0e06ce4efda23dfd078d4ff96c1441f53';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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/services/trackers/anilist.dart';
|
||||
import 'package:mangayomi/services/trackers/kitsu.dart';
|
||||
import 'package:mangayomi/services/trackers/myanimelist.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'track_state_providers.g.dart';
|
||||
|
|
@ -37,6 +38,16 @@ class TrackState extends _$TrackState {
|
|||
.read(anilistProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.updateLibAnime(track!);
|
||||
} else if (track!.syncId == 3) {
|
||||
updateTrack = isManga!
|
||||
? await ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.updateLibManga(track!)
|
||||
: await ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.updateLibAnime(track!);
|
||||
}
|
||||
|
||||
ref
|
||||
|
|
@ -46,7 +57,7 @@ class TrackState extends _$TrackState {
|
|||
|
||||
int getScoreMaxValue() {
|
||||
int? maxValue;
|
||||
if (track!.syncId == 1) {
|
||||
if (track!.syncId == 1 || track!.syncId == 3) {
|
||||
maxValue = 10;
|
||||
} else if (track!.syncId == 2) {
|
||||
maxValue = ref
|
||||
|
|
@ -59,8 +70,8 @@ class TrackState extends _$TrackState {
|
|||
}
|
||||
|
||||
String getTextMapper(String numberText) {
|
||||
if (track!.syncId == 1) {
|
||||
} else {
|
||||
if (track!.syncId == 1 || track!.syncId == 3) {
|
||||
} else if (track!.syncId == 2) {
|
||||
numberText = ref
|
||||
.read(anilistProvider(syncId: 2, isManga: isManga).notifier)
|
||||
.displayScore(int.parse(numberText));
|
||||
|
|
@ -70,7 +81,7 @@ class TrackState extends _$TrackState {
|
|||
|
||||
int getScoreStep() {
|
||||
int? step;
|
||||
if (track!.syncId == 1) {
|
||||
if (track!.syncId == 1 || track!.syncId == 3) {
|
||||
step = 1;
|
||||
} else if (track!.syncId == 2) {
|
||||
step = ref
|
||||
|
|
@ -84,14 +95,14 @@ class TrackState extends _$TrackState {
|
|||
|
||||
String displayScore(int score) {
|
||||
String? result;
|
||||
if (track!.syncId == 1) {
|
||||
if (track!.syncId == 1 || track!.syncId == 3) {
|
||||
result = score.toString();
|
||||
} else {
|
||||
} else if (track!.syncId == 2) {
|
||||
result = ref
|
||||
.read(anilistProvider(syncId: 2, isManga: isManga).notifier)
|
||||
.displayScore(score);
|
||||
}
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
Future setTrackSearch(
|
||||
|
|
@ -105,7 +116,7 @@ class TrackState extends _$TrackState {
|
|||
trackingUrl: trackSearch.trackingUrl,
|
||||
title: trackSearch.title,
|
||||
lastChapterRead: 0,
|
||||
totalChapter: 0,
|
||||
totalChapter: trackSearch.totalChapter,
|
||||
status: TrackStatus.planToRead,
|
||||
startedReadingDate: 0,
|
||||
finishedReadingDate: 0);
|
||||
|
|
@ -121,8 +132,7 @@ class TrackState extends _$TrackState {
|
|||
: await ref
|
||||
.read(anilistProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
.findLibAnime(track);
|
||||
if (findManga == null) {
|
||||
findManga = isManga!
|
||||
findManga ??= isManga!
|
||||
? await ref
|
||||
.read(
|
||||
anilistProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
|
|
@ -131,16 +141,14 @@ class TrackState extends _$TrackState {
|
|||
.read(
|
||||
anilistProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
.addLibAnime(track);
|
||||
findManga = isManga!
|
||||
? await ref
|
||||
.read(
|
||||
anilistProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
.findLibManga(track)
|
||||
: await ref
|
||||
.read(
|
||||
anilistProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
.findLibAnime(track);
|
||||
}
|
||||
} else if (syncId == 3) {
|
||||
findManga = isManga!
|
||||
? await ref
|
||||
.read(kitsuProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
.addLibManga(track)
|
||||
: await ref
|
||||
.read(kitsuProvider(syncId: syncId, isManga: isManga).notifier)
|
||||
.addLibAnime(track);
|
||||
}
|
||||
ref
|
||||
.read(tracksProvider(syncId: syncId).notifier)
|
||||
|
|
@ -172,6 +180,16 @@ class TrackState extends _$TrackState {
|
|||
.read(anilistProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.aniListStatusListAnime;
|
||||
} else if (track!.syncId == 3) {
|
||||
statusList = isManga!
|
||||
? ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.kitsuStatusListManga
|
||||
: ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.kitsuStatusListAnime;
|
||||
}
|
||||
for (var element in TrackStatus.values) {
|
||||
if (statusList.contains(element)) {
|
||||
|
|
@ -198,6 +216,16 @@ class TrackState extends _$TrackState {
|
|||
.read(anilistProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.findLibAnime(track!);
|
||||
} else if (track!.syncId == 3) {
|
||||
findManga = isManga!
|
||||
? await ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.findLibManga(track!)
|
||||
: await ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.findLibAnime(track!);
|
||||
}
|
||||
return findManga;
|
||||
}
|
||||
|
|
@ -219,6 +247,16 @@ class TrackState extends _$TrackState {
|
|||
.read(anilistProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.searchAnime(query);
|
||||
} else if (track!.syncId == 3) {
|
||||
tracks = isManga!
|
||||
? await ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.search(query)
|
||||
: await ref
|
||||
.read(kitsuProvider(syncId: track!.syncId!, isManga: isManga)
|
||||
.notifier)
|
||||
.searchAnime(query);
|
||||
}
|
||||
return tracks;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$updateMangaDetailHash() => r'09b3b2a4e8fbfbcbbabf41ec5c098e2b837bb4e8';
|
||||
String _$updateMangaDetailHash() => r'3fc355e233b300a45fe79e594be8ee9845fb2b72';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'download_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$downloadChapterHash() => r'5d77345658ca2fbc718d74b9e7bb4c52c30e1eaa';
|
||||
String _$downloadChapterHash() => r'58a9d03fdd3e4c413a33e6b0797ef7d34e21a86e';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import 'package:mangayomi/models/track_preference.dart';
|
|||
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/services/trackers/anilist.dart';
|
||||
import 'package:mangayomi/services/trackers/kitsu.dart';
|
||||
import 'package:mangayomi/services/trackers/myanimelist.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
|
||||
class TrackScreen extends ConsumerWidget {
|
||||
const TrackScreen({super.key});
|
||||
|
|
@ -28,6 +30,20 @@ class TrackScreen extends ConsumerWidget {
|
|||
snapshot.hasData ? snapshot.data : [];
|
||||
return Column(
|
||||
children: [
|
||||
TrackListile(
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(anilistProvider(syncId: 2).notifier)
|
||||
.login();
|
||||
},
|
||||
id: 2,
|
||||
entries: entries!),
|
||||
TrackListile(
|
||||
onTap: () async {
|
||||
_showDialogLogin(context, ref);
|
||||
},
|
||||
id: 3,
|
||||
entries: entries),
|
||||
TrackListile(
|
||||
onTap: () async {
|
||||
await ref
|
||||
|
|
@ -36,14 +52,6 @@ class TrackScreen extends ConsumerWidget {
|
|||
.login();
|
||||
},
|
||||
id: 1,
|
||||
entries: entries!),
|
||||
TrackListile(
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(anilistProvider(syncId: 2).notifier)
|
||||
.login();
|
||||
},
|
||||
id: 2,
|
||||
entries: entries)
|
||||
],
|
||||
);
|
||||
|
|
@ -52,3 +60,120 @@ class TrackScreen extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showDialogLogin(BuildContext context, WidgetRef ref) {
|
||||
final passwordController = TextEditingController();
|
||||
final emailController = TextEditingController();
|
||||
String email = "";
|
||||
String password = "";
|
||||
String errorMessage = "";
|
||||
bool isLoading = false;
|
||||
bool obscureText = true;
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.login_into("Kitsu"),
|
||||
style: const TextStyle(fontSize: 30),
|
||||
),
|
||||
content: SizedBox(
|
||||
height: 300,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
controller: emailController,
|
||||
autofocus: true,
|
||||
onChanged: (value) => setState(() {
|
||||
email = value;
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.email_adress,
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide()))),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
controller: passwordController,
|
||||
obscureText: obscureText,
|
||||
onChanged: (value) => setState(() {
|
||||
password = value;
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.password,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => setState(() {
|
||||
obscureText = !obscureText;
|
||||
}),
|
||||
icon: Icon(obscureText
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined)),
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide()))),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(errorMessage, style: const TextStyle(color: Colors.red)),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: SizedBox(
|
||||
width: mediaWidth(context, 1),
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
final res = await ref
|
||||
.read(kitsuProvider(syncId: 3).notifier)
|
||||
.login(email, password);
|
||||
if (!res.$1) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
errorMessage = res.$2;
|
||||
});
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: Text(l10n.login))),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/models/track_preference.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/colors.dart';
|
||||
import 'package:mangayomi/utils/constant.dart';
|
||||
|
||||
class TrackListile extends ConsumerWidget {
|
||||
|
|
@ -20,6 +22,7 @@ class TrackListile extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bool isLogged =
|
||||
entries.where((element) => element.syncId == id).isNotEmpty;
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
leading: ClipRRect(
|
||||
|
|
@ -46,29 +49,41 @@ class TrackListile extends ConsumerWidget {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
"Log out from ${trackInfos(id).$2}",
|
||||
),
|
||||
title: Text(l10n.log_out_from(trackInfos(id).$2)),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: secondaryColor(context)),
|
||||
borderRadius:
|
||||
BorderRadius.circular(20))),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("Cancel")),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
TextButton(
|
||||
child: Text(l10n.cancel)),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Colors.red.withOpacity(0.7)),
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(tracksProvider(syncId: id).notifier)
|
||||
.logout();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("Log out")),
|
||||
child: Text(
|
||||
l10n.log_out,
|
||||
style:
|
||||
TextStyle(color: secondaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
|
|||
|
|
@ -96,24 +96,9 @@ class RapidCloudExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
class Tracks {
|
||||
String? file;
|
||||
String? label;
|
||||
|
||||
Tracks({
|
||||
this.file,
|
||||
this.label,
|
||||
});
|
||||
|
||||
Tracks.fromJson(Map<String, dynamic> json) {
|
||||
file = json['file'];
|
||||
label = json['label'];
|
||||
}
|
||||
}
|
||||
|
||||
class Data {
|
||||
String? sources;
|
||||
List<Tracks>? tracks;
|
||||
List<Track>? tracks;
|
||||
bool? encrypted;
|
||||
|
||||
Data({
|
||||
|
|
@ -125,9 +110,9 @@ class Data {
|
|||
Data.fromJson(Map<String, dynamic> json) {
|
||||
sources = json['sources'];
|
||||
if (json['tracks'] != null) {
|
||||
tracks = <Tracks>[];
|
||||
tracks = <Track>[];
|
||||
json['tracks'].forEach((v) {
|
||||
tracks!.add(Tracks.fromJson(v));
|
||||
tracks!.add(Track.fromJson(v));
|
||||
});
|
||||
}
|
||||
encrypted = json['encrypted'];
|
||||
|
|
|
|||
525
lib/services/trackers/kitsu.dart
Normal file
525
lib/services/trackers/kitsu.dart
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
import 'dart:developer';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||
import 'package:mangayomi/models/track.dart';
|
||||
import 'package:mangayomi/models/track_preference.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:mangayomi/models/track_search.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/myanimelist/model.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'kitsu.g.dart';
|
||||
|
||||
@riverpod
|
||||
class Kitsu extends _$Kitsu {
|
||||
final String _clientId =
|
||||
'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd';
|
||||
final String _clientSecret =
|
||||
'54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151';
|
||||
final String _baseUrl = 'https://kitsu.io/api/edge/';
|
||||
final String _loginUrl = 'https://kitsu.io/api/oauth/token';
|
||||
final String _algoliaKeyUrl = 'https://kitsu.io/api/edge/algolia-keys/media/';
|
||||
final String _algoliaUrl =
|
||||
'https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/';
|
||||
final String _algoliaAppId = 'AWQO5J657S';
|
||||
final String _algoliaFilter =
|
||||
'&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D';
|
||||
final String _algoliaFilterAnime =
|
||||
'&facetFilters=%5B%22kind%3Aanime%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22episodeCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D';
|
||||
|
||||
String _mangaUrl(int id) {
|
||||
return 'https://kitsu.io/manga/$id';
|
||||
}
|
||||
|
||||
String _animeUrl(int id) {
|
||||
return 'https://kitsu.io/anime/$id';
|
||||
}
|
||||
|
||||
@override
|
||||
build({required int syncId, bool? isManga}) {}
|
||||
|
||||
Future<(bool, String)> login(String username, String password) async {
|
||||
try {
|
||||
final request = http.MultipartRequest('POST', Uri.parse(_loginUrl));
|
||||
request.fields.addAll({
|
||||
'username': username,
|
||||
'password': password,
|
||||
'grant_type': 'password',
|
||||
'client_id': _clientId,
|
||||
'client_secret': _clientSecret,
|
||||
});
|
||||
final response = await request.send();
|
||||
if (response.statusCode != 200) {
|
||||
return (false, "${response.reasonPhrase!} ${response.statusCode}");
|
||||
}
|
||||
final res = jsonDecode(await response.stream.bytesToString())
|
||||
as Map<String, dynamic>;
|
||||
final aKOAuth = OAuth.fromJson(res);
|
||||
final currenUser = await _getCurrentUser(aKOAuth.accessToken!);
|
||||
ref.read(tracksProvider(syncId: syncId).notifier).login(TrackPreference(
|
||||
username: currenUser.$1,
|
||||
syncId: syncId,
|
||||
prefs: jsonEncode({"ratingSystem": currenUser.$2}),
|
||||
oAuth: jsonEncode(aKOAuth.toJson())));
|
||||
|
||||
return (true, "");
|
||||
} catch (e) {
|
||||
return (false, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Track?> addLibManga(Track track) async {
|
||||
final userId = _getUserId();
|
||||
final accessToken = _getAccesToken();
|
||||
var data = jsonEncode({
|
||||
'data': {
|
||||
'type': 'libraryEntries',
|
||||
'attributes': {
|
||||
'status': toKitsuStatusManga(track.status),
|
||||
'progress': track.lastChapterRead
|
||||
},
|
||||
'relationships': {
|
||||
'user': {
|
||||
'data': {'id': userId, 'type': 'users'},
|
||||
},
|
||||
'media': {
|
||||
'data': {'id': track.mediaId, 'type': 'manga'},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var response = await http.post(
|
||||
Uri.parse('${_baseUrl}library-entries'),
|
||||
headers: {
|
||||
'Content-Type': 'application/vnd.api+json',
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: data,
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
return await findLibManga(track);
|
||||
}
|
||||
|
||||
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
track.libraryId = int.parse(jsonData['data']['id']);
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<Track?> addLibAnime(Track track) async {
|
||||
final userId = _getUserId();
|
||||
log(track.mediaId.toString());
|
||||
final accessToken = _getAccesToken();
|
||||
var data = jsonEncode({
|
||||
'data': {
|
||||
'type': 'libraryEntries',
|
||||
'attributes': {
|
||||
'status': toKitsuStatusManga(track.status),
|
||||
'progress': track.lastChapterRead,
|
||||
},
|
||||
'relationships': {
|
||||
'user': {
|
||||
'data': {'id': userId, 'type': 'users'},
|
||||
},
|
||||
'media': {
|
||||
'data': {'id': track.mediaId, 'type': 'anime'},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var response = await http.post(
|
||||
Uri.parse('${_baseUrl}library-entries'),
|
||||
headers: {
|
||||
'Content-Type': 'application/vnd.api+json',
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: data,
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
return await findLibAnime(track);
|
||||
}
|
||||
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
track.libraryId = int.parse(jsonData['data']['id']);
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<Track> updateLibManga(Track track) async {
|
||||
final accessToken = _getAccesToken();
|
||||
final data = jsonEncode({
|
||||
"data": {
|
||||
"type": "libraryEntries",
|
||||
"id": track.mediaId,
|
||||
"attributes": {
|
||||
"status": toKitsuStatusManga(track.status),
|
||||
"progress": track.lastChapterRead,
|
||||
"ratingTwenty": _toKitsuScore(track.score!),
|
||||
"startedAt": _convertDate(track.startedReadingDate!),
|
||||
"finishedAt": _convertDate(track.finishedReadingDate!),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await http.patch(
|
||||
Uri.parse('$_baseUrl/library-entries/${track.mediaId}'),
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: data,
|
||||
);
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<Track> updateLibAnime(Track track) async {
|
||||
final accessToken = _getAccesToken();
|
||||
final data = jsonEncode({
|
||||
"data": {
|
||||
"type": "libraryEntries",
|
||||
"id": track.mediaId,
|
||||
"attributes": {
|
||||
"status": toKitsuStatusManga(track.status),
|
||||
"progress": track.lastChapterRead,
|
||||
"ratingTwenty": _toKitsuScore(track.score!),
|
||||
"startedAt": _convertDate(track.startedReadingDate!),
|
||||
"finishedAt": _convertDate(track.finishedReadingDate!),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await http.patch(
|
||||
Uri.parse('$_baseUrl/library-entries/${track.mediaId}'),
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: data,
|
||||
);
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>> search(String search) async {
|
||||
final accessToken = _getAccesToken();
|
||||
|
||||
final algoliaKeyResponse = await http.get(
|
||||
Uri.parse(_algoliaKeyUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
);
|
||||
final key = json.decode(algoliaKeyResponse.body)["media"]["key"];
|
||||
final response = await http.post(
|
||||
Uri.parse(_algoliaUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Algolia-Application-Id": _algoliaAppId,
|
||||
"X-Algolia-API-Key": key,
|
||||
},
|
||||
body: json.encode(
|
||||
{'params': 'query=${Uri.encodeComponent(search)}$_algoliaFilter'}),
|
||||
);
|
||||
final data = json.decode(response.body);
|
||||
|
||||
final entries = List<Map<String, dynamic>>.from(data['hits'])
|
||||
.where((element) => element["subtype"] != "novel")
|
||||
.toList();
|
||||
return entries
|
||||
.map((jsonRes) => TrackSearch(
|
||||
libraryId: jsonRes['id'],
|
||||
syncId: syncId,
|
||||
trackingUrl: _mangaUrl(jsonRes['id']),
|
||||
mediaId: jsonRes['id'],
|
||||
summary: jsonRes['synopsis'] ?? "",
|
||||
totalChapter: jsonRes['chapterCount'] ?? 0,
|
||||
coverUrl: jsonRes['posterImage']['original'] ?? "",
|
||||
title: jsonRes['canonicalTitle'],
|
||||
startDate: "",
|
||||
publishingType: jsonRes["subtype"] ?? "s",
|
||||
publishingStatus:
|
||||
jsonRes['endDate'] == null ? "Publishing" : "Finished"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>> searchAnime(String search) async {
|
||||
final accessToken = _getAccesToken();
|
||||
|
||||
final algoliaKeyResponse = await http.get(
|
||||
Uri.parse(_algoliaKeyUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
);
|
||||
final key = json.decode(algoliaKeyResponse.body)["media"]["key"];
|
||||
final response = await http.post(
|
||||
Uri.parse(_algoliaUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Algolia-Application-Id": _algoliaAppId,
|
||||
"X-Algolia-API-Key": key,
|
||||
},
|
||||
body: json.encode({
|
||||
'params': 'query=${Uri.encodeComponent(search)}$_algoliaFilterAnime'
|
||||
}),
|
||||
);
|
||||
final data = json.decode(response.body);
|
||||
|
||||
final entries = List<Map<String, dynamic>>.from(data['hits'])
|
||||
.where((element) => element["subtype"] != "novel")
|
||||
.toList();
|
||||
return entries
|
||||
.map((jsonRes) => TrackSearch(
|
||||
libraryId: jsonRes['id'],
|
||||
syncId: syncId,
|
||||
trackingUrl: _animeUrl(jsonRes['id']),
|
||||
mediaId: jsonRes['id'],
|
||||
summary: jsonRes['synopsis'] ?? "",
|
||||
totalChapter: jsonRes['episodeCount'] ?? 0,
|
||||
coverUrl: jsonRes['posterImage']['original'] ?? "",
|
||||
title: jsonRes['canonicalTitle'],
|
||||
startDate: "",
|
||||
publishingType: jsonRes["subtype"] ?? "",
|
||||
publishingStatus:
|
||||
jsonRes['endDate'] == null ? "Publishing" : "Finished"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Track?> getManga(Track track) async {
|
||||
final accessToken = _getAccesToken();
|
||||
final url = Uri.parse(
|
||||
'${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=manga');
|
||||
final response = await http.get(url, headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
});
|
||||
if (response.statusCode == 200) {
|
||||
final jsonResponse = jsonDecode(response.body);
|
||||
|
||||
final List<dynamic> data = jsonResponse['data'];
|
||||
|
||||
if (data.isNotEmpty) {
|
||||
final obj = data[0];
|
||||
track.mediaId = int.parse(obj["id"]);
|
||||
track.libraryId = int.parse(obj["id"]);
|
||||
track.syncId = syncId;
|
||||
track.trackingUrl = _mangaUrl(int.parse(obj["id"]));
|
||||
track.status = _getKitsuTrackStatus(obj["attributes"]["status"]);
|
||||
track.title =
|
||||
jsonResponse['included'][0]["attributes"]["canonicalTitle"];
|
||||
track.totalChapter =
|
||||
jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0;
|
||||
track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt();
|
||||
track.lastChapterRead = obj["attributes"]["progress"];
|
||||
track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]);
|
||||
track.finishedReadingDate = _parseDate(obj["attributes"]["finishedAt"]);
|
||||
return track;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Track?> findLibManga(Track track) async {
|
||||
final userId = _getUserId();
|
||||
final accessToken = _getAccesToken();
|
||||
final url = Uri.parse(
|
||||
'${_baseUrl}library-entries?filter[manga_id]=${track.mediaId}&filter[user_id]=$userId&include=manga');
|
||||
final response = await http.get(url, headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
});
|
||||
if (response.statusCode == 200) {
|
||||
final jsonResponse = jsonDecode(response.body);
|
||||
|
||||
final List<dynamic> data = jsonResponse['data'];
|
||||
|
||||
if (data.isNotEmpty) {
|
||||
final obj = data[0];
|
||||
track.mediaId = int.parse(obj["id"]);
|
||||
track.libraryId = int.parse(obj["id"]);
|
||||
track.syncId = syncId;
|
||||
track.trackingUrl = _mangaUrl(int.parse(obj["id"]));
|
||||
track.title =
|
||||
jsonResponse['included'][0]["attributes"]["canonicalTitle"];
|
||||
track.totalChapter =
|
||||
jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0;
|
||||
track.status = _getKitsuTrackStatus(obj["attributes"]["status"]);
|
||||
track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt();
|
||||
track.lastChapterRead = obj["attributes"]["progress"];
|
||||
track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]);
|
||||
track.finishedReadingDate = _parseDate(obj["attributes"]["finishedAt"]);
|
||||
return track;
|
||||
}
|
||||
}
|
||||
return await getManga(track);
|
||||
}
|
||||
|
||||
Future<Track?> findLibAnime(Track track) async {
|
||||
final userId = _getUserId();
|
||||
final accessToken = _getAccesToken();
|
||||
final url = Uri.parse(
|
||||
'${_baseUrl}library-entries?filter[anime_id]=${track.mediaId}&filter[user_id]=$userId&include=anime');
|
||||
final response = await http.get(url, headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final jsonResponse = jsonDecode(response.body);
|
||||
|
||||
final List<dynamic> data = jsonResponse['data'];
|
||||
if (data.isNotEmpty) {
|
||||
track.mediaId = int.parse(data[0]["id"]);
|
||||
track.libraryId = int.parse(data[0]["id"]);
|
||||
track.syncId = syncId;
|
||||
track.trackingUrl = _mangaUrl(int.parse(data[0]["id"]));
|
||||
track.status = _getKitsuTrackStatus(data[0]["attributes"]["status"]);
|
||||
track.title =
|
||||
jsonResponse['included'][0]["attributes"]["canonicalTitle"];
|
||||
track.totalChapter =
|
||||
jsonResponse['included'][0]["attributes"]["episodeCount"] ?? 0;
|
||||
track.score =
|
||||
((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt();
|
||||
track.lastChapterRead = data[0]["attributes"]["progress"];
|
||||
track.startedReadingDate =
|
||||
_parseDate(data[0]["attributes"]["startedAt"]);
|
||||
track.finishedReadingDate =
|
||||
_parseDate(data[0]["attributes"]["finishedAt"]);
|
||||
return track;
|
||||
}
|
||||
}
|
||||
return await getAnime(track);
|
||||
}
|
||||
|
||||
Future<Track?> getAnime(Track track) async {
|
||||
final accessToken = _getAccesToken();
|
||||
final url = Uri.parse(
|
||||
'${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=anime');
|
||||
final response = await http.get(url, headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
});
|
||||
if (response.statusCode == 200) {
|
||||
final jsonResponse = jsonDecode(response.body);
|
||||
|
||||
final List<dynamic> data = jsonResponse['data'];
|
||||
if (data.isNotEmpty) {
|
||||
track.mediaId = int.parse(data[0]["id"]);
|
||||
track.libraryId = int.parse(data[0]["id"]);
|
||||
track.syncId = syncId;
|
||||
track.trackingUrl = _mangaUrl(int.parse(data[0]["id"]));
|
||||
track.status = _getKitsuTrackStatus(data[0]["attributes"]["status"]);
|
||||
track.score =
|
||||
((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt();
|
||||
track.title =
|
||||
jsonResponse['included'][0]["attributes"]["canonicalTitle"];
|
||||
track.totalChapter =
|
||||
jsonResponse['included'][0]["attributes"]["episodeCount"] ?? 0;
|
||||
track.lastChapterRead = data[0]["attributes"]["progress"];
|
||||
track.startedReadingDate =
|
||||
_parseDate(data[0]["attributes"]["startedAt"]);
|
||||
track.finishedReadingDate =
|
||||
_parseDate(data[0]["attributes"]["finishedAt"]);
|
||||
return track;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<(String, String)> _getCurrentUser(String accessToken) async {
|
||||
final response = await http.get(
|
||||
Uri.parse("${_baseUrl}users?filter[self]=true"),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
);
|
||||
final data = json.decode(response.body)['data'][0];
|
||||
return (
|
||||
data['id'].toString(),
|
||||
data['attributes']['ratingSystem'].toString(),
|
||||
);
|
||||
}
|
||||
|
||||
String _getAccesToken() {
|
||||
final track = ref.watch(tracksProvider(syncId: syncId));
|
||||
final mAKOAuth =
|
||||
OAuth.fromJson(jsonDecode(track!.oAuth!) as Map<String, dynamic>);
|
||||
final expiresIn = DateTime.fromMillisecondsSinceEpoch(mAKOAuth.expiresIn!);
|
||||
if (DateTime.now().isAfter(expiresIn)) {
|
||||
ref.read(tracksProvider(syncId: syncId).notifier).logout();
|
||||
botToast("Anilist Token expired");
|
||||
throw Exception("Token expired");
|
||||
}
|
||||
return mAKOAuth.accessToken!;
|
||||
}
|
||||
|
||||
String _getUserId() {
|
||||
final track = ref.watch(tracksProvider(syncId: syncId));
|
||||
return json.decode(track!.prefs!)["userId"];
|
||||
}
|
||||
|
||||
TrackStatus _getKitsuTrackStatus(String status) {
|
||||
return switch (status) {
|
||||
"current" => TrackStatus.reading,
|
||||
"completed" => TrackStatus.completed,
|
||||
"on_hold" => TrackStatus.onHold,
|
||||
_ => TrackStatus.planToRead,
|
||||
};
|
||||
}
|
||||
|
||||
List<TrackStatus> kitsuStatusListManga = [
|
||||
TrackStatus.reading,
|
||||
TrackStatus.completed,
|
||||
TrackStatus.onHold,
|
||||
TrackStatus.dropped,
|
||||
TrackStatus.planToRead
|
||||
];
|
||||
List<TrackStatus> kitsuStatusListAnime = [
|
||||
TrackStatus.watching,
|
||||
TrackStatus.completed,
|
||||
TrackStatus.onHold,
|
||||
TrackStatus.dropped,
|
||||
TrackStatus.planToWatch
|
||||
];
|
||||
|
||||
String? toKitsuStatusManga(TrackStatus status) {
|
||||
return switch (status) {
|
||||
TrackStatus.reading => "current",
|
||||
TrackStatus.completed => "completed",
|
||||
TrackStatus.onHold => "on_hold",
|
||||
TrackStatus.dropped => "dropped",
|
||||
_ => "planned",
|
||||
};
|
||||
}
|
||||
|
||||
String? tokitsuStatusAnime(TrackStatus status) {
|
||||
return switch (status) {
|
||||
TrackStatus.watching => "current",
|
||||
TrackStatus.completed => "completed",
|
||||
TrackStatus.onHold => "on_hold",
|
||||
TrackStatus.dropped => "dropped",
|
||||
_ => "planned",
|
||||
};
|
||||
}
|
||||
|
||||
var formatter = DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "en");
|
||||
String? _convertDate(int dateValue) {
|
||||
if (dateValue == 0) return null;
|
||||
|
||||
return formatter.format(DateTime.fromMillisecondsSinceEpoch(dateValue));
|
||||
}
|
||||
|
||||
int _parseDate(String? date) {
|
||||
if (date == null) return 0;
|
||||
|
||||
var dateValue = formatter.parse(date);
|
||||
|
||||
return dateValue.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
String? _toKitsuScore(int score) {
|
||||
return score > 0 ? (score / 5).toString() : null;
|
||||
}
|
||||
}
|
||||
192
lib/services/trackers/kitsu.g.dart
Normal file
192
lib/services/trackers/kitsu.g.dart
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'kitsu.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$kitsuHash() => r'560c510503436a38bb1283c28978de2dcec7a367';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$Kitsu extends BuildlessAutoDisposeNotifier<dynamic> {
|
||||
late final int syncId;
|
||||
late final bool? isManga;
|
||||
|
||||
dynamic build({
|
||||
required int syncId,
|
||||
bool? isManga,
|
||||
});
|
||||
}
|
||||
|
||||
/// See also [Kitsu].
|
||||
@ProviderFor(Kitsu)
|
||||
const kitsuProvider = KitsuFamily();
|
||||
|
||||
/// See also [Kitsu].
|
||||
class KitsuFamily extends Family<dynamic> {
|
||||
/// See also [Kitsu].
|
||||
const KitsuFamily();
|
||||
|
||||
/// See also [Kitsu].
|
||||
KitsuProvider call({
|
||||
required int syncId,
|
||||
bool? isManga,
|
||||
}) {
|
||||
return KitsuProvider(
|
||||
syncId: syncId,
|
||||
isManga: isManga,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
KitsuProvider getProviderOverride(
|
||||
covariant KitsuProvider provider,
|
||||
) {
|
||||
return call(
|
||||
syncId: provider.syncId,
|
||||
isManga: provider.isManga,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'kitsuProvider';
|
||||
}
|
||||
|
||||
/// See also [Kitsu].
|
||||
class KitsuProvider extends AutoDisposeNotifierProviderImpl<Kitsu, dynamic> {
|
||||
/// See also [Kitsu].
|
||||
KitsuProvider({
|
||||
required int syncId,
|
||||
bool? isManga,
|
||||
}) : this._internal(
|
||||
() => Kitsu()
|
||||
..syncId = syncId
|
||||
..isManga = isManga,
|
||||
from: kitsuProvider,
|
||||
name: r'kitsuProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$kitsuHash,
|
||||
dependencies: KitsuFamily._dependencies,
|
||||
allTransitiveDependencies: KitsuFamily._allTransitiveDependencies,
|
||||
syncId: syncId,
|
||||
isManga: isManga,
|
||||
);
|
||||
|
||||
KitsuProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.syncId,
|
||||
required this.isManga,
|
||||
}) : super.internal();
|
||||
|
||||
final int syncId;
|
||||
final bool? isManga;
|
||||
|
||||
@override
|
||||
dynamic runNotifierBuild(
|
||||
covariant Kitsu notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
syncId: syncId,
|
||||
isManga: isManga,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(Kitsu Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: KitsuProvider._internal(
|
||||
() => create()
|
||||
..syncId = syncId
|
||||
..isManga = isManga,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
syncId: syncId,
|
||||
isManga: isManga,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<Kitsu, dynamic> createElement() {
|
||||
return _KitsuProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is KitsuProvider &&
|
||||
other.syncId == syncId &&
|
||||
other.isManga == isManga;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, syncId.hashCode);
|
||||
hash = _SystemHash.combine(hash, isManga.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin KitsuRef on AutoDisposeNotifierProviderRef<dynamic> {
|
||||
/// The parameter `syncId` of this provider.
|
||||
int get syncId;
|
||||
|
||||
/// The parameter `isManga` of this provider.
|
||||
bool? get isManga;
|
||||
}
|
||||
|
||||
class _KitsuProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<Kitsu, dynamic> with KitsuRef {
|
||||
_KitsuProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
int get syncId => (origin as KitsuProvider).syncId;
|
||||
@override
|
||||
bool? get isManga => (origin as KitsuProvider).isManga;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
@ -58,7 +58,8 @@ TrackStatus toTrackStatus(TrackStatus status, bool isManga, int syncId) {
|
|||
(String, String) trackInfos(int id) {
|
||||
return switch (id) {
|
||||
1 => ("assets/trackers_icons/tracker_mal.webp", "MyAnimeList"),
|
||||
_ => ("assets/trackers_icons/tracker_anilist.webp", "Anilist"),
|
||||
2 => ("assets/trackers_icons/tracker_anilist.webp", "Anilist"),
|
||||
_ => ("assets/trackers_icons/tracker_kitsu.webp", "Kitsu"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue