mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 23:22:07 +00:00
Feature : Added AniList tracker service
This commit is contained in:
parent
dc26b51a70
commit
fb71379cb9
19 changed files with 1436 additions and 385 deletions
BIN
assets/tracker_anilist.webp
Normal file
BIN
assets/tracker_anilist.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -6,6 +6,8 @@ part 'track.g.dart';
|
|||
class Track {
|
||||
Id? id;
|
||||
|
||||
int? libraryId;
|
||||
|
||||
int? mediaId;
|
||||
|
||||
int? mangaId;
|
||||
|
|
@ -14,7 +16,7 @@ class Track {
|
|||
|
||||
String? title;
|
||||
|
||||
int ?lastChapterRead;
|
||||
int? lastChapterRead;
|
||||
|
||||
int? totalChapter;
|
||||
|
||||
|
|
@ -31,6 +33,7 @@ class Track {
|
|||
|
||||
Track(
|
||||
{this.id = Isar.autoIncrement,
|
||||
this.libraryId,
|
||||
this.mediaId,
|
||||
this.mangaId,
|
||||
this.syncId,
|
||||
|
|
|
|||
|
|
@ -27,49 +27,54 @@ const TrackSchema = CollectionSchema(
|
|||
name: r'lastChapterRead',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'mangaId': PropertySchema(
|
||||
r'libraryId': PropertySchema(
|
||||
id: 2,
|
||||
name: r'libraryId',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'mangaId': PropertySchema(
|
||||
id: 3,
|
||||
name: r'mangaId',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'mediaId': PropertySchema(
|
||||
id: 3,
|
||||
id: 4,
|
||||
name: r'mediaId',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'score': PropertySchema(
|
||||
id: 4,
|
||||
id: 5,
|
||||
name: r'score',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'startedReadingDate': PropertySchema(
|
||||
id: 5,
|
||||
id: 6,
|
||||
name: r'startedReadingDate',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'status': PropertySchema(
|
||||
id: 6,
|
||||
id: 7,
|
||||
name: r'status',
|
||||
type: IsarType.byte,
|
||||
enumMap: _TrackstatusEnumValueMap,
|
||||
),
|
||||
r'syncId': PropertySchema(
|
||||
id: 7,
|
||||
id: 8,
|
||||
name: r'syncId',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'title': PropertySchema(
|
||||
id: 8,
|
||||
id: 9,
|
||||
name: r'title',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'totalChapter': PropertySchema(
|
||||
id: 9,
|
||||
id: 10,
|
||||
name: r'totalChapter',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'trackingUrl': PropertySchema(
|
||||
id: 10,
|
||||
id: 11,
|
||||
name: r'trackingUrl',
|
||||
type: IsarType.string,
|
||||
)
|
||||
|
|
@ -117,15 +122,16 @@ void _trackSerialize(
|
|||
) {
|
||||
writer.writeLong(offsets[0], object.finishedReadingDate);
|
||||
writer.writeLong(offsets[1], object.lastChapterRead);
|
||||
writer.writeLong(offsets[2], object.mangaId);
|
||||
writer.writeLong(offsets[3], object.mediaId);
|
||||
writer.writeLong(offsets[4], object.score);
|
||||
writer.writeLong(offsets[5], object.startedReadingDate);
|
||||
writer.writeByte(offsets[6], object.status.index);
|
||||
writer.writeLong(offsets[7], object.syncId);
|
||||
writer.writeString(offsets[8], object.title);
|
||||
writer.writeLong(offsets[9], object.totalChapter);
|
||||
writer.writeString(offsets[10], object.trackingUrl);
|
||||
writer.writeLong(offsets[2], object.libraryId);
|
||||
writer.writeLong(offsets[3], object.mangaId);
|
||||
writer.writeLong(offsets[4], object.mediaId);
|
||||
writer.writeLong(offsets[5], object.score);
|
||||
writer.writeLong(offsets[6], object.startedReadingDate);
|
||||
writer.writeByte(offsets[7], object.status.index);
|
||||
writer.writeLong(offsets[8], object.syncId);
|
||||
writer.writeString(offsets[9], object.title);
|
||||
writer.writeLong(offsets[10], object.totalChapter);
|
||||
writer.writeString(offsets[11], object.trackingUrl);
|
||||
}
|
||||
|
||||
Track _trackDeserialize(
|
||||
|
|
@ -138,16 +144,17 @@ Track _trackDeserialize(
|
|||
finishedReadingDate: reader.readLongOrNull(offsets[0]),
|
||||
id: id,
|
||||
lastChapterRead: reader.readLongOrNull(offsets[1]),
|
||||
mangaId: reader.readLongOrNull(offsets[2]),
|
||||
mediaId: reader.readLongOrNull(offsets[3]),
|
||||
score: reader.readLongOrNull(offsets[4]),
|
||||
startedReadingDate: reader.readLongOrNull(offsets[5]),
|
||||
status: _TrackstatusValueEnumMap[reader.readByteOrNull(offsets[6])] ??
|
||||
libraryId: reader.readLongOrNull(offsets[2]),
|
||||
mangaId: reader.readLongOrNull(offsets[3]),
|
||||
mediaId: reader.readLongOrNull(offsets[4]),
|
||||
score: reader.readLongOrNull(offsets[5]),
|
||||
startedReadingDate: reader.readLongOrNull(offsets[6]),
|
||||
status: _TrackstatusValueEnumMap[reader.readByteOrNull(offsets[7])] ??
|
||||
TrackStatus.reading,
|
||||
syncId: reader.readLongOrNull(offsets[7]),
|
||||
title: reader.readStringOrNull(offsets[8]),
|
||||
totalChapter: reader.readLongOrNull(offsets[9]),
|
||||
trackingUrl: reader.readStringOrNull(offsets[10]),
|
||||
syncId: reader.readLongOrNull(offsets[8]),
|
||||
title: reader.readStringOrNull(offsets[9]),
|
||||
totalChapter: reader.readLongOrNull(offsets[10]),
|
||||
trackingUrl: reader.readStringOrNull(offsets[11]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
|
@ -172,15 +179,17 @@ P _trackDeserializeProp<P>(
|
|||
case 5:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 6:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 7:
|
||||
return (_TrackstatusValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
TrackStatus.reading) as P;
|
||||
case 7:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 9:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 9:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 10:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 11:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
|
@ -501,6 +510,75 @@ extension TrackQueryFilter on QueryBuilder<Track, Track, QFilterCondition> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> libraryIdIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'libraryId',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> libraryIdIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'libraryId',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> libraryIdEqualTo(
|
||||
int? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'libraryId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> libraryIdGreaterThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'libraryId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> libraryIdLessThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'libraryId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> libraryIdBetween(
|
||||
int? lower,
|
||||
int? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'libraryId',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterFilterCondition> mangaIdIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
|
|
@ -1286,6 +1364,18 @@ extension TrackQuerySortBy on QueryBuilder<Track, Track, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterSortBy> sortByLibraryId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'libraryId', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterSortBy> sortByLibraryIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'libraryId', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterSortBy> sortByMangaId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'mangaId', Sort.asc);
|
||||
|
|
@ -1432,6 +1522,18 @@ extension TrackQuerySortThenBy on QueryBuilder<Track, Track, QSortThenBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterSortBy> thenByLibraryId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'libraryId', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterSortBy> thenByLibraryIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'libraryId', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QAfterSortBy> thenByMangaId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'mangaId', Sort.asc);
|
||||
|
|
@ -1554,6 +1656,12 @@ extension TrackQueryWhereDistinct on QueryBuilder<Track, Track, QDistinct> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QDistinct> distinctByLibraryId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'libraryId');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, Track, QDistinct> distinctByMangaId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'mangaId');
|
||||
|
|
@ -1630,6 +1738,12 @@ extension TrackQueryProperty on QueryBuilder<Track, Track, QQueryProperty> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, int?, QQueryOperations> libraryIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'libraryId');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Track, int?, QQueryOperations> mangaIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'mangaId');
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@ class TrackPreference {
|
|||
|
||||
String? oAuth;
|
||||
|
||||
String? prefs;
|
||||
|
||||
TrackPreference({
|
||||
this.syncId,
|
||||
this.username,
|
||||
this.oAuth,
|
||||
this.prefs,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,13 @@ const TrackPreferenceSchema = CollectionSchema(
|
|||
name: r'oAuth',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'username': PropertySchema(
|
||||
r'prefs': PropertySchema(
|
||||
id: 1,
|
||||
name: r'prefs',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'username': PropertySchema(
|
||||
id: 2,
|
||||
name: r'username',
|
||||
type: IsarType.string,
|
||||
)
|
||||
|
|
@ -54,6 +59,12 @@ int _trackPreferenceEstimateSize(
|
|||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
{
|
||||
final value = object.prefs;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
{
|
||||
final value = object.username;
|
||||
if (value != null) {
|
||||
|
|
@ -70,7 +81,8 @@ void _trackPreferenceSerialize(
|
|||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeString(offsets[0], object.oAuth);
|
||||
writer.writeString(offsets[1], object.username);
|
||||
writer.writeString(offsets[1], object.prefs);
|
||||
writer.writeString(offsets[2], object.username);
|
||||
}
|
||||
|
||||
TrackPreference _trackPreferenceDeserialize(
|
||||
|
|
@ -81,8 +93,9 @@ TrackPreference _trackPreferenceDeserialize(
|
|||
) {
|
||||
final object = TrackPreference(
|
||||
oAuth: reader.readStringOrNull(offsets[0]),
|
||||
prefs: reader.readStringOrNull(offsets[1]),
|
||||
syncId: id,
|
||||
username: reader.readStringOrNull(offsets[1]),
|
||||
username: reader.readStringOrNull(offsets[2]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
|
@ -98,6 +111,8 @@ P _trackPreferenceDeserializeProp<P>(
|
|||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 1:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 2:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
|
|
@ -352,6 +367,160 @@ extension TrackPreferenceQueryFilter
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'prefs',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'prefs',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'prefs',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'prefs',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'prefs',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsBetween(
|
||||
String? lower,
|
||||
String? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'prefs',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'prefs',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'prefs',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'prefs',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'prefs',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'prefs',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
prefsIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'prefs',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterFilterCondition>
|
||||
syncIdIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
|
@ -602,6 +771,19 @@ extension TrackPreferenceQuerySortBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterSortBy> sortByPrefs() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'prefs', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterSortBy>
|
||||
sortByPrefsDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'prefs', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterSortBy>
|
||||
sortByUsername() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
|
@ -632,6 +814,19 @@ extension TrackPreferenceQuerySortThenBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterSortBy> thenByPrefs() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'prefs', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterSortBy>
|
||||
thenByPrefsDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'prefs', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QAfterSortBy> thenBySyncId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncId', Sort.asc);
|
||||
|
|
@ -669,6 +864,13 @@ extension TrackPreferenceQueryWhereDistinct
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QDistinct> distinctByPrefs(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'prefs', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, TrackPreference, QDistinct> distinctByUsername(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
|
@ -691,6 +893,12 @@ extension TrackPreferenceQueryProperty
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, String?, QQueryOperations> prefsProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'prefs');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TrackPreference, String?, QQueryOperations> usernameProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'username');
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
class TrackSearch {
|
||||
int? id;
|
||||
|
||||
int? libraryId;
|
||||
|
||||
int? mediaId;
|
||||
|
||||
int? syncId;
|
||||
|
||||
String? title;
|
||||
|
||||
String? lastChapterRead;
|
||||
int? lastChapterRead;
|
||||
|
||||
int? totalChapter;
|
||||
|
||||
|
|
@ -33,6 +35,7 @@ class TrackSearch {
|
|||
|
||||
TrackSearch(
|
||||
{this.id,
|
||||
this.libraryId,
|
||||
this.mediaId,
|
||||
this.syncId,
|
||||
this.title,
|
||||
|
|
@ -43,9 +46,9 @@ class TrackSearch {
|
|||
this.startedReadingDate,
|
||||
this.finishedReadingDate,
|
||||
this.trackingUrl,
|
||||
this.coverUrl= '',
|
||||
this.publishingStatus= '',
|
||||
this.publishingType= '',
|
||||
this.startDate= '',
|
||||
this.summary= ''});
|
||||
this.coverUrl = '',
|
||||
this.publishingStatus = '',
|
||||
this.publishingType = '',
|
||||
this.startDate = '',
|
||||
this.summary = ''});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1616,7 +1616,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
maxHeight: mediaHeight(context, 0.9),
|
||||
minHeight: 80,
|
||||
child: Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: Padding(
|
||||
|
|
@ -1641,7 +1641,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
? TrackerWidget(
|
||||
mangaId: widget.manga!.id!,
|
||||
trackPreference: entries[index],
|
||||
trackRes: trackRes[index],
|
||||
trackRes: trackRes.first,
|
||||
)
|
||||
: TrackListile(
|
||||
onTap: () async {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
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/myanimelist.dart';
|
||||
import 'package:mangayomi/services/trackers/anilist.dart';
|
||||
import 'package:mangayomi/services/trackers/myanimelist.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'track_state_providers.g.dart';
|
||||
|
||||
|
|
@ -13,20 +14,76 @@ class TrackState extends _$TrackState {
|
|||
}
|
||||
|
||||
Future updateManga() async {
|
||||
final updateTrack = await ref
|
||||
.read(myAnimeListProvider(syncId: 1).notifier)
|
||||
.updateManga(track!);
|
||||
Track? updateTrack;
|
||||
if (track!.syncId == 1) {
|
||||
updateTrack = await ref
|
||||
.read(myAnimeListProvider(syncId: track!.syncId!).notifier)
|
||||
.updateManga(track!);
|
||||
} else if (track!.syncId == 2) {
|
||||
updateTrack = await ref
|
||||
.read(anilistProvider(syncId: track!.syncId!).notifier)
|
||||
.updateLibManga(track!);
|
||||
}
|
||||
|
||||
ref
|
||||
.read(tracksProvider(syncId: track!.syncId!).notifier)
|
||||
.updateTrackManga(updateTrack);
|
||||
.updateTrackManga(updateTrack!);
|
||||
}
|
||||
|
||||
int getScoreMaxValue() {
|
||||
int? maxValue;
|
||||
if (track!.syncId == 1) {
|
||||
maxValue = 10;
|
||||
} else if (track!.syncId == 2) {
|
||||
maxValue = ref
|
||||
.read(anilistProvider(syncId: track!.syncId!).notifier)
|
||||
.getScoreValue()
|
||||
.$1;
|
||||
}
|
||||
return maxValue!;
|
||||
}
|
||||
|
||||
String getTextMapper(String numberText) {
|
||||
if (track!.syncId == 1) {
|
||||
} else {
|
||||
numberText = ref
|
||||
.read(anilistProvider(syncId: 2).notifier)
|
||||
.displayScore(int.parse(numberText));
|
||||
}
|
||||
return numberText;
|
||||
}
|
||||
|
||||
int getScoreStep() {
|
||||
int? step;
|
||||
if (track!.syncId == 1) {
|
||||
step = 1;
|
||||
} else if (track!.syncId == 2) {
|
||||
step = ref
|
||||
.read(anilistProvider(syncId: track!.syncId!).notifier)
|
||||
.getScoreValue()
|
||||
.$2;
|
||||
}
|
||||
return step!;
|
||||
}
|
||||
|
||||
String displayScore(int score) {
|
||||
String? result;
|
||||
if (track!.syncId == 1) {
|
||||
result = score.toString();
|
||||
} else {
|
||||
result =
|
||||
ref.read(anilistProvider(syncId: 2).notifier).displayScore(score);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future setTrackSearch(
|
||||
TrackSearch trackSearch, int mangaId, int syncId) async {
|
||||
Track? findManga;
|
||||
final track = Track(
|
||||
mangaId: mangaId,
|
||||
score: 0,
|
||||
syncId: syncId,
|
||||
mediaId: trackSearch.mediaId,
|
||||
trackingUrl: trackSearch.trackingUrl,
|
||||
title: trackSearch.title,
|
||||
|
|
@ -35,13 +92,26 @@ class TrackState extends _$TrackState {
|
|||
status: TrackStatus.planToRead,
|
||||
startedReadingDate: 0,
|
||||
finishedReadingDate: 0);
|
||||
final findManga = await ref
|
||||
.read(myAnimeListProvider(syncId: 1).notifier)
|
||||
.findManga(track);
|
||||
|
||||
if (syncId == 1) {
|
||||
findManga = await ref
|
||||
.read(myAnimeListProvider(syncId: syncId).notifier)
|
||||
.findManga(track);
|
||||
} else if (syncId == 2) {
|
||||
findManga = findManga = await ref
|
||||
.read(anilistProvider(syncId: syncId).notifier)
|
||||
.findLibManga(track);
|
||||
if (findManga == null) {
|
||||
await ref
|
||||
.read(anilistProvider(syncId: syncId).notifier)
|
||||
.addLibManga(track);
|
||||
findManga = await ref
|
||||
.read(anilistProvider(syncId: syncId).notifier)
|
||||
.findLibManga(track);
|
||||
}
|
||||
}
|
||||
ref
|
||||
.read(tracksProvider(syncId: syncId).notifier)
|
||||
.updateTrackManga(findManga);
|
||||
.updateTrackManga(findManga!);
|
||||
}
|
||||
|
||||
List<TrackStatus> getStatusList() {
|
||||
|
|
@ -49,8 +119,12 @@ class TrackState extends _$TrackState {
|
|||
List<TrackStatus> list = [];
|
||||
if (track!.syncId == 1) {
|
||||
statusList = ref
|
||||
.read(myAnimeListProvider(syncId: 1).notifier)
|
||||
.read(myAnimeListProvider(syncId: track!.syncId!).notifier)
|
||||
.myAnimeListStatusList;
|
||||
} else if (track!.syncId == 2) {
|
||||
statusList = ref
|
||||
.read(anilistProvider(syncId: track!.syncId!).notifier)
|
||||
.aniListStatusList;
|
||||
}
|
||||
for (var element in TrackStatus.values) {
|
||||
if (statusList.contains(element)) {
|
||||
|
|
@ -64,8 +138,12 @@ class TrackState extends _$TrackState {
|
|||
Track? findManga;
|
||||
if (track!.syncId == 1) {
|
||||
findManga = await ref
|
||||
.read(myAnimeListProvider(syncId: 1).notifier)
|
||||
.read(myAnimeListProvider(syncId: track!.syncId!).notifier)
|
||||
.findManga(track!);
|
||||
} else if (track!.syncId == 2) {
|
||||
findManga = findManga = await ref
|
||||
.read(anilistProvider(syncId: track!.syncId!).notifier)
|
||||
.findLibManga(track!);
|
||||
}
|
||||
return findManga;
|
||||
}
|
||||
|
|
@ -76,6 +154,10 @@ class TrackState extends _$TrackState {
|
|||
tracks = await ref
|
||||
.read(myAnimeListProvider(syncId: track!.syncId!).notifier)
|
||||
.search(query);
|
||||
} else if (track!.syncId == 2) {
|
||||
tracks = await ref
|
||||
.read(anilistProvider(syncId: track!.syncId!).notifier)
|
||||
.search(query);
|
||||
}
|
||||
return tracks;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'track_state_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$trackStateHash() => r'493c91f74d44d2fcffd75bdbf3e434580fd5ac03';
|
||||
String _$trackStateHash() => r'7004528230213b458c7db417947eecfc700d44d7';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -46,327 +46,370 @@ class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
trackInfos(widget.trackPreference.syncId!).$1,
|
||||
height: 30,
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(
|
||||
context,
|
||||
onPressed: () async {
|
||||
final trackSearch = await trackersSearchraggableMenu(context,
|
||||
track: widget.trackRes) as TrackSearch?;
|
||||
if (trackSearch != null) {
|
||||
await ref
|
||||
.read(trackStateProvider(track: null).notifier)
|
||||
.setTrackSearch(trackSearch, widget.mangaId,
|
||||
widget.trackPreference.syncId!);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
widget.trackRes.title!,
|
||||
style: TextStyle(
|
||||
color: secondaryColor(context),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(20)),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
color: const Color.fromRGBO(18, 25, 35, 1),
|
||||
width: 70,
|
||||
child: Image.asset(
|
||||
trackInfos(widget.trackPreference.syncId!).$1,
|
||||
height: 30,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(tracksProvider(
|
||||
syncId: widget.trackPreference.syncId!)
|
||||
.notifier)
|
||||
.deleteTrackManga(widget.trackRes);
|
||||
},
|
||||
icon: const Icon(Icons.cancel_outlined))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Status",
|
||||
Expanded(
|
||||
child: _elevatedButton(
|
||||
context,
|
||||
borderRadius:
|
||||
const BorderRadius.only(topRight: Radius.circular(20)),
|
||||
onPressed: () async {
|
||||
final trackSearch = await trackersSearchraggableMenu(
|
||||
context,
|
||||
track: widget.trackRes) as TrackSearch?;
|
||||
if (trackSearch != null) {
|
||||
await ref
|
||||
.read(trackStateProvider(track: null).notifier)
|
||||
.setTrackSearch(trackSearch, widget.mangaId,
|
||||
widget.trackPreference.syncId!);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
widget.trackRes.title!,
|
||||
style: TextStyle(
|
||||
color: secondaryColor(context),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: mediaWidth(context, 0.8),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: ref
|
||||
.read(
|
||||
trackStateProvider(track: widget.trackRes)
|
||||
.notifier)
|
||||
.getStatusList()
|
||||
.length,
|
||||
itemBuilder: (context, index) {
|
||||
final status = ref
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(tracksProvider(
|
||||
syncId: widget.trackPreference.syncId!)
|
||||
.notifier)
|
||||
.deleteTrackManga(widget.trackRes);
|
||||
},
|
||||
icon: const Icon(Icons.cancel_outlined))
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Status",
|
||||
),
|
||||
content: SizedBox(
|
||||
width: mediaWidth(context, 0.8),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes)
|
||||
.notifier)
|
||||
.getStatusList()[index];
|
||||
return RadioListTile(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
value: status,
|
||||
groupValue: widget.trackRes.status,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..status = status)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
title: Text(getTrackStatus(status)),
|
||||
);
|
||||
},
|
||||
)),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style:
|
||||
TextStyle(color: primaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}, text: getTrackStatus(widget.trackRes.status)),
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () {
|
||||
int currentIntValue = widget.trackRes.lastChapterRead!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Chapters",
|
||||
),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
.getStatusList()
|
||||
.length,
|
||||
itemBuilder: (context, index) {
|
||||
final status = ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes)
|
||||
.notifier)
|
||||
.getStatusList()[index];
|
||||
return RadioListTile(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
value: status,
|
||||
groupValue: widget.trackRes.status,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..status = status)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
title: Text(getTrackStatus(status)),
|
||||
);
|
||||
},
|
||||
)),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
NumberPicker(
|
||||
value: currentIntValue,
|
||||
minValue: 0,
|
||||
maxValue: widget.trackRes.totalChapter != 0
|
||||
? widget.trackRes.totalChapter!
|
||||
: 10000,
|
||||
step: 1,
|
||||
haptics: true,
|
||||
onChanged: (value) =>
|
||||
setState(() => currentIntValue = value),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: TextStyle(
|
||||
color: primaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}, text: getTrackStatus(widget.trackRes.status)),
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () {
|
||||
int currentIntValue = widget.trackRes.lastChapterRead!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Chapters",
|
||||
),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
NumberPicker(
|
||||
value: currentIntValue,
|
||||
minValue: 0,
|
||||
maxValue: widget.trackRes.totalChapter != 0
|
||||
? widget.trackRes.totalChapter!
|
||||
: 10000,
|
||||
step: 1,
|
||||
haptics: true,
|
||||
onChanged: (value) =>
|
||||
setState(() => currentIntValue = value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style:
|
||||
TextStyle(color: primaryColor(context)),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..lastChapterRead =
|
||||
currentIntValue)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"OK",
|
||||
style:
|
||||
TextStyle(color: primaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
text: widget.trackRes.totalChapter != 0
|
||||
? "${widget.trackRes.lastChapterRead}/${widget.trackRes.totalChapter}"
|
||||
: "${widget.trackRes.lastChapterRead == 0 ? "Not Started" : widget.trackRes.lastChapterRead}"),
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () {
|
||||
int currentIntValue = widget.trackRes.score!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Score",
|
||||
),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
NumberPicker(
|
||||
value: currentIntValue,
|
||||
minValue: 0,
|
||||
maxValue: 10,
|
||||
step: 1,
|
||||
haptics: true,
|
||||
onChanged: (value) =>
|
||||
setState(() => currentIntValue = value),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: TextStyle(
|
||||
color: primaryColor(context)),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..lastChapterRead =
|
||||
currentIntValue)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"OK",
|
||||
style: TextStyle(
|
||||
color: primaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
text: widget.trackRes.totalChapter != 0
|
||||
? "${widget.trackRes.lastChapterRead}/${widget.trackRes.totalChapter}"
|
||||
: "${widget.trackRes.lastChapterRead == 0 ? "Not Started" : widget.trackRes.lastChapterRead}"),
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () {
|
||||
int currentIntValue = widget.trackRes.score!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"Score",
|
||||
),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
NumberPicker(
|
||||
value: currentIntValue,
|
||||
minValue: 0,
|
||||
maxValue: ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes)
|
||||
.notifier)
|
||||
.getScoreMaxValue(),
|
||||
textMapper: (numberText) {
|
||||
return ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes)
|
||||
.notifier)
|
||||
.getTextMapper(numberText);
|
||||
},
|
||||
step: ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes)
|
||||
.notifier)
|
||||
.getScoreStep(),
|
||||
haptics: true,
|
||||
onChanged: (value) =>
|
||||
setState(() => currentIntValue = value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style:
|
||||
TextStyle(color: primaryColor(context)),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..score = currentIntValue)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"OK",
|
||||
style:
|
||||
TextStyle(color: primaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
text: widget.trackRes.score != 0
|
||||
? widget.trackRes.score.toString()
|
||||
: "Score"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () async {
|
||||
DateTime? newDate = await showDatePicker(
|
||||
helpText: 'Start date',
|
||||
locale: const Locale("fr", "FR"),
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100));
|
||||
if (newDate == null) return;
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..startedReadingDate =
|
||||
newDate.millisecondsSinceEpoch)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
},
|
||||
text: widget.trackRes.startedReadingDate != null &&
|
||||
widget.trackRes.startedReadingDate! >
|
||||
DateTime(1970).millisecondsSinceEpoch
|
||||
? dateFormat(
|
||||
widget.trackRes.startedReadingDate.toString(),
|
||||
ref: ref,
|
||||
useRelativeTimesTamps: false,
|
||||
context: context)
|
||||
: "Start date"),
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(context, onPressed: () async {
|
||||
DateTime? newDate = await showDatePicker(
|
||||
helpText: 'Finish date',
|
||||
locale: const Locale("fr", "FR"),
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100));
|
||||
if (newDate == null) return;
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..finishedReadingDate =
|
||||
newDate.millisecondsSinceEpoch)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
},
|
||||
text: widget.trackRes.finishedReadingDate != null &&
|
||||
widget.trackRes.finishedReadingDate! >
|
||||
DateTime(1970).millisecondsSinceEpoch
|
||||
? dateFormat(
|
||||
widget.trackRes.finishedReadingDate.toString(),
|
||||
ref: ref,
|
||||
useRelativeTimesTamps: false,
|
||||
context: context)
|
||||
: "Finish date"),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: TextStyle(
|
||||
color: primaryColor(context)),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..score = currentIntValue)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"OK",
|
||||
style: TextStyle(
|
||||
color: primaryColor(context)),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
text: widget.trackRes.score != 0
|
||||
? ref
|
||||
.read(trackStateProvider(track: widget.trackRes)
|
||||
.notifier)
|
||||
.displayScore(widget.trackRes.score!)
|
||||
: "Score"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _elevatedButton(context,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20)), onPressed: () async {
|
||||
DateTime? newDate = await showDatePicker(
|
||||
helpText: 'Start date',
|
||||
locale: const Locale("fr", "FR"),
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100));
|
||||
if (newDate == null) return;
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..startedReadingDate =
|
||||
newDate.millisecondsSinceEpoch)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
},
|
||||
text: widget.trackRes.startedReadingDate != null &&
|
||||
widget.trackRes.startedReadingDate! >
|
||||
DateTime(1970).millisecondsSinceEpoch
|
||||
? dateFormat(
|
||||
widget.trackRes.startedReadingDate.toString(),
|
||||
ref: ref,
|
||||
useRelativeTimesTamps: false,
|
||||
context: context)
|
||||
: "Start date"),
|
||||
),
|
||||
Expanded(
|
||||
child: _elevatedButton(context,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomRight: Radius.circular(20)), onPressed: () async {
|
||||
DateTime? newDate = await showDatePicker(
|
||||
helpText: 'Finish date',
|
||||
locale: const Locale("fr", "FR"),
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100));
|
||||
if (newDate == null) return;
|
||||
ref
|
||||
.read(trackStateProvider(
|
||||
track: widget.trackRes
|
||||
..finishedReadingDate =
|
||||
newDate.millisecondsSinceEpoch)
|
||||
.notifier)
|
||||
.updateManga();
|
||||
},
|
||||
text: widget.trackRes.finishedReadingDate != null &&
|
||||
widget.trackRes.finishedReadingDate! >
|
||||
DateTime(1970).millisecondsSinceEpoch
|
||||
? dateFormat(
|
||||
widget.trackRes.finishedReadingDate.toString(),
|
||||
ref: ref,
|
||||
useRelativeTimesTamps: false,
|
||||
context: context)
|
||||
: "Finish date"),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _elevatedButton(BuildContext context,
|
||||
{required VoidCallback onPressed, String text = "", Widget? child}) {
|
||||
{required VoidCallback onPressed,
|
||||
String text = "",
|
||||
Widget? child,
|
||||
BorderRadiusGeometry? borderRadius}) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(0),
|
||||
|
|
@ -375,7 +418,7 @@ Widget _elevatedButton(BuildContext context,
|
|||
shadowColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(width: 0.05, color: secondaryColor(context)),
|
||||
borderRadius: BorderRadius.circular(0))),
|
||||
borderRadius: borderRadius ?? BorderRadius.circular(0))),
|
||||
onPressed: onPressed,
|
||||
child: child ??
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
class MyAnimeListOAuth {
|
||||
class OAuth {
|
||||
String? tokenType;
|
||||
int? expiresIn;
|
||||
String? accessToken;
|
||||
String? refreshToken;
|
||||
|
||||
MyAnimeListOAuth(
|
||||
OAuth(
|
||||
{this.tokenType, this.expiresIn, this.accessToken, this.refreshToken});
|
||||
|
||||
MyAnimeListOAuth.fromJson(Map<String, dynamic> json) {
|
||||
OAuth.fromJson(Map<String, dynamic> json) {
|
||||
tokenType = json['token_type'];
|
||||
expiresIn = (json['expires_in'] as int) * 1000 +
|
||||
DateTime.now().millisecondsSinceEpoch;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class Tracks extends _$Tracks {
|
|||
track.id = tra.first.id;
|
||||
}
|
||||
}
|
||||
|
||||
isar.writeTxnSync(() => isar.tracks.putSync(track..syncId = syncId));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import 'package:isar/isar.dart';
|
|||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/track_preference.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
|
||||
import 'package:mangayomi/services/myanimelist.dart';
|
||||
import 'package:mangayomi/services/trackers/anilist.dart';
|
||||
import 'package:mangayomi/services/trackers/myanimelist.dart';
|
||||
|
||||
class TrackScreen extends ConsumerWidget {
|
||||
const TrackScreen({super.key});
|
||||
|
|
@ -32,7 +33,15 @@ class TrackScreen extends ConsumerWidget {
|
|||
.login();
|
||||
},
|
||||
id: 1,
|
||||
entries: entries!)
|
||||
entries: entries!),
|
||||
TrackListile(
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(anilistProvider(syncId: 2).notifier)
|
||||
.login();
|
||||
},
|
||||
id: 2,
|
||||
entries: entries)
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -22,13 +22,18 @@ class TrackListile extends ConsumerWidget {
|
|||
entries.where((element) => element.syncId == id).isNotEmpty;
|
||||
return ListTile(
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Image.asset(
|
||||
trackInfos(id).$1,
|
||||
height: 30,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
color: const Color.fromRGBO(18, 25, 35, 1),
|
||||
width: 70,
|
||||
child: Image.asset(
|
||||
trackInfos(id).$1,
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: trailing ?? (isLogged
|
||||
trailing: trailing ??
|
||||
(isLogged
|
||||
? const Icon(
|
||||
Icons.check,
|
||||
size: 30,
|
||||
|
|
@ -75,4 +80,3 @@ class TrackListile extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
458
lib/services/trackers/anilist.dart
Normal file
458
lib/services/trackers/anilist.dart
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/main.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 'anilist.g.dart';
|
||||
|
||||
@riverpod
|
||||
class Anilist extends _$Anilist {
|
||||
String clientId =
|
||||
(Platform.isWindows || Platform.isLinux) ? '13587' : '13588';
|
||||
static const String baseApiUrl = "https://graphql.anilist.co/";
|
||||
static const String baseUrl = "https://anilist.co/api/v2/";
|
||||
static const String baseMangaUrl = "https://anilist.co/manga/";
|
||||
String redirectUri = (Platform.isWindows || Platform.isLinux)
|
||||
? 'http://localhost:43824/success?code=1337'
|
||||
: 'mangayomi://success?code=1337';
|
||||
String clientSecret = (Platform.isWindows || Platform.isLinux)
|
||||
? 'tJA13cAR2tCCXrJCwwvmwEDbWRoIaahFiJTXToHd'
|
||||
: 'G2fFUiGtgFd60D0lCkhgGKvMmrCfDmZXADQIzWXr';
|
||||
|
||||
@override
|
||||
build({required int syncId}) {}
|
||||
|
||||
String _authUrl() {
|
||||
return 'https://anilist.co/api/v2/oauth/authorize?client_id=$clientId&redirect_uri=$redirectUri&response_type=code';
|
||||
}
|
||||
|
||||
Future<bool?> login() async {
|
||||
final callbackUrlScheme = (Platform.isWindows || Platform.isLinux)
|
||||
? 'http://localhost:43824'
|
||||
: 'mangayomi';
|
||||
final loginUrl = _authUrl();
|
||||
|
||||
try {
|
||||
final uri = await FlutterWebAuth2.authenticate(
|
||||
url: loginUrl, callbackUrlScheme: callbackUrlScheme);
|
||||
|
||||
final code = Uri.parse(uri).queryParameters['code'];
|
||||
final response = await http.post(
|
||||
Uri.parse('https://anilist.co/api/v2/oauth/token'),
|
||||
body: {
|
||||
'grant_type': 'authorization_code',
|
||||
'client_id': clientId,
|
||||
'client_secret': clientSecret,
|
||||
'redirect_uri': redirectUri,
|
||||
'code': code
|
||||
},
|
||||
);
|
||||
final res = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final aLOAuth = OAuth.fromJson(res);
|
||||
final currenUser = await _getCurrentUser(aLOAuth.accessToken!);
|
||||
ref.read(tracksProvider(syncId: syncId).notifier).login(TrackPreference(
|
||||
syncId: syncId,
|
||||
username: currenUser.$1,
|
||||
prefs: jsonEncode({"scoreFormat": currenUser.$2}),
|
||||
oAuth: jsonEncode(aLOAuth.toJson())));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Track> addLibManga(Track track) async {
|
||||
final accessToken = await _getAccesToken();
|
||||
|
||||
const query = '''
|
||||
mutation AddManga(\$mangaId: Int, \$progress: Int, \$status: MediaListStatus) {
|
||||
SaveMediaListEntry(mediaId: \$mangaId, progress: \$progress, status: \$status) {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
final body = {
|
||||
"query": query,
|
||||
"variables": {
|
||||
"mangaId": track.mediaId,
|
||||
"progress": track.lastChapterRead,
|
||||
"status": toAniListStatus(track.status),
|
||||
}
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(baseApiUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: json.encode(body),
|
||||
);
|
||||
final data = json.decode(response.body);
|
||||
track.libraryId = data['data']['SaveMediaListEntry']['id'];
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<Track> updateLibManga(Track track) async {
|
||||
final accessToken = await _getAccesToken();
|
||||
const query = '''
|
||||
mutation UpdateManga(\$listId: Int, \$progress: Int, \$status: MediaListStatus, \$score: Int, \$startedAt: FuzzyDateInput, \$completedAt: FuzzyDateInput) {
|
||||
SaveMediaListEntry(
|
||||
id: \$listId,
|
||||
progress: \$progress,
|
||||
status: \$status,
|
||||
scoreRaw: \$score,
|
||||
startedAt: \$startedAt,
|
||||
completedAt: \$completedAt,
|
||||
) {
|
||||
id
|
||||
status
|
||||
progress
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
final body = {
|
||||
"query": query,
|
||||
"variables": {
|
||||
"listId": track.libraryId,
|
||||
"progress": track.lastChapterRead,
|
||||
"status": toAniListStatus(track.status),
|
||||
"score": track.score!,
|
||||
"startedAt": createDate(track.startedReadingDate!),
|
||||
"completedAt": createDate(track.finishedReadingDate!),
|
||||
}
|
||||
};
|
||||
|
||||
final dd = await http.post(
|
||||
Uri.parse(baseApiUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: json.encode(body),
|
||||
);
|
||||
log(dd.body);
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>> search(String search) async {
|
||||
final accessToken = await _getAccesToken();
|
||||
const query = '''
|
||||
query Search(\$query: String) {
|
||||
Page(perPage: 50) {
|
||||
media(search: \$query, type: MANGA, format_not_in: [NOVEL]) {
|
||||
id
|
||||
title {
|
||||
userPreferred
|
||||
}
|
||||
coverImage {
|
||||
large
|
||||
}
|
||||
format
|
||||
status
|
||||
chapters
|
||||
description
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
final body = {
|
||||
"query": query,
|
||||
"variables": {
|
||||
"query": search,
|
||||
}
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(baseApiUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: json.encode(body),
|
||||
);
|
||||
|
||||
final data = json.decode(response.body);
|
||||
|
||||
final entries =
|
||||
List<Map<String, dynamic>>.from(data['data']['Page']['media']);
|
||||
return entries
|
||||
.map((jsonRes) => TrackSearch(
|
||||
libraryId: jsonRes['id'],
|
||||
syncId: syncId,
|
||||
trackingUrl: "",
|
||||
mediaId: jsonRes['id'],
|
||||
summary: jsonRes['description'] ?? "",
|
||||
totalChapter: jsonRes['chapters'] ?? 0,
|
||||
coverUrl: jsonRes['coverImage']['large'] ?? "",
|
||||
title: jsonRes['title']['userPreferred'],
|
||||
startDate: jsonRes["start_date"] ??
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
parseDate(jsonRes, 'startDate'))
|
||||
.toString(),
|
||||
publishingType: "",
|
||||
publishingStatus: jsonRes['status']))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Track?> findLibManga(
|
||||
Track track,
|
||||
) async {
|
||||
final userId = ref.watch(tracksProvider(syncId: syncId))!.username;
|
||||
|
||||
final accessToken = await _getAccesToken();
|
||||
const query = '''
|
||||
query(\$id: Int!, \$manga_id: Int!) {
|
||||
Page {
|
||||
mediaList(userId: \$id, type: MANGA, mediaId: \$manga_id) {
|
||||
id
|
||||
status
|
||||
scoreRaw: score(format: POINT_100)
|
||||
progress
|
||||
startedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
completedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
media {
|
||||
id
|
||||
title {
|
||||
userPreferred
|
||||
}
|
||||
coverImage {
|
||||
large
|
||||
}
|
||||
format
|
||||
status
|
||||
chapters
|
||||
description
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
final body = {
|
||||
"query": query,
|
||||
"variables": {
|
||||
"id": int.parse(userId!),
|
||||
"manga_id": track.mediaId,
|
||||
}
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(baseApiUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: json.encode(body),
|
||||
);
|
||||
final data = json.decode(response.body);
|
||||
final entries =
|
||||
List<Map<String, dynamic>>.from(data['data']['Page']['mediaList']);
|
||||
if (entries.isNotEmpty) {
|
||||
final jsonRes = entries.first;
|
||||
track.libraryId = jsonRes['id'];
|
||||
track.syncId = syncId;
|
||||
track.mediaId = jsonRes['media']['id'];
|
||||
track.status = _getALTrackStatus(jsonRes['status']);
|
||||
track.title = jsonRes['media']['title']['userPreferred'] ?? '';
|
||||
track.score = jsonRes['scoreRaw'] ?? 0;
|
||||
track.lastChapterRead = jsonRes['progress'] ?? 0;
|
||||
track.startedReadingDate = parseDate(jsonRes, 'startedAt');
|
||||
track.finishedReadingDate = parseDate(jsonRes, 'completedAt');
|
||||
track.totalChapter = jsonRes['media']["chapters"] ?? 0;
|
||||
}
|
||||
return entries.isNotEmpty ? track : null;
|
||||
}
|
||||
|
||||
Future<(String, String)> _getCurrentUser(String accessToken) async {
|
||||
const query = '''
|
||||
query User {
|
||||
Viewer {
|
||||
id
|
||||
mediaListOptions {
|
||||
scoreFormat
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
final body = {
|
||||
"query": query,
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(baseApiUrl),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: json.encode(body),
|
||||
);
|
||||
final data = json.decode(response.body);
|
||||
|
||||
final viewer = data['data']['Viewer'];
|
||||
return (
|
||||
viewer['id'].toString(),
|
||||
viewer['mediaListOptions']['scoreFormat'].toString()
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getAccesToken() async {
|
||||
final track = ref.watch(tracksProvider(syncId: syncId));
|
||||
final mALOAuth =
|
||||
OAuth.fromJson(jsonDecode(track!.oAuth!) as Map<String, dynamic>);
|
||||
final expiresIn = DateTime.fromMillisecondsSinceEpoch(mALOAuth.expiresIn!);
|
||||
if (DateTime.now().isAfter(expiresIn)) {
|
||||
ref.read(tracksProvider(syncId: syncId).notifier).logout();
|
||||
throw Exception("Token expired");
|
||||
}
|
||||
return mALOAuth.accessToken!;
|
||||
}
|
||||
|
||||
String _toAnilistScore(int score) {
|
||||
final prefs = isar.trackPreferences.getSync(syncId)!.prefs;
|
||||
final scoreFormat = jsonDecode(prefs!)['scoreFormat'];
|
||||
return switch (scoreFormat) {
|
||||
"POINT_10" => (score / 10).toString(),
|
||||
"POINT_100" => score.toString(),
|
||||
"POINT_5" => score == 0
|
||||
? "0"
|
||||
: score < 30
|
||||
? "1"
|
||||
: score < 50
|
||||
? "2"
|
||||
: score < 70
|
||||
? "3"
|
||||
: score < 90
|
||||
? "4"
|
||||
: "5",
|
||||
"POINT_3" => score == 0
|
||||
? "0"
|
||||
: score <= 35
|
||||
? ":("
|
||||
: score <= 60
|
||||
? ":|"
|
||||
: ":)",
|
||||
"POINT_10_DECIMAL" => (score / 10).toString(),
|
||||
_ => throw ("Unknown score type")
|
||||
};
|
||||
}
|
||||
|
||||
TrackStatus _getALTrackStatus(String status) {
|
||||
return switch (status) {
|
||||
"CURRENT" => TrackStatus.reading,
|
||||
"COMPLETED" => TrackStatus.completed,
|
||||
"PAUSED" => TrackStatus.onHold,
|
||||
"DROPPED" => TrackStatus.dropped,
|
||||
"PLANNING" => TrackStatus.planToRead,
|
||||
_ => TrackStatus.rereading,
|
||||
};
|
||||
}
|
||||
|
||||
List<TrackStatus> aniListStatusList = [
|
||||
TrackStatus.reading,
|
||||
TrackStatus.completed,
|
||||
TrackStatus.onHold,
|
||||
TrackStatus.dropped,
|
||||
TrackStatus.planToRead,
|
||||
TrackStatus.rereading
|
||||
];
|
||||
|
||||
String? toAniListStatus(TrackStatus status) {
|
||||
return switch (status) {
|
||||
TrackStatus.reading => "CURRENT",
|
||||
TrackStatus.completed => "COMPLETED",
|
||||
TrackStatus.onHold => "PAUSED",
|
||||
TrackStatus.dropped => "DROPPED",
|
||||
TrackStatus.planToRead => "PLANNING",
|
||||
TrackStatus.rereading => "REPEATING",
|
||||
};
|
||||
}
|
||||
|
||||
int parseDate(Map<String, dynamic> json, String dateKey) {
|
||||
try {
|
||||
final year = json[dateKey]['year'];
|
||||
final month = json[dateKey]['month'];
|
||||
final day = json[dateKey]['day'];
|
||||
final date = DateTime(year, month, day);
|
||||
return date.millisecondsSinceEpoch;
|
||||
} catch (_) {
|
||||
return DateTime(1970, 01, 01).millisecondsSinceEpoch;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> createDate(int dateValue) {
|
||||
if (dateValue == 0) {
|
||||
return {
|
||||
"year": null,
|
||||
"month": null,
|
||||
"day": null,
|
||||
};
|
||||
}
|
||||
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(dateValue);
|
||||
return {
|
||||
"year": date.year,
|
||||
"month": date.month,
|
||||
"day": date.day,
|
||||
};
|
||||
}
|
||||
|
||||
String displayScore(int score) {
|
||||
final prefs = isar.trackPreferences.getSync(syncId)!.prefs;
|
||||
final scoreFormat = jsonDecode(prefs!)['scoreFormat'];
|
||||
return switch (scoreFormat) {
|
||||
'POINT_5' => score == 0 ? "0 ★" : "${(score + 10) ~/ 20} ★",
|
||||
'POINT_3' => score == 0
|
||||
? "-"
|
||||
: score <= 35
|
||||
? "😦"
|
||||
: score <= 60
|
||||
? "😐"
|
||||
: "😊",
|
||||
_ => _toAnilistScore(score),
|
||||
};
|
||||
}
|
||||
|
||||
(int, int) getScoreValue() {
|
||||
final prefs = isar.trackPreferences.getSync(syncId)!.prefs;
|
||||
String scoreFormat = jsonDecode(prefs!)['scoreFormat'];
|
||||
return switch (scoreFormat) {
|
||||
'POINT_10' => (100, 10),
|
||||
'POINT_100' => (100, 1),
|
||||
'POINT_5' => (100, 20),
|
||||
'POINT_3' => (100, 30),
|
||||
_ => (100, 1),
|
||||
};
|
||||
}
|
||||
}
|
||||
124
lib/services/trackers/anilist.g.dart
Normal file
124
lib/services/trackers/anilist.g.dart
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'anilist.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$anilistHash() => r'd09ca797106b31f075d5ded66ae138639b6ca745';
|
||||
|
||||
/// 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 _$Anilist extends BuildlessAutoDisposeNotifier<dynamic> {
|
||||
late final int syncId;
|
||||
|
||||
dynamic build({
|
||||
required int syncId,
|
||||
});
|
||||
}
|
||||
|
||||
/// See also [Anilist].
|
||||
@ProviderFor(Anilist)
|
||||
const anilistProvider = AnilistFamily();
|
||||
|
||||
/// See also [Anilist].
|
||||
class AnilistFamily extends Family<dynamic> {
|
||||
/// See also [Anilist].
|
||||
const AnilistFamily();
|
||||
|
||||
/// See also [Anilist].
|
||||
AnilistProvider call({
|
||||
required int syncId,
|
||||
}) {
|
||||
return AnilistProvider(
|
||||
syncId: syncId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AnilistProvider getProviderOverride(
|
||||
covariant AnilistProvider provider,
|
||||
) {
|
||||
return call(
|
||||
syncId: provider.syncId,
|
||||
);
|
||||
}
|
||||
|
||||
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'anilistProvider';
|
||||
}
|
||||
|
||||
/// See also [Anilist].
|
||||
class AnilistProvider
|
||||
extends AutoDisposeNotifierProviderImpl<Anilist, dynamic> {
|
||||
/// See also [Anilist].
|
||||
AnilistProvider({
|
||||
required this.syncId,
|
||||
}) : super.internal(
|
||||
() => Anilist()..syncId = syncId,
|
||||
from: anilistProvider,
|
||||
name: r'anilistProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$anilistHash,
|
||||
dependencies: AnilistFamily._dependencies,
|
||||
allTransitiveDependencies: AnilistFamily._allTransitiveDependencies,
|
||||
);
|
||||
|
||||
final int syncId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AnilistProvider && other.syncId == syncId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, syncId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic runNotifierBuild(
|
||||
covariant Anilist notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
syncId: syncId,
|
||||
);
|
||||
}
|
||||
}
|
||||
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions
|
||||
|
|
@ -38,13 +38,12 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
if (queryParams['code'] == null) return null;
|
||||
|
||||
final oAuth = await _getOAuth(queryParams['code']!);
|
||||
final myAnimeListoAuth =
|
||||
MyAnimeListOAuth.fromJson(oAuth as Map<String, dynamic>);
|
||||
final username = await _getUserName(myAnimeListoAuth.accessToken!);
|
||||
final mALOAuth = OAuth.fromJson(oAuth as Map<String, dynamic>);
|
||||
final username = await _getUserName(mALOAuth.accessToken!);
|
||||
ref.read(tracksProvider(syncId: syncId).notifier).login(TrackPreference(
|
||||
syncId: syncId,
|
||||
username: username,
|
||||
oAuth: jsonEncode(myAnimeListoAuth.toJson())));
|
||||
oAuth: jsonEncode(mALOAuth.toJson())));
|
||||
|
||||
return true;
|
||||
} catch (_) {
|
||||
|
|
@ -54,28 +53,28 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
|
||||
Future<String> _getAccesToken() async {
|
||||
final track = ref.watch(tracksProvider(syncId: syncId));
|
||||
final myAnimeListoAuth = MyAnimeListOAuth.fromJson(
|
||||
jsonDecode(track!.oAuth!) as Map<String, dynamic>);
|
||||
final expiresIn =
|
||||
DateTime.fromMillisecondsSinceEpoch(myAnimeListoAuth.expiresIn!);
|
||||
final mALOAuth =
|
||||
OAuth.fromJson(jsonDecode(track!.oAuth!) as Map<String, dynamic>);
|
||||
final expiresIn = DateTime.fromMillisecondsSinceEpoch(mALOAuth.expiresIn!);
|
||||
if (DateTime.now().isAfter(expiresIn)) {
|
||||
final params = {
|
||||
'client_id': clientId,
|
||||
'grant_type': 'refresh_token',
|
||||
'refresh_token': myAnimeListoAuth.refreshToken,
|
||||
'refresh_token': mALOAuth.refreshToken,
|
||||
};
|
||||
final response =
|
||||
await http.post(Uri.parse('$baseOAuthUrl/token'), body: params);
|
||||
final oAuth = MyAnimeListOAuth.fromJson(
|
||||
jsonDecode(response.body) as Map<String, dynamic>);
|
||||
final oAuth =
|
||||
OAuth.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
final username = await _getUserName(oAuth.accessToken!);
|
||||
ref.read(tracksProvider(syncId: syncId).notifier).login(TrackPreference(
|
||||
syncId: syncId,
|
||||
username: username,
|
||||
prefs: "",
|
||||
oAuth: jsonEncode(oAuth.toJson())));
|
||||
return oAuth.accessToken!;
|
||||
}
|
||||
return myAnimeListoAuth.accessToken!;
|
||||
return mALOAuth.accessToken!;
|
||||
}
|
||||
|
||||
Future<List<TrackSearch>> search(String query) async {
|
||||
|
|
@ -250,4 +249,4 @@ class MyAnimeList extends _$MyAnimeList {
|
|||
final mJson = jsonDecode(await response.stream.bytesToString());
|
||||
return _parseMangaItem(mJson, track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ part of 'myanimelist.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$myAnimeListHash() => r'4fca14c944acc71eb514057708b8c60e28aa1744';
|
||||
String _$myAnimeListHash() => r'f96b3d08fd5c59b0811a7bf42ab13211903fdd5a';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -23,7 +23,7 @@ IconData getMangaStatusIcon(Status status) {
|
|||
Status.ongoing => Icons.schedule_rounded,
|
||||
Status.onHiatus => Icons.pause_circle_rounded,
|
||||
Status.canceled => Icons.cancel_rounded,
|
||||
Status.completed => Icons.done,
|
||||
Status.completed => Icons.done_all_outlined,
|
||||
Status.publishingFinished => Icons.done,
|
||||
_ => Icons.block_outlined,
|
||||
};
|
||||
|
|
@ -43,6 +43,6 @@ String getTrackStatus(TrackStatus status) {
|
|||
(String, String) trackInfos(int id) {
|
||||
return switch (id) {
|
||||
1 => ("assets/tracker_mal.webp", "MyAnimeList"),
|
||||
_ => ("", ""),
|
||||
_ => ("assets/tracker_anilist.webp", "Anilist"),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue