Skip to content

Commit 0043214

Browse files
committed
rewrite: Implementation of update anime media entry
1 parent ce50647 commit 0043214

22 files changed

Lines changed: 1342 additions & 500 deletions

lib/application/cubits/anime_details_cubit.dart

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import 'package:unyo/domain/entities/media_list.dart';
3232
import 'package:unyo/domain/entities/media_list_entry.dart';
3333
import 'package:unyo/domain/entities/settings.dart';
3434
import 'package:unyo/domain/entities/user.dart';
35-
import 'package:unyo/presentation/dialogs/anime_server_selection_dialog.dart';
35+
import 'package:unyo/presentation/dialogs/anime_details_media_entry_dialog.dart';
3636

3737
class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeDetailsState> {
3838
// Repositories
@@ -67,7 +67,7 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
6767
loggedUser: UserModel.empty(),
6868
selectedMediaList: MediaListModel.empty(),
6969
selectedAnime: AnimeModel.empty(),
70-
mediaListEntry:MediaListEntryModel.empty(),
70+
mediaListEntry: MediaListEntryModel.empty(),
7171
characters: (false, []),
7272
recommendations: (false, []),
7373
episodesInfo: [],
@@ -133,9 +133,9 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
133133

134134
void navigateToAnimeAdvancedSearchScreenWithFilters(BuildContext context, String newAnimeGenre) {
135135
_logger.i("Navigating to Anime Advanced Search Filters");
136-
bool hasAnimeSearchInStack = AutoRouter.of(context).stackData.any(
137-
(route) => route.name == "AnimeAdvancedSearchRoute",
138-
);
136+
bool hasAnimeSearchInStack = AutoRouter.of(
137+
context,
138+
).stackData.any((route) => route.name == "AnimeAdvancedSearchRoute");
139139
if (hasAnimeSearchInStack) {
140140
_selectedAnimeAdvancedSearchGenresFilters.updateSelectedAnimeGenre(newAnimeGenre);
141141
popRouteEffect(context);
@@ -175,7 +175,108 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
175175
}
176176

177177
void openAnimeServerSelectionDialog(BuildContext context) {
178-
showWidgetDialogEffect(dialog: const AnimeServerSelectionDialog());
178+
showWidgetDialogEffect(dialog: AnimeDetailsMediaEntryDialog(cubit: this));
179+
}
180+
181+
Future<void> updateMediaListEntry(BuildContext context) async {
182+
MediaListEntry desiredMediaListEntry = state.mediaListEntry;
183+
try {
184+
switch (state.loggedUser.settings.service) {
185+
case Service.anilist:
186+
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Anilist");
187+
MediaListEntry savedMediaListEntry = await _animeRepositoryAnilist.updateMediaListEntry(
188+
desiredMediaListEntry,
189+
state.selectedAnime,
190+
state.loggedUser,
191+
);
192+
emit(state.copyWith(mediaListEntry: savedMediaListEntry));
193+
case Service.mal:
194+
_logger.i("Updating Media List Entry to $desiredMediaListEntry on MyAnimeList");
195+
case Service.shikimori:
196+
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Shikimori");
197+
case Service.kitsu:
198+
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Kitsu");
199+
case Service.simkl:
200+
_logger.i("Updating Media List Entry to $desiredMediaListEntry on Simkl");
201+
}
202+
203+
} on HttpServerException catch (e, stackTrace) {
204+
handleError("Error updating Anime Entry:", responseBody: e.message, stackTrace: stackTrace);
205+
} catch (e, stackTrace) {
206+
handleError("Error updating Anime Entry: $e", stackTrace: stackTrace);
207+
}
208+
if (!context.mounted) return;
209+
popRouteEffect(context);
210+
}
211+
212+
Future<void> updateMediaListEntryStatus(String? newStatus) async {
213+
if (newStatus == null) return;
214+
try {
215+
MediaListEntry updatedMediaListEntry = (state.mediaListEntry as MediaListEntryModel).copyWith(status: newStatus);
216+
emit(state.copyWith(mediaListEntry: updatedMediaListEntry));
217+
} catch (e, stackTrace) {
218+
handleError("Error updating Anime Entry status: $e", stackTrace: stackTrace);
219+
}
220+
}
221+
222+
Future<void> updateMediaListEntryProgress(int? newProgress) async {
223+
if (newProgress == null) return;
224+
try {
225+
MediaListEntry updatedMediaListEntry = (state.mediaListEntry as MediaListEntryModel).copyWith(progress: newProgress);
226+
emit(state.copyWith(mediaListEntry: updatedMediaListEntry));
227+
} catch (e, stackTrace) {
228+
handleError("Error updating Anime Entry progress: $e", stackTrace: stackTrace);
229+
}
230+
}
231+
232+
Future<void> updateMediaListEntryScore(double? newScore) async {
233+
if (newScore == null) return;
234+
try {
235+
MediaListEntry updatedMediaListEntry = (state.mediaListEntry as MediaListEntryModel).copyWith(score: newScore);
236+
emit(state.copyWith(mediaListEntry: updatedMediaListEntry));
237+
} catch (e, stackTrace) {
238+
handleError("Error updating Anime Entry score: $e", stackTrace: stackTrace);
239+
}
240+
}
241+
242+
Future<void> updateMediaListEntryRepeat(int? newRepeat) async {
243+
if (newRepeat == null) return;
244+
try {
245+
MediaListEntry updatedMediaListEntry = (state.mediaListEntry as MediaListEntryModel).copyWith(repeat: newRepeat);
246+
emit(state.copyWith(mediaListEntry: updatedMediaListEntry));
247+
} catch (e, stackTrace) {
248+
handleError("Error updating Anime Entry repeat: $e", stackTrace: stackTrace);
249+
}
250+
}
251+
252+
Future<void> updateMediaListEntryStartedAt(BuildContext context) async {
253+
DateTime? selectedDateTime = await showDatePicker(
254+
context: context,
255+
firstDate: DateTime.now().subtract(const Duration(days: 365)),
256+
lastDate: DateTime.now().add(const Duration(days: 365)),
257+
);
258+
if (selectedDateTime == null ) return;
259+
try {
260+
MediaListEntry updatedMediaListEntry = (state.mediaListEntry as MediaListEntryModel).copyWith(startedAt: [selectedDateTime.day.toString(), selectedDateTime.month.toString(), selectedDateTime.year.toString()]);
261+
emit(state.copyWith(mediaListEntry: updatedMediaListEntry));
262+
} catch (e, stackTrace) {
263+
handleError("Error updating Anime Entry started at: $e", stackTrace: stackTrace);
264+
}
265+
}
266+
267+
Future<void> updateMediaListEntryCompletedAt(BuildContext context) async {
268+
DateTime? selectedDateTime = await showDatePicker(
269+
context: context,
270+
firstDate: DateTime.now().subtract(const Duration(days: 365)),
271+
lastDate: DateTime.now().add(const Duration(days: 365)),
272+
);
273+
if (selectedDateTime == null ) return;
274+
try {
275+
MediaListEntry updatedMediaListEntry = (state.mediaListEntry as MediaListEntryModel).copyWith(completedAt: [selectedDateTime.day.toString(), selectedDateTime.month.toString(), selectedDateTime.year.toString()]);
276+
emit(state.copyWith(mediaListEntry: updatedMediaListEntry));
277+
} catch (e, stackTrace) {
278+
handleError("Error updating Anime Entry completed at: $e", stackTrace: stackTrace);
279+
}
179280
}
180281

181282
Future<void> _getAnimeDetails(User loggedUser, Anime selectedAnime) async {
@@ -198,7 +299,10 @@ class AnimeDetailsCubit extends Cubit<AnimeDetailsState> with EffectMixin<AnimeD
198299
),
199300
);
200301
if (animeDetails.$2.recommendedAnimes.isEmpty) {
201-
(bool, List<Anime>) trendingAnimes = await _animeRepositoryAnilist.getTrendingAnimes(1, loggedUser);
302+
(bool, List<Anime>) trendingAnimes = await _animeRepositoryAnilist.getTrendingAnimes(
303+
1,
304+
loggedUser,
305+
);
202306
emit(state.copyWith(recommendations: (trendingAnimes.$1, trendingAnimes.$2.shuffled(Random()))));
203307
}
204308
_getAlternativeImage(loggedUser, selectedAnime);

lib/application/cubits/login_cubit.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ import 'package:unyo/application/states/login_state.dart';
1313
import 'package:unyo/core/di/locator.dart';
1414
import 'package:unyo/core/enums/login_card_type.dart';
1515
import 'package:unyo/core/notification/user_notifier.dart';
16-
import 'package:unyo/core/services/api/dto/anilist/api_dtos.dart';
16+
import 'package:unyo/core/services/api/dto/anilist/auth_token_dto.dart';
1717
import 'package:unyo/core/services/api/http/api_response.dart';
1818
import 'package:unyo/core/theme/color_image_service.dart';
1919
import 'package:unyo/core/theme/theme_service.dart';
2020
import 'package:unyo/data/models/models.dart';
21-
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
2221
import 'package:unyo/data/repositories/repositories.dart';
2322
import 'package:unyo/application/effects/app_effects.dart';
2423
import 'package:unyo/domain/entities/user.dart';
@@ -179,7 +178,7 @@ class LoginCubit extends Cubit<LoginState> with EffectMixin<LoginState> {
179178
if (dateTime.isBefore(DateTime.now())) {
180179
_logger.w("Anilist User token is expired, getting new accessToken");
181180
try {
182-
ApiResponse<AuthTokenDto> authToken = await _userRepositoryAnilist
181+
ApiResponse<AuthTokenEntity> authToken = await _userRepositoryAnilist
183182
.getAuthToken(user.accessCode);
184183
User updatedUser = user.copyWith(
185184
accessToken: authToken.data.accessToken,

lib/core/services/api/dto/anilist/api_dtos.dart

Lines changed: 0 additions & 1 deletion
This file was deleted.

lib/core/services/api/dto/anilist/auth_token_dto.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
class AuthTokenDto {
1+
class AuthTokenEntity {
22
final String accessToken;
33
final String refreshToken;
44

5-
AuthTokenDto({
5+
AuthTokenEntity({
66
required this.accessToken,
77
required this.refreshToken,
88
});
99

10-
factory AuthTokenDto.fromJson(Map<String, dynamic> json) {
11-
return AuthTokenDto(
10+
factory AuthTokenEntity.fromJson(Map<String, dynamic> json) {
11+
return AuthTokenEntity(
1212
accessToken: json['access_token'],
1313
refreshToken: json['refresh_token'],
1414
);

lib/core/services/api/dto/anilist/media_details_graphql_entity.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,13 +288,13 @@ class MediaDetailsGraphqlMediaCharactersNodesDateOfBirth {
288288
@JsonSerializable()
289289
class MediaDetailsGraphqlMediaMediaListEntry {
290290
int progress = 0;
291+
int progressVolumes = 0;
291292
double score = 0;
292293
int repeat = 0;
293294
String status = '';
294295
late MediaDetailsGraphqlMediaMediaListEntryStartedAt startedAt;
295296
late MediaDetailsGraphqlMediaMediaListEntryCompletedAt completedAt;
296297
List<MediaDetailsGraphqlMediaMediaListEntryCustomLists> customLists = [];
297-
dynamic progressVolumes;
298298

299299
MediaDetailsGraphqlMediaMediaListEntry();
300300

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import 'package:unyo/generated/json/base/json_field.dart';
2+
import 'package:unyo/generated/json/save_media_list_entry_entity.g.dart';
3+
import 'dart:convert';
4+
export 'package:unyo/generated/json/save_media_list_entry_entity.g.dart';
5+
6+
@JsonSerializable()
7+
class SaveMediaListEntryEntity {
8+
@JSONField(name: 'SaveMediaListEntry')
9+
late SaveMediaListEntrySaveMediaListEntry saveMediaListEntry;
10+
11+
SaveMediaListEntryEntity();
12+
13+
factory SaveMediaListEntryEntity.fromJson(Map<String, dynamic> json) => $SaveMediaListEntryEntityFromJson(json);
14+
15+
Map<String, dynamic> toJson() => $SaveMediaListEntryEntityToJson(this);
16+
17+
@override
18+
String toString() {
19+
return jsonEncode(this);
20+
}
21+
}
22+
23+
@JsonSerializable()
24+
class SaveMediaListEntrySaveMediaListEntry {
25+
int progress = 0;
26+
int progressVolumes = 0;
27+
int repeat = 0;
28+
double score = 0;
29+
String status = '';
30+
late SaveMediaListEntrySaveMediaListEntryStartedAt startedAt;
31+
late SaveMediaListEntrySaveMediaListEntryCompletedAt completedAt;
32+
33+
SaveMediaListEntrySaveMediaListEntry();
34+
35+
factory SaveMediaListEntrySaveMediaListEntry.fromJson(Map<String, dynamic> json) => $SaveMediaListEntrySaveMediaListEntryFromJson(json);
36+
37+
Map<String, dynamic> toJson() => $SaveMediaListEntrySaveMediaListEntryToJson(this);
38+
39+
@override
40+
String toString() {
41+
return jsonEncode(this);
42+
}
43+
}
44+
45+
@JsonSerializable()
46+
class SaveMediaListEntrySaveMediaListEntryStartedAt {
47+
dynamic day;
48+
dynamic month;
49+
dynamic year;
50+
51+
SaveMediaListEntrySaveMediaListEntryStartedAt();
52+
53+
factory SaveMediaListEntrySaveMediaListEntryStartedAt.fromJson(Map<String, dynamic> json) => $SaveMediaListEntrySaveMediaListEntryStartedAtFromJson(json);
54+
55+
Map<String, dynamic> toJson() => $SaveMediaListEntrySaveMediaListEntryStartedAtToJson(this);
56+
57+
@override
58+
String toString() {
59+
return jsonEncode(this);
60+
}
61+
}
62+
63+
@JsonSerializable()
64+
class SaveMediaListEntrySaveMediaListEntryCompletedAt {
65+
int day = 0;
66+
int month = 0;
67+
int year = 0;
68+
69+
SaveMediaListEntrySaveMediaListEntryCompletedAt();
70+
71+
factory SaveMediaListEntrySaveMediaListEntryCompletedAt.fromJson(Map<String, dynamic> json) => $SaveMediaListEntrySaveMediaListEntryCompletedAtFromJson(json);
72+
73+
Map<String, dynamic> toJson() => $SaveMediaListEntrySaveMediaListEntryCompletedAtToJson(this);
74+
75+
@override
76+
String toString() {
77+
return jsonEncode(this);
78+
}
79+
}

lib/core/services/api/graphql/queries/anilist_queries.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,23 @@ id
521521
}
522522
}
523523
''';
524+
const updateMediaEntryQuery = '''
525+
mutation SaveMediaListEntry(\$mediaId: Int, \$progress: Int, \$progressVolumes: Int, \$repeat: Int, \$score: Float, \$startedAt: FuzzyDateInput, \$completedAt: FuzzyDateInput, \$status: MediaListStatus) {
526+
SaveMediaListEntry(mediaId: \$mediaId, progress: \$progress, progressVolumes: \$progressVolumes, repeat: \$repeat, score: \$score, startedAt: \$startedAt, completedAt: \$completedAt, status: \$status) {
527+
progress
528+
repeat
529+
score
530+
status
531+
startedAt {
532+
day
533+
month
534+
year
535+
}
536+
completedAt {
537+
day
538+
year
539+
}
540+
progressVolumes
541+
}
542+
}
543+
''';

lib/data/models/anilist_anime_details.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ abstract class AnilistAnimeDetailsModel with _$AnilistAnimeDetailsModel implemen
4242
return AnilistAnimeDetailsModel(
4343
mediaListEntry: MediaListEntryModel(
4444
progress: animeDetailsMediaList.mediaListEntry?.progress ?? -1,
45+
progressVolumes: animeDetailsMediaList.mediaListEntry?.progressVolumes ?? -1,
4546
score: animeDetailsMediaList.mediaListEntry?.score ?? -1,
4647
repeat: animeDetailsMediaList.mediaListEntry?.repeat ?? -1,
4748
status: animeDetailsMediaList.mediaListEntry?.status ?? "ADD TO LIST",

lib/data/models/anilist_manga_details.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ abstract class AnilistMangaDetailsModel
4747
return AnilistMangaDetailsModel(
4848
mediaListEntry: MediaListEntryModel(
4949
progress: mangaDetailsMediaList.mediaListEntry?.progress ?? -1,
50+
progressVolumes: mangaDetailsMediaList.mediaListEntry?.progressVolumes ?? -1,
5051
score: mangaDetailsMediaList.mediaListEntry?.score ?? -1,
5152
repeat: mangaDetailsMediaList.mediaListEntry?.repeat ?? -1,
5253
status: mangaDetailsMediaList.mediaListEntry?.status ?? "ADD TO LIST",

lib/data/repositories/anime_repository_anilist.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:unyo/core/services/api/dto/anilist/media_collection_recently_com
1414
import 'package:unyo/core/services/api/dto/anilist/media_collection_trendingOrPopular_graphql_entity.dart';
1515
import 'package:unyo/core/services/api/dto/anilist/media_collection_upcoming_graphql_entity.dart';
1616
import 'package:unyo/core/services/api/dto/anilist/media_details_graphql_entity.dart';
17+
import 'package:unyo/core/services/api/dto/anilist/save_media_list_entry_entity.dart';
1718
import 'package:unyo/core/services/api/graphql/queries/anilist_queries.dart' as anilist_queries;
1819
import 'package:unyo/core/services/api/dto/anilist/media_collection_recently_released_graphql_entity.dart';
1920
import 'package:unyo/core/services/api/graphql/graphql_response.dart';
@@ -24,6 +25,7 @@ import 'package:unyo/data/models/anilist_user_model.dart';
2425
import 'package:unyo/data/repositories/repository_mixin.dart';
2526
import 'package:unyo/domain/entities/anime.dart';
2627
import 'package:unyo/domain/entities/anime_details.dart';
28+
import 'package:unyo/domain/entities/media_list_entry.dart';
2729
import 'package:unyo/domain/entities/user.dart';
2830
import 'package:unyo/domain/repositories/anime_repository.dart';
2931
import 'package:unyo/presentation/widgets/text/text_utils.dart';
@@ -368,6 +370,38 @@ class AnimeRepositoryAnilist with RepositoryMixin implements AnimeRepository {
368370
.map((schedule) => AnilistAnimeModel.fromScheduleEntry(schedule))
369371
.toList();
370372
}
373+
374+
@override
375+
Future<MediaListEntry> updateMediaListEntry(MediaListEntry newMediaListEntry, Anime selectedAnime, User loggedUser) async {
376+
Map<String, String>? graphQlHeaders =
377+
loggedUser is AnilistUserModel ? {"Authorization": "Bearer ${(loggedUser).accessToken}"} : null;
378+
ApiGraphQLResponse<SaveMediaListEntryEntity> saveMediaListEntry = await _anilistGraphQLService
379+
.mutation<SaveMediaListEntryEntity>(
380+
query: anilist_queries.updateMediaEntryQuery,
381+
fromJson: SaveMediaListEntryEntity.fromJson,
382+
variables: {
383+
"mediaId": selectedAnime.id,
384+
"status": newMediaListEntry.status.toUpperCase() != "ADD TO LIST" ? newMediaListEntry.status.toUpperCase() : null,
385+
if (newMediaListEntry.progress != -1)
386+
"progress": newMediaListEntry.progress,
387+
if (newMediaListEntry.score != -1.0)
388+
"score": newMediaListEntry.score,
389+
"startedAt": {
390+
"day": int.tryParse(newMediaListEntry.startedAt[0]),
391+
"month": int.tryParse(newMediaListEntry.startedAt[1]),
392+
"year": int.tryParse(newMediaListEntry.startedAt[2]),
393+
},
394+
"completedAt": {
395+
"day": int.tryParse(newMediaListEntry.completedAt[0]),
396+
"month": int.tryParse(newMediaListEntry.completedAt[1]),
397+
"year": int.tryParse(newMediaListEntry.completedAt[2]),
398+
},
399+
},
400+
headers: graphQlHeaders,
401+
);
402+
throwIfGraphQlError(saveMediaListEntry);
403+
return MediaListEntryModel.fromSaveMediaListEntryEntity(saveMediaListEntry.data.saveMediaListEntry);
404+
}
371405
}
372406

373407
enum AnlistGenreFilters {

0 commit comments

Comments
 (0)