From 13b1fea0a3d14dbbecc2a7fb61047427bedfab1a Mon Sep 17 00:00:00 2001 From: kodjomoustapha <107993382+kodjodevf@users.noreply.github.com> Date: Sat, 15 Jul 2023 17:35:00 +0100 Subject: [PATCH] Feature : Added MyAnimeList tracker service --- android/app/src/main/AndroidManifest.xml | 10 + assets/tracker_mal.webp | Bin 0 -> 1404 bytes lib/models/track.dart | 47 + lib/models/track.g.dart | 1686 +++++++++++++++++ lib/models/track_preference.dart | 18 + lib/models/track_preference.g.dart | 699 +++++++ lib/models/track_search.dart | 51 + .../manga/detail/manga_detail_view.dart | 837 +++++++- .../manga/detail/manga_details_view.dart | 74 +- .../providers/track_state_providers.dart | 63 + .../providers/track_state_providers.g.dart | 125 ++ .../providers/downloads_state_provider.dart | 1 - .../more/settings/settings_screen.dart | 5 + .../settings/track/myanimelist/model.dart | 26 + .../track/providers/track_providers.dart | 44 + .../track/providers/track_providers.g.dart | 124 ++ lib/modules/more/settings/track/track.dart | 41 + .../settings/track/widgets/track_listile.dart | 81 + .../more/widgets/list_tile_widget.dart | 12 +- lib/providers/l10n_providers.g.dart | 2 +- lib/providers/storage_provider.dart | 6 +- lib/router/router.dart | 14 + lib/services/get_chapter_url.dart | 8 +- lib/services/get_chapter_url.g.dart | 2 +- lib/services/myanimelist.dart | 294 +++ lib/services/myanimelist.g.dart | 125 ++ lib/utils/constant.dart | 13 + lib/utils/headers.dart | 13 +- lib/utils/headers.g.dart | 2 +- lib/utils/language.dart | 3 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + macos/Runner/DebugProfile.entitlements | 2 + pubspec.lock | 40 + pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 38 files changed, 4418 insertions(+), 65 deletions(-) create mode 100644 assets/tracker_mal.webp create mode 100644 lib/models/track.dart create mode 100644 lib/models/track.g.dart create mode 100644 lib/models/track_preference.dart create mode 100644 lib/models/track_preference.g.dart create mode 100644 lib/models/track_search.dart create mode 100644 lib/modules/manga/detail/providers/track_state_providers.dart create mode 100644 lib/modules/manga/detail/providers/track_state_providers.g.dart create mode 100644 lib/modules/more/settings/track/myanimelist/model.dart create mode 100644 lib/modules/more/settings/track/providers/track_providers.dart create mode 100644 lib/modules/more/settings/track/providers/track_providers.g.dart create mode 100644 lib/modules/more/settings/track/track.dart create mode 100644 lib/modules/more/settings/track/widgets/track_listile.dart create mode 100644 lib/services/myanimelist.dart create mode 100644 lib/services/myanimelist.g.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6f410b9..4ba6511 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,6 +29,16 @@ + + + + + + + + R7N6 z2~FFp&7b%5+REN*mv=qyXZYV%`(-@EdB1vH-k#o=$ZYWU_|DrazTaPJ-P(8&rDkS6 zKYpiJlIkso_nJbLDpu`VpMPz6pN{;IE+;RQhI1mR|Q(`~{C(RZ>%uD=6@ z7tGJrM>PT@MtjzM^Ceo`E}tCNuXRezgZIEl3O$gp?ksx3magU4z%e?|U>?V!ZVY}@ zkb7VJ&Rb-et6`1Z?%pyiwAL*l^HzA;NrMxzK*DC_B=2h_NQP(ZQdKXi&a~{n%f3SE;xy2N#C;zE{p$7$Jq`jV-H*#SCBl_gDrHb0@P|W1z4XY;0g!;@5S}o zhjeqJX>=dAoz0mbo86bY#;=OBNz&!R-xF2p{%_MNJC$(|fj>2?d!&N{U%8Qrbao|A zF~MNv*}?e%uN~D3cTg2Uf_G7-Tm<*8Q|I!I<2bVC3#Ue$7K@Nya^90Ue$Drx*CAP>YamTp|yg$fgFGR8SYof3S+ype3T11V%r| z?-oJm5g+(bK#-&DJN}jGtnj~K@^F)S@~UQ%5%F?M_$C%E#1|oF9$6DIbzAGBus22z zX*Om3_pE*@?eDXZx*Me5|Jlv7%Dv7SsYe+LG=V|F8$WIBv}m42xp~;Y%-SiICwaF) zt;}7ckdq!w9|KFyyu7-tUrFFO5WXTh04}iD@9Zvaew;FlNfAWrD!-xE|H{xDBUE%{ z-?GoFsc5`Q&0uS`xyz)^ikVn`@4s0pm6;;k12ac~27)iYpNr(v{<=zCkYR>$3I$L? zV$ke?Ijgf?8s#(GUO={dCG9h|rVgVNiOMrOrG89BX)&S}Z?}7WQxz3&Y+~XV=Ls%l zj@cE$ne6`m2KnGbv-_zfeG`5#`rC^Zn*UTwyRO{#Sfn;&F=+cEk({<_$*CNiw@9wD z#sOU9Cn4N^*VQifLMQNZjb$Oj)Bm)TzIF~{+(fz0VTwrP_mC}oLR*fNIg$wV^RBzk zFc@ey-8pCUTR`Xi8K?`cMVOUbqc#=f+zMi}-UnERa}z!+kT>r;RnIw`y3? z>pc$GForu|zP}`35ARyC{r%-`niWj9X&b|CYucfF#htw>xp~ z@9DPLa{S+rORMk|RWZBYb&6+hSO2cKg?qgNfuEwxd5t+1FYBnUO*K&Z`^7sQ4P$S4 zZt0DgDMVg)?LQ>&CQ0_VJfNY)G%563C0G_E23IB`>}vWw%^NWMRaeS0T{!W%bmvShF>%wC#ZZ!P$U zXh+1FsYXySbczLnW6M8qYxnC^-&=TYDEgEH<3)%Pa*7gk~s$K>;&tF_H9ZG@0hmy*LQkw~wnLMHA`pa9wc K1y}$80001V0ljko literal 0 HcmV?d00001 diff --git a/lib/models/track.dart b/lib/models/track.dart new file mode 100644 index 0000000..f6989cb --- /dev/null +++ b/lib/models/track.dart @@ -0,0 +1,47 @@ +import 'package:isar/isar.dart'; +part 'track.g.dart'; + +@collection +@Name("Track") +class Track { + Id? id; + + int? mediaId; + + int? mangaId; + + int? syncId; + + String? title; + + int ?lastChapterRead; + + int? totalChapter; + + int? score; + + @enumerated + TrackStatus status; + + int? startedReadingDate; + + int? finishedReadingDate; + + String? trackingUrl; + + Track( + {this.id = Isar.autoIncrement, + this.mediaId, + this.mangaId, + this.syncId, + this.title, + this.lastChapterRead, + this.totalChapter, + this.score, + required this.status, + this.startedReadingDate, + this.finishedReadingDate, + this.trackingUrl}); +} + +enum TrackStatus { reading, completed, onHold, dropped, planToRead, rereading } diff --git a/lib/models/track.g.dart b/lib/models/track.g.dart new file mode 100644 index 0000000..b31d550 --- /dev/null +++ b/lib/models/track.g.dart @@ -0,0 +1,1686 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'track.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetTrackCollection on Isar { + IsarCollection get tracks => this.collection(); +} + +const TrackSchema = CollectionSchema( + name: r'Track', + id: 6244076704169336260, + properties: { + r'finishedReadingDate': PropertySchema( + id: 0, + name: r'finishedReadingDate', + type: IsarType.long, + ), + r'lastChapterRead': PropertySchema( + id: 1, + name: r'lastChapterRead', + type: IsarType.long, + ), + r'mangaId': PropertySchema( + id: 2, + name: r'mangaId', + type: IsarType.long, + ), + r'mediaId': PropertySchema( + id: 3, + name: r'mediaId', + type: IsarType.long, + ), + r'score': PropertySchema( + id: 4, + name: r'score', + type: IsarType.long, + ), + r'startedReadingDate': PropertySchema( + id: 5, + name: r'startedReadingDate', + type: IsarType.long, + ), + r'status': PropertySchema( + id: 6, + name: r'status', + type: IsarType.byte, + enumMap: _TrackstatusEnumValueMap, + ), + r'syncId': PropertySchema( + id: 7, + name: r'syncId', + type: IsarType.long, + ), + r'title': PropertySchema( + id: 8, + name: r'title', + type: IsarType.string, + ), + r'totalChapter': PropertySchema( + id: 9, + name: r'totalChapter', + type: IsarType.long, + ), + r'trackingUrl': PropertySchema( + id: 10, + name: r'trackingUrl', + type: IsarType.string, + ) + }, + estimateSize: _trackEstimateSize, + serialize: _trackSerialize, + deserialize: _trackDeserialize, + deserializeProp: _trackDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _trackGetId, + getLinks: _trackGetLinks, + attach: _trackAttach, + version: '3.1.0+1', +); + +int _trackEstimateSize( + Track object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.title; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.trackingUrl; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _trackSerialize( + Track object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + 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); +} + +Track _trackDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Track( + 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])] ?? + TrackStatus.reading, + syncId: reader.readLongOrNull(offsets[7]), + title: reader.readStringOrNull(offsets[8]), + totalChapter: reader.readLongOrNull(offsets[9]), + trackingUrl: reader.readStringOrNull(offsets[10]), + ); + return object; +} + +P _trackDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLongOrNull(offset)) as P; + case 1: + return (reader.readLongOrNull(offset)) as P; + case 2: + return (reader.readLongOrNull(offset)) as P; + case 3: + return (reader.readLongOrNull(offset)) as P; + case 4: + return (reader.readLongOrNull(offset)) as P; + case 5: + return (reader.readLongOrNull(offset)) as P; + case 6: + 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 10: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _TrackstatusEnumValueMap = { + 'reading': 0, + 'completed': 1, + 'onHold': 2, + 'dropped': 3, + 'planToRead': 4, + 'rereading': 5, +}; +const _TrackstatusValueEnumMap = { + 0: TrackStatus.reading, + 1: TrackStatus.completed, + 2: TrackStatus.onHold, + 3: TrackStatus.dropped, + 4: TrackStatus.planToRead, + 5: TrackStatus.rereading, +}; + +Id _trackGetId(Track object) { + return object.id ?? Isar.autoIncrement; +} + +List> _trackGetLinks(Track object) { + return []; +} + +void _trackAttach(IsarCollection col, Id id, Track object) { + object.id = id; +} + +extension TrackQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension TrackQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension TrackQueryFilter on QueryBuilder { + QueryBuilder + finishedReadingDateIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'finishedReadingDate', + )); + }); + } + + QueryBuilder + finishedReadingDateIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'finishedReadingDate', + )); + }); + } + + QueryBuilder finishedReadingDateEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'finishedReadingDate', + value: value, + )); + }); + } + + QueryBuilder + finishedReadingDateGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'finishedReadingDate', + value: value, + )); + }); + } + + QueryBuilder finishedReadingDateLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'finishedReadingDate', + value: value, + )); + }); + } + + QueryBuilder finishedReadingDateBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'finishedReadingDate', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'id', + )); + }); + } + + QueryBuilder idIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'id', + )); + }); + } + + QueryBuilder idEqualTo(Id? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id? lower, + Id? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder lastChapterReadIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'lastChapterRead', + )); + }); + } + + QueryBuilder lastChapterReadIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'lastChapterRead', + )); + }); + } + + QueryBuilder lastChapterReadEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'lastChapterRead', + value: value, + )); + }); + } + + QueryBuilder lastChapterReadGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'lastChapterRead', + value: value, + )); + }); + } + + QueryBuilder lastChapterReadLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'lastChapterRead', + value: value, + )); + }); + } + + QueryBuilder lastChapterReadBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'lastChapterRead', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder mangaIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'mangaId', + )); + }); + } + + QueryBuilder mangaIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'mangaId', + )); + }); + } + + QueryBuilder mangaIdEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mangaId', + value: value, + )); + }); + } + + QueryBuilder mangaIdGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mangaId', + value: value, + )); + }); + } + + QueryBuilder mangaIdLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mangaId', + value: value, + )); + }); + } + + QueryBuilder mangaIdBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mangaId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder mediaIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'mediaId', + )); + }); + } + + QueryBuilder mediaIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'mediaId', + )); + }); + } + + QueryBuilder mediaIdEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mediaId', + value: value, + )); + }); + } + + QueryBuilder mediaIdGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mediaId', + value: value, + )); + }); + } + + QueryBuilder mediaIdLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mediaId', + value: value, + )); + }); + } + + QueryBuilder mediaIdBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mediaId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder scoreIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'score', + )); + }); + } + + QueryBuilder scoreIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'score', + )); + }); + } + + QueryBuilder scoreEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'score', + value: value, + )); + }); + } + + QueryBuilder scoreGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'score', + value: value, + )); + }); + } + + QueryBuilder scoreLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'score', + value: value, + )); + }); + } + + QueryBuilder scoreBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'score', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder startedReadingDateIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'startedReadingDate', + )); + }); + } + + QueryBuilder + startedReadingDateIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'startedReadingDate', + )); + }); + } + + QueryBuilder startedReadingDateEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'startedReadingDate', + value: value, + )); + }); + } + + QueryBuilder + startedReadingDateGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'startedReadingDate', + value: value, + )); + }); + } + + QueryBuilder startedReadingDateLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'startedReadingDate', + value: value, + )); + }); + } + + QueryBuilder startedReadingDateBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'startedReadingDate', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder statusEqualTo( + TrackStatus value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'status', + value: value, + )); + }); + } + + QueryBuilder statusGreaterThan( + TrackStatus value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'status', + value: value, + )); + }); + } + + QueryBuilder statusLessThan( + TrackStatus value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'status', + value: value, + )); + }); + } + + QueryBuilder statusBetween( + TrackStatus lower, + TrackStatus upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'status', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder syncIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'syncId', + )); + }); + } + + QueryBuilder syncIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'syncId', + )); + }); + } + + QueryBuilder syncIdEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'syncId', + value: value, + )); + }); + } + + QueryBuilder syncIdGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'syncId', + value: value, + )); + }); + } + + QueryBuilder syncIdLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'syncId', + value: value, + )); + }); + } + + QueryBuilder syncIdBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'syncId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder titleIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'title', + )); + }); + } + + QueryBuilder titleIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'title', + )); + }); + } + + QueryBuilder titleEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'title', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'title', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'title', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleBetween( + 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'title', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'title', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'title', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'title', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'title', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder titleIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'title', + value: '', + )); + }); + } + + QueryBuilder titleIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'title', + value: '', + )); + }); + } + + QueryBuilder totalChapterIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'totalChapter', + )); + }); + } + + QueryBuilder totalChapterIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'totalChapter', + )); + }); + } + + QueryBuilder totalChapterEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'totalChapter', + value: value, + )); + }); + } + + QueryBuilder totalChapterGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'totalChapter', + value: value, + )); + }); + } + + QueryBuilder totalChapterLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'totalChapter', + value: value, + )); + }); + } + + QueryBuilder totalChapterBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'totalChapter', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder trackingUrlIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'trackingUrl', + )); + }); + } + + QueryBuilder trackingUrlIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'trackingUrl', + )); + }); + } + + QueryBuilder trackingUrlEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'trackingUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'trackingUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'trackingUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlBetween( + 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'trackingUrl', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'trackingUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'trackingUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'trackingUrl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'trackingUrl', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder trackingUrlIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'trackingUrl', + value: '', + )); + }); + } + + QueryBuilder trackingUrlIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'trackingUrl', + value: '', + )); + }); + } +} + +extension TrackQueryObject on QueryBuilder {} + +extension TrackQueryLinks on QueryBuilder {} + +extension TrackQuerySortBy on QueryBuilder { + QueryBuilder sortByFinishedReadingDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'finishedReadingDate', Sort.asc); + }); + } + + QueryBuilder sortByFinishedReadingDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'finishedReadingDate', Sort.desc); + }); + } + + QueryBuilder sortByLastChapterRead() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastChapterRead', Sort.asc); + }); + } + + QueryBuilder sortByLastChapterReadDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastChapterRead', Sort.desc); + }); + } + + QueryBuilder sortByMangaId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mangaId', Sort.asc); + }); + } + + QueryBuilder sortByMangaIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mangaId', Sort.desc); + }); + } + + QueryBuilder sortByMediaId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mediaId', Sort.asc); + }); + } + + QueryBuilder sortByMediaIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mediaId', Sort.desc); + }); + } + + QueryBuilder sortByScore() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'score', Sort.asc); + }); + } + + QueryBuilder sortByScoreDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'score', Sort.desc); + }); + } + + QueryBuilder sortByStartedReadingDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedReadingDate', Sort.asc); + }); + } + + QueryBuilder sortByStartedReadingDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedReadingDate', Sort.desc); + }); + } + + QueryBuilder sortByStatus() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.asc); + }); + } + + QueryBuilder sortByStatusDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.desc); + }); + } + + QueryBuilder sortBySyncId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'syncId', Sort.asc); + }); + } + + QueryBuilder sortBySyncIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'syncId', Sort.desc); + }); + } + + QueryBuilder sortByTitle() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'title', Sort.asc); + }); + } + + QueryBuilder sortByTitleDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'title', Sort.desc); + }); + } + + QueryBuilder sortByTotalChapter() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'totalChapter', Sort.asc); + }); + } + + QueryBuilder sortByTotalChapterDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'totalChapter', Sort.desc); + }); + } + + QueryBuilder sortByTrackingUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'trackingUrl', Sort.asc); + }); + } + + QueryBuilder sortByTrackingUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'trackingUrl', Sort.desc); + }); + } +} + +extension TrackQuerySortThenBy on QueryBuilder { + QueryBuilder thenByFinishedReadingDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'finishedReadingDate', Sort.asc); + }); + } + + QueryBuilder thenByFinishedReadingDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'finishedReadingDate', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByLastChapterRead() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastChapterRead', Sort.asc); + }); + } + + QueryBuilder thenByLastChapterReadDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastChapterRead', Sort.desc); + }); + } + + QueryBuilder thenByMangaId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mangaId', Sort.asc); + }); + } + + QueryBuilder thenByMangaIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mangaId', Sort.desc); + }); + } + + QueryBuilder thenByMediaId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mediaId', Sort.asc); + }); + } + + QueryBuilder thenByMediaIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mediaId', Sort.desc); + }); + } + + QueryBuilder thenByScore() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'score', Sort.asc); + }); + } + + QueryBuilder thenByScoreDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'score', Sort.desc); + }); + } + + QueryBuilder thenByStartedReadingDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedReadingDate', Sort.asc); + }); + } + + QueryBuilder thenByStartedReadingDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startedReadingDate', Sort.desc); + }); + } + + QueryBuilder thenByStatus() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.asc); + }); + } + + QueryBuilder thenByStatusDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.desc); + }); + } + + QueryBuilder thenBySyncId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'syncId', Sort.asc); + }); + } + + QueryBuilder thenBySyncIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'syncId', Sort.desc); + }); + } + + QueryBuilder thenByTitle() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'title', Sort.asc); + }); + } + + QueryBuilder thenByTitleDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'title', Sort.desc); + }); + } + + QueryBuilder thenByTotalChapter() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'totalChapter', Sort.asc); + }); + } + + QueryBuilder thenByTotalChapterDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'totalChapter', Sort.desc); + }); + } + + QueryBuilder thenByTrackingUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'trackingUrl', Sort.asc); + }); + } + + QueryBuilder thenByTrackingUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'trackingUrl', Sort.desc); + }); + } +} + +extension TrackQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByFinishedReadingDate() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'finishedReadingDate'); + }); + } + + QueryBuilder distinctByLastChapterRead() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'lastChapterRead'); + }); + } + + QueryBuilder distinctByMangaId() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'mangaId'); + }); + } + + QueryBuilder distinctByMediaId() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'mediaId'); + }); + } + + QueryBuilder distinctByScore() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'score'); + }); + } + + QueryBuilder distinctByStartedReadingDate() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'startedReadingDate'); + }); + } + + QueryBuilder distinctByStatus() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'status'); + }); + } + + QueryBuilder distinctBySyncId() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'syncId'); + }); + } + + QueryBuilder distinctByTitle( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'title', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTotalChapter() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'totalChapter'); + }); + } + + QueryBuilder distinctByTrackingUrl( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'trackingUrl', caseSensitive: caseSensitive); + }); + } +} + +extension TrackQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder finishedReadingDateProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'finishedReadingDate'); + }); + } + + QueryBuilder lastChapterReadProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'lastChapterRead'); + }); + } + + QueryBuilder mangaIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'mangaId'); + }); + } + + QueryBuilder mediaIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'mediaId'); + }); + } + + QueryBuilder scoreProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'score'); + }); + } + + QueryBuilder startedReadingDateProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'startedReadingDate'); + }); + } + + QueryBuilder statusProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'status'); + }); + } + + QueryBuilder syncIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'syncId'); + }); + } + + QueryBuilder titleProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'title'); + }); + } + + QueryBuilder totalChapterProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'totalChapter'); + }); + } + + QueryBuilder trackingUrlProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'trackingUrl'); + }); + } +} diff --git a/lib/models/track_preference.dart b/lib/models/track_preference.dart new file mode 100644 index 0000000..56f8275 --- /dev/null +++ b/lib/models/track_preference.dart @@ -0,0 +1,18 @@ +import 'package:isar/isar.dart'; +part 'track_preference.g.dart'; + +@collection +@Name("Track Preference") +class TrackPreference { + Id? syncId; + + String? username; + + String? oAuth; + + TrackPreference({ + this.syncId, + this.username, + this.oAuth, + }); +} diff --git a/lib/models/track_preference.g.dart b/lib/models/track_preference.g.dart new file mode 100644 index 0000000..d7bc82c --- /dev/null +++ b/lib/models/track_preference.g.dart @@ -0,0 +1,699 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'track_preference.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetTrackPreferenceCollection on Isar { + IsarCollection get trackPreferences => this.collection(); +} + +const TrackPreferenceSchema = CollectionSchema( + name: r'Track Preference', + id: -7260395670212271073, + properties: { + r'oAuth': PropertySchema( + id: 0, + name: r'oAuth', + type: IsarType.string, + ), + r'username': PropertySchema( + id: 1, + name: r'username', + type: IsarType.string, + ) + }, + estimateSize: _trackPreferenceEstimateSize, + serialize: _trackPreferenceSerialize, + deserialize: _trackPreferenceDeserialize, + deserializeProp: _trackPreferenceDeserializeProp, + idName: r'syncId', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _trackPreferenceGetId, + getLinks: _trackPreferenceGetLinks, + attach: _trackPreferenceAttach, + version: '3.1.0+1', +); + +int _trackPreferenceEstimateSize( + TrackPreference object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.oAuth; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.username; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _trackPreferenceSerialize( + TrackPreference object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.oAuth); + writer.writeString(offsets[1], object.username); +} + +TrackPreference _trackPreferenceDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = TrackPreference( + oAuth: reader.readStringOrNull(offsets[0]), + syncId: id, + username: reader.readStringOrNull(offsets[1]), + ); + return object; +} + +P _trackPreferenceDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _trackPreferenceGetId(TrackPreference object) { + return object.syncId ?? Isar.autoIncrement; +} + +List> _trackPreferenceGetLinks(TrackPreference object) { + return []; +} + +void _trackPreferenceAttach( + IsarCollection col, Id id, TrackPreference object) { + object.syncId = id; +} + +extension TrackPreferenceQueryWhereSort + on QueryBuilder { + QueryBuilder anySyncId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension TrackPreferenceQueryWhere + on QueryBuilder { + QueryBuilder + syncIdEqualTo(Id syncId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: syncId, + upper: syncId, + )); + }); + } + + QueryBuilder + syncIdNotEqualTo(Id syncId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: syncId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: syncId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: syncId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: syncId, includeUpper: false), + ); + } + }); + } + + QueryBuilder + syncIdGreaterThan(Id syncId, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: syncId, includeLower: include), + ); + }); + } + + QueryBuilder + syncIdLessThan(Id syncId, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: syncId, includeUpper: include), + ); + }); + } + + QueryBuilder + syncIdBetween( + Id lowerSyncId, + Id upperSyncId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerSyncId, + includeLower: includeLower, + upper: upperSyncId, + includeUpper: includeUpper, + )); + }); + } +} + +extension TrackPreferenceQueryFilter + on QueryBuilder { + QueryBuilder + oAuthIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'oAuth', + )); + }); + } + + QueryBuilder + oAuthIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'oAuth', + )); + }); + } + + QueryBuilder + oAuthEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'oAuth', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'oAuth', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'oAuth', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthBetween( + 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'oAuth', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'oAuth', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'oAuth', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'oAuth', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'oAuth', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + oAuthIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'oAuth', + value: '', + )); + }); + } + + QueryBuilder + oAuthIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'oAuth', + value: '', + )); + }); + } + + QueryBuilder + syncIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'syncId', + )); + }); + } + + QueryBuilder + syncIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'syncId', + )); + }); + } + + QueryBuilder + syncIdEqualTo(Id? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'syncId', + value: value, + )); + }); + } + + QueryBuilder + syncIdGreaterThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'syncId', + value: value, + )); + }); + } + + QueryBuilder + syncIdLessThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'syncId', + value: value, + )); + }); + } + + QueryBuilder + syncIdBetween( + Id? lower, + Id? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'syncId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + usernameIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'username', + )); + }); + } + + QueryBuilder + usernameIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'username', + )); + }); + } + + QueryBuilder + usernameEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'username', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'username', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'username', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameBetween( + 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'username', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'username', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'username', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'username', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'username', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + usernameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'username', + value: '', + )); + }); + } + + QueryBuilder + usernameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'username', + value: '', + )); + }); + } +} + +extension TrackPreferenceQueryObject + on QueryBuilder {} + +extension TrackPreferenceQueryLinks + on QueryBuilder {} + +extension TrackPreferenceQuerySortBy + on QueryBuilder { + QueryBuilder sortByOAuth() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'oAuth', Sort.asc); + }); + } + + QueryBuilder + sortByOAuthDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'oAuth', Sort.desc); + }); + } + + QueryBuilder + sortByUsername() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'username', Sort.asc); + }); + } + + QueryBuilder + sortByUsernameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'username', Sort.desc); + }); + } +} + +extension TrackPreferenceQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByOAuth() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'oAuth', Sort.asc); + }); + } + + QueryBuilder + thenByOAuthDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'oAuth', Sort.desc); + }); + } + + QueryBuilder thenBySyncId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'syncId', Sort.asc); + }); + } + + QueryBuilder + thenBySyncIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'syncId', Sort.desc); + }); + } + + QueryBuilder + thenByUsername() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'username', Sort.asc); + }); + } + + QueryBuilder + thenByUsernameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'username', Sort.desc); + }); + } +} + +extension TrackPreferenceQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByOAuth( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'oAuth', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByUsername( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'username', caseSensitive: caseSensitive); + }); + } +} + +extension TrackPreferenceQueryProperty + on QueryBuilder { + QueryBuilder syncIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'syncId'); + }); + } + + QueryBuilder oAuthProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'oAuth'); + }); + } + + QueryBuilder usernameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'username'); + }); + } +} diff --git a/lib/models/track_search.dart b/lib/models/track_search.dart new file mode 100644 index 0000000..43b9723 --- /dev/null +++ b/lib/models/track_search.dart @@ -0,0 +1,51 @@ +class TrackSearch { + int? id; + + int? mediaId; + + int? syncId; + + String? title; + + String? lastChapterRead; + + int? totalChapter; + + double? score; + + String? status; + + int? startedReadingDate; + + int? finishedReadingDate; + + String? trackingUrl; + + String? coverUrl; + + String? summary; + + String? publishingStatus; + + String? publishingType; + + String? startDate; + + TrackSearch( + {this.id, + this.mediaId, + this.syncId, + this.title, + this.lastChapterRead, + this.totalChapter, + this.score, + this.status = '', + this.startedReadingDate, + this.finishedReadingDate, + this.trackingUrl, + this.coverUrl= '', + this.publishingStatus= '', + this.publishingType= '', + this.startDate= '', + this.summary= ''}); +} diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 9209137..40a6e51 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -13,11 +13,20 @@ import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/download.dart'; import 'package:mangayomi/models/manga.dart'; +import 'package:mangayomi/models/track.dart'; +import 'package:mangayomi/models/track_preference.dart'; +import 'package:mangayomi/models/track_search.dart'; import 'package:mangayomi/modules/library/providers/local_archive.dart'; +import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart'; +import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart'; +import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; +import 'package:mangayomi/services/myanimelist.dart'; import 'package:mangayomi/sources/utils/utils.dart'; import 'package:mangayomi/utils/cached_network.dart'; import 'package:mangayomi/utils/colors.dart'; +import 'package:mangayomi/utils/constant.dart'; +import 'package:mangayomi/utils/date.dart'; import 'package:mangayomi/utils/headers.dart'; import 'package:mangayomi/utils/media_query.dart'; import 'package:mangayomi/utils/utils.dart'; @@ -30,6 +39,7 @@ import 'package:mangayomi/modules/manga/detail/widgets/chapter_sort_list_tile_wi import 'package:mangayomi/modules/manga/download/providers/download_provider.dart'; import 'package:mangayomi/modules/widgets/error_text.dart'; import 'package:mangayomi/modules/widgets/progress_center.dart'; +import 'package:numberpicker/numberpicker.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:share_plus/share_plus.dart'; @@ -1286,11 +1296,64 @@ class _MangaDetailViewState extends ConsumerState mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ widget.action!, - const SizedBox( - width: 5, - ), + StreamBuilder( + stream: isar.trackPreferences + .filter() + .syncIdIsNotNull() + .watch(fireImmediately: true), + builder: (context, snapshot) { + List? entries = + snapshot.hasData ? snapshot.data! : []; + if (entries.isEmpty) { + return Container(); + } + return SizedBox( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + elevation: 0), + onPressed: () { + _trackingDialog(entries); + }, + child: StreamBuilder( + stream: isar.tracks + .filter() + .idIsNotNull() + .mangaIdEqualTo(widget.manga!.id!) + .watch(fireImmediately: true), + builder: (context, snapshot) { + List? trackRes = + snapshot.hasData ? snapshot.data : []; + bool isNotEmpty = trackRes!.isNotEmpty; + Color color = isNotEmpty + ? primaryColor(context) + : secondaryColor(context); + return Column( + children: [ + Icon( + isNotEmpty + ? Icons.done + : Icons.screen_rotation_alt_rounded, + size: 22, + color: color, + ), + const SizedBox( + height: 4, + ), + Text( + isNotEmpty + ? '${trackRes.length} traker' + : 'Tracking', + style: TextStyle(fontSize: 13, color: color), + ), + ], + ); + }), + ), + ); + }), SizedBox( - width: isTablet(context) ? null : mediaWidth(context, 0.4), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).scaffoldBackgroundColor, @@ -1549,4 +1612,770 @@ class _MangaDetailViewState extends ConsumerState ); }); } + + _trackingDialog(List? entries) { + DraggableMenu.open( + context, + DraggableMenu( + ui: SoftModernDraggableMenu(radius: 20, barItem: Container()), + maxHeight: mediaHeight(context, 0.9), + minHeight: 80, + child: Material( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(20), + clipBehavior: Clip.antiAliasWithSaveLayer, + child: ListView.separated( + padding: const EdgeInsets.all(0), + itemCount: entries!.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return StreamBuilder( + stream: isar.tracks + .filter() + .idIsNotNull() + .syncIdEqualTo(entries[index].syncId) + .mangaIdEqualTo(widget.manga!.id!) + .watch(fireImmediately: true), + builder: (context, snapshot) { + List? trackRes = + snapshot.hasData ? snapshot.data : []; + + return trackRes!.isNotEmpty + ? Container( + decoration: BoxDecoration(border: Border.all()), + child: Column( + children: [ + Row( + children: [ + Image.asset( + "assets/tracker_mal.webp", + height: 30, + ), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(0), + + shape: RoundedRectangleBorder( + side: BorderSide( + color: + primaryColor(context), + width: 0.2), + borderRadius: + BorderRadius.circular( + 0))), + onPressed: () async { + final trackSearch = + await trackersSearchraggableMenu( + syncId: entries[index] + .syncId!, + query: trackRes + .first.title!) + as TrackSearch?; + if (trackSearch != null) { + await ref + .read(trackStateProvider( + track: null) + .notifier) + .setTrackSearch( + trackSearch, + widget.manga!.id!, + entries[index].syncId!); + } + }, + child: Row( + children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.all(8), + child: Text( + trackRes.first.title!, + style: TextStyle( + color: secondaryColor( + context),fontSize: 16, + fontWeight: + FontWeight.bold), + ), + ), + ), + IconButton( + onPressed: () { + ref + .read(tracksProvider( + syncId: entries[ + index] + .syncId!) + .notifier) + .deleteTrackManga( + trackRes.first); + }, + icon: const Icon( + Icons.cancel_outlined)) + ], + ), + ), + ) + ], + ), + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + side: BorderSide( + color: primaryColor( + context), + width: 0.2), + borderRadius: + BorderRadius.circular( + 0))), + 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: trackRes + .first) + .notifier) + .getStatusList() + .length, + itemBuilder: + (context, + index) { + final status = ref + .read(trackStateProvider( + track: + trackRes.first) + .notifier) + .getStatusList()[index]; + return RadioListTile( + dense: true, + contentPadding: + const EdgeInsets + .all(0), + value: status, + groupValue: + trackRes + .first + .status, + onChanged: + (value) { + ref + .read(trackStateProvider( + track: trackRes.first..status = status) + .notifier) + .updateItem(); + 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)), + )), + ], + ) + ], + ); + }); + }, + child: Text(getTrackStatus( + trackRes.first.status))), + ), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + side: BorderSide( + color: primaryColor( + context), + width: 0.2), + borderRadius: + BorderRadius.circular( + 0))), + onPressed: () { + int currentIntValue = trackRes + .first.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: trackRes + .first + .totalChapter != + 0 + ? trackRes + .first + .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: trackRes.first..lastChapterRead = currentIntValue) + .notifier) + .updateItem(); + Navigator.pop( + context); + }, + child: Text( + "OK", + style: TextStyle( + color: primaryColor( + context)), + )), + ], + ) + ], + ); + }); + }, + child: Text(trackRes + .first.totalChapter != + 0 + ? "${trackRes.first.lastChapterRead}/${trackRes.first.totalChapter}" + : "${trackRes.first.lastChapterRead == 0 ? "Not Started" : trackRes.first.lastChapterRead}")), + ), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + side: BorderSide( + color: primaryColor( + context), + width: 0.2), + borderRadius: + BorderRadius.circular( + 0))), + onPressed: () { + int currentIntValue = + trackRes.first.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: 10, + 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: trackRes.first..score = currentIntValue) + .notifier) + .updateItem(); + Navigator.pop( + context); + }, + child: Text( + "OK", + style: TextStyle( + color: primaryColor( + context)), + )), + ], + ) + ], + ); + }); + }, + child: Text( + trackRes.first.score != 0 + ? trackRes.first.score + .toString() + : "Score")), + ) + ], + ), + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + side: BorderSide( + color: + primaryColor(context), + width: 0.2), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular( + 0))), + 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: trackRes.first + ..startedReadingDate = + newDate + .millisecondsSinceEpoch) + .notifier) + .updateItem(); + }, + child: Text(trackRes.first + .startedReadingDate != + null && + trackRes.first + .startedReadingDate! > + DateTime(1970) + .millisecondsSinceEpoch + ? dateFormat( + trackRes.first + .startedReadingDate + .toString(), + ref: ref, + useRelativeTimesTamps: false, + context: context) + : "Start date")), + ), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + side: BorderSide( + color: primaryColor( + context), + width: 0.2), + borderRadius: + BorderRadius.circular( + 0))), + 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: trackRes.first + ..startedReadingDate = + newDate + .millisecondsSinceEpoch) + .notifier) + .updateItem(); + }, + child: Text(trackRes.first + .finishedReadingDate != + null && + trackRes.first.finishedReadingDate! > + DateTime(1970) + .millisecondsSinceEpoch + ? dateFormat( + trackRes.first + .finishedReadingDate + .toString(), + ref: ref, + useRelativeTimesTamps: false, + context: context) + : "Finish date")), + ) + ], + ), + ], + ), + ) + : TrackListile( + onTap: () async { + final trackSearch = + await trackersSearchraggableMenu( + syncId: entries[index].syncId!, + query: widget.manga!.name!) + as TrackSearch?; + if (trackSearch != null) { + await ref + .read(trackStateProvider(track: null) + .notifier) + .setTrackSearch( + trackSearch, + widget.manga!.id!, + entries[index].syncId!); + } + }, + id: entries[index].syncId!, + entries: const []); + }); + }, + separatorBuilder: (BuildContext context, int index) { + return const Divider(); + }, + ), + ), + )); + } + + trackersSearchraggableMenu( + {required int syncId, required String query}) async { + return await DraggableMenu.open( + context, + DraggableMenu( + blockMenuClosing: true, + ui: SoftModernDraggableMenu( + radius: 20, + barItem: Container( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20))), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon(Icons.clear)), + ) + ], + ), + )), + maxHeight: mediaHeight(context, 0.9), + child: TrackerWidgetSearch( + query: query, + syncId: syncId, + ))); + } +} + +class TrackerWidgetSearch extends ConsumerStatefulWidget { + final int syncId; + final String query; + const TrackerWidgetSearch( + {required this.syncId, required this.query, super.key}); + + @override + ConsumerState createState() => + _TrackerWidgetSearchState(); +} + +class _TrackerWidgetSearchState extends ConsumerState { + @override + initState() { + _init(); + super.initState(); + } + + late List tracks = []; + _init() async { + await Future.delayed(const Duration(microseconds: 100)); + tracks = await ref + .read(myAnimeListProvider(syncId: widget.syncId).notifier) + .search(widget.query); + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + + late String query = widget.query; + late final _controller = TextEditingController(text: query); + bool _isLoading = true; + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + clipBehavior: Clip.antiAliasWithSaveLayer, + child: _isLoading + ? const ProgressCenter() + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SizedBox( + height: mediaHeight(context, 0.8), + child: Column( + children: [ + Flexible( + child: ListView.separated( + padding: const EdgeInsets.only(top: 20), + itemCount: tracks.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(top: 5), + child: InkWell( + onTap: () { + Navigator.pop(context, tracks[index]); + }, + child: Column( + children: [ + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Material( + borderRadius: BorderRadius.circular(5), + color: Colors.transparent, + clipBehavior: + Clip.antiAliasWithSaveLayer, + child: Ink.image( + height: 120, + width: 80, + fit: BoxFit.cover, + image: CachedNetworkImageProvider( + tracks[index].coverUrl!), + ), + ), + const SizedBox( + width: 10, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox( + width: mediaWidth(context, 0.6), + child: Text( + tracks[index].title!, + style: const TextStyle( + fontWeight: FontWeight.bold), + ), + ), + Row( + children: [ + const Text( + "Type : ", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12), + ), + Text( + tracks[index].publishingType!, + style: const TextStyle( + fontSize: 12), + ), + ], + ), + Row( + children: [ + const Text( + "Status : ", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12), + ), + Text( + tracks[index].publishingStatus!, + style: const TextStyle( + fontSize: 12), + ), + ], + ), + ], + ) + ], + ), + Text( + tracks[index].summary!, + style: const TextStyle( + fontSize: 12, + overflow: TextOverflow.ellipsis, + ), + maxLines: 3, + ), + ], + ), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return const Divider(); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TextFormField( + controller: _controller, + keyboardType: TextInputType.text, + onChanged: (d) { + setState(() { + query = d; + }); + }, + onFieldSubmitted: (d) async { + setState(() { + _isLoading = true; + }); + tracks = await ref + .read(myAnimeListProvider(syncId: widget.syncId) + .notifier) + .search(d); + if (mounted) { + setState(() { + _isLoading = false; + }); + } + }, + decoration: InputDecoration( + isDense: true, + filled: true, + fillColor: Colors.transparent, + suffixIcon: query.isEmpty + ? null + : IconButton( + onPressed: () { + _controller.clear(); + }, + icon: const Icon(Icons.clear)), + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: primaryColor(context)), + ), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: primaryColor(context)), + ), + border: OutlineInputBorder( + borderSide: + BorderSide(color: primaryColor(context)))), + ), + ), + ], + ), + ), + ), + ); + } } diff --git a/lib/modules/manga/detail/manga_details_view.dart b/lib/modules/manga/detail/manga_details_view.dart index e48954e..b8159cf 100644 --- a/lib/modules/manga/detail/manga_details_view.dart +++ b/lib/modules/manga/detail/manga_details_view.dart @@ -122,7 +122,6 @@ class _MangaDetailsViewState extends ConsumerState { ), action: widget.manga.favorite ? SizedBox( - width: isTablet(context) ? null : mediaWidth(context, 0.4), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: @@ -154,46 +153,43 @@ class _MangaDetailsViewState extends ConsumerState { ), ), ) - : SizedBox( - width: isTablet(context) ? null : mediaWidth(context, 0.4), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - elevation: 0), - onPressed: () { - final checkCategoryList = - isar.categorys.filter().idIsNotNull().isNotEmptySync(); - if (checkCategoryList) { - _openCategory(widget.manga); - } else { - final model = widget.manga; - isar.writeTxnSync(() { - model.favorite = true; - model.dateAdded = DateTime.now().millisecondsSinceEpoch; - isar.mangas.putSync(model); - }); - } - }, - child: Column( - children: [ - Icon( - Icons.favorite_border_rounded, - size: 22, - color: secondaryColor(context), - ), - const SizedBox( - height: 4, - ), - Text( - l10n.add_to_library, - style: TextStyle( - color: secondaryColor(context), fontSize: 13), - ) - ], + : ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + elevation: 0), + onPressed: () { + final checkCategoryList = + isar.categorys.filter().idIsNotNull().isNotEmptySync(); + if (checkCategoryList) { + _openCategory(widget.manga); + } else { + final model = widget.manga; + isar.writeTxnSync(() { + model.favorite = true; + model.dateAdded = DateTime.now().millisecondsSinceEpoch; + isar.mangas.putSync(model); + }); + } + }, + child: Column( + children: [ + Icon( + Icons.favorite_border_rounded, + size: 22, + color: secondaryColor(context), ), - ), + const SizedBox( + height: 4, + ), + Text( + l10n.add_to_library, + style: TextStyle( + color: secondaryColor(context), fontSize: 13), + ) + ], ), + ), manga: widget.manga, isExtended: (value) { ref.read(isExtendedStateProvider.notifier).update(value); diff --git a/lib/modules/manga/detail/providers/track_state_providers.dart b/lib/modules/manga/detail/providers/track_state_providers.dart new file mode 100644 index 0000000..b43b2b1 --- /dev/null +++ b/lib/modules/manga/detail/providers/track_state_providers.dart @@ -0,0 +1,63 @@ +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:riverpod_annotation/riverpod_annotation.dart'; +part 'track_state_providers.g.dart'; + +@riverpod +class TrackState extends _$TrackState { + @override + Track build({Track? track}) { + return track!; + } + + Future updateItem() async { + final updateTrack = await ref + .read(myAnimeListProvider(syncId: 1).notifier) + .updateItem(track!); + + ref + .read(tracksProvider(syncId: track!.syncId!).notifier) + .updateTrackManga(updateTrack); + } + + Future setTrackSearch( + TrackSearch trackSearch, int mangaId, int syncId) async { + + final track = Track( + mangaId: mangaId, + score: 0, + mediaId: trackSearch.mediaId, + trackingUrl: trackSearch.trackingUrl, + title: trackSearch.title, + lastChapterRead: 0, + totalChapter: 0, + status: TrackStatus.planToRead, + startedReadingDate: 0, + finishedReadingDate: 0); + final findTrack = await ref + .read(myAnimeListProvider(syncId: 1).notifier) + .findListItem(track); + + ref + .read(tracksProvider(syncId: syncId).notifier) + .updateTrackManga(findTrack); + } + + List getStatusList() { + List statusList = []; + List list = []; + if (track!.syncId == 1) { + statusList = ref + .read(myAnimeListProvider(syncId: 1).notifier) + .myAnimeListStatusList; + } + for (var element in TrackStatus.values) { + if (statusList.contains(element)) { + list.add(element); + } + } + return list; + } +} diff --git a/lib/modules/manga/detail/providers/track_state_providers.g.dart b/lib/modules/manga/detail/providers/track_state_providers.g.dart new file mode 100644 index 0000000..646b4e2 --- /dev/null +++ b/lib/modules/manga/detail/providers/track_state_providers.g.dart @@ -0,0 +1,125 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'track_state_providers.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$trackStateHash() => r'3e7b916624f8035766d9a6408812bf1cc1247915'; + +/// 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 _$TrackState extends BuildlessAutoDisposeNotifier { + late final Track? track; + + Track build({ + Track? track, + }); +} + +/// See also [TrackState]. +@ProviderFor(TrackState) +const trackStateProvider = TrackStateFamily(); + +/// See also [TrackState]. +class TrackStateFamily extends Family { + /// See also [TrackState]. + const TrackStateFamily(); + + /// See also [TrackState]. + TrackStateProvider call({ + Track? track, + }) { + return TrackStateProvider( + track: track, + ); + } + + @override + TrackStateProvider getProviderOverride( + covariant TrackStateProvider provider, + ) { + return call( + track: provider.track, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'trackStateProvider'; +} + +/// See also [TrackState]. +class TrackStateProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [TrackState]. + TrackStateProvider({ + this.track, + }) : super.internal( + () => TrackState()..track = track, + from: trackStateProvider, + name: r'trackStateProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$trackStateHash, + dependencies: TrackStateFamily._dependencies, + allTransitiveDependencies: + TrackStateFamily._allTransitiveDependencies, + ); + + final Track? track; + + @override + bool operator ==(Object other) { + return other is TrackStateProvider && other.track == track; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, track.hashCode); + + return _SystemHash.finish(hash); + } + + @override + Track runNotifierBuild( + covariant TrackState notifier, + ) { + return notifier.build( + track: track, + ); + } +} +// 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 diff --git a/lib/modules/more/settings/downloads/providers/downloads_state_provider.dart b/lib/modules/more/settings/downloads/providers/downloads_state_provider.dart index fe23117..d734dec 100644 --- a/lib/modules/more/settings/downloads/providers/downloads_state_provider.dart +++ b/lib/modules/more/settings/downloads/providers/downloads_state_provider.dart @@ -1,5 +1,4 @@ import 'dart:io'; - import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/providers/storage_provider.dart'; diff --git a/lib/modules/more/settings/settings_screen.dart b/lib/modules/more/settings/settings_screen.dart index 189f522..911a689 100644 --- a/lib/modules/more/settings/settings_screen.dart +++ b/lib/modules/more/settings/settings_screen.dart @@ -41,6 +41,11 @@ class SettingsScreen extends StatelessWidget { subtitle: l10n.downloads_subtitle, icon: Icons.download_outlined, onTap: () => context.push('/downloads')), + ListTileWidget( + title: "Tracking", + subtitle: "", + icon: Icons.screen_rotation_alt_rounded, + onTap: () => context.push('/track')), ListTileWidget( title: l10n.browse, subtitle: l10n.browse_subtitle, diff --git a/lib/modules/more/settings/track/myanimelist/model.dart b/lib/modules/more/settings/track/myanimelist/model.dart new file mode 100644 index 0000000..c57d6b8 --- /dev/null +++ b/lib/modules/more/settings/track/myanimelist/model.dart @@ -0,0 +1,26 @@ +class MyAnimeListOAuth { + String? tokenType; + int? expiresIn; + String? accessToken; + String? refreshToken; + + MyAnimeListOAuth( + {this.tokenType, this.expiresIn, this.accessToken, this.refreshToken}); + + MyAnimeListOAuth.fromJson(Map json) { + tokenType = json['token_type']; + expiresIn = (json['expires_in'] as int) * 1000 + + DateTime.now().millisecondsSinceEpoch; + accessToken = json['access_token']; + refreshToken = json['refresh_token']; + } + + Map toJson() { + final Map data = {}; + data['token_type'] = tokenType; + data['expires_in'] = expiresIn; + data['access_token'] = accessToken; + data['refresh_token'] = refreshToken; + return data; + } +} diff --git a/lib/modules/more/settings/track/providers/track_providers.dart b/lib/modules/more/settings/track/providers/track_providers.dart new file mode 100644 index 0000000..2c3b43b --- /dev/null +++ b/lib/modules/more/settings/track/providers/track_providers.dart @@ -0,0 +1,44 @@ +import 'package:isar/isar.dart'; +import 'package:mangayomi/main.dart'; +import 'package:mangayomi/models/track.dart'; +import 'package:mangayomi/models/track_preference.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'track_providers.g.dart'; + +@riverpod +class Tracks extends _$Tracks { + @override + TrackPreference? build({required int? syncId}) { + return isar.trackPreferences.getSync(syncId!); + } + + void login(TrackPreference trackPreference) { + isar.writeTxnSync(() { + isar.trackPreferences.putSync(trackPreference); + }); + } + + void logout() { + isar.writeTxnSync(() { + isar.trackPreferences.deleteSync(syncId!); + }); + } + + void updateTrackManga(Track track) { + final tra = isar.tracks + .filter() + .syncIdEqualTo(syncId) + .mangaIdEqualTo(track.mangaId) + .findAllSync(); + if (tra.isNotEmpty) { + if (tra.first.mediaId != track.mangaId) { + track.id = tra.first.id; + } + } + isar.writeTxnSync(() => isar.tracks.putSync(track..syncId = syncId)); + } + + void deleteTrackManga(Track track) { + isar.writeTxnSync(() => isar.tracks.deleteSync(track.id!)); + } +} diff --git a/lib/modules/more/settings/track/providers/track_providers.g.dart b/lib/modules/more/settings/track/providers/track_providers.g.dart new file mode 100644 index 0000000..13f5eb7 --- /dev/null +++ b/lib/modules/more/settings/track/providers/track_providers.g.dart @@ -0,0 +1,124 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'track_providers.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$tracksHash() => r'11a1c74c458db7f5b790de1451d239662cec1ed3'; + +/// 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 _$Tracks extends BuildlessAutoDisposeNotifier { + late final int? syncId; + + TrackPreference? build({ + required int? syncId, + }); +} + +/// See also [Tracks]. +@ProviderFor(Tracks) +const tracksProvider = TracksFamily(); + +/// See also [Tracks]. +class TracksFamily extends Family { + /// See also [Tracks]. + const TracksFamily(); + + /// See also [Tracks]. + TracksProvider call({ + required int? syncId, + }) { + return TracksProvider( + syncId: syncId, + ); + } + + @override + TracksProvider getProviderOverride( + covariant TracksProvider provider, + ) { + return call( + syncId: provider.syncId, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'tracksProvider'; +} + +/// See also [Tracks]. +class TracksProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [Tracks]. + TracksProvider({ + required this.syncId, + }) : super.internal( + () => Tracks()..syncId = syncId, + from: tracksProvider, + name: r'tracksProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$tracksHash, + dependencies: TracksFamily._dependencies, + allTransitiveDependencies: TracksFamily._allTransitiveDependencies, + ); + + final int? syncId; + + @override + bool operator ==(Object other) { + return other is TracksProvider && 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 + TrackPreference? runNotifierBuild( + covariant Tracks 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 diff --git a/lib/modules/more/settings/track/track.dart b/lib/modules/more/settings/track/track.dart new file mode 100644 index 0000000..6883208 --- /dev/null +++ b/lib/modules/more/settings/track/track.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +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'; + +class TrackScreen extends ConsumerWidget { + const TrackScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: const Text("Tracking"), + ), + body: StreamBuilder( + stream: isar.trackPreferences + .filter() + .syncIdIsNotNull() + .watch(fireImmediately: true), + builder: (context, snapshot) { + List? entries = + snapshot.hasData ? snapshot.data : []; + return Column( + children: [ + TrackListile( + onTap: () async { + await ref + .read(myAnimeListProvider(syncId: 1).notifier) + .login(); + }, + id: 1, + entries: entries!) + ], + ); + }), + ); + } +} diff --git a/lib/modules/more/settings/track/widgets/track_listile.dart b/lib/modules/more/settings/track/widgets/track_listile.dart new file mode 100644 index 0000000..7bdbb1a --- /dev/null +++ b/lib/modules/more/settings/track/widgets/track_listile.dart @@ -0,0 +1,81 @@ +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'; + +class TrackListile extends ConsumerWidget { + final VoidCallback onTap; + final int id; + final List entries; + const TrackListile( + {super.key, + required this.onTap, + required this.id, + required this.entries}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bool isLogged = + entries.where((element) => element.syncId == id).isNotEmpty; + return ListTile( + leading: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.asset( + _track(id).$1, + height: 30, + ), + ), + trailing: isLogged + ? const Icon( + Icons.check, + size: 30, + color: Colors.green, + ) + : null, + onTap: isLogged + ? () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text( + "Log out from ${_track(id).$2}", + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("Cancel")), + const SizedBox( + width: 15, + ), + TextButton( + onPressed: () { + ref + .read(tracksProvider(syncId: id).notifier) + .logout(); + Navigator.pop(context); + }, + child: const Text("Log out")), + ], + ) + ], + ); + }); + } + : onTap, + title: Text(_track(id).$2), + ); + } +} + +(String, String) _track(int id) { + return switch (id) { + 1 => ("assets/tracker_mal.webp", "MyAnimeList"), + _ => ("", ""), + }; +} diff --git a/lib/modules/more/widgets/list_tile_widget.dart b/lib/modules/more/widgets/list_tile_widget.dart index 9818f02..e23a95d 100644 --- a/lib/modules/more/widgets/list_tile_widget.dart +++ b/lib/modules/more/widgets/list_tile_widget.dart @@ -19,12 +19,12 @@ class ListTileWidget extends StatelessWidget { Widget build(BuildContext context) { return ListTile( onTap: onTap, - subtitle: subtitle != null - ? Text( - subtitle!, - style: TextStyle(fontSize: 11, color: secondaryColor(context)), - ) - : null, + // subtitle: subtitle != null + // ? Text( + // subtitle!, + // style: TextStyle(fontSize: 11, color: secondaryColor(context)), + // ) + // : null, leading: SizedBox( height: 40, child: Icon( diff --git a/lib/providers/l10n_providers.g.dart b/lib/providers/l10n_providers.g.dart index 22c4cfd..f70ba92 100644 --- a/lib/providers/l10n_providers.g.dart +++ b/lib/providers/l10n_providers.g.dart @@ -6,7 +6,7 @@ part of 'l10n_providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$l10nLocaleStateHash() => r'a1f842ccc55a111698bf7e096644858008014123'; +String _$l10nLocaleStateHash() => r'1c6cb9d6c0a56d54a6a5e7bc1b2dbc6c29538593'; /// See also [L10nLocaleState]. @ProviderFor(L10nLocaleState) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index d659973..2a4d527 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -10,6 +10,8 @@ import 'package:mangayomi/models/history.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/models/source.dart'; +import 'package:mangayomi/models/track.dart'; +import 'package:mangayomi/models/track_preference.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:path/path.dart' as path; @@ -103,7 +105,9 @@ class StorageProvider { HistorySchema, DownloadSchema, SourceSchema, - SettingsSchema + SettingsSchema, + TrackPreferenceSchema, + TrackSchema ], directory: dir!.path, name: "mangayomiDb", inspector: inspector!); if (isar.settings.filter().idEqualTo(227).isEmptySync()) { diff --git a/lib/router/router.dart b/lib/router/router.dart index a0851ce..7dfa551 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -5,6 +5,7 @@ import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/source.dart'; import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart'; import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart'; +import 'package:mangayomi/modules/more/settings/track/track.dart'; import 'package:mangayomi/modules/updates/updates_screen.dart'; import 'package:mangayomi/modules/webview/webview.dart'; import 'package:mangayomi/modules/browse/browse_screen.dart'; @@ -236,6 +237,19 @@ class RouterNotifier extends ChangeNotifier { ); }, ), + GoRoute( + path: "/track", + name: "track", + builder: (context, state) { + return const TrackScreen(); + }, + pageBuilder: (context, state) { + return CustomTransition( + key: state.pageKey, + child: const TrackScreen(), + ); + }, + ), GoRoute( path: "/sourceFilter", name: "sourceFilter", diff --git a/lib/services/get_chapter_url.dart b/lib/services/get_chapter_url.dart index 7ece5e2..455f5af 100644 --- a/lib/services/get_chapter_url.dart +++ b/lib/services/get_chapter_url.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:dart_eval/stdlib/core.dart'; +import 'package:mangayomi/eval/bridge_class/manga_model.dart'; import 'package:mangayomi/eval/bridge_class/model.dart'; import 'package:mangayomi/eval/compiler/compiler.dart'; import 'package:mangayomi/main.dart'; @@ -16,8 +17,6 @@ import 'package:mangayomi/sources/utils/utils.dart'; import 'package:mangayomi/utils/reg_exp_matcher.dart'; import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; - -import '../eval/bridge_class/manga_model.dart'; part 'get_chapter_url.g.dart'; class GetChapterUrlModel { @@ -52,11 +51,12 @@ Future getChapterUrl( final storageProvider = StorageProvider(); path = await storageProvider.getMangaChapterDirectory(chapter); final mangaDirectory = await storageProvider.getMangaMainDirectory(chapter); - final source = - getSource(chapter.manga.value!.lang!, chapter.manga.value!.source!); + List archiveImages = []; final isLocalArchive = (chapter.archivePath ?? '').isNotEmpty; if (!chapter.manga.value!.isLocalArchive!) { + final source = + getSource(chapter.manga.value!.lang!, chapter.manga.value!.source!); if (isarPageUrls.isNotEmpty && isarPageUrls.first.urls != null && isarPageUrls.first.urls!.isNotEmpty) { diff --git a/lib/services/get_chapter_url.g.dart b/lib/services/get_chapter_url.g.dart index 56dac5a..78efc9a 100644 --- a/lib/services/get_chapter_url.g.dart +++ b/lib/services/get_chapter_url.g.dart @@ -6,7 +6,7 @@ part of 'get_chapter_url.dart'; // RiverpodGenerator // ************************************************************************** -String _$getChapterUrlHash() => r'46fc82b7ddeb8f6f1658dbc942db69f651505aad'; +String _$getChapterUrlHash() => r'289384e134e65a4c0a78724d3ea4b98a605e2a52'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/services/myanimelist.dart b/lib/services/myanimelist.dart new file mode 100644 index 0000000..57f971e --- /dev/null +++ b/lib/services/myanimelist.dart @@ -0,0 +1,294 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; +import 'package:mangayomi/models/track.dart'; +import 'package:mangayomi/models/track_preference.dart'; +import 'package:mangayomi/models/track_search.dart'; +import 'package:mangayomi/modules/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 'myanimelist.g.dart'; + +@riverpod +class MyAnimeList extends _$MyAnimeList { + String baseOAuthUrl = 'https://myanimelist.net/v1/oauth2'; + String baseApiUrl = 'https://api.myanimelist.net/v2'; + String codeVerifier = ""; + String clientId = (Platform.isWindows || Platform.isLinux) + ? '39e9be346b4e7dbcc59a98357e2f8472' + : '0c9100ccd443ddb441a319a881180f7f'; + int listPaginationAmount = 250; + + @override + build({required int syncId}) {} + + Future 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 queryParams = Uri.parse(uri).queryParameters; + if (queryParams['code'] == null) return null; + + final oAuth = await _getOAuth(queryParams['code']!); + final myAnimeListoAuth = + MyAnimeListOAuth.fromJson(oAuth as Map); + final username = await _getUserName(myAnimeListoAuth.accessToken!); + ref.read(tracksProvider(syncId: syncId).notifier).login(TrackPreference( + syncId: syncId, + username: username, + oAuth: jsonEncode(myAnimeListoAuth.toJson()))); + + return true; + } catch (_) { + return false; + } + } + + Future _getAccesToken() async { + final track = ref.watch(tracksProvider(syncId: syncId)); + final myAnimeListoAuth = MyAnimeListOAuth.fromJson( + jsonDecode(track!.oAuth!) as Map); + final expiresIn = + DateTime.fromMillisecondsSinceEpoch(myAnimeListoAuth.expiresIn!); + if (DateTime.now().isAfter(expiresIn)) { + final params = { + 'client_id': clientId, + 'grant_type': 'refresh_token', + 'refresh_token': myAnimeListoAuth.refreshToken, + }; + final response = + await http.post(Uri.parse('$baseOAuthUrl/token'), body: params); + final oAuth = MyAnimeListOAuth.fromJson( + jsonDecode(response.body) as Map); + final username = await _getUserName(oAuth.accessToken!); + ref.read(tracksProvider(syncId: syncId).notifier).login(TrackPreference( + syncId: syncId, + username: username, + oAuth: jsonEncode(oAuth.toJson()))); + return oAuth.accessToken!; + } + return myAnimeListoAuth.accessToken!; + } + + Future> search(String query) async { + final accessToken = await _getAccesToken(); + final url = Uri.parse('$baseApiUrl/manga').replace(queryParameters: { + 'q': query.trim(), + 'nsfw': 'true', + }); + final result = + await http.get(url, headers: {'Authorization': 'Bearer $accessToken'}); + final res = jsonDecode(result.body) as Map; + + List mangaIds = + (res['data'] as List).map((e) => e['node']["id"] as int).toList(); + List trackSearchResult = []; + for (var mangaId in mangaIds) { + final trackSearch = await getMangaDetails(mangaId, accessToken); + trackSearchResult.add(trackSearch); + } + + return trackSearchResult + .where((element) => !element.publishingType!.contains("novel")) + .toList(); + } + + Future getMangaDetails(int id, String accessToken) async { + final url = Uri.parse('$baseApiUrl/manga/$id').replace(queryParameters: { + 'fields': + 'id,title,synopsis,num_chapters,main_picture,status,media_type,start_date', + }); + + final result = + await http.get(url, headers: {'Authorization': 'Bearer $accessToken'}); + final res = jsonDecode(result.body) as Map; + + return TrackSearch( + mediaId: res["id"], + summary: res["synopsis"] ?? "", + totalChapter: res["num_chapters"], + coverUrl: res["main_picture"]["large"] ?? "", + title: res["title"], + startDate: res["start_date"] ?? "", + publishingType: res["media_type"].toString().replaceAll("_", " "), + publishingStatus: res["status"].toString().replaceAll("_", " "), + trackingUrl: "https://myanimelist.net/manga/${res["id"]}"); + } + + String _convertToIsoDate(int? epochTime) { + String date = ""; + try { + date = DateFormat("yyyy-MM-dd", "en_US") + .format(DateTime.fromMillisecondsSinceEpoch(epochTime!)); + } catch (_) {} + return date; + } + + String _codeVerifier() { + final random = Random.secure(); + final values = List.generate(200, (i) => random.nextInt(256)); + codeVerifier = base64UrlEncode(values).substring(0, 128); + return codeVerifier; + } + + String _authUrl() { + _codeVerifier(); + return '$baseOAuthUrl/authorize?client_id=$clientId&code_challenge=$codeVerifier&response_type=code'; + } + + TrackStatus _getMALTrackStatus(String status) { + return switch (status) { + "reading" => TrackStatus.reading, + "completed" => TrackStatus.completed, + "on_hold" => TrackStatus.onHold, + "dropped" => TrackStatus.dropped, + "plan_to_read" => TrackStatus.planToRead, + _ => TrackStatus.rereading, + }; + } + + List myAnimeListStatusList = [ + TrackStatus.reading, + TrackStatus.completed, + TrackStatus.onHold, + TrackStatus.dropped, + TrackStatus.planToRead, + TrackStatus.rereading + ]; + + String? toMyAnimeListStatus(TrackStatus status) { + return switch (status) { + TrackStatus.reading => "reading", + TrackStatus.completed => "completed", + TrackStatus.onHold => "on_hold", + TrackStatus.dropped => "dropped", + TrackStatus.planToRead => "plan_to_read", + TrackStatus.rereading => "reading", + }; + } + + Future _getOAuth(String code) async { + final params = { + 'client_id': clientId, + 'code': code, + 'code_verifier': codeVerifier, + 'grant_type': 'authorization_code', + }; + final response = + await http.post(Uri.parse('$baseOAuthUrl/token'), body: params); + return jsonDecode(response.body); + } + + Future _getUserName(String accessToken) async { + final response = await http.get(Uri.parse('$baseApiUrl/users/@me'), + headers: {'Authorization': 'Bearer $accessToken'}); + return jsonDecode(response.body)['name']; + } + + Future findListItem(Track track) async { + final accessToken = await _getAccesToken(); + final uri = Uri.parse('$baseApiUrl/manga/${track.mediaId}') + .replace(queryParameters: { + 'fields': 'num_chapters,my_list_status{start_date,finish_date}', + }); + final response = + await http.get(uri, headers: {'Authorization': 'Bearer $accessToken'}); + final mJson = jsonDecode(response.body); + track.totalChapter = mJson['num_chapters'] ?? 0; + if (mJson['my_list_status'] != null) { + track = parseMangaItem(mJson["my_list_status"], track); + } else { + track = await updateItem(track); + } + return track; + } + + Future> findListItems(String query, + {int offset = 0}) async { + final accessToken = await _getAccesToken(); + final json = await getListPage(offset); + final obj = json['data'] as List; + List mangaIds = obj + .where((data) => data['node']['title'] + .toString() + .toLowerCase() + .contains(query.toLowerCase())) + .map((data) => data['node']['id'] as int) + .toList(); + List trackSearchResult = []; + for (var mangaId in mangaIds) { + final trackSearch = await getMangaDetails(mangaId, accessToken); + trackSearchResult.add(trackSearch); + } + + if (json['paging']['next'] != null) { + final newV = + await findListItems(query, offset: offset + listPaginationAmount); + trackSearchResult.addAll(newV); + } + return trackSearchResult; + } + + Future> getListPage(int offset) async { + final urlBuilder = + Uri.parse('$baseApiUrl/users/@me/mangalist').replace(queryParameters: { + 'fields': 'list_status{start_date,finish_date}', + 'limit': listPaginationAmount.toString(), + }); + if (offset > 0) { + urlBuilder.queryParameters['offset'] = offset.toString(); + } + final url = urlBuilder.toString(); + final response = + await http.get(Uri.parse(url), headers: {'X-MAL-CLIENT-ID': clientId}); + return jsonDecode(response.body); + } + + Track parseMangaItem(Map mJson, Track track) { + bool isRereading = mJson["is_rereading"] ?? false; + track.status = isRereading + ? TrackStatus.rereading + : _getMALTrackStatus(mJson["status"]); + track.lastChapterRead = int.parse(mJson["num_chapters_read"].toString()); + track.score = int.parse(mJson["score"].toString()); + track.startedReadingDate = parseDate(mJson["start_date"]); + track.finishedReadingDate = parseDate(mJson["finish_date"]); + return track; + } + + int? parseDate(String? isoDate) { + if (isoDate == null) return null; + + final date = DateFormat('yyyy-MM-dd', 'en_US').parse(isoDate); + return date.millisecondsSinceEpoch; + } + + Future updateItem(Track track) async { + final accessToken = await _getAccesToken(); + final formBody = { + 'status': (toMyAnimeListStatus(track.status) ?? 'reading').toString(), + 'is_rereading': (track.status == TrackStatus.rereading).toString(), + 'score': track.score.toString(), + 'num_chapters_read': track.lastChapterRead.toString(), + if (track.startedReadingDate != null) + 'start_date': _convertToIsoDate(track.startedReadingDate), + if (track.finishedReadingDate != null) + 'finish_date': _convertToIsoDate(track.finishedReadingDate) + }; + final request = http.Request( + 'PUT', Uri.parse('$baseApiUrl/manga/${track.mediaId}/my_list_status')); + request.bodyFields = formBody; + request.headers.addAll({'Authorization': 'Bearer $accessToken'}); + final response = await http.Client().send(request); + final mJson = jsonDecode(await response.stream.bytesToString()); + return parseMangaItem(mJson, track); + } +} diff --git a/lib/services/myanimelist.g.dart b/lib/services/myanimelist.g.dart new file mode 100644 index 0000000..56c0388 --- /dev/null +++ b/lib/services/myanimelist.g.dart @@ -0,0 +1,125 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'myanimelist.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myAnimeListHash() => r'd3ec65023fe7f920fad11afa4866072fb75ee257'; + +/// 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 _$MyAnimeList extends BuildlessAutoDisposeNotifier { + late final int syncId; + + dynamic build({ + required int syncId, + }); +} + +/// See also [MyAnimeList]. +@ProviderFor(MyAnimeList) +const myAnimeListProvider = MyAnimeListFamily(); + +/// See also [MyAnimeList]. +class MyAnimeListFamily extends Family { + /// See also [MyAnimeList]. + const MyAnimeListFamily(); + + /// See also [MyAnimeList]. + MyAnimeListProvider call({ + required int syncId, + }) { + return MyAnimeListProvider( + syncId: syncId, + ); + } + + @override + MyAnimeListProvider getProviderOverride( + covariant MyAnimeListProvider provider, + ) { + return call( + syncId: provider.syncId, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'myAnimeListProvider'; +} + +/// See also [MyAnimeList]. +class MyAnimeListProvider + extends AutoDisposeNotifierProviderImpl { + /// See also [MyAnimeList]. + MyAnimeListProvider({ + required this.syncId, + }) : super.internal( + () => MyAnimeList()..syncId = syncId, + from: myAnimeListProvider, + name: r'myAnimeListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$myAnimeListHash, + dependencies: MyAnimeListFamily._dependencies, + allTransitiveDependencies: + MyAnimeListFamily._allTransitiveDependencies, + ); + + final int syncId; + + @override + bool operator ==(Object other) { + return other is MyAnimeListProvider && 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 MyAnimeList 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 diff --git a/lib/utils/constant.dart b/lib/utils/constant.dart index e344aec..094c3b3 100644 --- a/lib/utils/constant.dart +++ b/lib/utils/constant.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mangayomi/models/manga.dart'; +import 'package:mangayomi/models/track.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; const defaultUserAgent = @@ -27,3 +28,15 @@ IconData getMangaStatusIcon(Status status) { _ => Icons.block_outlined, }; } + +String getTrackStatus(TrackStatus status) { + return switch (status) { + TrackStatus.reading => "Reading", + TrackStatus.completed => "Completed", + TrackStatus.onHold => "On Hold", + TrackStatus.dropped => "Dropped", + TrackStatus.planToRead => "Plan To Read", + TrackStatus.rereading => "Rereading", + }; +} + diff --git a/lib/utils/headers.dart b/lib/utils/headers.dart index eef0408..62e75c6 100644 --- a/lib/utils/headers.dart +++ b/lib/utils/headers.dart @@ -11,18 +11,21 @@ part 'headers.g.dart'; Map headers(HeadersRef ref, {required String source, required String lang}) { final sourceM = getSource(lang, source); - if (sourceM.headers!.isEmpty) { + if (sourceM.headers!.isEmpty && !sourceM.hasCloudflare!) { return {}; } Map newHeaders = {}; - final headers = jsonDecode(sourceM.headers!) as Map; - newHeaders = - headers.map((key, value) => MapEntry(key.toString(), value.toString())); + if (sourceM.headers!.isNotEmpty) { + final headers = jsonDecode(sourceM.headers!) as Map; + newHeaders = + headers.map((key, value) => MapEntry(key.toString(), value.toString())); + } + if (sourceM.hasCloudflare!) { final userAgent = isar.settings.getSync(227)!.userAgent!; final cookie = ref.watch(cookieStateProvider(source)); + newHeaders.addAll({'User-Agent': userAgent, "Cookie": cookie}); } - return newHeaders; } diff --git a/lib/utils/headers.g.dart b/lib/utils/headers.g.dart index dea3282..6bfe56e 100644 --- a/lib/utils/headers.g.dart +++ b/lib/utils/headers.g.dart @@ -6,7 +6,7 @@ part of 'headers.dart'; // RiverpodGenerator // ************************************************************************** -String _$headersHash() => r'bb9238fcab606b6fe4ae2d2dab249820d7b4e2ff'; +String _$headersHash() => r'e154417a4c4e9416c52cb13060ffb04a07fd489e'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/language.dart b/lib/utils/language.dart index 2ca12f7..c18bc04 100644 --- a/lib/utils/language.dart +++ b/lib/utils/language.dart @@ -1,7 +1,7 @@ completeLanguageName(String lang) { lang = lang.toLowerCase(); for (var element in languagesMap.entries) { - if (element.value.toString().toLowerCase() == lang) { + if (element.value.toLowerCase() == lang) { return element.key; } } @@ -64,5 +64,4 @@ final languagesMap = { "čeština": "cs", "Kurdî": "ku", "Magyar": "hu", - "polski": "pl", }; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index aaf9800..b9df774 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = @@ -24,4 +25,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) window_to_front_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowToFrontPlugin"); + window_to_front_plugin_register_with_registrar(window_to_front_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index ed0d56e..77962a9 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_js isar_flutter_libs url_launcher_linux + window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9fd621c..9c124e1 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,21 +8,25 @@ import Foundation import desktop_webview_window import flutter_inappwebview import flutter_js +import flutter_web_auth_2 import isar_flutter_libs import package_info_plus import path_provider_foundation import share_plus import sqflite import url_launcher_macos +import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin")) + FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a3..c946719 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/pubspec.lock b/pubspec.lock index 7989804..912dcc9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -504,6 +504,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_auth_2: + dependency: "direct main" + description: + name: flutter_web_auth_2 + sha256: "70e4df72940183b8e269c4163f78dd5bf9102ba3329bfe00c0f2373f30fb32d0" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + flutter_web_auth_2_platform_interface: + dependency: transitive + description: + name: flutter_web_auth_2_platform_interface + sha256: f6fa7059ff3428c19cd756c02fef8eb0147131c7e64591f9060c90b5ab84f094 + url: "https://pub.dev" + source: hosted + version: "2.1.4" flutter_web_plugins: dependency: transitive description: flutter @@ -621,6 +637,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.0" + infinite_listview: + dependency: transitive + description: + name: infinite_listview + sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + url: "https://pub.dev" + source: hosted + version: "1.1.0" intl: dependency: "direct main" description: @@ -757,6 +781,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + numberpicker: + dependency: "direct main" + description: + name: numberpicker + sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" + url: "https://pub.dev" + source: hosted + version: "2.1.2" octo_image: dependency: transitive description: @@ -1314,6 +1346,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.4" + window_to_front: + dependency: transitive + description: + name: window_to_front + sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" + url: "https://pub.dev" + source: hosted + version: "0.0.3" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3874b5b..c7ab486 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,8 @@ dependencies: dart_eval: ^0.6.0 json_path: ^0.6.0 bot_toast: ^4.0.4 + flutter_web_auth_2: ^2.1.5 + numberpicker: ^2.1.2 cupertino_icons: ^1.0.2 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index cd8836f..e17e5db 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,6 +12,7 @@ #include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DesktopWebviewWindowPluginRegisterWithRegistrar( @@ -26,4 +27,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowToFrontPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowToFrontPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 790c70b..4636454 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST permission_handler_windows share_plus url_launcher_windows + window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST