From 9f5de142b3ee25e0d90cefd8e8cac701118f0231 Mon Sep 17 00:00:00 2001 From: Jiangqn Date: Wed, 11 Oct 2023 15:41:44 +0800 Subject: [PATCH 1/5] airing schedule query graphql --- lib/core/network/ani_list_data_source.dart | 25 ++++++++++++++--- .../airing_schedules_query_graphql.dart.dart | 21 +++++++++++++++ .../network/api/ani_auth_mution_graphql.dart | 7 +++-- .../network/api/ani_detail_query_graphql.dart | 5 ++-- .../network/api/ani_list_query_graphql.dart | 5 ++-- .../ani_save_media_list_mution_graphql.dart | 5 ++-- .../api/user_anime_list_query_graphql.dart | 5 ++-- lib/core/network/auth_data_source.dart | 4 +-- lib/core/network/model/airing_schedule.dart | 1 + .../model/airing_schedule.freezed.dart | 27 +++++++++++++++++-- lib/core/network/model/airing_schedule.g.dart | 2 ++ lib/util/time_util.dart | 23 ++++++++++++++++ test/core/common/common_test.dart | 4 +++ .../network/ani_list_data_source_test.dart | 8 ++++++ 14 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 lib/core/network/api/airing_schedules_query_graphql.dart.dart diff --git a/lib/core/network/ani_list_data_source.dart b/lib/core/network/ani_list_data_source.dart index f41dc91a..5e83e596 100644 --- a/lib/core/network/ani_list_data_source.dart +++ b/lib/core/network/ani_list_data_source.dart @@ -1,6 +1,8 @@ +import 'package:anime_tracker/core/network/api/airing_schedules_query_graphql.dart.dart'; import 'package:anime_tracker/core/network/api/ani_detail_query_graphql.dart'; import 'package:anime_tracker/core/network/api/user_anime_list_query_graphql.dart'; import 'package:anime_tracker/core/network/client/ani_list_dio.dart'; +import 'package:anime_tracker/core/network/model/airing_schedule.dart'; import 'package:anime_tracker/core/network/model/detail_anime_dto.dart'; import 'package:anime_tracker/core/network/api/ani_list_query_graphql.dart'; @@ -16,7 +18,7 @@ class AniListDataSource { AniListDataSource._(); Future getNetworkAnime({required int id}) async { - final queryGraphQL = createDetailAnimeQueryGraphQLString(); + final queryGraphQL = detailAnimeQueryGraphQLString; final variablesMap = { 'id': id, }; @@ -32,7 +34,7 @@ class AniListDataSource { Future> getNetworkAnimePage({ required AnimePageQueryParam param, }) async { - final queryGraphQL = createAnimeListQueryGraphQLString(); + final queryGraphQL = animeListQueryGraphQLString; final hasSeasonYear = param.seasonYear != null; final hasSeason = param.season != null; final hasStatus = param.status != null; @@ -74,7 +76,7 @@ class AniListDataSource { Future> getUserMediaListPage({ required UserAnimeListPageQueryParam param, }) async { - final queryGraphQL = createUserAnimeListGraphQLString(); + final queryGraphQL = userAnimeListGraphQLString; final hasStatus = param.status.isNotEmpty; final hasPerPage = param.perPage != null; final variablesMap = { @@ -98,4 +100,21 @@ class AniListDataSource { return animeList; } + + Future> getAiringSchedules( + AiringSchedulesQueryParam param) async { + final queryGraphQL = airingSchedulesQueryGraphQLString; + final variablesMap = { + 'airingAt_greater': param.airingAtGreater, + 'airingAt_lesser': param.airingAtLesser, + }; + + final response = await AniListDio().dio.post(AniListDio.aniListUrl, + data: {'query': queryGraphQL, 'variables': variablesMap}); + final List resultJson = response.data['data']['Page']['airingSchedules']; + final airingSchedules = + resultJson.map((e) => AiringSchedule.fromJson(e)).toList(); + + return airingSchedules; + } } diff --git a/lib/core/network/api/airing_schedules_query_graphql.dart.dart b/lib/core/network/api/airing_schedules_query_graphql.dart.dart new file mode 100644 index 00000000..e1494c7e --- /dev/null +++ b/lib/core/network/api/airing_schedules_query_graphql.dart.dart @@ -0,0 +1,21 @@ +class AiringSchedulesQueryParam { + final int airingAtGreater; + final int airingAtLesser; + + AiringSchedulesQueryParam( + {required this.airingAtGreater, required this.airingAtLesser}); +} + +String get airingSchedulesQueryGraphQLString => ''' +query(\$airingAt_greater: Int, \$airingAt_lesser: Int){ + Page(page: 1) { + airingSchedules(airingAt_greater: \$airingAt_greater, airingAt_lesser: \$airingAt_lesser) { + id + airingAt + timeUntilAiring + episode + mediaId + } + } +} +'''; diff --git a/lib/core/network/api/ani_auth_mution_graphql.dart b/lib/core/network/api/ani_auth_mution_graphql.dart index ccdc1479..c0ec8bc3 100644 --- a/lib/core/network/api/ani_auth_mution_graphql.dart +++ b/lib/core/network/api/ani_auth_mution_graphql.dart @@ -7,8 +7,8 @@ mutation MediaListQuery { '''; /// Graph ql to query user info. -String createUserInfoMotionGraphQLString() { - return ''' +String get userInfoMotionGraphQLString => +''' mutation UpdateUserMutation { UpdateUser { id @@ -20,5 +20,4 @@ mutation UpdateUserMutation { bannerImage } } - '''; -} +'''; diff --git a/lib/core/network/api/ani_detail_query_graphql.dart b/lib/core/network/api/ani_detail_query_graphql.dart index 3fe509de..ca9e13af 100644 --- a/lib/core/network/api/ani_detail_query_graphql.dart +++ b/lib/core/network/api/ani_detail_query_graphql.dart @@ -1,5 +1,5 @@ -String createDetailAnimeQueryGraphQLString() { - return ''' +String get detailAnimeQueryGraphQLString => +''' query (\$id: Int) { Media(id: \$id, type: ANIME) { id @@ -114,4 +114,3 @@ query (\$id: Int) { } } '''; -} diff --git a/lib/core/network/api/ani_list_query_graphql.dart b/lib/core/network/api/ani_list_query_graphql.dart index e63f3af9..52b472a1 100644 --- a/lib/core/network/api/ani_list_query_graphql.dart +++ b/lib/core/network/api/ani_list_query_graphql.dart @@ -19,8 +19,8 @@ class AnimePageQueryParam { this.animeFormat = const []}); } -String createAnimeListQueryGraphQLString() { - return ''' +String get animeListQueryGraphQLString => +''' query (\$page: Int, \$perPage: Int, \$seasonYear: Int, \$season: MediaSeason, \$status: MediaStatus, \$sort: [MediaSort], \$format_in: [MediaFormat]) { Page(page: \$page, perPage: \$perPage) { media: media(type: ANIME, seasonYear: \$seasonYear, season: \$season, status: \$status, sort: \$sort, format_in: \$format_in) { @@ -44,4 +44,3 @@ query (\$page: Int, \$perPage: Int, \$seasonYear: Int, \$season: MediaSeason, \$ } } '''; -} diff --git a/lib/core/network/api/ani_save_media_list_mution_graphql.dart b/lib/core/network/api/ani_save_media_list_mution_graphql.dart index 637e7a1e..65ca424c 100644 --- a/lib/core/network/api/ani_save_media_list_mution_graphql.dart +++ b/lib/core/network/api/ani_save_media_list_mution_graphql.dart @@ -11,8 +11,8 @@ class MediaListMutationParam { final int? score; } -String createSaveMediaListMotionGraphQLString() { - return ''' +String get saveMediaListMotionGraphQLString => +''' mutation(\$id: Int, \$mediaId: Int, \$progress: Int, \$status: MediaListStatus, \$score: Float) { SaveMediaListEntry(id: \$id, mediaId: \$mediaId, progress: \$progress, status: \$status, score: \$score) { id @@ -29,4 +29,3 @@ mutation(\$id: Int, \$mediaId: Int, \$progress: Int, \$status: MediaListStatus, } } '''; -} diff --git a/lib/core/network/api/user_anime_list_query_graphql.dart b/lib/core/network/api/user_anime_list_query_graphql.dart index 4eb1e0fb..e2ac1568 100644 --- a/lib/core/network/api/user_anime_list_query_graphql.dart +++ b/lib/core/network/api/user_anime_list_query_graphql.dart @@ -12,8 +12,8 @@ class UserAnimeListPageQueryParam { this.status = const []}); } -String createUserAnimeListGraphQLString() { - return ''' +String get userAnimeListGraphQLString => +''' query(\$page: Int, \$perPage: Int, \$userId: Int, \$status_in: [MediaListStatus]){ Page(page: \$page, perPage: \$perPage) { mediaList(userId: \$userId, type: ANIME, status_in: \$status_in) { @@ -58,4 +58,3 @@ query(\$page: Int, \$perPage: Int, \$userId: Int, \$status_in: [MediaListStatus] } } '''; -} diff --git a/lib/core/network/auth_data_source.dart b/lib/core/network/auth_data_source.dart index a4ac832e..b67f62fa 100644 --- a/lib/core/network/auth_data_source.dart +++ b/lib/core/network/auth_data_source.dart @@ -56,7 +56,7 @@ class AuthDataSource { Future getUserDataDto() async { final response = await AniListDio().dio.post( AniListDio.aniListUrl, - queryParameters: {'query': createUserInfoMotionGraphQLString()}, + queryParameters: {'query': userInfoMotionGraphQLString}, options: _createQueryOptions(), ); @@ -85,7 +85,7 @@ class AuthDataSource { final response = await AniListDio().dio.post( AniListDio.aniListUrl, data: { - 'query': createSaveMediaListMotionGraphQLString(), + 'query': saveMediaListMotionGraphQLString, 'variables': variablesMap, }, options: _createQueryOptions(), diff --git a/lib/core/network/model/airing_schedule.dart b/lib/core/network/model/airing_schedule.dart index ecf28180..6791f545 100644 --- a/lib/core/network/model/airing_schedule.dart +++ b/lib/core/network/model/airing_schedule.dart @@ -8,6 +8,7 @@ part 'airing_schedule.g.dart'; class AiringSchedule with _$AiringSchedule { factory AiringSchedule({ @Default(-1) @JsonKey(name: 'id') int id, + @JsonKey(name: 'mediaId') int? mediaId, @JsonKey(name: 'airingAt') int? airingAt, @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, @JsonKey(name: 'episode') int? episode, diff --git a/lib/core/network/model/airing_schedule.freezed.dart b/lib/core/network/model/airing_schedule.freezed.dart index 5ccacc65..ceea3b57 100644 --- a/lib/core/network/model/airing_schedule.freezed.dart +++ b/lib/core/network/model/airing_schedule.freezed.dart @@ -22,6 +22,8 @@ AiringSchedule _$AiringScheduleFromJson(Map json) { mixin _$AiringSchedule { @JsonKey(name: 'id') int get id => throw _privateConstructorUsedError; + @JsonKey(name: 'mediaId') + int? get mediaId => throw _privateConstructorUsedError; @JsonKey(name: 'airingAt') int? get airingAt => throw _privateConstructorUsedError; @JsonKey(name: 'timeUntilAiring') @@ -43,6 +45,7 @@ abstract class $AiringScheduleCopyWith<$Res> { @useResult $Res call( {@JsonKey(name: 'id') int id, + @JsonKey(name: 'mediaId') int? mediaId, @JsonKey(name: 'airingAt') int? airingAt, @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, @JsonKey(name: 'episode') int? episode}); @@ -62,6 +65,7 @@ class _$AiringScheduleCopyWithImpl<$Res, $Val extends AiringSchedule> @override $Res call({ Object? id = null, + Object? mediaId = freezed, Object? airingAt = freezed, Object? timeUntilAiring = freezed, Object? episode = freezed, @@ -71,6 +75,10 @@ class _$AiringScheduleCopyWithImpl<$Res, $Val extends AiringSchedule> ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, + mediaId: freezed == mediaId + ? _value.mediaId + : mediaId // ignore: cast_nullable_to_non_nullable + as int?, airingAt: freezed == airingAt ? _value.airingAt : airingAt // ignore: cast_nullable_to_non_nullable @@ -97,6 +105,7 @@ abstract class _$$_AiringScheduleCopyWith<$Res> @useResult $Res call( {@JsonKey(name: 'id') int id, + @JsonKey(name: 'mediaId') int? mediaId, @JsonKey(name: 'airingAt') int? airingAt, @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, @JsonKey(name: 'episode') int? episode}); @@ -114,6 +123,7 @@ class __$$_AiringScheduleCopyWithImpl<$Res> @override $Res call({ Object? id = null, + Object? mediaId = freezed, Object? airingAt = freezed, Object? timeUntilAiring = freezed, Object? episode = freezed, @@ -123,6 +133,10 @@ class __$$_AiringScheduleCopyWithImpl<$Res> ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, + mediaId: freezed == mediaId + ? _value.mediaId + : mediaId // ignore: cast_nullable_to_non_nullable + as int?, airingAt: freezed == airingAt ? _value.airingAt : airingAt // ignore: cast_nullable_to_non_nullable @@ -144,6 +158,7 @@ class __$$_AiringScheduleCopyWithImpl<$Res> class _$_AiringSchedule implements _AiringSchedule { _$_AiringSchedule( {@JsonKey(name: 'id') this.id = -1, + @JsonKey(name: 'mediaId') this.mediaId, @JsonKey(name: 'airingAt') this.airingAt, @JsonKey(name: 'timeUntilAiring') this.timeUntilAiring, @JsonKey(name: 'episode') this.episode}); @@ -155,6 +170,9 @@ class _$_AiringSchedule implements _AiringSchedule { @JsonKey(name: 'id') final int id; @override + @JsonKey(name: 'mediaId') + final int? mediaId; + @override @JsonKey(name: 'airingAt') final int? airingAt; @override @@ -166,7 +184,7 @@ class _$_AiringSchedule implements _AiringSchedule { @override String toString() { - return 'AiringSchedule(id: $id, airingAt: $airingAt, timeUntilAiring: $timeUntilAiring, episode: $episode)'; + return 'AiringSchedule(id: $id, mediaId: $mediaId, airingAt: $airingAt, timeUntilAiring: $timeUntilAiring, episode: $episode)'; } @override @@ -175,6 +193,7 @@ class _$_AiringSchedule implements _AiringSchedule { (other.runtimeType == runtimeType && other is _$_AiringSchedule && (identical(other.id, id) || other.id == id) && + (identical(other.mediaId, mediaId) || other.mediaId == mediaId) && (identical(other.airingAt, airingAt) || other.airingAt == airingAt) && (identical(other.timeUntilAiring, timeUntilAiring) || @@ -185,7 +204,7 @@ class _$_AiringSchedule implements _AiringSchedule { @JsonKey(ignore: true) @override int get hashCode => - Object.hash(runtimeType, id, airingAt, timeUntilAiring, episode); + Object.hash(runtimeType, id, mediaId, airingAt, timeUntilAiring, episode); @JsonKey(ignore: true) @override @@ -204,6 +223,7 @@ class _$_AiringSchedule implements _AiringSchedule { abstract class _AiringSchedule implements AiringSchedule { factory _AiringSchedule( {@JsonKey(name: 'id') final int id, + @JsonKey(name: 'mediaId') final int? mediaId, @JsonKey(name: 'airingAt') final int? airingAt, @JsonKey(name: 'timeUntilAiring') final int? timeUntilAiring, @JsonKey(name: 'episode') final int? episode}) = _$_AiringSchedule; @@ -215,6 +235,9 @@ abstract class _AiringSchedule implements AiringSchedule { @JsonKey(name: 'id') int get id; @override + @JsonKey(name: 'mediaId') + int? get mediaId; + @override @JsonKey(name: 'airingAt') int? get airingAt; @override diff --git a/lib/core/network/model/airing_schedule.g.dart b/lib/core/network/model/airing_schedule.g.dart index 501dfda7..9e1e01bf 100644 --- a/lib/core/network/model/airing_schedule.g.dart +++ b/lib/core/network/model/airing_schedule.g.dart @@ -9,6 +9,7 @@ part of 'airing_schedule.dart'; _$_AiringSchedule _$$_AiringScheduleFromJson(Map json) => _$_AiringSchedule( id: json['id'] as int? ?? -1, + mediaId: json['mediaId'] as int?, airingAt: json['airingAt'] as int?, timeUntilAiring: json['timeUntilAiring'] as int?, episode: json['episode'] as int?, @@ -17,6 +18,7 @@ _$_AiringSchedule _$$_AiringScheduleFromJson(Map json) => Map _$$_AiringScheduleToJson(_$_AiringSchedule instance) => { 'id': instance.id, + 'mediaId': instance.mediaId, 'airingAt': instance.airingAt, 'timeUntilAiring': instance.timeUntilAiring, 'episode': instance.episode, diff --git a/lib/util/time_util.dart b/lib/util/time_util.dart index e3144eeb..53498647 100644 --- a/lib/util/time_util.dart +++ b/lib/util/time_util.dart @@ -28,4 +28,27 @@ mixin TimeUtil { } return result.firstOrNull; } + + /// Get range of millisecondsSinceEpoch, which is [daysAgo] from today and [daysAfter] after today. + /// For example: + /// current time is: 2023-10-11 15:10:26.818764 + /// return rage is: (2023-10-05 00:00:00.000, 2023-10-17 23:59:59.999) + static (int, int) getTimeRange(DateTime time, + {required int daysAgo, required int daysAfter}) { + final timeAgo = time.subtract(Duration(days: daysAgo)); + final timeAfter = time.add(Duration(days: daysAfter)); + + final rangeStart = DateTime(timeAgo.year, timeAgo.month, timeAgo.day) + .millisecondsSinceEpoch; + final rangeEnd = + DateTime(timeAfter.year, timeAfter.month, timeAfter.day + 1) + .millisecondsSinceEpoch - + 1; + + return (rangeStart, rangeEnd); + } + + static (int, int) getTimeRangeOfTheTargetDay(DateTime time) { + return getTimeRange(time, daysAgo: 0, daysAfter: 0); + } } diff --git a/test/core/common/common_test.dart b/test/core/common/common_test.dart index 1d73da08..d15873ab 100644 --- a/test/core/common/common_test.dart +++ b/test/core/common/common_test.dart @@ -1,3 +1,4 @@ +import 'package:anime_tracker/util/time_util.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -10,5 +11,8 @@ void main() { .difference(DateTime.fromMillisecondsSinceEpoch(0)) .inMinutes; }); + test('today', () async { + TimeUtil.getTimeRange(DateTime.now(), daysAfter: 0, daysAgo: 0); + }); }); } diff --git a/test/core/network/ani_list_data_source_test.dart b/test/core/network/ani_list_data_source_test.dart index f48833dc..fa20020b 100644 --- a/test/core/network/ani_list_data_source_test.dart +++ b/test/core/network/ani_list_data_source_test.dart @@ -1,6 +1,7 @@ import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; import 'package:anime_tracker/core/network/ani_list_data_source.dart'; +import 'package:anime_tracker/core/network/api/airing_schedules_query_graphql.dart.dart'; import 'package:anime_tracker/core/network/api/ani_list_query_graphql.dart'; import 'package:anime_tracker/core/network/api/user_anime_list_query_graphql.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -30,5 +31,12 @@ void main() { ), ); }); + + test('get_airing_schedule', () async { + await AniListDataSource().getAiringSchedules( + AiringSchedulesQueryParam( + airingAtGreater: 1696953600, airingAtLesser: 1697039999), + ); + }); }); } From 8836f1b9d58b8fbfe3906f26e05341e917e13fde Mon Sep 17 00:00:00 2001 From: Jiangqn Date: Wed, 11 Oct 2023 16:17:34 +0800 Subject: [PATCH 2/5] airing schedule entity --- .../airing_schedule_and_anime_model.dart | 10 + .../data/model/airing_schedule_model.dart | 23 ++ .../model/airing_schedule_model.freezed.dart | 190 +++++++++++++ .../data/repository/ani_list_repository.dart | 63 +++++ lib/core/database/anime_dao.dart | 70 ++++- lib/core/database/anime_database.dart | 21 +- .../model/airing_schedules_entity.dart | 31 +++ .../airing_schedules_entity.freezed.dart | 261 ++++++++++++++++++ .../model/airing_schedules_entity.g.dart | 27 ++ .../relations/airing_schedule_and_anime.dart | 9 + lib/core/network/ani_list_data_source.dart | 6 +- .../airing_schedules_query_graphql.dart.dart | 18 ++ lib/core/network/model/airing_schedule.dart | 19 -- .../network/model/airing_schedule_dto.dart | 21 ++ ....dart => airing_schedule_dto.freezed.dart} | 138 +++++---- ...dule.g.dart => airing_schedule_dto.g.dart} | 13 +- lib/core/network/model/detail_anime_dto.dart | 4 +- .../model/detail_anime_dto.freezed.dart | 25 +- .../network/model/detail_anime_dto.g.dart | 2 +- lib/util/time_util.dart | 3 +- .../repository/ani_lsit_repository_test.dart | 10 + test/core/database/anime_database_test.dart | 45 ++- 22 files changed, 907 insertions(+), 102 deletions(-) create mode 100644 lib/core/data/model/airing_schedule_and_anime_model.dart create mode 100644 lib/core/data/model/airing_schedule_model.dart create mode 100644 lib/core/data/model/airing_schedule_model.freezed.dart create mode 100644 lib/core/database/model/airing_schedules_entity.dart create mode 100644 lib/core/database/model/airing_schedules_entity.freezed.dart create mode 100644 lib/core/database/model/airing_schedules_entity.g.dart create mode 100644 lib/core/database/model/relations/airing_schedule_and_anime.dart delete mode 100644 lib/core/network/model/airing_schedule.dart create mode 100644 lib/core/network/model/airing_schedule_dto.dart rename lib/core/network/model/{airing_schedule.freezed.dart => airing_schedule_dto.freezed.dart} (59%) rename lib/core/network/model/{airing_schedule.g.dart => airing_schedule_dto.g.dart} (62%) diff --git a/lib/core/data/model/airing_schedule_and_anime_model.dart b/lib/core/data/model/airing_schedule_and_anime_model.dart new file mode 100644 index 00000000..08d43c50 --- /dev/null +++ b/lib/core/data/model/airing_schedule_and_anime_model.dart @@ -0,0 +1,10 @@ + +import 'package:anime_tracker/core/data/model/airing_schedule_model.dart'; +import 'package:anime_tracker/core/data/model/anime_model.dart'; + +class AiringScheduleAndAnimeModel { + AiringScheduleAndAnimeModel({required this.airingSchedule, required this.animeModel}); + + final AiringScheduleModel airingSchedule; + final AnimeModel animeModel; +} \ No newline at end of file diff --git a/lib/core/data/model/airing_schedule_model.dart b/lib/core/data/model/airing_schedule_model.dart new file mode 100644 index 00000000..08367004 --- /dev/null +++ b/lib/core/data/model/airing_schedule_model.dart @@ -0,0 +1,23 @@ +import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'airing_schedule_model.freezed.dart'; + +@freezed +class AiringScheduleModel with _$AiringScheduleModel { + factory AiringScheduleModel({ + @Default('') String id, + int? airingAt, + int? timeUntilAiring, + int? episode, + }) = _AiringScheduleModel; + + static AiringScheduleModel fromEntity(AiringSchedulesEntity entity) { + return AiringScheduleModel( + id: entity.id, + airingAt: entity.airingAt, + timeUntilAiring: entity.timeUntilAiring, + episode: entity.episode + ); + } +} diff --git a/lib/core/data/model/airing_schedule_model.freezed.dart b/lib/core/data/model/airing_schedule_model.freezed.dart new file mode 100644 index 00000000..9676256e --- /dev/null +++ b/lib/core/data/model/airing_schedule_model.freezed.dart @@ -0,0 +1,190 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'airing_schedule_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$AiringScheduleModel { + String get id => throw _privateConstructorUsedError; + int? get airingAt => throw _privateConstructorUsedError; + int? get timeUntilAiring => throw _privateConstructorUsedError; + int? get episode => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AiringScheduleModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AiringScheduleModelCopyWith<$Res> { + factory $AiringScheduleModelCopyWith( + AiringScheduleModel value, $Res Function(AiringScheduleModel) then) = + _$AiringScheduleModelCopyWithImpl<$Res, AiringScheduleModel>; + @useResult + $Res call({String id, int? airingAt, int? timeUntilAiring, int? episode}); +} + +/// @nodoc +class _$AiringScheduleModelCopyWithImpl<$Res, $Val extends AiringScheduleModel> + implements $AiringScheduleModelCopyWith<$Res> { + _$AiringScheduleModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? airingAt = freezed, + Object? timeUntilAiring = freezed, + Object? episode = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + airingAt: freezed == airingAt + ? _value.airingAt + : airingAt // ignore: cast_nullable_to_non_nullable + as int?, + timeUntilAiring: freezed == timeUntilAiring + ? _value.timeUntilAiring + : timeUntilAiring // ignore: cast_nullable_to_non_nullable + as int?, + episode: freezed == episode + ? _value.episode + : episode // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_AiringScheduleModelCopyWith<$Res> + implements $AiringScheduleModelCopyWith<$Res> { + factory _$$_AiringScheduleModelCopyWith(_$_AiringScheduleModel value, + $Res Function(_$_AiringScheduleModel) then) = + __$$_AiringScheduleModelCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, int? airingAt, int? timeUntilAiring, int? episode}); +} + +/// @nodoc +class __$$_AiringScheduleModelCopyWithImpl<$Res> + extends _$AiringScheduleModelCopyWithImpl<$Res, _$_AiringScheduleModel> + implements _$$_AiringScheduleModelCopyWith<$Res> { + __$$_AiringScheduleModelCopyWithImpl(_$_AiringScheduleModel _value, + $Res Function(_$_AiringScheduleModel) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? airingAt = freezed, + Object? timeUntilAiring = freezed, + Object? episode = freezed, + }) { + return _then(_$_AiringScheduleModel( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + airingAt: freezed == airingAt + ? _value.airingAt + : airingAt // ignore: cast_nullable_to_non_nullable + as int?, + timeUntilAiring: freezed == timeUntilAiring + ? _value.timeUntilAiring + : timeUntilAiring // ignore: cast_nullable_to_non_nullable + as int?, + episode: freezed == episode + ? _value.episode + : episode // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc + +class _$_AiringScheduleModel implements _AiringScheduleModel { + _$_AiringScheduleModel( + {this.id = '', this.airingAt, this.timeUntilAiring, this.episode}); + + @override + @JsonKey() + final String id; + @override + final int? airingAt; + @override + final int? timeUntilAiring; + @override + final int? episode; + + @override + String toString() { + return 'AiringScheduleModel(id: $id, airingAt: $airingAt, timeUntilAiring: $timeUntilAiring, episode: $episode)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_AiringScheduleModel && + (identical(other.id, id) || other.id == id) && + (identical(other.airingAt, airingAt) || + other.airingAt == airingAt) && + (identical(other.timeUntilAiring, timeUntilAiring) || + other.timeUntilAiring == timeUntilAiring) && + (identical(other.episode, episode) || other.episode == episode)); + } + + @override + int get hashCode => + Object.hash(runtimeType, id, airingAt, timeUntilAiring, episode); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_AiringScheduleModelCopyWith<_$_AiringScheduleModel> get copyWith => + __$$_AiringScheduleModelCopyWithImpl<_$_AiringScheduleModel>( + this, _$identity); +} + +abstract class _AiringScheduleModel implements AiringScheduleModel { + factory _AiringScheduleModel( + {final String id, + final int? airingAt, + final int? timeUntilAiring, + final int? episode}) = _$_AiringScheduleModel; + + @override + String get id; + @override + int? get airingAt; + @override + int? get timeUntilAiring; + @override + int? get episode; + @override + @JsonKey(ignore: true) + _$$_AiringScheduleModelCopyWith<_$_AiringScheduleModel> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/core/data/repository/ani_list_repository.dart b/lib/core/data/repository/ani_list_repository.dart index 36f42f35..dfd3ebb1 100644 --- a/lib/core/data/repository/ani_list_repository.dart +++ b/lib/core/data/repository/ani_list_repository.dart @@ -1,9 +1,14 @@ +import 'package:anime_tracker/core/data/model/airing_schedule_and_anime_model.dart'; +import 'package:anime_tracker/core/data/model/airing_schedule_model.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; +import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; import 'package:anime_tracker/core/database/model/character_entity.dart'; import 'package:anime_tracker/core/database/model/staff_entity.dart'; +import 'package:anime_tracker/core/network/api/airing_schedules_query_graphql.dart.dart'; import 'package:anime_tracker/core/network/model/character_edge.dart'; import 'package:anime_tracker/core/network/model/detail_anime_dto.dart'; import 'package:anime_tracker/core/network/model/staff_edge.dart'; +import 'package:anime_tracker/util/time_util.dart'; import 'package:dio/dio.dart'; import 'package:equatable/equatable.dart'; @@ -16,6 +21,7 @@ import 'package:anime_tracker/core/shared_preference/user_data.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/network/util/http_status_util.dart'; import 'package:sqflite/sqflite.dart'; part 'load_result.dart'; @@ -113,6 +119,12 @@ abstract class AniListRepository { Stream getDetailAnimeInfoStream(String id); Future> startFetchDetailAnimeInfo(String id); + + Future> getAiringScheduleAndAnimeByDateTime( + DateTime dateTime); + + Future> refreshAiringSchedule(DateTime now, + {required int dayAgo, required int dayAfter}); } class AniListRepositoryImpl extends AniListRepository { @@ -342,4 +354,55 @@ class AniListRepositoryImpl extends AniListRepository { return LoadError(e); } } + + @override + Future> getAiringScheduleAndAnimeByDateTime( + DateTime dateTime) async { + final (startMs, endMs) = TimeUtil.getTimeRangeOfTheTargetDay(dateTime); + final entities = + await animeDao.getAiringSchedulesByTimeRange(timeRange: (startMs, endMs)); + + return entities + .map( + (e) => AiringScheduleAndAnimeModel( + airingSchedule: AiringScheduleModel.fromEntity(e.airingSchedule), + animeModel: AnimeModel.fromDatabaseModel(e.anime), + ), + ) + .toList(); + } + + @override + Future> refreshAiringSchedule(DateTime now, + {required int dayAgo, required int dayAfter}) async { + /// Clear old airing schedule. + await animeDao.clearAiringSchedule(); + + try { + final (startMs, endMs) = + TimeUtil.getTimeRange(now, daysAgo: dayAgo, daysAfter: dayAfter); + + /// Get all airing schedule from network source. + final networkResults = await aniListDataSource.getAiringSchedules( + AiringSchedulesQueryParam( + airingAtGreater: startMs ~/ 1000, airingAtLesser: endMs ~/ 1000), + ); + + /// insert airing schedule data to db. + final scheduleEntities = + networkResults.map((e) => AiringSchedulesEntity.fromDto(e)).toList(); + await animeDao.upsertAiringSchedules(schedules: scheduleEntities); + + /// insert anime data to db if not exist. + final animeEntities = networkResults + .map((e) => AnimeEntity.fromShortNetworkModel(e.media!)) + .toList(); + await animeDao.upsertAnimeInformation(animeEntities, + conflictAlgorithm: ConflictAlgorithm.ignore); + + return LoadSuccess(data: []); + } on NetworkException catch (exception) { + return LoadError(exception); + } + } } diff --git a/lib/core/database/anime_dao.dart b/lib/core/database/anime_dao.dart index 4e70b2a9..095fcba0 100644 --- a/lib/core/database/anime_dao.dart +++ b/lib/core/database/anime_dao.dart @@ -1,7 +1,9 @@ import 'package:anime_tracker/core/common/stream_util.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; +import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; import 'package:anime_tracker/core/database/model/character_entity.dart'; +import 'package:anime_tracker/core/database/model/relations/airing_schedule_and_anime.dart'; import 'package:anime_tracker/core/database/model/relations/anime_and_detail_info.dart'; import 'package:anime_tracker/core/database/model/staff_entity.dart'; import 'package:flutter/cupertino.dart'; @@ -110,6 +112,15 @@ mixin AnimeStaffCrossRefColumns { static const String staffRole = 'anime_character_cross_staff_role'; } +/// [Tables.airingSchedulesTable] +mixin AiringSchedulesColumns { + static const String id = 'airing_schedules_id'; + static const String mediaId = 'airing_schedules_media_id'; + static const String airingAt = 'airing_schedules_airing_at'; + static const String timeUntilAiring = 'airing_schedules_time_until_airing'; + static const String episode = 'airing_schedules_episode'; +} + class AnimeStaffCrossRef { AnimeStaffCrossRef( {required this.animeId, required this.staffId, required this.staffRole}); @@ -145,6 +156,15 @@ abstract class AnimeListDao { Future upsertAnimeStaffCrossRef( {required List crossRefs}); + Future upsertAiringSchedules( + {required List schedules}); + + /// Get airing schedules in time range of [startMs, endMs] + Future> getAiringSchedulesByTimeRange( + {required (int startMs, int endMs) timeRange}); + + Future clearAiringSchedule(); + void notifyAnimeDetailInfoChanged(); } @@ -216,15 +236,15 @@ class AnimeDaoImpl extends AnimeListDao { final characterSql = 'select * from ${Tables.characterTable} as c ' 'join ${Tables.animeCharacterCrossRefTable} as ac ' - 'on c.${CharacterColumns.id} = ac.${AnimeCharacterCrossRefColumns.characterId} ' + ' on c.${CharacterColumns.id} = ac.${AnimeCharacterCrossRefColumns.characterId} ' 'join ${Tables.staffTable} as v ' - 'on c.${CharacterColumns.voiceActorId} = v.${StaffColumns.id} ' + ' on c.${CharacterColumns.voiceActorId} = v.${StaffColumns.id} ' 'where ac.${AnimeCharacterCrossRefColumns.animeId} = \'$id\' '; List characterResults = await database.animeDB.rawQuery(characterSql); String staffSql = 'select * from ${Tables.staffTable} as s ' 'join ${Tables.animeStaffCrossRefTable} as animeStaff ' - 'on s.${StaffColumns.id} = animeStaff.${AnimeStaffCrossRefColumns.staffId} ' + ' on s.${StaffColumns.id} = animeStaff.${AnimeStaffCrossRefColumns.staffId} ' 'where animeStaff.${AnimeStaffCrossRefColumns.animeId} = \'$id\' '; List staffResults = await database.animeDB.rawQuery(staffSql); @@ -322,6 +342,45 @@ class AnimeDaoImpl extends AnimeListDao { return await batch.commit(noResult: true); } + @override + Future upsertAiringSchedules( + {required List schedules}) async { + final batch = database.animeDB.batch(); + for (final schedule in schedules) { + batch.insert( + Tables.airingSchedulesTable, + schedule.toJson(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + return await batch.commit(noResult: true); + } + + @override + Future> getAiringSchedulesByTimeRange( + {required (int startMs, int endMs) timeRange}) async { + final (startMs, endMs) = timeRange; + final (startSecond, endSecond) = (startMs ~/ 1000, endMs ~/ 1000); + + final sql = 'select * from ${Tables.airingSchedulesTable} as air ' + 'join ${Tables.animeTable} as anime ' + ' on air.${AiringSchedulesColumns.mediaId} = anime.${AnimeTableColumns.id} ' + 'where air.${AiringSchedulesColumns.airingAt} >= $startSecond ' + ' and air.${AiringSchedulesColumns.airingAt} < $endSecond ' + 'order by air.${AiringSchedulesColumns.airingAt} asc '; + + List results = await database.animeDB.rawQuery(sql); + + return results + .map( + (e) => AiringScheduleAndAnime( + airingSchedule: AiringSchedulesEntity.fromJson(e), + anime: AnimeEntity.fromJson(e), + ), + ) + .toList(); + } + @override Stream getDetailAnimeInfoStream(String id) { return StreamUtil.createStream( @@ -334,4 +393,9 @@ class AnimeDaoImpl extends AnimeListDao { void notifyAnimeDetailInfoChanged() { detailListChangeSource.value = detailListChangeSource.value++; } + + @override + Future clearAiringSchedule() { + return database.animeDB.delete(Tables.airingSchedulesTable); + } } diff --git a/lib/core/database/anime_database.dart b/lib/core/database/anime_database.dart index 3ba5a35e..9a94e3c2 100644 --- a/lib/core/database/anime_database.dart +++ b/lib/core/database/anime_database.dart @@ -20,6 +20,7 @@ mixin Tables { static const String staffTable = 'voice_actor_table'; static const String userDataTable = 'user_data_table'; static const String animeTrackListTable = 'usr_anime_list_table'; + static const String airingSchedulesTable = 'airing_schedules_table'; } class AnimeDatabase { @@ -101,14 +102,14 @@ class AnimeDatabase { ')'); await _animeDB! - .execute('CREATE TABLE IF NOT EXISTS ${Tables.userDataTable} (' + .execute('create table if not exists ${Tables.userDataTable} (' '${UserDataTableColumns.id} text primary key, ' '${UserDataTableColumns.name} text, ' '${UserDataTableColumns.avatarImage} text, ' '${UserDataTableColumns.bannerImage} text)'); await _animeDB! - .execute('CREATE TABLE IF NOT EXISTS ${Tables.characterTable} (' + .execute('create table if not exists ${Tables.characterTable} (' '${CharacterColumns.id} text primary key, ' '${CharacterColumns.voiceActorId} text, ' '${CharacterColumns.role} text, ' @@ -116,7 +117,7 @@ class AnimeDatabase { '${CharacterColumns.nameEnglish} text, ' '${CharacterColumns.nameNative} text)'); - await _animeDB!.execute('CREATE TABLE IF NOT EXISTS ${Tables.staffTable} (' + await _animeDB!.execute('create table if not exists ${Tables.staffTable} (' '${StaffColumns.id} text primary key, ' '${StaffColumns.image} text, ' '${StaffColumns.nameEnglish} text, ' @@ -142,13 +143,23 @@ class AnimeDatabase { ')'); await _animeDB! - .execute('CREATE TABLE IF NOT EXISTS ${Tables.animeTrackListTable} (' + .execute('create table if not exists ${Tables.animeTrackListTable} (' '${AnimeTrackItemTableColumns.id} text primary key,' '${AnimeTrackItemTableColumns.userId} text,' '${AnimeTrackItemTableColumns.animeId} text,' '${AnimeTrackItemTableColumns.status} text,' '${AnimeTrackItemTableColumns.progress} integer,' '${AnimeTrackItemTableColumns.score} integer,' - '${AnimeTrackItemTableColumns.updatedAt} integer)'); + '${AnimeTrackItemTableColumns.updatedAt} integer' + ')'); + + await _animeDB! + .execute('create table if not exists ${Tables.airingSchedulesTable} (' + '${AiringSchedulesColumns.id} text primary key,' + '${AiringSchedulesColumns.mediaId} text,' + '${AiringSchedulesColumns.airingAt} integer,' + '${AiringSchedulesColumns.timeUntilAiring} integer,' + '${AiringSchedulesColumns.episode} episode' + ')'); } } diff --git a/lib/core/database/model/airing_schedules_entity.dart b/lib/core/database/model/airing_schedules_entity.dart new file mode 100644 index 00000000..f2002139 --- /dev/null +++ b/lib/core/database/model/airing_schedules_entity.dart @@ -0,0 +1,31 @@ +import 'package:anime_tracker/core/database/anime_dao.dart'; +import 'package:anime_tracker/core/network/model/airing_schedule_dto.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'airing_schedules_entity.freezed.dart'; + +part 'airing_schedules_entity.g.dart'; + +@freezed +class AiringSchedulesEntity with _$AiringSchedulesEntity { + factory AiringSchedulesEntity({ + @Default('') @JsonKey(name: AiringSchedulesColumns.id) String id, + @Default('') @JsonKey(name: AiringSchedulesColumns.mediaId) String mediaId, + @JsonKey(name: AiringSchedulesColumns.airingAt) int? airingAt, + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) int? timeUntilAiring, + @JsonKey(name: AiringSchedulesColumns.episode) int? episode, + }) = _AiringSchedulesEntity; + + factory AiringSchedulesEntity.fromJson(Map json) => + _$$_AiringSchedulesEntityFromJson(json); + + static AiringSchedulesEntity fromDto(AiringScheduleDto dto) { + return AiringSchedulesEntity( + id: dto.id.toString(), + mediaId: dto.mediaId.toString(), + airingAt: dto.airingAt, + timeUntilAiring: dto.timeUntilAiring, + episode: dto.episode + ); + } +} diff --git a/lib/core/database/model/airing_schedules_entity.freezed.dart b/lib/core/database/model/airing_schedules_entity.freezed.dart new file mode 100644 index 00000000..28006519 --- /dev/null +++ b/lib/core/database/model/airing_schedules_entity.freezed.dart @@ -0,0 +1,261 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'airing_schedules_entity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +AiringSchedulesEntity _$AiringSchedulesEntityFromJson( + Map json) { + return _AiringSchedulesEntity.fromJson(json); +} + +/// @nodoc +mixin _$AiringSchedulesEntity { + @JsonKey(name: AiringSchedulesColumns.id) + String get id => throw _privateConstructorUsedError; + @JsonKey(name: AiringSchedulesColumns.mediaId) + String get mediaId => throw _privateConstructorUsedError; + @JsonKey(name: AiringSchedulesColumns.airingAt) + int? get airingAt => throw _privateConstructorUsedError; + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + int? get timeUntilAiring => throw _privateConstructorUsedError; + @JsonKey(name: AiringSchedulesColumns.episode) + int? get episode => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AiringSchedulesEntityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AiringSchedulesEntityCopyWith<$Res> { + factory $AiringSchedulesEntityCopyWith(AiringSchedulesEntity value, + $Res Function(AiringSchedulesEntity) then) = + _$AiringSchedulesEntityCopyWithImpl<$Res, AiringSchedulesEntity>; + @useResult + $Res call( + {@JsonKey(name: AiringSchedulesColumns.id) String id, + @JsonKey(name: AiringSchedulesColumns.mediaId) String mediaId, + @JsonKey(name: AiringSchedulesColumns.airingAt) int? airingAt, + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + int? timeUntilAiring, + @JsonKey(name: AiringSchedulesColumns.episode) int? episode}); +} + +/// @nodoc +class _$AiringSchedulesEntityCopyWithImpl<$Res, + $Val extends AiringSchedulesEntity> + implements $AiringSchedulesEntityCopyWith<$Res> { + _$AiringSchedulesEntityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? mediaId = null, + Object? airingAt = freezed, + Object? timeUntilAiring = freezed, + Object? episode = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + mediaId: null == mediaId + ? _value.mediaId + : mediaId // ignore: cast_nullable_to_non_nullable + as String, + airingAt: freezed == airingAt + ? _value.airingAt + : airingAt // ignore: cast_nullable_to_non_nullable + as int?, + timeUntilAiring: freezed == timeUntilAiring + ? _value.timeUntilAiring + : timeUntilAiring // ignore: cast_nullable_to_non_nullable + as int?, + episode: freezed == episode + ? _value.episode + : episode // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_AiringSchedulesEntityCopyWith<$Res> + implements $AiringSchedulesEntityCopyWith<$Res> { + factory _$$_AiringSchedulesEntityCopyWith(_$_AiringSchedulesEntity value, + $Res Function(_$_AiringSchedulesEntity) then) = + __$$_AiringSchedulesEntityCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: AiringSchedulesColumns.id) String id, + @JsonKey(name: AiringSchedulesColumns.mediaId) String mediaId, + @JsonKey(name: AiringSchedulesColumns.airingAt) int? airingAt, + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + int? timeUntilAiring, + @JsonKey(name: AiringSchedulesColumns.episode) int? episode}); +} + +/// @nodoc +class __$$_AiringSchedulesEntityCopyWithImpl<$Res> + extends _$AiringSchedulesEntityCopyWithImpl<$Res, _$_AiringSchedulesEntity> + implements _$$_AiringSchedulesEntityCopyWith<$Res> { + __$$_AiringSchedulesEntityCopyWithImpl(_$_AiringSchedulesEntity _value, + $Res Function(_$_AiringSchedulesEntity) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? mediaId = null, + Object? airingAt = freezed, + Object? timeUntilAiring = freezed, + Object? episode = freezed, + }) { + return _then(_$_AiringSchedulesEntity( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + mediaId: null == mediaId + ? _value.mediaId + : mediaId // ignore: cast_nullable_to_non_nullable + as String, + airingAt: freezed == airingAt + ? _value.airingAt + : airingAt // ignore: cast_nullable_to_non_nullable + as int?, + timeUntilAiring: freezed == timeUntilAiring + ? _value.timeUntilAiring + : timeUntilAiring // ignore: cast_nullable_to_non_nullable + as int?, + episode: freezed == episode + ? _value.episode + : episode // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$_AiringSchedulesEntity implements _AiringSchedulesEntity { + _$_AiringSchedulesEntity( + {@JsonKey(name: AiringSchedulesColumns.id) this.id = '', + @JsonKey(name: AiringSchedulesColumns.mediaId) this.mediaId = '', + @JsonKey(name: AiringSchedulesColumns.airingAt) this.airingAt, + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + this.timeUntilAiring, + @JsonKey(name: AiringSchedulesColumns.episode) this.episode}); + + factory _$_AiringSchedulesEntity.fromJson(Map json) => + _$$_AiringSchedulesEntityFromJson(json); + + @override + @JsonKey(name: AiringSchedulesColumns.id) + final String id; + @override + @JsonKey(name: AiringSchedulesColumns.mediaId) + final String mediaId; + @override + @JsonKey(name: AiringSchedulesColumns.airingAt) + final int? airingAt; + @override + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + final int? timeUntilAiring; + @override + @JsonKey(name: AiringSchedulesColumns.episode) + final int? episode; + + @override + String toString() { + return 'AiringSchedulesEntity(id: $id, mediaId: $mediaId, airingAt: $airingAt, timeUntilAiring: $timeUntilAiring, episode: $episode)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_AiringSchedulesEntity && + (identical(other.id, id) || other.id == id) && + (identical(other.mediaId, mediaId) || other.mediaId == mediaId) && + (identical(other.airingAt, airingAt) || + other.airingAt == airingAt) && + (identical(other.timeUntilAiring, timeUntilAiring) || + other.timeUntilAiring == timeUntilAiring) && + (identical(other.episode, episode) || other.episode == episode)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, id, mediaId, airingAt, timeUntilAiring, episode); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_AiringSchedulesEntityCopyWith<_$_AiringSchedulesEntity> get copyWith => + __$$_AiringSchedulesEntityCopyWithImpl<_$_AiringSchedulesEntity>( + this, _$identity); + + @override + Map toJson() { + return _$$_AiringSchedulesEntityToJson( + this, + ); + } +} + +abstract class _AiringSchedulesEntity implements AiringSchedulesEntity { + factory _AiringSchedulesEntity( + {@JsonKey(name: AiringSchedulesColumns.id) final String id, + @JsonKey(name: AiringSchedulesColumns.mediaId) final String mediaId, + @JsonKey(name: AiringSchedulesColumns.airingAt) final int? airingAt, + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + final int? timeUntilAiring, + @JsonKey(name: AiringSchedulesColumns.episode) final int? episode}) = + _$_AiringSchedulesEntity; + + factory _AiringSchedulesEntity.fromJson(Map json) = + _$_AiringSchedulesEntity.fromJson; + + @override + @JsonKey(name: AiringSchedulesColumns.id) + String get id; + @override + @JsonKey(name: AiringSchedulesColumns.mediaId) + String get mediaId; + @override + @JsonKey(name: AiringSchedulesColumns.airingAt) + int? get airingAt; + @override + @JsonKey(name: AiringSchedulesColumns.timeUntilAiring) + int? get timeUntilAiring; + @override + @JsonKey(name: AiringSchedulesColumns.episode) + int? get episode; + @override + @JsonKey(ignore: true) + _$$_AiringSchedulesEntityCopyWith<_$_AiringSchedulesEntity> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/core/database/model/airing_schedules_entity.g.dart b/lib/core/database/model/airing_schedules_entity.g.dart new file mode 100644 index 00000000..94631058 --- /dev/null +++ b/lib/core/database/model/airing_schedules_entity.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'airing_schedules_entity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$_AiringSchedulesEntity _$$_AiringSchedulesEntityFromJson( + Map json) => + _$_AiringSchedulesEntity( + id: json['airing_schedules_id'] as String? ?? '', + mediaId: json['airing_schedules_media_id'] as String? ?? '', + airingAt: json['airing_schedules_airing_at'] as int?, + timeUntilAiring: json['airing_schedules_time_until_airing'] as int?, + episode: json['airing_schedules_episode'] as int?, + ); + +Map _$$_AiringSchedulesEntityToJson( + _$_AiringSchedulesEntity instance) => + { + 'airing_schedules_id': instance.id, + 'airing_schedules_media_id': instance.mediaId, + 'airing_schedules_airing_at': instance.airingAt, + 'airing_schedules_time_until_airing': instance.timeUntilAiring, + 'airing_schedules_episode': instance.episode, + }; diff --git a/lib/core/database/model/relations/airing_schedule_and_anime.dart b/lib/core/database/model/relations/airing_schedule_and_anime.dart new file mode 100644 index 00000000..386bd814 --- /dev/null +++ b/lib/core/database/model/relations/airing_schedule_and_anime.dart @@ -0,0 +1,9 @@ +import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; +import 'package:anime_tracker/core/database/model/anime_entity.dart'; + +class AiringScheduleAndAnime { + final AiringSchedulesEntity airingSchedule; + final AnimeEntity anime; + + AiringScheduleAndAnime({required this.airingSchedule, required this.anime}); +} diff --git a/lib/core/network/ani_list_data_source.dart b/lib/core/network/ani_list_data_source.dart index 5e83e596..2ffea851 100644 --- a/lib/core/network/ani_list_data_source.dart +++ b/lib/core/network/ani_list_data_source.dart @@ -2,7 +2,7 @@ import 'package:anime_tracker/core/network/api/airing_schedules_query_graphql.da import 'package:anime_tracker/core/network/api/ani_detail_query_graphql.dart'; import 'package:anime_tracker/core/network/api/user_anime_list_query_graphql.dart'; import 'package:anime_tracker/core/network/client/ani_list_dio.dart'; -import 'package:anime_tracker/core/network/model/airing_schedule.dart'; +import 'package:anime_tracker/core/network/model/airing_schedule_dto.dart'; import 'package:anime_tracker/core/network/model/detail_anime_dto.dart'; import 'package:anime_tracker/core/network/api/ani_list_query_graphql.dart'; @@ -101,7 +101,7 @@ class AniListDataSource { return animeList; } - Future> getAiringSchedules( + Future> getAiringSchedules( AiringSchedulesQueryParam param) async { final queryGraphQL = airingSchedulesQueryGraphQLString; final variablesMap = { @@ -113,7 +113,7 @@ class AniListDataSource { data: {'query': queryGraphQL, 'variables': variablesMap}); final List resultJson = response.data['data']['Page']['airingSchedules']; final airingSchedules = - resultJson.map((e) => AiringSchedule.fromJson(e)).toList(); + resultJson.map((e) => AiringScheduleDto.fromJson(e)).toList(); return airingSchedules; } diff --git a/lib/core/network/api/airing_schedules_query_graphql.dart.dart b/lib/core/network/api/airing_schedules_query_graphql.dart.dart index e1494c7e..b78d6ed7 100644 --- a/lib/core/network/api/airing_schedules_query_graphql.dart.dart +++ b/lib/core/network/api/airing_schedules_query_graphql.dart.dart @@ -15,6 +15,24 @@ query(\$airingAt_greater: Int, \$airingAt_lesser: Int){ timeUntilAiring episode mediaId + media { + id + type + format + status + season + coverImage { + extraLarge + large + medium + color + } + title { + romaji + english + native + } + } } } } diff --git a/lib/core/network/model/airing_schedule.dart b/lib/core/network/model/airing_schedule.dart deleted file mode 100644 index 6791f545..00000000 --- a/lib/core/network/model/airing_schedule.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'airing_schedule.freezed.dart'; - -part 'airing_schedule.g.dart'; - -@freezed -class AiringSchedule with _$AiringSchedule { - factory AiringSchedule({ - @Default(-1) @JsonKey(name: 'id') int id, - @JsonKey(name: 'mediaId') int? mediaId, - @JsonKey(name: 'airingAt') int? airingAt, - @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, - @JsonKey(name: 'episode') int? episode, - }) = _AiringSchedule; - - factory AiringSchedule.fromJson(Map json) => - _$$_AiringScheduleFromJson(json); -} diff --git a/lib/core/network/model/airing_schedule_dto.dart b/lib/core/network/model/airing_schedule_dto.dart new file mode 100644 index 00000000..55d55ed0 --- /dev/null +++ b/lib/core/network/model/airing_schedule_dto.dart @@ -0,0 +1,21 @@ +import 'package:anime_tracker/core/network/model/short_anime_dto.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'airing_schedule_dto.freezed.dart'; + +part 'airing_schedule_dto.g.dart'; + +@freezed +class AiringScheduleDto with _$AiringScheduleDto { + factory AiringScheduleDto({ + @Default(-1) @JsonKey(name: 'id') int id, + @JsonKey(name: 'mediaId') int? mediaId, + @JsonKey(name: 'airingAt') int? airingAt, + @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, + @JsonKey(name: 'episode') int? episode, + @JsonKey(name: 'media') ShortcutAnimeDto? media, + }) = _AiringScheduleDto; + + factory AiringScheduleDto.fromJson(Map json) => + _$$_AiringScheduleDtoFromJson(json); +} diff --git a/lib/core/network/model/airing_schedule.freezed.dart b/lib/core/network/model/airing_schedule_dto.freezed.dart similarity index 59% rename from lib/core/network/model/airing_schedule.freezed.dart rename to lib/core/network/model/airing_schedule_dto.freezed.dart index ceea3b57..efab4efd 100644 --- a/lib/core/network/model/airing_schedule.freezed.dart +++ b/lib/core/network/model/airing_schedule_dto.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'airing_schedule.dart'; +part of 'airing_schedule_dto.dart'; // ************************************************************************** // FreezedGenerator @@ -14,12 +14,12 @@ T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); -AiringSchedule _$AiringScheduleFromJson(Map json) { - return _AiringSchedule.fromJson(json); +AiringScheduleDto _$AiringScheduleDtoFromJson(Map json) { + return _AiringScheduleDto.fromJson(json); } /// @nodoc -mixin _$AiringSchedule { +mixin _$AiringScheduleDto { @JsonKey(name: 'id') int get id => throw _privateConstructorUsedError; @JsonKey(name: 'mediaId') @@ -30,31 +30,36 @@ mixin _$AiringSchedule { int? get timeUntilAiring => throw _privateConstructorUsedError; @JsonKey(name: 'episode') int? get episode => throw _privateConstructorUsedError; + @JsonKey(name: 'media') + ShortcutAnimeDto? get media => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) - $AiringScheduleCopyWith get copyWith => + $AiringScheduleDtoCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $AiringScheduleCopyWith<$Res> { - factory $AiringScheduleCopyWith( - AiringSchedule value, $Res Function(AiringSchedule) then) = - _$AiringScheduleCopyWithImpl<$Res, AiringSchedule>; +abstract class $AiringScheduleDtoCopyWith<$Res> { + factory $AiringScheduleDtoCopyWith( + AiringScheduleDto value, $Res Function(AiringScheduleDto) then) = + _$AiringScheduleDtoCopyWithImpl<$Res, AiringScheduleDto>; @useResult $Res call( {@JsonKey(name: 'id') int id, @JsonKey(name: 'mediaId') int? mediaId, @JsonKey(name: 'airingAt') int? airingAt, @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, - @JsonKey(name: 'episode') int? episode}); + @JsonKey(name: 'episode') int? episode, + @JsonKey(name: 'media') ShortcutAnimeDto? media}); + + $ShortcutAnimeDtoCopyWith<$Res>? get media; } /// @nodoc -class _$AiringScheduleCopyWithImpl<$Res, $Val extends AiringSchedule> - implements $AiringScheduleCopyWith<$Res> { - _$AiringScheduleCopyWithImpl(this._value, this._then); +class _$AiringScheduleDtoCopyWithImpl<$Res, $Val extends AiringScheduleDto> + implements $AiringScheduleDtoCopyWith<$Res> { + _$AiringScheduleDtoCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; @@ -69,6 +74,7 @@ class _$AiringScheduleCopyWithImpl<$Res, $Val extends AiringSchedule> Object? airingAt = freezed, Object? timeUntilAiring = freezed, Object? episode = freezed, + Object? media = freezed, }) { return _then(_value.copyWith( id: null == id @@ -91,16 +97,32 @@ class _$AiringScheduleCopyWithImpl<$Res, $Val extends AiringSchedule> ? _value.episode : episode // ignore: cast_nullable_to_non_nullable as int?, + media: freezed == media + ? _value.media + : media // ignore: cast_nullable_to_non_nullable + as ShortcutAnimeDto?, ) as $Val); } + + @override + @pragma('vm:prefer-inline') + $ShortcutAnimeDtoCopyWith<$Res>? get media { + if (_value.media == null) { + return null; + } + + return $ShortcutAnimeDtoCopyWith<$Res>(_value.media!, (value) { + return _then(_value.copyWith(media: value) as $Val); + }); + } } /// @nodoc -abstract class _$$_AiringScheduleCopyWith<$Res> - implements $AiringScheduleCopyWith<$Res> { - factory _$$_AiringScheduleCopyWith( - _$_AiringSchedule value, $Res Function(_$_AiringSchedule) then) = - __$$_AiringScheduleCopyWithImpl<$Res>; +abstract class _$$_AiringScheduleDtoCopyWith<$Res> + implements $AiringScheduleDtoCopyWith<$Res> { + factory _$$_AiringScheduleDtoCopyWith(_$_AiringScheduleDto value, + $Res Function(_$_AiringScheduleDto) then) = + __$$_AiringScheduleDtoCopyWithImpl<$Res>; @override @useResult $Res call( @@ -108,15 +130,19 @@ abstract class _$$_AiringScheduleCopyWith<$Res> @JsonKey(name: 'mediaId') int? mediaId, @JsonKey(name: 'airingAt') int? airingAt, @JsonKey(name: 'timeUntilAiring') int? timeUntilAiring, - @JsonKey(name: 'episode') int? episode}); + @JsonKey(name: 'episode') int? episode, + @JsonKey(name: 'media') ShortcutAnimeDto? media}); + + @override + $ShortcutAnimeDtoCopyWith<$Res>? get media; } /// @nodoc -class __$$_AiringScheduleCopyWithImpl<$Res> - extends _$AiringScheduleCopyWithImpl<$Res, _$_AiringSchedule> - implements _$$_AiringScheduleCopyWith<$Res> { - __$$_AiringScheduleCopyWithImpl( - _$_AiringSchedule _value, $Res Function(_$_AiringSchedule) _then) +class __$$_AiringScheduleDtoCopyWithImpl<$Res> + extends _$AiringScheduleDtoCopyWithImpl<$Res, _$_AiringScheduleDto> + implements _$$_AiringScheduleDtoCopyWith<$Res> { + __$$_AiringScheduleDtoCopyWithImpl( + _$_AiringScheduleDto _value, $Res Function(_$_AiringScheduleDto) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -127,8 +153,9 @@ class __$$_AiringScheduleCopyWithImpl<$Res> Object? airingAt = freezed, Object? timeUntilAiring = freezed, Object? episode = freezed, + Object? media = freezed, }) { - return _then(_$_AiringSchedule( + return _then(_$_AiringScheduleDto( id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable @@ -149,22 +176,27 @@ class __$$_AiringScheduleCopyWithImpl<$Res> ? _value.episode : episode // ignore: cast_nullable_to_non_nullable as int?, + media: freezed == media + ? _value.media + : media // ignore: cast_nullable_to_non_nullable + as ShortcutAnimeDto?, )); } } /// @nodoc @JsonSerializable() -class _$_AiringSchedule implements _AiringSchedule { - _$_AiringSchedule( +class _$_AiringScheduleDto implements _AiringScheduleDto { + _$_AiringScheduleDto( {@JsonKey(name: 'id') this.id = -1, @JsonKey(name: 'mediaId') this.mediaId, @JsonKey(name: 'airingAt') this.airingAt, @JsonKey(name: 'timeUntilAiring') this.timeUntilAiring, - @JsonKey(name: 'episode') this.episode}); + @JsonKey(name: 'episode') this.episode, + @JsonKey(name: 'media') this.media}); - factory _$_AiringSchedule.fromJson(Map json) => - _$$_AiringScheduleFromJson(json); + factory _$_AiringScheduleDto.fromJson(Map json) => + _$$_AiringScheduleDtoFromJson(json); @override @JsonKey(name: 'id') @@ -181,55 +213,62 @@ class _$_AiringSchedule implements _AiringSchedule { @override @JsonKey(name: 'episode') final int? episode; + @override + @JsonKey(name: 'media') + final ShortcutAnimeDto? media; @override String toString() { - return 'AiringSchedule(id: $id, mediaId: $mediaId, airingAt: $airingAt, timeUntilAiring: $timeUntilAiring, episode: $episode)'; + return 'AiringScheduleDto(id: $id, mediaId: $mediaId, airingAt: $airingAt, timeUntilAiring: $timeUntilAiring, episode: $episode, media: $media)'; } @override bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_AiringSchedule && + other is _$_AiringScheduleDto && (identical(other.id, id) || other.id == id) && (identical(other.mediaId, mediaId) || other.mediaId == mediaId) && (identical(other.airingAt, airingAt) || other.airingAt == airingAt) && (identical(other.timeUntilAiring, timeUntilAiring) || other.timeUntilAiring == timeUntilAiring) && - (identical(other.episode, episode) || other.episode == episode)); + (identical(other.episode, episode) || other.episode == episode) && + (identical(other.media, media) || other.media == media)); } @JsonKey(ignore: true) @override - int get hashCode => - Object.hash(runtimeType, id, mediaId, airingAt, timeUntilAiring, episode); + int get hashCode => Object.hash( + runtimeType, id, mediaId, airingAt, timeUntilAiring, episode, media); @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_AiringScheduleCopyWith<_$_AiringSchedule> get copyWith => - __$$_AiringScheduleCopyWithImpl<_$_AiringSchedule>(this, _$identity); + _$$_AiringScheduleDtoCopyWith<_$_AiringScheduleDto> get copyWith => + __$$_AiringScheduleDtoCopyWithImpl<_$_AiringScheduleDto>( + this, _$identity); @override Map toJson() { - return _$$_AiringScheduleToJson( + return _$$_AiringScheduleDtoToJson( this, ); } } -abstract class _AiringSchedule implements AiringSchedule { - factory _AiringSchedule( - {@JsonKey(name: 'id') final int id, - @JsonKey(name: 'mediaId') final int? mediaId, - @JsonKey(name: 'airingAt') final int? airingAt, - @JsonKey(name: 'timeUntilAiring') final int? timeUntilAiring, - @JsonKey(name: 'episode') final int? episode}) = _$_AiringSchedule; +abstract class _AiringScheduleDto implements AiringScheduleDto { + factory _AiringScheduleDto( + {@JsonKey(name: 'id') final int id, + @JsonKey(name: 'mediaId') final int? mediaId, + @JsonKey(name: 'airingAt') final int? airingAt, + @JsonKey(name: 'timeUntilAiring') final int? timeUntilAiring, + @JsonKey(name: 'episode') final int? episode, + @JsonKey(name: 'media') final ShortcutAnimeDto? media}) = + _$_AiringScheduleDto; - factory _AiringSchedule.fromJson(Map json) = - _$_AiringSchedule.fromJson; + factory _AiringScheduleDto.fromJson(Map json) = + _$_AiringScheduleDto.fromJson; @override @JsonKey(name: 'id') @@ -247,7 +286,10 @@ abstract class _AiringSchedule implements AiringSchedule { @JsonKey(name: 'episode') int? get episode; @override + @JsonKey(name: 'media') + ShortcutAnimeDto? get media; + @override @JsonKey(ignore: true) - _$$_AiringScheduleCopyWith<_$_AiringSchedule> get copyWith => + _$$_AiringScheduleDtoCopyWith<_$_AiringScheduleDto> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/core/network/model/airing_schedule.g.dart b/lib/core/network/model/airing_schedule_dto.g.dart similarity index 62% rename from lib/core/network/model/airing_schedule.g.dart rename to lib/core/network/model/airing_schedule_dto.g.dart index 9e1e01bf..fd886ebc 100644 --- a/lib/core/network/model/airing_schedule.g.dart +++ b/lib/core/network/model/airing_schedule_dto.g.dart @@ -1,25 +1,30 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'airing_schedule.dart'; +part of 'airing_schedule_dto.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -_$_AiringSchedule _$$_AiringScheduleFromJson(Map json) => - _$_AiringSchedule( +_$_AiringScheduleDto _$$_AiringScheduleDtoFromJson(Map json) => + _$_AiringScheduleDto( id: json['id'] as int? ?? -1, mediaId: json['mediaId'] as int?, airingAt: json['airingAt'] as int?, timeUntilAiring: json['timeUntilAiring'] as int?, episode: json['episode'] as int?, + media: json['media'] == null + ? null + : ShortcutAnimeDto.fromJson(json['media'] as Map), ); -Map _$$_AiringScheduleToJson(_$_AiringSchedule instance) => +Map _$$_AiringScheduleDtoToJson( + _$_AiringScheduleDto instance) => { 'id': instance.id, 'mediaId': instance.mediaId, 'airingAt': instance.airingAt, 'timeUntilAiring': instance.timeUntilAiring, 'episode': instance.episode, + 'media': instance.media, }; diff --git a/lib/core/network/model/detail_anime_dto.dart b/lib/core/network/model/detail_anime_dto.dart index 67e06b02..a3cfdd95 100644 --- a/lib/core/network/model/detail_anime_dto.dart +++ b/lib/core/network/model/detail_anime_dto.dart @@ -1,6 +1,6 @@ import 'package:anime_tracker/core/data/model/anime_source.dart'; import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/network/model/airing_schedule.dart'; +import 'package:anime_tracker/core/network/model/airing_schedule_dto.dart'; import 'package:anime_tracker/core/network/model/anime_rank.dart'; import 'package:anime_tracker/core/network/model/staff_connection.dart'; import 'package:anime_tracker/core/network/model/trailer_dto.dart'; @@ -32,7 +32,7 @@ class DetailAnimeDto with _$DetailAnimeDto { @JsonKey(name: 'favourites') int? favourites, @Default([]) @JsonKey(name: 'genres') List genres, @JsonKey(name: 'trailer') TrailerDto? trailer, - @JsonKey(name: 'nextAiringEpisode') AiringSchedule? nextAiringEpisode, + @JsonKey(name: 'nextAiringEpisode') AiringScheduleDto? nextAiringEpisode, @Default([]) @JsonKey(name: 'rankings') List rankings, @JsonKey(name: 'characters') CharacterConnection? characters, @JsonKey(name: 'staff') StaffConnection? staff, diff --git a/lib/core/network/model/detail_anime_dto.freezed.dart b/lib/core/network/model/detail_anime_dto.freezed.dart index c1179057..02652bcb 100644 --- a/lib/core/network/model/detail_anime_dto.freezed.dart +++ b/lib/core/network/model/detail_anime_dto.freezed.dart @@ -53,7 +53,8 @@ mixin _$DetailAnimeDto { @JsonKey(name: 'trailer') TrailerDto? get trailer => throw _privateConstructorUsedError; @JsonKey(name: 'nextAiringEpisode') - AiringSchedule? get nextAiringEpisode => throw _privateConstructorUsedError; + AiringScheduleDto? get nextAiringEpisode => + throw _privateConstructorUsedError; @JsonKey(name: 'rankings') List get rankings => throw _privateConstructorUsedError; @JsonKey(name: 'characters') @@ -90,14 +91,14 @@ abstract class $DetailAnimeDtoCopyWith<$Res> { @JsonKey(name: 'favourites') int? favourites, @JsonKey(name: 'genres') List genres, @JsonKey(name: 'trailer') TrailerDto? trailer, - @JsonKey(name: 'nextAiringEpisode') AiringSchedule? nextAiringEpisode, + @JsonKey(name: 'nextAiringEpisode') AiringScheduleDto? nextAiringEpisode, @JsonKey(name: 'rankings') List rankings, @JsonKey(name: 'characters') CharacterConnection? characters, @JsonKey(name: 'staff') StaffConnection? staff}); $AnimeTitleCopyWith<$Res>? get title; $TrailerDtoCopyWith<$Res>? get trailer; - $AiringScheduleCopyWith<$Res>? get nextAiringEpisode; + $AiringScheduleDtoCopyWith<$Res>? get nextAiringEpisode; $CharacterConnectionCopyWith<$Res>? get characters; $StaffConnectionCopyWith<$Res>? get staff; } @@ -204,7 +205,7 @@ class _$DetailAnimeDtoCopyWithImpl<$Res, $Val extends DetailAnimeDto> nextAiringEpisode: freezed == nextAiringEpisode ? _value.nextAiringEpisode : nextAiringEpisode // ignore: cast_nullable_to_non_nullable - as AiringSchedule?, + as AiringScheduleDto?, rankings: null == rankings ? _value.rankings : rankings // ignore: cast_nullable_to_non_nullable @@ -246,12 +247,12 @@ class _$DetailAnimeDtoCopyWithImpl<$Res, $Val extends DetailAnimeDto> @override @pragma('vm:prefer-inline') - $AiringScheduleCopyWith<$Res>? get nextAiringEpisode { + $AiringScheduleDtoCopyWith<$Res>? get nextAiringEpisode { if (_value.nextAiringEpisode == null) { return null; } - return $AiringScheduleCopyWith<$Res>(_value.nextAiringEpisode!, (value) { + return $AiringScheduleDtoCopyWith<$Res>(_value.nextAiringEpisode!, (value) { return _then(_value.copyWith(nextAiringEpisode: value) as $Val); }); } @@ -306,7 +307,7 @@ abstract class _$$_DetailAnimeDtoCopyWith<$Res> @JsonKey(name: 'favourites') int? favourites, @JsonKey(name: 'genres') List genres, @JsonKey(name: 'trailer') TrailerDto? trailer, - @JsonKey(name: 'nextAiringEpisode') AiringSchedule? nextAiringEpisode, + @JsonKey(name: 'nextAiringEpisode') AiringScheduleDto? nextAiringEpisode, @JsonKey(name: 'rankings') List rankings, @JsonKey(name: 'characters') CharacterConnection? characters, @JsonKey(name: 'staff') StaffConnection? staff}); @@ -316,7 +317,7 @@ abstract class _$$_DetailAnimeDtoCopyWith<$Res> @override $TrailerDtoCopyWith<$Res>? get trailer; @override - $AiringScheduleCopyWith<$Res>? get nextAiringEpisode; + $AiringScheduleDtoCopyWith<$Res>? get nextAiringEpisode; @override $CharacterConnectionCopyWith<$Res>? get characters; @override @@ -423,7 +424,7 @@ class __$$_DetailAnimeDtoCopyWithImpl<$Res> nextAiringEpisode: freezed == nextAiringEpisode ? _value.nextAiringEpisode : nextAiringEpisode // ignore: cast_nullable_to_non_nullable - as AiringSchedule?, + as AiringScheduleDto?, rankings: null == rankings ? _value._rankings : rankings // ignore: cast_nullable_to_non_nullable @@ -534,7 +535,7 @@ class _$_DetailAnimeDto implements _DetailAnimeDto { final TrailerDto? trailer; @override @JsonKey(name: 'nextAiringEpisode') - final AiringSchedule? nextAiringEpisode; + final AiringScheduleDto? nextAiringEpisode; final List _rankings; @override @JsonKey(name: 'rankings') @@ -652,7 +653,7 @@ abstract class _DetailAnimeDto implements DetailAnimeDto { @JsonKey(name: 'genres') final List genres, @JsonKey(name: 'trailer') final TrailerDto? trailer, @JsonKey(name: 'nextAiringEpisode') - final AiringSchedule? nextAiringEpisode, + final AiringScheduleDto? nextAiringEpisode, @JsonKey(name: 'rankings') final List rankings, @JsonKey(name: 'characters') final CharacterConnection? characters, @JsonKey(name: 'staff') final StaffConnection? staff}) = @@ -711,7 +712,7 @@ abstract class _DetailAnimeDto implements DetailAnimeDto { TrailerDto? get trailer; @override @JsonKey(name: 'nextAiringEpisode') - AiringSchedule? get nextAiringEpisode; + AiringScheduleDto? get nextAiringEpisode; @override @JsonKey(name: 'rankings') List get rankings; diff --git a/lib/core/network/model/detail_anime_dto.g.dart b/lib/core/network/model/detail_anime_dto.g.dart index c07c49e8..f0c338f1 100644 --- a/lib/core/network/model/detail_anime_dto.g.dart +++ b/lib/core/network/model/detail_anime_dto.g.dart @@ -33,7 +33,7 @@ _$_DetailAnimeDto _$$_DetailAnimeDtoFromJson(Map json) => : TrailerDto.fromJson(json['trailer'] as Map), nextAiringEpisode: json['nextAiringEpisode'] == null ? null - : AiringSchedule.fromJson( + : AiringScheduleDto.fromJson( json['nextAiringEpisode'] as Map), rankings: (json['rankings'] as List?) ?.map((e) => e == null diff --git a/lib/util/time_util.dart b/lib/util/time_util.dart index 53498647..85049942 100644 --- a/lib/util/time_util.dart +++ b/lib/util/time_util.dart @@ -42,8 +42,7 @@ mixin TimeUtil { .millisecondsSinceEpoch; final rangeEnd = DateTime(timeAfter.year, timeAfter.month, timeAfter.day + 1) - .millisecondsSinceEpoch - - 1; + .millisecondsSinceEpoch - 1; return (rangeStart, rangeEnd); } diff --git a/test/core/data/repository/ani_lsit_repository_test.dart b/test/core/data/repository/ani_lsit_repository_test.dart index 81d8c3f4..4f98b504 100644 --- a/test/core/data/repository/ani_lsit_repository_test.dart +++ b/test/core/data/repository/ani_lsit_repository_test.dart @@ -98,5 +98,15 @@ void main() { .first; expect(res.id, equals('789')); }); + + test('ani_list_refresh_airing_schedule', () async { + await aniListRepository.refreshAiringSchedule(DateTime.now(), dayAgo: 6, dayAfter: 6); + }); + + test('ani_list_refresh_and_get_airing_schedule', () async { + await aniListRepository.refreshAiringSchedule(DateTime.now(), dayAgo: 0, dayAfter: 2); + final res = await aniListRepository.getAiringScheduleAndAnimeByDateTime(DateTime.now()); + expect(res, equals([])); + }); }); } diff --git a/test/core/database/anime_database_test.dart b/test/core/database/anime_database_test.dart index 071e7345..4987ec39 100644 --- a/test/core/database/anime_database_test.dart +++ b/test/core/database/anime_database_test.dart @@ -1,6 +1,7 @@ import 'package:anime_tracker/core/database/anime_dao.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; import 'package:anime_tracker/core/database/model/character_entity.dart'; import 'package:anime_tracker/core/database/model/user_data_entity.dart'; @@ -36,6 +37,13 @@ void main() { nativeTitle: 'みのりスクランブル!', coverImage: 'https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/9523.jpg', + coverImageColor: '#f10000'), + AnimeEntity( + id: '4353', + englishTitle: '', + romajiTitle: 'test test test!', + coverImage: + 'https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/91234123.jpg', coverImageColor: '#f10000') ]; @@ -81,6 +89,13 @@ void main() { final dummyUserData = UserDataEntity(id: 'aa', avatar: "bb"); + final dummyAiringSchedule = [ + AiringSchedulesEntity(id: '122', mediaId: '5784', airingAt: 1), + AiringSchedulesEntity(id: '132', mediaId: '8917', airingAt: 3), + AiringSchedulesEntity(id: '142', mediaId: '4353', airingAt: 2), + AiringSchedulesEntity(id: '152', mediaId: '9523', airingAt: 4), + ]; + setUp(() async { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; @@ -95,6 +110,7 @@ void main() { await animeDatabase.animeDB.delete(Tables.userDataTable); await animeDatabase.animeDB.delete(Tables.animeCharacterCrossRefTable); await animeDatabase.animeDB.delete(Tables.characterTable); + await animeDatabase.animeDB.delete(Tables.airingSchedulesTable); }); test('anime_dao_clear_all', () async { @@ -178,8 +194,10 @@ void main() { await animeDao.upsertAnimeStaffCrossRef( crossRefs: [ - AnimeStaffCrossRef(animeId: '5784', staffId: '1234', staffRole: 'job 1'), - AnimeStaffCrossRef(animeId: '5784', staffId: '4567', staffRole: 'job b'), + AnimeStaffCrossRef( + animeId: '5784', staffId: '1234', staffRole: 'job 1'), + AnimeStaffCrossRef( + animeId: '5784', staffId: '4567', staffRole: 'job b'), ], ); @@ -203,7 +221,6 @@ void main() { ); }); - /// user test. test('get_user_data_stream_test', () async { final userDataDao = animeDatabase.getUserDataDao(); @@ -211,12 +228,14 @@ void main() { final res = await userDataDao.getUserDataStream().first; expect(res, equals(dummyUserData)); }); + test('get_none_user_data_stream_test', () async { final userDataDao = animeDatabase.getUserDataDao(); final res = await userDataDao.getUserDataStream().first; expect(res, equals(null)); }); + test('remove_user_data_stream_test', () async { final userDataDao = animeDatabase.getUserDataDao(); await userDataDao.updateUserData(dummyUserData); @@ -225,5 +244,25 @@ void main() { final res = await userDataDao.getUserDataStream().first; expect(res, equals(null)); }); + + test('insert_airing_schedule', () async { + final animeDao = animeDatabase.getAnimeDao(); + await animeDao.upsertAiringSchedules(schedules: dummyAiringSchedule); + }); + + test('get_airing_schedule_by_range', () async { + final animeDao = animeDatabase.getAnimeDao(); + await animeDao.upsertAiringSchedules(schedules: dummyAiringSchedule); + await animeDao.upsertAnimeInformation(dummyAnimeData); + + final result = + await animeDao.getAiringSchedulesByTimeRange(timeRange: (1000, 4000)); + + expect( + result.map((e) => e.airingSchedule).toList(), + equals(dummyAiringSchedule + .sublist(0, 3) + ..sort((a, b) => a.airingAt!.compareTo(b.airingAt!)))); + }); }); } From 9410be42ea91524247216a284a7bcf4562136d66 Mon Sep 17 00:00:00 2001 From: Jiangqn Date: Thu, 12 Oct 2023 10:07:07 +0800 Subject: [PATCH 3/5] airing schedule method in repository --- lib/app/app.dart | 15 +- .../local}/util/anime_model_extension.dart | 8 +- lib/app/navigation/ani_flow_route_path.dart | 2 +- lib/app/navigation/ani_flow_router.dart | 2 +- lib/core/channel/auth_event_channel.dart | 2 +- lib/core/common/model/anime_category.dart | 13 ++ lib/core/common/model/anime_format.dart | 10 ++ lib/core/common/model/anime_season.dart | 55 +++++++ lib/core/common/model/anime_sort.dart | 9 ++ .../{data => common}/model/anime_source.dart | 0 .../{data => common}/model/anime_status.dart | 3 +- .../model/character_role.dart | 2 +- .../common}/util/anime_season_util.dart | 2 +- .../{ => util}/global_static_constants.dart | 0 .../{data/logger => common/util}/logger.dart | 0 lib/core/common/{ => util}/stream_util.dart | 0 lib/{ => core/common}/util/time_util.dart | 0 ...pository.dart => ani_list_repository.dart} | 10 +- .../{repository => }/auth_repository.dart | 0 .../data/{repository => }/load_result.dart | 2 - ...dart => media_information_repository.dart} | 149 ++---------------- .../data/model/anime_list_item_model.dart | 2 +- lib/core/data/model/anime_list_status.dart | 2 +- lib/core/data/model/anime_model.dart | 5 +- lib/core/data/model/anime_season.dart | 18 --- lib/core/data/model/character_model.dart | 2 +- .../anime_list_item_model_extension.dart} | 2 +- .../user_data_repository.dart | 2 +- lib/core/database/anime_dao.dart | 6 +- lib/core/database/anime_track_list_dao.dart | 6 +- lib/core/database/model/anime_entity.dart | 5 +- .../model/anime_track_item_entity.dart | 2 +- lib/core/database/model/character_entity.dart | 2 +- lib/core/database/user_data_dao.dart | 2 +- .../widget/af_network_image.dart | 2 +- .../anime_character_and_voice_actor.dart | 2 +- .../widget/anime_track_item.dart | 4 +- .../network/api/ani_list_query_graphql.dart | 49 +++++- .../ani_save_media_list_mution_graphql.dart | 2 +- .../api/user_anime_list_query_graphql.dart | 2 +- lib/core/network/auth_data_source.dart | 4 +- lib/core/network/model/character_edge.dart | 2 +- lib/core/network/model/detail_anime_dto.dart | 5 +- lib/core/network/model/media_list_dto.dart | 2 +- lib/core/shared_preference/user_data.dart | 2 +- lib/feature/anime_list/anime_list.dart | 13 +- .../anime_list/bloc/anime_list_bloc.dart | 18 ++- lib/feature/anime_track/anime_track.dart | 2 +- lib/feature/anime_track/bloc/track_bloc.dart | 12 +- lib/feature/auth/auth_dialog.dart | 2 +- lib/feature/auth/bloc/auth_bloc.dart | 4 +- .../detail_anime/bloc/detail_anime_bloc.dart | 17 +- lib/feature/detail_anime/detail_anime.dart | 14 +- lib/feature/discover/bloc/discover_bloc.dart | 21 +-- lib/feature/discover/discover.dart | 4 +- lib/main.dart | 12 +- test/core/common/common_test.dart | 2 +- .../repository/ani_lsit_repository_test.dart | 9 +- .../user_anime_list_repository_test.dart | 9 +- test/core/database/anime_database_test.dart | 2 +- .../database/user_anime_list_dao_test.dart | 4 +- .../network/ani_list_data_source_test.dart | 6 +- test/core/network/auth_data_source_test.dart | 4 +- 63 files changed, 294 insertions(+), 277 deletions(-) rename lib/{core/data => app/local}/util/anime_model_extension.dart (90%) create mode 100644 lib/core/common/model/anime_category.dart create mode 100644 lib/core/common/model/anime_format.dart create mode 100644 lib/core/common/model/anime_season.dart create mode 100644 lib/core/common/model/anime_sort.dart rename lib/core/{data => common}/model/anime_source.dart (100%) rename lib/core/{data => common}/model/anime_status.dart (82%) rename lib/core/{data => common}/model/character_role.dart (82%) rename lib/{ => core/common}/util/anime_season_util.dart (88%) rename lib/core/common/{ => util}/global_static_constants.dart (100%) rename lib/core/{data/logger => common/util}/logger.dart (100%) rename lib/core/common/{ => util}/stream_util.dart (100%) rename lib/{ => core/common}/util/time_util.dart (100%) rename lib/core/data/{repository/anime_track_list_repository.dart => ani_list_repository.dart} (95%) rename lib/core/data/{repository => }/auth_repository.dart (100%) rename lib/core/data/{repository => }/load_result.dart (88%) rename lib/core/data/{repository/ani_list_repository.dart => media_information_repository.dart} (72%) delete mode 100644 lib/core/data/model/anime_season.dart rename lib/core/data/{util/anime_list_item_model_util.dart => model/extension/anime_list_item_model_extension.dart} (91%) rename lib/core/data/{repository => }/user_data_repository.dart (93%) diff --git a/lib/app/app.dart b/lib/app/app.dart index 0152bf29..d23e8649 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -1,18 +1,19 @@ import 'package:anime_tracker/core/design_system/theme/colors.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/feature/anime_track/bloc/track_bloc.dart'; import 'package:anime_tracker/feature/discover/bloc/discover_bloc.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:anime_tracker/core/data/repository/user_data_repository.dart'; +import 'package:anime_tracker/core/data/user_data_repository.dart'; import 'package:anime_tracker/app/local/ani_flow_localizations_delegate.dart'; import 'package:anime_tracker/app/navigation/ani_flow_router.dart'; import 'package:anime_tracker/app/navigation/top_level_navigation.dart'; + /// context of app root. BuildContext? globalContext; @@ -126,14 +127,14 @@ class _AnimeTrackerAppScaffoldState extends State { BlocProvider( create: (context) => DiscoverBloc( userDataRepository: context.read(), - aniListRepository: context.read(), + aniListRepository: context.read(), authRepository: context.read(), - animeTrackListRepository: context.read(), + animeTrackListRepository: context.read(), ), ), BlocProvider( create: (context) => TrackBloc( - animeTrackListRepository: context.read(), + animeTrackListRepository: context.read(), authRepository: context.read(), ), ), diff --git a/lib/core/data/util/anime_model_extension.dart b/lib/app/local/util/anime_model_extension.dart similarity index 90% rename from lib/core/data/util/anime_model_extension.dart rename to lib/app/local/util/anime_model_extension.dart index 922c630e..1f6ae351 100644 --- a/lib/core/data/util/anime_model_extension.dart +++ b/lib/app/local/util/anime_model_extension.dart @@ -1,8 +1,10 @@ import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/model/anime_status.dart'; +import 'package:anime_tracker/core/common/model/character_role.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; -import 'package:anime_tracker/core/data/model/anime_source.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/util/time_util.dart'; +import 'package:anime_tracker/core/common/model/anime_source.dart'; +import 'package:anime_tracker/core/common/util/time_util.dart'; import 'package:flutter/material.dart'; extension AnimeSourceEx on AnimeSource { diff --git a/lib/app/navigation/ani_flow_route_path.dart b/lib/app/navigation/ani_flow_route_path.dart index 73fed78f..e186038a 100644 --- a/lib/app/navigation/ani_flow_route_path.dart +++ b/lib/app/navigation/ani_flow_route_path.dart @@ -1,5 +1,5 @@ import 'package:anime_tracker/app/navigation/top_level_navigation.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; import 'package:anime_tracker/feature/anime_list/anime_list.dart'; import 'package:anime_tracker/feature/anime_search/anime_search.dart'; import 'package:anime_tracker/feature/anime_track/anime_track.dart'; diff --git a/lib/app/navigation/ani_flow_router.dart b/lib/app/navigation/ani_flow_router.dart index 43e4836d..ea0a4b0e 100644 --- a/lib/app/navigation/ani_flow_router.dart +++ b/lib/app/navigation/ani_flow_router.dart @@ -1,5 +1,5 @@ import 'package:anime_tracker/app/navigation/top_level_navigation.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; import 'package:flutter/material.dart'; import 'package:anime_tracker/app/navigation/ani_flow_route_path.dart'; diff --git a/lib/core/channel/auth_event_channel.dart b/lib/core/channel/auth_event_channel.dart index 6dd71457..20424f23 100644 --- a/lib/core/channel/auth_event_channel.dart +++ b/lib/core/channel/auth_event_channel.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import 'package:anime_tracker/core/data/logger/logger.dart'; +import 'package:anime_tracker/core/common/util/logger.dart'; class AuthResult { AuthResult({required this.token, required this.expiresInTime}); diff --git a/lib/core/common/model/anime_category.dart b/lib/core/common/model/anime_category.dart new file mode 100644 index 00000000..5dfc7dd8 --- /dev/null +++ b/lib/core/common/model/anime_category.dart @@ -0,0 +1,13 @@ +enum AnimeCategory { + /// current season releasing anime. + currentSeason, + + /// next season not yet released anime. + nextSeason, + + /// now trending anime. + trending, + + /// popular movie. + movie, +} diff --git a/lib/core/common/model/anime_format.dart b/lib/core/common/model/anime_format.dart new file mode 100644 index 00000000..c42bd69e --- /dev/null +++ b/lib/core/common/model/anime_format.dart @@ -0,0 +1,10 @@ +/// Anime format +enum AnimeFormat { + tv(['TV', 'TV_SHORT']), + movie(['MOVIE']), + ova(['OVA', 'SPECIAL', 'ONA']); + + final List sqlTypeString; + + const AnimeFormat(this.sqlTypeString); +} \ No newline at end of file diff --git a/lib/core/common/model/anime_season.dart b/lib/core/common/model/anime_season.dart new file mode 100644 index 00000000..131be2ce --- /dev/null +++ b/lib/core/common/model/anime_season.dart @@ -0,0 +1,55 @@ + +import 'package:equatable/equatable.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +/// Bangumi releasing season. +@JsonEnum() +enum AnimeSeason { + @JsonValue('WINTER') + winter('WINTER'), + @JsonValue('SPRING') + spring('SPRING'), + @JsonValue('SUMMER') + summer('SUMMER'), + @JsonValue('FALL') + fall('FALL'); + + final String sqlTypeString; + + const AnimeSeason(this.sqlTypeString); +} + +/// parameter present to anime season. +class AnimeSeasonParam extends Equatable { + final int seasonYear; + final AnimeSeason season; + + const AnimeSeasonParam({required this.seasonYear, required this.season}); + + @override + List get props => [seasonYear, season]; +} + +/// get next bangumi season. +AnimeSeasonParam getNextSeasonParam(AnimeSeasonParam current) { + int nextSeasonYear; + AnimeSeason nextSeason; + switch (current.season) { + case AnimeSeason.winter: + nextSeasonYear = current.seasonYear; + nextSeason = AnimeSeason.spring; + case AnimeSeason.spring: + nextSeasonYear = current.seasonYear; + nextSeason = AnimeSeason.summer; + case AnimeSeason.summer: + nextSeasonYear = current.seasonYear; + nextSeason = AnimeSeason.fall; + case AnimeSeason.fall: + nextSeasonYear = current.seasonYear + 1; + nextSeason = AnimeSeason.winter; + } + return AnimeSeasonParam( + seasonYear: nextSeasonYear, + season: nextSeason, + ); +} diff --git a/lib/core/common/model/anime_sort.dart b/lib/core/common/model/anime_sort.dart new file mode 100644 index 00000000..76dc919c --- /dev/null +++ b/lib/core/common/model/anime_sort.dart @@ -0,0 +1,9 @@ +/// Bangumi sort. +enum AnimeSort { + trending('TRENDING_DESC'), + latestUpdate('UPDATED_AT_DESC'); + + final String sqlTypeString; + + const AnimeSort(this.sqlTypeString); +} diff --git a/lib/core/data/model/anime_source.dart b/lib/core/common/model/anime_source.dart similarity index 100% rename from lib/core/data/model/anime_source.dart rename to lib/core/common/model/anime_source.dart diff --git a/lib/core/data/model/anime_status.dart b/lib/core/common/model/anime_status.dart similarity index 82% rename from lib/core/data/model/anime_status.dart rename to lib/core/common/model/anime_status.dart index baed3cc8..49569c54 100644 --- a/lib/core/data/model/anime_status.dart +++ b/lib/core/common/model/anime_status.dart @@ -1,4 +1,5 @@ -part of '../repository/ani_list_repository.dart'; + +import 'package:freezed_annotation/freezed_annotation.dart'; /// Bangumi status. @JsonEnum() diff --git a/lib/core/data/model/character_role.dart b/lib/core/common/model/character_role.dart similarity index 82% rename from lib/core/data/model/character_role.dart rename to lib/core/common/model/character_role.dart index 035e8f9f..7b362170 100644 --- a/lib/core/data/model/character_role.dart +++ b/lib/core/common/model/character_role.dart @@ -1,4 +1,4 @@ -part of '../repository/ani_list_repository.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; /// Bangumi character role. @JsonEnum() diff --git a/lib/util/anime_season_util.dart b/lib/core/common/util/anime_season_util.dart similarity index 88% rename from lib/util/anime_season_util.dart rename to lib/core/common/util/anime_season_util.dart index e94aa9de..c0af7322 100644 --- a/lib/util/anime_season_util.dart +++ b/lib/core/common/util/anime_season_util.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; mixin AnimeSeasonUtil { /// get anime season params by DateTime. diff --git a/lib/core/common/global_static_constants.dart b/lib/core/common/util/global_static_constants.dart similarity index 100% rename from lib/core/common/global_static_constants.dart rename to lib/core/common/util/global_static_constants.dart diff --git a/lib/core/data/logger/logger.dart b/lib/core/common/util/logger.dart similarity index 100% rename from lib/core/data/logger/logger.dart rename to lib/core/common/util/logger.dart diff --git a/lib/core/common/stream_util.dart b/lib/core/common/util/stream_util.dart similarity index 100% rename from lib/core/common/stream_util.dart rename to lib/core/common/util/stream_util.dart diff --git a/lib/util/time_util.dart b/lib/core/common/util/time_util.dart similarity index 100% rename from lib/util/time_util.dart rename to lib/core/common/util/time_util.dart diff --git a/lib/core/data/repository/anime_track_list_repository.dart b/lib/core/data/ani_list_repository.dart similarity index 95% rename from lib/core/data/repository/anime_track_list_repository.dart rename to lib/core/data/ani_list_repository.dart index a75d33f3..05c0194c 100644 --- a/lib/core/data/repository/anime_track_list_repository.dart +++ b/lib/core/data/ani_list_repository.dart @@ -1,6 +1,6 @@ -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/anime_list_item_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; import 'package:anime_tracker/core/database/anime_dao.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; @@ -17,9 +17,9 @@ import 'package:dio/dio.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:sqflite/sqflite.dart'; -part '../model/anime_list_status.dart'; +part 'model/anime_list_status.dart'; -abstract class AnimeTrackListRepository { +abstract class AniListRepository { Future> getUserAnimeList( {required List status, int page = 1, @@ -46,7 +46,7 @@ abstract class AnimeTrackListRepository { int? score}); } -class AnimeTrackListRepositoryImpl extends AnimeTrackListRepository { +class AnimeTrackListRepositoryImpl extends AniListRepository { final AnimeTrackListDao animeTrackListDao = AnimeDatabase().getAnimeTrackListDao(); final UserDataDao userDataDao = AnimeDatabase().getUserDataDao(); diff --git a/lib/core/data/repository/auth_repository.dart b/lib/core/data/auth_repository.dart similarity index 100% rename from lib/core/data/repository/auth_repository.dart rename to lib/core/data/auth_repository.dart diff --git a/lib/core/data/repository/load_result.dart b/lib/core/data/load_result.dart similarity index 88% rename from lib/core/data/repository/load_result.dart rename to lib/core/data/load_result.dart index 63d99096..95a57496 100644 --- a/lib/core/data/repository/load_result.dart +++ b/lib/core/data/load_result.dart @@ -1,5 +1,3 @@ -part of 'ani_list_repository.dart'; - enum LoadType { refresh, append } sealed class LoadResult {} diff --git a/lib/core/data/repository/ani_list_repository.dart b/lib/core/data/media_information_repository.dart similarity index 72% rename from lib/core/data/repository/ani_list_repository.dart rename to lib/core/data/media_information_repository.dart index dfd3ebb1..92e2b63b 100644 --- a/lib/core/data/repository/ani_list_repository.dart +++ b/lib/core/data/media_information_repository.dart @@ -1,3 +1,5 @@ +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/airing_schedule_and_anime_model.dart'; import 'package:anime_tracker/core/data/model/airing_schedule_model.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; @@ -8,9 +10,8 @@ import 'package:anime_tracker/core/network/api/airing_schedules_query_graphql.da import 'package:anime_tracker/core/network/model/character_edge.dart'; import 'package:anime_tracker/core/network/model/detail_anime_dto.dart'; import 'package:anime_tracker/core/network/model/staff_edge.dart'; -import 'package:anime_tracker/util/time_util.dart'; +import 'package:anime_tracker/core/common/util/time_util.dart'; import 'package:dio/dio.dart'; -import 'package:equatable/equatable.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; import 'package:anime_tracker/core/database/anime_dao.dart'; @@ -18,92 +19,13 @@ import 'package:anime_tracker/core/database/model/anime_entity.dart'; import 'package:anime_tracker/core/network/ani_list_data_source.dart'; import 'package:anime_tracker/core/network/api/ani_list_query_graphql.dart'; import 'package:anime_tracker/core/shared_preference/user_data.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:anime_tracker/core/network/util/http_status_util.dart'; import 'package:sqflite/sqflite.dart'; -part 'load_result.dart'; - -part '../model/anime_season.dart'; - -part '../model/character_role.dart'; - -part '../model/anime_status.dart'; - -enum AnimeCategory { - /// current season releasing anime. - currentSeason, - - /// next season not yet released anime. - nextSeason, - - /// now trending anime. - trending, - - /// popular movie. - movie, -} - -/// Bangumi sort. -enum AnimeSort { - trending('TRENDING_DESC'), - latestUpdate('UPDATED_AT_DESC'); - - final String sqlTypeString; - - const AnimeSort(this.sqlTypeString); -} - -/// Anime format -enum AnimeFormat { - tv(['TV', 'TV_SHORT']), - movie(['MOVIE']), - ova(['OVA', 'SPECIAL', 'ONA']); - - final List sqlTypeString; - - const AnimeFormat(this.sqlTypeString); -} - -/// parameter present to anime season. -class AnimeSeasonParam extends Equatable { - final int seasonYear; - final AnimeSeason season; - - const AnimeSeasonParam({required this.seasonYear, required this.season}); - - @override - List get props => [seasonYear, season]; -} - -/// get next bangumi season. -AnimeSeasonParam getNextSeasonParam(AnimeSeasonParam current) { - int nextSeasonYear; - AnimeSeason nextSeason; - switch (current.season) { - case AnimeSeason.winter: - nextSeasonYear = current.seasonYear; - nextSeason = AnimeSeason.spring; - case AnimeSeason.spring: - nextSeasonYear = current.seasonYear; - nextSeason = AnimeSeason.summer; - case AnimeSeason.summer: - nextSeasonYear = current.seasonYear; - nextSeason = AnimeSeason.fall; - case AnimeSeason.fall: - nextSeasonYear = current.seasonYear + 1; - nextSeason = AnimeSeason.winter; - } - return AnimeSeasonParam( - seasonYear: nextSeasonYear, - season: nextSeason, - ); -} - /// repository for get anime list. -abstract class AniListRepository { +abstract class MediaInformationRepository { /// refresh the category anime table, which will deleted the table and get /// data from network data source. Future> refreshAnimeByCategory( @@ -120,14 +42,18 @@ abstract class AniListRepository { Future> startFetchDetailAnimeInfo(String id); + /// Get all the airing schedule of the day of [dateTime]. Future> getAiringScheduleAndAnimeByDateTime( DateTime dateTime); + /// Refresh airing schedule data in range of [now - dayAgo, not + dayAfter]. + /// The ani list api restrict the count to 50. so maybe we can only get + /// one or two days airing schedule when using this method. Future> refreshAiringSchedule(DateTime now, - {required int dayAgo, required int dayAfter}); + {int dayAgo = 0, int dayAfter = 0}); } -class AniListRepositoryImpl extends AniListRepository { +class AniListRepositoryImpl extends MediaInformationRepository { final AniListDataSource aniListDataSource = AniListDataSource(); final AnimeListDao animeDao = AnimeDatabase().getAnimeDao(); final AniFlowPreferences preferences = AniFlowPreferences(); @@ -140,7 +66,7 @@ class AniListRepositoryImpl extends AniListRepository { return _loadAnimePage( category: category, type: LoadType.append, - animeListParam: _createAnimePageQueryParam( + animeListParam: createAnimePageQueryParam( category, page, perPage, @@ -156,7 +82,7 @@ class AniListRepositoryImpl extends AniListRepository { return _loadAnimePage( category: category, type: LoadType.refresh, - animeListParam: _createAnimePageQueryParam( + animeListParam: createAnimePageQueryParam( category, 1, Config.defaultPerPageCount, @@ -165,49 +91,6 @@ class AniListRepositoryImpl extends AniListRepository { )); } - AnimePageQueryParam _createAnimePageQueryParam(AnimeCategory category, - int page, int perPage, AnimeSeason currentSeason, int currentSeasonYear) { - AnimeStatus? status; - AnimeSeasonParam? seasonParam; - List sorts = []; - List format = []; - - AnimeSeasonParam currentSeasonParam = AnimeSeasonParam( - seasonYear: preferences.getCurrentSeasonYear(), - season: preferences.getCurrentSeason(), - ); - switch (category) { - case AnimeCategory.currentSeason: - status = null; - seasonParam = currentSeasonParam; - // sorts = [AnimeSort.latestUpdate]; - format = [AnimeFormat.tv, AnimeFormat.ova]; - case AnimeCategory.nextSeason: - status = null; - seasonParam = getNextSeasonParam(currentSeasonParam); - format = [AnimeFormat.tv, AnimeFormat.ova]; - case AnimeCategory.trending: - status = null; - seasonParam = null; - sorts = [AnimeSort.trending]; - case AnimeCategory.movie: - status = null; - seasonParam = null; - format = [AnimeFormat.movie]; - sorts = [AnimeSort.trending]; - } - - return AnimePageQueryParam( - page: page, - perPage: perPage, - seasonYear: seasonParam?.seasonYear, - season: seasonParam?.season, - status: status, - animeSort: sorts, - animeFormat: format, - ); - } - Future> _loadAnimePage( {required AnimeCategory category, required LoadType type, @@ -359,8 +242,8 @@ class AniListRepositoryImpl extends AniListRepository { Future> getAiringScheduleAndAnimeByDateTime( DateTime dateTime) async { final (startMs, endMs) = TimeUtil.getTimeRangeOfTheTargetDay(dateTime); - final entities = - await animeDao.getAiringSchedulesByTimeRange(timeRange: (startMs, endMs)); + final entities = await animeDao + .getAiringSchedulesByTimeRange(timeRange: (startMs, endMs)); return entities .map( @@ -374,7 +257,7 @@ class AniListRepositoryImpl extends AniListRepository { @override Future> refreshAiringSchedule(DateTime now, - {required int dayAgo, required int dayAfter}) async { + {int dayAgo = 0, int dayAfter = 0}) async { /// Clear old airing schedule. await animeDao.clearAiringSchedule(); diff --git a/lib/core/data/model/anime_list_item_model.dart b/lib/core/data/model/anime_list_item_model.dart index c375787c..db5d4b39 100644 --- a/lib/core/data/model/anime_list_item_model.dart +++ b/lib/core/data/model/anime_list_item_model.dart @@ -1,5 +1,5 @@ import 'package:anime_tracker/core/data/model/anime_model.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/database/model/relations/user_anime_list_and_anime.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/lib/core/data/model/anime_list_status.dart b/lib/core/data/model/anime_list_status.dart index 5071bde8..80f26d78 100644 --- a/lib/core/data/model/anime_list_status.dart +++ b/lib/core/data/model/anime_list_status.dart @@ -1,4 +1,4 @@ -part of '../repository/anime_track_list_repository.dart'; +part of '../ani_list_repository.dart'; /// User anime list status. @JsonEnum() diff --git a/lib/core/data/model/anime_model.dart b/lib/core/data/model/anime_model.dart index 092e0c88..4ee9b9f1 100644 --- a/lib/core/data/model/anime_model.dart +++ b/lib/core/data/model/anime_model.dart @@ -1,9 +1,10 @@ import 'dart:convert'; -import 'package:anime_tracker/core/data/model/anime_source.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/model/anime_source.dart'; +import 'package:anime_tracker/core/common/model/anime_status.dart'; import 'package:anime_tracker/core/data/model/staff_and_role_model.dart'; import 'package:anime_tracker/core/data/model/trailter_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:anime_tracker/core/data/model/anime_title_modle.dart'; diff --git a/lib/core/data/model/anime_season.dart b/lib/core/data/model/anime_season.dart deleted file mode 100644 index a3088e64..00000000 --- a/lib/core/data/model/anime_season.dart +++ /dev/null @@ -1,18 +0,0 @@ -part of '../repository/ani_list_repository.dart'; - -/// Bangumi releasing season. -@JsonEnum() -enum AnimeSeason { - @JsonValue('WINTER') - winter('WINTER'), - @JsonValue('SPRING') - spring('SPRING'), - @JsonValue('SUMMER') - summer('SUMMER'), - @JsonValue('FALL') - fall('FALL'); - - final String sqlTypeString; - - const AnimeSeason(this.sqlTypeString); -} diff --git a/lib/core/data/model/character_model.dart b/lib/core/data/model/character_model.dart index d87bd90c..99eecf34 100644 --- a/lib/core/data/model/character_model.dart +++ b/lib/core/data/model/character_model.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/character_role.dart'; import 'package:anime_tracker/core/database/model/character_entity.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/lib/core/data/util/anime_list_item_model_util.dart b/lib/core/data/model/extension/anime_list_item_model_extension.dart similarity index 91% rename from lib/core/data/util/anime_list_item_model_util.dart rename to lib/core/data/model/extension/anime_list_item_model_extension.dart index 68e7f899..b69bdfd1 100644 --- a/lib/core/data/util/anime_list_item_model_util.dart +++ b/lib/core/data/model/extension/anime_list_item_model_extension.dart @@ -1,5 +1,5 @@ +import 'package:anime_tracker/core/common/model/anime_status.dart'; import 'package:anime_tracker/core/data/model/anime_list_item_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; extension AnimeListItemModelEx on AnimeListItemModel { bool get hasNextReleasingEpisode { diff --git a/lib/core/data/repository/user_data_repository.dart b/lib/core/data/user_data_repository.dart similarity index 93% rename from lib/core/data/repository/user_data_repository.dart rename to lib/core/data/user_data_repository.dart index 44dd5d5d..bdf5de87 100644 --- a/lib/core/data/repository/user_data_repository.dart +++ b/lib/core/data/user_data_repository.dart @@ -1,5 +1,5 @@ +import 'package:anime_tracker/core/common/model/anime_season.dart'; import 'package:anime_tracker/core/shared_preference/user_data.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; abstract class UserDataRepository { AnimeSeasonParam getAnimeSeasonParam(); diff --git a/lib/core/database/anime_dao.dart b/lib/core/database/anime_dao.dart index 095fcba0..81952127 100644 --- a/lib/core/database/anime_dao.dart +++ b/lib/core/database/anime_dao.dart @@ -1,4 +1,5 @@ -import 'package:anime_tracker/core/common/stream_util.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/common/util/stream_util.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; @@ -8,9 +9,8 @@ import 'package:anime_tracker/core/database/model/relations/anime_and_detail_inf import 'package:anime_tracker/core/database/model/staff_entity.dart'; import 'package:flutter/cupertino.dart'; import 'package:sqflite/sqflite.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; /// [Tables.animeTable] mixin AnimeTableColumns { diff --git a/lib/core/database/anime_track_list_dao.dart b/lib/core/database/anime_track_list_dao.dart index e08eed72..1811a0d7 100644 --- a/lib/core/database/anime_track_list_dao.dart +++ b/lib/core/database/anime_track_list_dao.dart @@ -1,12 +1,12 @@ import 'dart:async'; -import 'package:anime_tracker/core/common/stream_util.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/common/util/stream_util.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/database/anime_dao.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; import 'package:anime_tracker/core/database/model/anime_track_item_entity.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:anime_tracker/core/database/model/relations/user_anime_list_and_anime.dart'; import 'package:flutter/cupertino.dart'; import 'package:sqflite/sqflite.dart'; diff --git a/lib/core/database/model/anime_entity.dart b/lib/core/database/model/anime_entity.dart index 88b659ac..31570bd6 100644 --- a/lib/core/database/model/anime_entity.dart +++ b/lib/core/database/model/anime_entity.dart @@ -1,7 +1,8 @@ import 'dart:convert'; -import 'package:anime_tracker/core/data/model/anime_source.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/model/anime_source.dart'; +import 'package:anime_tracker/core/common/model/anime_status.dart'; import 'package:anime_tracker/core/database/anime_dao.dart'; import 'package:anime_tracker/core/network/model/detail_anime_dto.dart'; import 'package:anime_tracker/core/network/model/short_anime_dto.dart'; diff --git a/lib/core/database/model/anime_track_item_entity.dart b/lib/core/database/model/anime_track_item_entity.dart index 09b9063c..33d282e5 100644 --- a/lib/core/database/model/anime_track_item_entity.dart +++ b/lib/core/database/model/anime_track_item_entity.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/database/anime_track_list_dao.dart'; import 'package:anime_tracker/core/network/model/media_list_dto.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/lib/core/database/model/character_entity.dart b/lib/core/database/model/character_entity.dart index 69fb4b3c..54fc6d2d 100644 --- a/lib/core/database/model/character_entity.dart +++ b/lib/core/database/model/character_entity.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/character_role.dart'; import 'package:anime_tracker/core/database/anime_dao.dart'; import 'package:anime_tracker/core/network/model/character_edge.dart'; import 'package:anime_tracker/core/network/model/staff_dto.dart'; diff --git a/lib/core/database/user_data_dao.dart b/lib/core/database/user_data_dao.dart index 739504a8..38d4e45e 100644 --- a/lib/core/database/user_data_dao.dart +++ b/lib/core/database/user_data_dao.dart @@ -4,7 +4,7 @@ import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/database/model/user_data_entity.dart'; import 'package:flutter/cupertino.dart'; import 'package:sqflite/sqflite.dart'; -import 'package:anime_tracker/core/common/stream_util.dart'; +import 'package:anime_tracker/core/common/util/stream_util.dart'; mixin UserDataTableColumns { static const String id = 'id'; diff --git a/lib/core/design_system/widget/af_network_image.dart b/lib/core/design_system/widget/af_network_image.dart index b5bdf1ac..6209a106 100644 --- a/lib/core/design_system/widget/af_network_image.dart +++ b/lib/core/design_system/widget/af_network_image.dart @@ -3,7 +3,7 @@ import 'package:anime_tracker/core/design_system/widget/image_load_initial_widge import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; class AFNetworkImage extends StatefulWidget { const AFNetworkImage( diff --git a/lib/core/design_system/widget/anime_character_and_voice_actor.dart b/lib/core/design_system/widget/anime_character_and_voice_actor.dart index 877f3a4e..19109c84 100644 --- a/lib/core/design_system/widget/anime_character_and_voice_actor.dart +++ b/lib/core/design_system/widget/anime_character_and_voice_actor.dart @@ -1,5 +1,5 @@ import 'package:anime_tracker/core/data/model/character_and_voice_actor_model.dart'; -import 'package:anime_tracker/core/data/util/anime_model_extension.dart'; +import 'package:anime_tracker/app/local/util/anime_model_extension.dart'; import 'package:flutter/material.dart'; import 'package:anime_tracker/core/design_system/widget/af_network_image.dart'; diff --git a/lib/core/design_system/widget/anime_track_item.dart b/lib/core/design_system/widget/anime_track_item.dart index e57d5d47..8f21c105 100644 --- a/lib/core/design_system/widget/anime_track_item.dart +++ b/lib/core/design_system/widget/anime_track_item.dart @@ -1,7 +1,7 @@ import 'package:anime_tracker/core/data/model/anime_list_item_model.dart'; import 'package:anime_tracker/core/data/model/anime_title_modle.dart'; -import 'package:anime_tracker/core/data/util/anime_list_item_model_util.dart'; -import 'package:anime_tracker/core/data/util/anime_model_extension.dart'; +import 'package:anime_tracker/core/data/model/extension/anime_list_item_model_extension.dart'; +import 'package:anime_tracker/app/local/util/anime_model_extension.dart'; import 'package:flutter/material.dart'; import 'package:anime_tracker/core/design_system/widget/af_network_image.dart'; diff --git a/lib/core/network/api/ani_list_query_graphql.dart b/lib/core/network/api/ani_list_query_graphql.dart index 52b472a1..3315fa49 100644 --- a/lib/core/network/api/ani_list_query_graphql.dart +++ b/lib/core/network/api/ani_list_query_graphql.dart @@ -1,4 +1,8 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/common/model/anime_format.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/model/anime_sort.dart'; +import 'package:anime_tracker/core/common/model/anime_status.dart'; class AnimePageQueryParam { final int page; @@ -19,6 +23,49 @@ class AnimePageQueryParam { this.animeFormat = const []}); } +AnimePageQueryParam createAnimePageQueryParam(AnimeCategory category, + int page, int perPage, AnimeSeason currentSeason, int currentSeasonYear) { + AnimeStatus? status; + AnimeSeasonParam? seasonParam; + List sorts = []; + List format = []; + + AnimeSeasonParam currentSeasonParam = AnimeSeasonParam( + seasonYear: currentSeasonYear, + season: currentSeason, + ); + switch (category) { + case AnimeCategory.currentSeason: + status = null; + seasonParam = currentSeasonParam; + // sorts = [AnimeSort.latestUpdate]; + format = [AnimeFormat.tv, AnimeFormat.ova]; + case AnimeCategory.nextSeason: + status = null; + seasonParam = getNextSeasonParam(currentSeasonParam); + format = [AnimeFormat.tv, AnimeFormat.ova]; + case AnimeCategory.trending: + status = null; + seasonParam = null; + sorts = [AnimeSort.trending]; + case AnimeCategory.movie: + status = null; + seasonParam = null; + format = [AnimeFormat.movie]; + sorts = [AnimeSort.trending]; + } + + return AnimePageQueryParam( + page: page, + perPage: perPage, + seasonYear: seasonParam?.seasonYear, + season: seasonParam?.season, + status: status, + animeSort: sorts, + animeFormat: format, + ); +} + String get animeListQueryGraphQLString => ''' query (\$page: Int, \$perPage: Int, \$seasonYear: Int, \$season: MediaSeason, \$status: MediaStatus, \$sort: [MediaSort], \$format_in: [MediaFormat]) { diff --git a/lib/core/network/api/ani_save_media_list_mution_graphql.dart b/lib/core/network/api/ani_save_media_list_mution_graphql.dart index 65ca424c..f2574382 100644 --- a/lib/core/network/api/ani_save_media_list_mution_graphql.dart +++ b/lib/core/network/api/ani_save_media_list_mution_graphql.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; class MediaListMutationParam { MediaListMutationParam( diff --git a/lib/core/network/api/user_anime_list_query_graphql.dart b/lib/core/network/api/user_anime_list_query_graphql.dart index e2ac1568..9517904a 100644 --- a/lib/core/network/api/user_anime_list_query_graphql.dart +++ b/lib/core/network/api/user_anime_list_query_graphql.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; class UserAnimeListPageQueryParam { final int page; diff --git a/lib/core/network/auth_data_source.dart b/lib/core/network/auth_data_source.dart index b67f62fa..de31e841 100644 --- a/lib/core/network/auth_data_source.dart +++ b/lib/core/network/auth_data_source.dart @@ -5,8 +5,8 @@ import 'package:anime_tracker/core/network/model/media_list_dto.dart'; import 'package:anime_tracker/core/network/util/http_status_util.dart'; import 'package:anime_tracker/core/shared_preference/user_data.dart'; import 'package:dio/dio.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; -import 'package:anime_tracker/core/data/logger/logger.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/logger.dart'; import 'package:anime_tracker/core/network/model/user_data_dto.dart' show UserDataDto; diff --git a/lib/core/network/model/character_edge.dart b/lib/core/network/model/character_edge.dart index 5f38fec1..76f7fb2e 100644 --- a/lib/core/network/model/character_edge.dart +++ b/lib/core/network/model/character_edge.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/character_role.dart'; import 'package:anime_tracker/core/network/model/character_dto.dart'; import 'package:anime_tracker/core/network/model/staff_dto.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/lib/core/network/model/detail_anime_dto.dart b/lib/core/network/model/detail_anime_dto.dart index a3cfdd95..3140078f 100644 --- a/lib/core/network/model/detail_anime_dto.dart +++ b/lib/core/network/model/detail_anime_dto.dart @@ -1,5 +1,6 @@ -import 'package:anime_tracker/core/data/model/anime_source.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/model/anime_source.dart'; +import 'package:anime_tracker/core/common/model/anime_status.dart'; import 'package:anime_tracker/core/network/model/airing_schedule_dto.dart'; import 'package:anime_tracker/core/network/model/anime_rank.dart'; import 'package:anime_tracker/core/network/model/staff_connection.dart'; diff --git a/lib/core/network/model/media_list_dto.dart b/lib/core/network/model/media_list_dto.dart index 7cddba67..a2bffc0d 100644 --- a/lib/core/network/model/media_list_dto.dart +++ b/lib/core/network/model/media_list_dto.dart @@ -1,5 +1,5 @@ -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/network/model/detail_anime_dto.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/lib/core/shared_preference/user_data.dart b/lib/core/shared_preference/user_data.dart index 0a8f3c2d..002dc3a6 100644 --- a/lib/core/shared_preference/user_data.dart +++ b/lib/core/shared_preference/user_data.dart @@ -1,7 +1,7 @@ import 'dart:async'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; import 'package:anime_tracker/core/shared_preference/model/user_setting_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; mixin UserDataKey { static const currentSeasonYear = "current_season_year"; diff --git a/lib/feature/anime_list/anime_list.dart b/lib/feature/anime_list/anime_list.dart index 924cb556..807d3643 100644 --- a/lib/feature/anime_list/anime_list.dart +++ b/lib/feature/anime_list/anime_list.dart @@ -1,10 +1,11 @@ import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; import 'package:anime_tracker/app/navigation/ani_flow_router.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; import 'package:anime_tracker/core/design_system/widget/anime_preview_item.dart'; import 'package:anime_tracker/feature/anime_list/bloc/anime_list_bloc.dart'; import 'package:anime_tracker/feature/anime_list/bloc/anime_list_state.dart'; @@ -36,9 +37,9 @@ class AnimeListRoute extends PageRoute with MaterialRouteTransitionMixin { return BlocProvider( create: (context) => AnimeListBloc( category: category, - aniListRepository: context.read(), + aniListRepository: context.read(), authRepository: context.read(), - animeTrackListRepository: context.read(), + animeTrackListRepository: context.read(), ), child: const Scaffold( body: _AnimeListPageContent(), diff --git a/lib/feature/anime_list/bloc/anime_list_bloc.dart b/lib/feature/anime_list/bloc/anime_list_bloc.dart index fb7c82e0..6a6b3c95 100644 --- a/lib/feature/anime_list/bloc/anime_list_bloc.dart +++ b/lib/feature/anime_list/bloc/anime_list_bloc.dart @@ -1,13 +1,15 @@ import 'dart:async'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; import 'package:anime_tracker/core/data/model/page_loading_state.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:anime_tracker/feature/anime_list/bloc/anime_list_state.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; sealed class AnimeListEvent {} @@ -38,8 +40,8 @@ class AnimeListBloc extends Bloc { AnimeListBloc({ required this.category, required AuthRepository authRepository, - required AniListRepository aniListRepository, - required AnimeTrackListRepository animeTrackListRepository, + required MediaInformationRepository aniListRepository, + required AniListRepository animeTrackListRepository, }) : _aniListRepository = aniListRepository, _authRepository = authRepository, _animeTrackListRepository = animeTrackListRepository, @@ -54,8 +56,8 @@ class AnimeListBloc extends Bloc { } final AnimeCategory category; - final AniListRepository _aniListRepository; - final AnimeTrackListRepository _animeTrackListRepository; + final MediaInformationRepository _aniListRepository; + final AniListRepository _animeTrackListRepository; final AuthRepository _authRepository; StreamSubscription? _trackingIdsStream; diff --git a/lib/feature/anime_track/anime_track.dart b/lib/feature/anime_track/anime_track.dart index 42ccd917..f840f7c4 100644 --- a/lib/feature/anime_track/anime_track.dart +++ b/lib/feature/anime_track/anime_track.dart @@ -1,5 +1,5 @@ import 'package:anime_tracker/app/navigation/ani_flow_router.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:anime_tracker/core/data/model/anime_list_item_model.dart'; import 'package:anime_tracker/core/design_system/widget/af_toogle_button.dart'; import 'package:anime_tracker/core/design_system/widget/anime_track_item.dart'; diff --git a/lib/feature/anime_track/bloc/track_bloc.dart b/lib/feature/anime_track/bloc/track_bloc.dart index 033e9d4b..37236083 100644 --- a/lib/feature/anime_track/bloc/track_bloc.dart +++ b/lib/feature/anime_track/bloc/track_bloc.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/anime_list_item_model.dart'; import 'package:anime_tracker/core/data/model/user_data_model.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/util/anime_list_item_model_util.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/model/extension/anime_list_item_model_extension.dart'; import 'package:anime_tracker/core/design_system/widget/anime_tracker_snackbar.dart'; import 'package:anime_tracker/feature/anime_track/bloc/track_ui_state.dart'; import 'package:anime_tracker/feature/anime_track/bloc/user_anime_list_load_state.dart'; @@ -49,7 +49,7 @@ class OnAnimeMarkWatched extends TrackEvent { class TrackBloc extends Bloc { TrackBloc( - {required AnimeTrackListRepository animeTrackListRepository, + {required AniListRepository animeTrackListRepository, required AuthRepository authRepository}) : _animeTrackListRepository = animeTrackListRepository, _authRepository = authRepository, @@ -65,7 +65,7 @@ class TrackBloc extends Bloc { StreamSubscription? _userContentSub; StreamSubscription? _userStateSub; - final AnimeTrackListRepository _animeTrackListRepository; + final AniListRepository _animeTrackListRepository; final AuthRepository _authRepository; var _watchingAnimeList = []; diff --git a/lib/feature/auth/auth_dialog.dart b/lib/feature/auth/auth_dialog.dart index cd42920c..23e467e3 100644 --- a/lib/feature/auth/auth_dialog.dart +++ b/lib/feature/auth/auth_dialog.dart @@ -1,6 +1,6 @@ import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; import 'package:anime_tracker/core/data/model/user_data_model.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; import 'package:anime_tracker/core/design_system/widget/avatar_icon.dart'; import 'package:anime_tracker/feature/auth/bloc/auth_bloc.dart'; import 'package:anime_tracker/feature/auth/bloc/auth_ui_state.dart'; diff --git a/lib/feature/auth/bloc/auth_bloc.dart b/lib/feature/auth/bloc/auth_bloc.dart index f7295e7e..87ca299f 100644 --- a/lib/feature/auth/bloc/auth_bloc.dart +++ b/lib/feature/auth/bloc/auth_bloc.dart @@ -3,11 +3,11 @@ import 'dart:async'; import 'package:anime_tracker/app/app.dart'; import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; import 'package:anime_tracker/core/data/model/user_data_model.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; import 'package:anime_tracker/core/design_system/widget/anime_tracker_snackbar.dart'; import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; -import 'package:anime_tracker/core/data/logger/logger.dart'; +import 'package:anime_tracker/core/common/util/logger.dart'; import 'package:anime_tracker/feature/auth/bloc/auth_ui_state.dart'; sealed class AuthEvent {} diff --git a/lib/feature/detail_anime/bloc/detail_anime_bloc.dart b/lib/feature/detail_anime/bloc/detail_anime_bloc.dart index 8ed32404..4dc159fb 100644 --- a/lib/feature/detail_anime/bloc/detail_anime_bloc.dart +++ b/lib/feature/detail_anime/bloc/detail_anime_bloc.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; -import 'package:anime_tracker/core/data/logger/logger.dart'; +import 'package:anime_tracker/core/common/util/logger.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; import 'package:anime_tracker/core/design_system/widget/anime_tracker_snackbar.dart'; import 'package:bloc/bloc.dart'; import 'package:anime_tracker/feature/detail_anime/bloc/detail_anime_ui_state.dart'; @@ -39,9 +40,9 @@ class _OnLoadingStateChanged extends DetailAnimeEvent { class DetailAnimeBloc extends Bloc { DetailAnimeBloc({ required String animeId, - required AniListRepository aniListRepository, + required MediaInformationRepository aniListRepository, required AuthRepository authRepository, - required AnimeTrackListRepository animeTrackListRepository, + required AniListRepository animeTrackListRepository, }) : _animeId = animeId, _aniListRepository = aniListRepository, _animeTrackListRepository = animeTrackListRepository, @@ -56,8 +57,8 @@ class DetailAnimeBloc extends Bloc { } final String _animeId; - final AniListRepository _aniListRepository; - final AnimeTrackListRepository _animeTrackListRepository; + final MediaInformationRepository _aniListRepository; + final AniListRepository _animeTrackListRepository; final AuthRepository _authRepository; StreamSubscription? _detailAnimeSub; diff --git a/lib/feature/detail_anime/detail_anime.dart b/lib/feature/detail_anime/detail_anime.dart index 75931459..f36fc125 100644 --- a/lib/feature/detail_anime/detail_anime.dart +++ b/lib/feature/detail_anime/detail_anime.dart @@ -1,16 +1,16 @@ import 'dart:math'; import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:anime_tracker/core/data/model/anime_title_modle.dart'; import 'package:anime_tracker/core/data/model/character_and_voice_actor_model.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; import 'package:anime_tracker/core/data/model/staff_and_role_model.dart'; import 'package:anime_tracker/core/data/model/trailter_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; -import 'package:anime_tracker/core/data/util/anime_model_extension.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; +import 'package:anime_tracker/app/local/util/anime_model_extension.dart'; import 'package:anime_tracker/core/design_system/widget/af_network_image.dart'; import 'package:anime_tracker/core/design_system/widget/anime_character_and_voice_actor.dart'; import 'package:anime_tracker/core/design_system/widget/anime_staff_item.dart'; @@ -48,9 +48,9 @@ class DetailAnimeRoute extends PageRoute with MaterialRouteTransitionMixin { return BlocProvider( create: (context) => DetailAnimeBloc( animeId: animeId, - aniListRepository: context.read(), + aniListRepository: context.read(), authRepository: context.read(), - animeTrackListRepository: context.read(), + animeTrackListRepository: context.read(), ), child: const _DetailAnimePageContent(), ); diff --git a/lib/feature/discover/bloc/discover_bloc.dart b/lib/feature/discover/bloc/discover_bloc.dart index 2e3a9e55..a24f2d28 100644 --- a/lib/feature/discover/bloc/discover_bloc.dart +++ b/lib/feature/discover/bloc/discover_bloc.dart @@ -1,19 +1,22 @@ import 'dart:async'; import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; -import 'package:anime_tracker/core/data/logger/logger.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/util/logger.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; import 'package:anime_tracker/core/data/model/page_loading_state.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/design_system/widget/anime_tracker_snackbar.dart'; import 'package:anime_tracker/feature/discover/bloc/discover_ui_state.dart'; import 'package:bloc/bloc.dart'; import 'package:anime_tracker/core/data/model/user_data_model.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; -import 'package:anime_tracker/core/data/repository/user_data_repository.dart'; -import 'package:anime_tracker/util/anime_season_util.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; +import 'package:anime_tracker/core/data/user_data_repository.dart'; +import 'package:anime_tracker/core/common/util/anime_season_util.dart'; sealed class DiscoverEvent {} @@ -58,7 +61,7 @@ class DiscoverBloc extends Bloc { {required AuthRepository authRepository, required userDataRepository, required aniListRepository, - required AnimeTrackListRepository animeTrackListRepository}) + required AniListRepository animeTrackListRepository}) : _userDataRepository = userDataRepository, _aniListRepository = aniListRepository, _animeTrackListRepository = animeTrackListRepository, @@ -78,8 +81,8 @@ class DiscoverBloc extends Bloc { } final UserDataRepository _userDataRepository; - final AniListRepository _aniListRepository; - final AnimeTrackListRepository _animeTrackListRepository; + final MediaInformationRepository _aniListRepository; + final AniListRepository _animeTrackListRepository; StreamSubscription? _userDataSub; StreamSubscription? _trackedAnimeIdsSub; diff --git a/lib/feature/discover/discover.dart b/lib/feature/discover/discover.dart index 5a5f4234..25156d3c 100644 --- a/lib/feature/discover/discover.dart +++ b/lib/feature/discover/discover.dart @@ -1,6 +1,6 @@ +import 'package:anime_tracker/core/common/model/anime_category.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; import 'package:anime_tracker/core/data/model/page_loading_state.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; import 'package:anime_tracker/core/design_system/animetion/page_transaction_animetion.dart'; import 'package:anime_tracker/core/design_system/widget/anime_preview_item.dart'; import 'package:anime_tracker/core/design_system/widget/avatar_icon.dart'; @@ -12,7 +12,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; import 'package:anime_tracker/app/navigation/ani_flow_router.dart'; import 'package:anime_tracker/feature/auth/auth_dialog.dart'; -import 'package:anime_tracker/core/common/global_static_constants.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart'; class DiscoverPage extends Page { diff --git a/lib/main.dart b/lib/main.dart index baeae6a4..ebfda523 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/auth_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/user_data_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/data/auth_repository.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/user_data_repository.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/shared_preference/user_data.dart'; import 'package:flutter/material.dart'; @@ -19,7 +19,7 @@ void main() async { /// run app after core instance initialized. runApp(MultiRepositoryProvider(providers: [ - RepositoryProvider( + RepositoryProvider( create: (context) => AniListRepositoryImpl(), ), RepositoryProvider( @@ -28,7 +28,7 @@ void main() async { RepositoryProvider( create: (context) => AuthRepositoryImpl(), ), - RepositoryProvider( + RepositoryProvider( create: (context) => AnimeTrackListRepositoryImpl(), ), ], child: const AnimeTrackerApp())); diff --git a/test/core/common/common_test.dart b/test/core/common/common_test.dart index d15873ab..3da2b307 100644 --- a/test/core/common/common_test.dart +++ b/test/core/common/common_test.dart @@ -1,4 +1,4 @@ -import 'package:anime_tracker/util/time_util.dart'; +import 'package:anime_tracker/core/common/util/time_util.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/test/core/data/repository/ani_lsit_repository_test.dart b/test/core/data/repository/ani_lsit_repository_test.dart index 4f98b504..8ddcce95 100644 --- a/test/core/data/repository/ani_lsit_repository_test.dart +++ b/test/core/data/repository/ani_lsit_repository_test.dart @@ -1,5 +1,8 @@ +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; import 'package:anime_tracker/core/data/model/anime_model.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/shared_preference/user_data.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -9,7 +12,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { group('anime_database_test', () { final animeDatabase = AnimeDatabase(); - late AniListRepository aniListRepository; + late MediaInformationRepository aniListRepository; setUp(() async { SharedPreferences.setMockInitialValues({}); @@ -106,7 +109,7 @@ void main() { test('ani_list_refresh_and_get_airing_schedule', () async { await aniListRepository.refreshAiringSchedule(DateTime.now(), dayAgo: 0, dayAfter: 2); final res = await aniListRepository.getAiringScheduleAndAnimeByDateTime(DateTime.now()); - expect(res, equals([])); + expect(res.isNotEmpty, equals(true)); }); }); } diff --git a/test/core/data/repository/user_anime_list_repository_test.dart b/test/core/data/repository/user_anime_list_repository_test.dart index 7c964fa7..47a2bf32 100644 --- a/test/core/data/repository/user_anime_list_repository_test.dart +++ b/test/core/data/repository/user_anime_list_repository_test.dart @@ -1,6 +1,7 @@ -import 'package:anime_tracker/core/common/global_static_constants.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/network/auth_data_source.dart'; import 'package:anime_tracker/core/shared_preference/user_data.dart'; @@ -11,7 +12,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { group('anime_database_test', () { final animeDatabase = AnimeDatabase(); - late AnimeTrackListRepository repository; + late AniListRepository repository; setUp(() async { SharedPreferences.setMockInitialValues({}); diff --git a/test/core/database/anime_database_test.dart b/test/core/database/anime_database_test.dart index 4987ec39..2d3a73ce 100644 --- a/test/core/database/anime_database_test.dart +++ b/test/core/database/anime_database_test.dart @@ -1,6 +1,6 @@ +import 'package:anime_tracker/core/common/model/anime_category.dart'; import 'package:anime_tracker/core/database/anime_dao.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; import 'package:anime_tracker/core/database/model/airing_schedules_entity.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; import 'package:anime_tracker/core/database/model/character_entity.dart'; diff --git a/test/core/database/user_anime_list_dao_test.dart b/test/core/database/user_anime_list_dao_test.dart index 7666eb60..17266d89 100644 --- a/test/core/database/user_anime_list_dao_test.dart +++ b/test/core/database/user_anime_list_dao_test.dart @@ -1,5 +1,5 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/database/anime_database.dart'; import 'package:anime_tracker/core/database/model/anime_entity.dart'; import 'package:anime_tracker/core/database/model/anime_track_item_entity.dart'; diff --git a/test/core/network/ani_list_data_source_test.dart b/test/core/network/ani_list_data_source_test.dart index fa20020b..246ae725 100644 --- a/test/core/network/ani_list_data_source_test.dart +++ b/test/core/network/ani_list_data_source_test.dart @@ -1,5 +1,7 @@ -import 'package:anime_tracker/core/data/repository/ani_list_repository.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/common/model/anime_season.dart'; +import 'package:anime_tracker/core/common/model/anime_sort.dart'; +import 'package:anime_tracker/core/common/model/anime_status.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/network/ani_list_data_source.dart'; import 'package:anime_tracker/core/network/api/airing_schedules_query_graphql.dart.dart'; import 'package:anime_tracker/core/network/api/ani_list_query_graphql.dart'; diff --git a/test/core/network/auth_data_source_test.dart b/test/core/network/auth_data_source_test.dart index 6a55a07c..4b3ea24e 100644 --- a/test/core/network/auth_data_source_test.dart +++ b/test/core/network/auth_data_source_test.dart @@ -1,5 +1,5 @@ -import 'package:anime_tracker/core/common/global_static_constants.dart'; -import 'package:anime_tracker/core/data/repository/anime_track_list_repository.dart'; +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; +import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/network/api/ani_save_media_list_mution_graphql.dart'; import 'package:anime_tracker/core/network/auth_data_source.dart'; import 'package:anime_tracker/core/network/util/http_status_util.dart'; From 25a419cce9ff7f327597cb537ac93198b0e91efc Mon Sep 17 00:00:00 2001 From: Jiangqn Date: Thu, 12 Oct 2023 11:05:59 +0800 Subject: [PATCH 4/5] add airing schedule feature ui --- lib/app/app.dart | 1 - lib/app/local/util/anime_model_extension.dart | 3 +- lib/app/navigation/ani_flow_route_path.dart | 13 +- lib/app/navigation/ani_flow_router.dart | 6 + lib/core/common/util/collection_util.dart | 6 + .../data/media_information_repository.dart | 5 +- ...ng_schedule_and_anime_model_extension.dart | 9 + .../widget/airing_anime_item.dart | 71 +++++ .../widget/loading_indicator.dart | 22 ++ lib/core/network/client/ani_list_dio.dart | 2 +- .../airing_schedule/airing_schedule.dart | 267 ++++++++++++++++++ .../bloc/airing_schedule_bloc.dart | 94 ++++++ .../bloc/airing_schedule_state.dart | 13 + .../bloc/airing_schedule_state.freezed.dart | 179 ++++++++++++ .../bloc/airing_schedule_state_extension.dart | 8 + .../bloc/schedule_category.dart | 13 + .../bloc/schedule_category.freezed.dart | 167 +++++++++++ .../bloc/schedule_page_key.dart | 32 +++ .../bloc/schedule_page_state.dart | 50 ++++ lib/feature/anime_list/anime_list.dart | 9 +- .../anime_list/bloc/anime_list_bloc.dart | 6 +- lib/feature/anime_track/anime_track.dart | 29 +- lib/feature/detail_anime/detail_anime.dart | 11 +- lib/feature/discover/discover.dart | 11 +- lib/main.dart | 2 +- pubspec.lock | 10 +- pubspec.yaml | 2 + .../repository/ani_lsit_repository_test.dart | 2 +- .../bloc/airing_schedule_bloc_test.dart | 36 +++ 29 files changed, 1024 insertions(+), 55 deletions(-) create mode 100644 lib/core/common/util/collection_util.dart create mode 100644 lib/core/data/model/extension/airing_schedule_and_anime_model_extension.dart create mode 100644 lib/core/design_system/widget/airing_anime_item.dart create mode 100644 lib/core/design_system/widget/loading_indicator.dart create mode 100644 lib/feature/airing_schedule/airing_schedule.dart create mode 100644 lib/feature/airing_schedule/bloc/airing_schedule_bloc.dart create mode 100644 lib/feature/airing_schedule/bloc/airing_schedule_state.dart create mode 100644 lib/feature/airing_schedule/bloc/airing_schedule_state.freezed.dart create mode 100644 lib/feature/airing_schedule/bloc/airing_schedule_state_extension.dart create mode 100644 lib/feature/airing_schedule/bloc/schedule_category.dart create mode 100644 lib/feature/airing_schedule/bloc/schedule_category.freezed.dart create mode 100644 lib/feature/airing_schedule/bloc/schedule_page_key.dart create mode 100644 lib/feature/airing_schedule/bloc/schedule_page_state.dart create mode 100644 test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart diff --git a/lib/app/app.dart b/lib/app/app.dart index d23e8649..110ed7ba 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -102,7 +102,6 @@ class _AnimeTrackerAppScaffoldState extends State { @override void initState() { super.initState(); - animeTrackerRouterDelegate.addListener(() { setState(() { currentNavigation = diff --git a/lib/app/local/util/anime_model_extension.dart b/lib/app/local/util/anime_model_extension.dart index 1f6ae351..66941cb7 100644 --- a/lib/app/local/util/anime_model_extension.dart +++ b/lib/app/local/util/anime_model_extension.dart @@ -86,7 +86,8 @@ extension AnimeModelEx on AnimeModel { itemList.add(status!.getAnimeStatusString(context)); } - return itemList.join(' · '); + final result = itemList.join(' · '); + return result.isEmpty ? '----' : result; } String getReleasingTimeString(BuildContext context) { diff --git a/lib/app/navigation/ani_flow_route_path.dart b/lib/app/navigation/ani_flow_route_path.dart index e186038a..5bc7170f 100644 --- a/lib/app/navigation/ani_flow_route_path.dart +++ b/lib/app/navigation/ani_flow_route_path.dart @@ -1,5 +1,6 @@ import 'package:anime_tracker/app/navigation/top_level_navigation.dart'; import 'package:anime_tracker/core/common/model/anime_category.dart'; +import 'package:anime_tracker/feature/airing_schedule/airing_schedule.dart'; import 'package:anime_tracker/feature/anime_list/anime_list.dart'; import 'package:anime_tracker/feature/anime_search/anime_search.dart'; import 'package:anime_tracker/feature/anime_track/anime_track.dart'; @@ -8,12 +9,14 @@ import 'package:anime_tracker/feature/discover/discover.dart'; import 'package:anime_tracker/feature/profile/profile.dart'; import 'package:flutter/material.dart'; -sealed class AniFlowRoutePath {} +sealed class AniFlowRoutePath { + const AniFlowRoutePath(); +} abstract class TopLevelRoutePath extends AniFlowRoutePath { final TopLevelNavigation topLevel; - TopLevelRoutePath(this.topLevel); + const TopLevelRoutePath(this.topLevel); @override bool operator ==(Object other) => @@ -54,6 +57,10 @@ class DetailAnimeRoutePath extends AniFlowRoutePath { final String animeId; } +class AiringScheduleRoutePath extends AniFlowRoutePath { + const AiringScheduleRoutePath(); +} + extension AniFlowRoutePathEx on AniFlowRoutePath { Page generatePage() { switch (this) { @@ -75,6 +82,8 @@ extension AniFlowRoutePathEx on AniFlowRoutePath { return const AnimeSearchPage(key: ValueKey('AnimeSearchPage')); case ProfileRoutePath(topLevel: final _): return const ProfilePage(key: ValueKey('ProfilePage')); + case AiringScheduleRoutePath(): + return const AiringSchedule(key: ValueKey('AiringSchedule')); default: return const MaterialPage(child: SizedBox()); } diff --git a/lib/app/navigation/ani_flow_router.dart b/lib/app/navigation/ani_flow_router.dart index ea0a4b0e..a979c1b0 100644 --- a/lib/app/navigation/ani_flow_router.dart +++ b/lib/app/navigation/ani_flow_router.dart @@ -88,4 +88,10 @@ class AnimeTrackerRouterDelegate extends RouterDelegate notifyListeners(); } + + void navigateToAiringSchedule() { + _backStack += [const AiringScheduleRoutePath()]; + + notifyListeners(); + } } diff --git a/lib/core/common/util/collection_util.dart b/lib/core/common/util/collection_util.dart new file mode 100644 index 00000000..7124b753 --- /dev/null +++ b/lib/core/common/util/collection_util.dart @@ -0,0 +1,6 @@ + +extension MapEx on Map { + Map toMutableMap() { + return {...this}; + } +} \ No newline at end of file diff --git a/lib/core/data/media_information_repository.dart b/lib/core/data/media_information_repository.dart index 92e2b63b..fd40cd31 100644 --- a/lib/core/data/media_information_repository.dart +++ b/lib/core/data/media_information_repository.dart @@ -53,7 +53,7 @@ abstract class MediaInformationRepository { {int dayAgo = 0, int dayAfter = 0}); } -class AniListRepositoryImpl extends MediaInformationRepository { +class MediaInformationRepositoryImpl extends MediaInformationRepository { final AniListDataSource aniListDataSource = AniListDataSource(); final AnimeListDao animeDao = AnimeDatabase().getAnimeDao(); final AniFlowPreferences preferences = AniFlowPreferences(); @@ -258,9 +258,6 @@ class AniListRepositoryImpl extends MediaInformationRepository { @override Future> refreshAiringSchedule(DateTime now, {int dayAgo = 0, int dayAfter = 0}) async { - /// Clear old airing schedule. - await animeDao.clearAiringSchedule(); - try { final (startMs, endMs) = TimeUtil.getTimeRange(now, daysAgo: dayAgo, daysAfter: dayAfter); diff --git a/lib/core/data/model/extension/airing_schedule_and_anime_model_extension.dart b/lib/core/data/model/extension/airing_schedule_and_anime_model_extension.dart new file mode 100644 index 00000000..e71ef2fe --- /dev/null +++ b/lib/core/data/model/extension/airing_schedule_and_anime_model_extension.dart @@ -0,0 +1,9 @@ +import 'package:anime_tracker/core/data/model/airing_schedule_and_anime_model.dart'; +import 'package:flutter/material.dart'; + +extension AiringScheduleAndAnimeModelEx on AiringScheduleAndAnimeModel { + bool get isAired => airingSchedule.timeUntilAiring?.isNegative ?? false; + + TimeOfDay get airAtTimeOfDay => TimeOfDay.fromDateTime( + DateTime.fromMillisecondsSinceEpoch(airingSchedule.airingAt! * 1000)); +} diff --git a/lib/core/design_system/widget/airing_anime_item.dart b/lib/core/design_system/widget/airing_anime_item.dart new file mode 100644 index 00000000..b8e8cf76 --- /dev/null +++ b/lib/core/design_system/widget/airing_anime_item.dart @@ -0,0 +1,71 @@ +import 'package:anime_tracker/core/data/model/airing_schedule_and_anime_model.dart'; +import 'package:anime_tracker/core/data/model/anime_title_modle.dart'; +import 'package:anime_tracker/app/local/util/anime_model_extension.dart'; +import 'package:flutter/material.dart'; + +import 'package:anime_tracker/core/design_system/widget/af_network_image.dart'; + +class AiringAnimeItem extends StatelessWidget { + const AiringAnimeItem( + {required this.model, + required this.onClick, + super.key}); + + final AiringScheduleAndAnimeModel model; + final VoidCallback onClick; + + @override + Widget build(BuildContext context) { + final textColor = Theme.of(context).colorScheme.onSurfaceVariant; + final textTheme = Theme.of(context).textTheme; + return Card( + elevation: 0, + color: Theme.of(context).colorScheme.surfaceVariant, + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: onClick, + child: Row(children: [ + AspectRatio( + aspectRatio: 3.0 / 4, + child: AFNetworkImage( + imageUrl: model.animeModel.coverImage, + ), + ), + const SizedBox(width: 16), + Expanded( + flex: 1, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + model.animeModel.title!.getLocalTitle(context), + style: textTheme.titleMedium?.copyWith(color: textColor), + maxLines: 2, + softWrap: true, + ), + const Expanded(child: SizedBox()), + _buildWatchingInfoLabel(context, model), + const Expanded(child: SizedBox()), + Text( + model.animeModel.getAnimeInfoString(context), + style: textTheme.bodySmall?.copyWith(color: textColor), + ), + const SizedBox(height: 4), + ], + ), + ), + ]), + ), + ); + } + + Widget _buildWatchingInfoLabel( + BuildContext context, AiringScheduleAndAnimeModel model) { + return Text( + 'EP.${model.airingSchedule.episode}', + style: Theme.of(context).textTheme.labelLarge, + ); + } +} diff --git a/lib/core/design_system/widget/loading_indicator.dart b/lib/core/design_system/widget/loading_indicator.dart new file mode 100644 index 00000000..eb7b4007 --- /dev/null +++ b/lib/core/design_system/widget/loading_indicator.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import 'package:anime_tracker/core/common/util/global_static_constants.dart'; +import 'package:loading_animation_widget/loading_animation_widget.dart'; + +class LoadingIndicator extends StatelessWidget { + const LoadingIndicator({required this.isLoading, super.key}); + + final bool isLoading; + + @override + Widget build(BuildContext context) { + return AnimatedOpacity( + opacity: isLoading ? 1.0 : 0.0, + duration: Config.defaultAnimationDuration, + child: LoadingAnimationWidget.fourRotatingDots( + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 33.0, + ), + ); + } +} diff --git a/lib/core/network/client/ani_list_dio.dart b/lib/core/network/client/ani_list_dio.dart index 55a2b119..6ae64ed4 100644 --- a/lib/core/network/client/ani_list_dio.dart +++ b/lib/core/network/client/ani_list_dio.dart @@ -14,7 +14,7 @@ class AniListDio { headers: { "Content-Type": "application/json", "Accept": "application/json", - }); + },); dio.interceptors.add(LogInterceptor()); diff --git a/lib/feature/airing_schedule/airing_schedule.dart b/lib/feature/airing_schedule/airing_schedule.dart new file mode 100644 index 00000000..b5a814bb --- /dev/null +++ b/lib/feature/airing_schedule/airing_schedule.dart @@ -0,0 +1,267 @@ +import 'package:anime_tracker/core/common/util/logger.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/design_system/widget/airing_anime_item.dart'; +import 'package:anime_tracker/core/design_system/widget/loading_indicator.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/airing_schedule_bloc.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/airing_schedule_state.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/airing_schedule_state_extension.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_category.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_key.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_state.dart'; +import 'package:flutter/material.dart'; + +import 'package:anime_tracker/core/design_system/animetion/page_transaction_animetion.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class AiringSchedule extends Page { + const AiringSchedule({super.key}); + + @override + Route createRoute(BuildContext context) { + return AiringScheduleRoute(settings: this); + } +} + +class AiringScheduleRoute extends PageRoute with MaterialRouteTransitionMixin { + AiringScheduleRoute({super.settings}); + + @override + Widget buildContent(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => AiringScheduleBloc( + mediaInfoRepository: context.read(), + ), + lazy: false, + child: const Scaffold( + body: _AiringScheduleContent(), + ), + ); + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return getFistPageTransaction( + animation: animation, + child: getSecondaryPageTransaction( + animation: secondaryAnimation, + child: child, + ), + ); + } + + @override + bool get maintainState => false; +} + +class _AiringScheduleContent extends StatefulWidget { + const _AiringScheduleContent(); + + @override + State<_AiringScheduleContent> createState() => _AiringScheduleContentState(); +} + +class _AiringScheduleContentState extends State<_AiringScheduleContent> + with TickerProviderStateMixin { + late final TabController _tabController; + + AiringScheduleBloc? _bloc; + + int get _currentPageIndex => _tabController.index; + + final int _pageCount = 13; + final int _initPageIndex = 6; + + @override + void initState() { + super.initState(); + _tabController = + TabController(initialIndex: _initPageIndex, length: 13, vsync: this); + + _tabController.addListener(() { + _bloc?.add(OnRequestScheduleData(_currentPageIndex)); + }); + } + + @override + void dispose() { + super.dispose(); + _tabController.dispose(); + } + + @override + Widget build(BuildContext context) { + _bloc = context.read(); + return BlocBuilder( + builder: (context, state) { + final scheduleKeys = state.scheduleKeys; + final schedulePages = state.schedulePages; + + logger.d(schedulePages); + if (scheduleKeys.isEmpty) { + return const SizedBox(); + } + return Scaffold( + appBar: AppBar( + title: const Text("Schedule"), + bottom: _buildAiringScheduleTabs(context, scheduleKeys), + ), + body: TabBarView( + controller: _tabController, + children: List.generate( + _pageCount, + (index) => _SchedulePageWidget( + schedulePage: schedulePages[index], + ), + ), + ), + ); + }, + ); + } + + TabBar _buildAiringScheduleTabs( + BuildContext context, List scheduleKeys) { + return TabBar( + controller: _tabController, + isScrollable: true, + tabs: scheduleKeys.map((e) => _buildTabBarItems(context, e)).toList(), + ); + } + + Widget _buildTabBarItems(BuildContext context, SchedulePageKey key) { + final localization = Localizations.of( + context, MaterialLocalizations)!; + final isToday = key.isToday; + final backgroundColor = + isToday ? Theme.of(context).colorScheme.tertiaryContainer : null; + return Padding( + padding: const EdgeInsets.only(bottom: 2.0), + child: Container( + decoration: ShapeDecoration( + color: backgroundColor, + shape: const StadiumBorder(), + ), + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), + child: Text(localization.formatMediumDate(key.dateTime)), + ), + ); + } +} + +class _SchedulePageWidget extends StatelessWidget { + const _SchedulePageWidget({required this.schedulePage}); + + final SchedulePageState schedulePage; + + @override + Widget build(BuildContext context) { + switch (schedulePage) { + case SchedulePageInit(): + case SchedulePageLoading(): + return _buildLoadingWidget(); + case SchedulePageError(): + return const Placeholder(); + case SchedulePageReady(schedules: final _): + final categories = (schedulePage as SchedulePageReady).categories; + return CustomScrollView( + slivers: [ + SliverList.builder( + itemCount: categories.length, + itemBuilder: (context, index) => _TimeLineItem( + category: categories[index], + ), + ) + ], + ); + } + } + + Widget _buildLoadingWidget() { + return const Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 10), + LoadingIndicator(isLoading: true), + ], + ); + } +} + +class _TimeLineItem extends StatefulWidget { + const _TimeLineItem({required this.category}); + + final ScheduleCategory category; + + @override + State<_TimeLineItem> createState() => _TimeLineItemState(); +} + +class _TimeLineItemState extends State<_TimeLineItem> { + @override + Widget build(BuildContext context) { + final localization = Localizations.of( + context, MaterialLocalizations)!; + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return Padding( + padding: const EdgeInsets.only(left: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(size: 16, Icons.circle_outlined, color: colorScheme.primary), + const SizedBox(width: 16), + Text( + localization.formatTimeOfDay(widget.category.timeOfDayHeader!), + style: textTheme.labelLarge, + ), + ], + ), + Stack( + children: [ + Positioned.fill( + child: _buildTimeLineDecoration( + color: colorScheme.tertiary.withAlpha(128), + ), + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.category.schedules.length, + itemBuilder: (BuildContext context, int index) { + final schedule = widget.category.schedules[index]; + return Padding( + padding: const EdgeInsets.only( + top: 2.0, bottom: 2.0, left: 16.0, right: 8.0), + child: SizedBox( + height: 120, + child: AiringAnimeItem(model: schedule, onClick: () {}), + ), + ); + }, + ), + ], + ), + ], + ), + ); + } + + Widget _buildTimeLineDecoration({required Color color}) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 6), + Container( + width: 4, + color: color, + ), + ], + ); + } +} diff --git a/lib/feature/airing_schedule/bloc/airing_schedule_bloc.dart b/lib/feature/airing_schedule/bloc/airing_schedule_bloc.dart new file mode 100644 index 00000000..9fffdca4 --- /dev/null +++ b/lib/feature/airing_schedule/bloc/airing_schedule_bloc.dart @@ -0,0 +1,94 @@ +import 'dart:async'; + +import 'package:anime_tracker/core/common/util/collection_util.dart'; +import 'package:anime_tracker/core/data/load_result.dart'; +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/airing_schedule_state.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_key.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_state.dart'; +import 'package:bloc/bloc.dart'; + +sealed class AiringScheduleEvent {} + +class _OnScheduleKeyInitialized extends AiringScheduleEvent { + final List keys; + + _OnScheduleKeyInitialized(this.keys); +} + +class OnRequestScheduleData extends AiringScheduleEvent { + final int pageIndex; + + OnRequestScheduleData(this.pageIndex); +} + +class AiringScheduleBloc + extends Bloc { + AiringScheduleBloc({ + required MediaInformationRepository mediaInfoRepository, + }) : _mediaInfoRepository = mediaInfoRepository, + super(AiringScheduleState()) { + on<_OnScheduleKeyInitialized>(_onScheduleKeyInitialized); + on(_onRequestScheduleData); + + _init(); + } + + final MediaInformationRepository _mediaInfoRepository; + + final _currentDateTime = DateTime.now(); + + void _init() async { + /// create schedule keys of 6 days before and 6 days after. + List keys = + createScheduleKeys(_currentDateTime, daysAgo: 6, daysAfter: 6); + add(_OnScheduleKeyInitialized(keys)); + + /// load today schedule. + add(OnRequestScheduleData(6)); + } + + FutureOr _onScheduleKeyInitialized( + _OnScheduleKeyInitialized event, Emitter emit) { + emit(state.copyWith(scheduleKeys: event.keys)); + } + + Future _onRequestScheduleData( + OnRequestScheduleData event, Emitter emit) async { + final pageIndex = event.pageIndex; + + final pageKey = state.scheduleKeys[pageIndex]; + final pageState = state.schedulePageMap[pageKey] ?? SchedulePageInit(); + + if (pageState is SchedulePageReady || pageState is SchedulePageLoading) { + return; + } + + /// Change to loading state. + emit( + state.copyWith( + schedulePageMap: state.schedulePageMap.toMutableMap() + ..[pageKey] = SchedulePageLoading(), + ), + ); + + /// refresh airing schedule. + final result = + await _mediaInfoRepository.refreshAiringSchedule(pageKey.dateTime); + + if (result is LoadError) { +// TODO: error show snack bar + return; + } + + /// Change to ready state. + final airingSchedules = await _mediaInfoRepository + .getAiringScheduleAndAnimeByDateTime(pageKey.dateTime); + emit( + state.copyWith( + schedulePageMap: state.schedulePageMap.toMutableMap() + ..[pageKey] = SchedulePageReady(schedules: airingSchedules), + ), + ); + } +} diff --git a/lib/feature/airing_schedule/bloc/airing_schedule_state.dart b/lib/feature/airing_schedule/bloc/airing_schedule_state.dart new file mode 100644 index 00000000..31aca4c2 --- /dev/null +++ b/lib/feature/airing_schedule/bloc/airing_schedule_state.dart @@ -0,0 +1,13 @@ +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_key.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_state.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'airing_schedule_state.freezed.dart'; + +@freezed +class AiringScheduleState with _$AiringScheduleState { + factory AiringScheduleState({ + @Default([]) List scheduleKeys, + @Default({}) Map schedulePageMap, + }) = _AiringScheduleState; +} diff --git a/lib/feature/airing_schedule/bloc/airing_schedule_state.freezed.dart b/lib/feature/airing_schedule/bloc/airing_schedule_state.freezed.dart new file mode 100644 index 00000000..23203ff9 --- /dev/null +++ b/lib/feature/airing_schedule/bloc/airing_schedule_state.freezed.dart @@ -0,0 +1,179 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'airing_schedule_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$AiringScheduleState { + List get scheduleKeys => throw _privateConstructorUsedError; + Map get schedulePageMap => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AiringScheduleStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AiringScheduleStateCopyWith<$Res> { + factory $AiringScheduleStateCopyWith( + AiringScheduleState value, $Res Function(AiringScheduleState) then) = + _$AiringScheduleStateCopyWithImpl<$Res, AiringScheduleState>; + @useResult + $Res call( + {List scheduleKeys, + Map schedulePageMap}); +} + +/// @nodoc +class _$AiringScheduleStateCopyWithImpl<$Res, $Val extends AiringScheduleState> + implements $AiringScheduleStateCopyWith<$Res> { + _$AiringScheduleStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? scheduleKeys = null, + Object? schedulePageMap = null, + }) { + return _then(_value.copyWith( + scheduleKeys: null == scheduleKeys + ? _value.scheduleKeys + : scheduleKeys // ignore: cast_nullable_to_non_nullable + as List, + schedulePageMap: null == schedulePageMap + ? _value.schedulePageMap + : schedulePageMap // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_AiringScheduleStateCopyWith<$Res> + implements $AiringScheduleStateCopyWith<$Res> { + factory _$$_AiringScheduleStateCopyWith(_$_AiringScheduleState value, + $Res Function(_$_AiringScheduleState) then) = + __$$_AiringScheduleStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List scheduleKeys, + Map schedulePageMap}); +} + +/// @nodoc +class __$$_AiringScheduleStateCopyWithImpl<$Res> + extends _$AiringScheduleStateCopyWithImpl<$Res, _$_AiringScheduleState> + implements _$$_AiringScheduleStateCopyWith<$Res> { + __$$_AiringScheduleStateCopyWithImpl(_$_AiringScheduleState _value, + $Res Function(_$_AiringScheduleState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? scheduleKeys = null, + Object? schedulePageMap = null, + }) { + return _then(_$_AiringScheduleState( + scheduleKeys: null == scheduleKeys + ? _value._scheduleKeys + : scheduleKeys // ignore: cast_nullable_to_non_nullable + as List, + schedulePageMap: null == schedulePageMap + ? _value._schedulePageMap + : schedulePageMap // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_AiringScheduleState implements _AiringScheduleState { + _$_AiringScheduleState( + {final List scheduleKeys = const [], + final Map schedulePageMap = const {}}) + : _scheduleKeys = scheduleKeys, + _schedulePageMap = schedulePageMap; + + final List _scheduleKeys; + @override + @JsonKey() + List get scheduleKeys { + if (_scheduleKeys is EqualUnmodifiableListView) return _scheduleKeys; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_scheduleKeys); + } + + final Map _schedulePageMap; + @override + @JsonKey() + Map get schedulePageMap { + if (_schedulePageMap is EqualUnmodifiableMapView) return _schedulePageMap; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_schedulePageMap); + } + + @override + String toString() { + return 'AiringScheduleState(scheduleKeys: $scheduleKeys, schedulePageMap: $schedulePageMap)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_AiringScheduleState && + const DeepCollectionEquality() + .equals(other._scheduleKeys, _scheduleKeys) && + const DeepCollectionEquality() + .equals(other._schedulePageMap, _schedulePageMap)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_scheduleKeys), + const DeepCollectionEquality().hash(_schedulePageMap)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_AiringScheduleStateCopyWith<_$_AiringScheduleState> get copyWith => + __$$_AiringScheduleStateCopyWithImpl<_$_AiringScheduleState>( + this, _$identity); +} + +abstract class _AiringScheduleState implements AiringScheduleState { + factory _AiringScheduleState( + {final List scheduleKeys, + final Map schedulePageMap}) = + _$_AiringScheduleState; + + @override + List get scheduleKeys; + @override + Map get schedulePageMap; + @override + @JsonKey(ignore: true) + _$$_AiringScheduleStateCopyWith<_$_AiringScheduleState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/feature/airing_schedule/bloc/airing_schedule_state_extension.dart b/lib/feature/airing_schedule/bloc/airing_schedule_state_extension.dart new file mode 100644 index 00000000..f1d045aa --- /dev/null +++ b/lib/feature/airing_schedule/bloc/airing_schedule_state_extension.dart @@ -0,0 +1,8 @@ +import 'package:anime_tracker/feature/airing_schedule/bloc/airing_schedule_state.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_state.dart'; + +extension AiringScheduleStateEx on AiringScheduleState { + List get schedulePages => scheduleKeys + .map((e) => schedulePageMap[e] ?? SchedulePageInit()) + .toList(); +} diff --git a/lib/feature/airing_schedule/bloc/schedule_category.dart b/lib/feature/airing_schedule/bloc/schedule_category.dart new file mode 100644 index 00000000..8e7b5c2e --- /dev/null +++ b/lib/feature/airing_schedule/bloc/schedule_category.dart @@ -0,0 +1,13 @@ +import 'package:anime_tracker/core/data/model/airing_schedule_and_anime_model.dart'; +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'schedule_category.freezed.dart'; + +@freezed +class ScheduleCategory with _$ScheduleCategory { + factory ScheduleCategory({ + TimeOfDay? timeOfDayHeader, + @Default([]) List schedules + }) = _ScheduleCategory; +} diff --git a/lib/feature/airing_schedule/bloc/schedule_category.freezed.dart b/lib/feature/airing_schedule/bloc/schedule_category.freezed.dart new file mode 100644 index 00000000..632036f8 --- /dev/null +++ b/lib/feature/airing_schedule/bloc/schedule_category.freezed.dart @@ -0,0 +1,167 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'schedule_category.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$ScheduleCategory { + TimeOfDay? get timeOfDayHeader => throw _privateConstructorUsedError; + List get schedules => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ScheduleCategoryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ScheduleCategoryCopyWith<$Res> { + factory $ScheduleCategoryCopyWith( + ScheduleCategory value, $Res Function(ScheduleCategory) then) = + _$ScheduleCategoryCopyWithImpl<$Res, ScheduleCategory>; + @useResult + $Res call( + {TimeOfDay? timeOfDayHeader, + List schedules}); +} + +/// @nodoc +class _$ScheduleCategoryCopyWithImpl<$Res, $Val extends ScheduleCategory> + implements $ScheduleCategoryCopyWith<$Res> { + _$ScheduleCategoryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? timeOfDayHeader = freezed, + Object? schedules = null, + }) { + return _then(_value.copyWith( + timeOfDayHeader: freezed == timeOfDayHeader + ? _value.timeOfDayHeader + : timeOfDayHeader // ignore: cast_nullable_to_non_nullable + as TimeOfDay?, + schedules: null == schedules + ? _value.schedules + : schedules // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_ScheduleCategoryCopyWith<$Res> + implements $ScheduleCategoryCopyWith<$Res> { + factory _$$_ScheduleCategoryCopyWith( + _$_ScheduleCategory value, $Res Function(_$_ScheduleCategory) then) = + __$$_ScheduleCategoryCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {TimeOfDay? timeOfDayHeader, + List schedules}); +} + +/// @nodoc +class __$$_ScheduleCategoryCopyWithImpl<$Res> + extends _$ScheduleCategoryCopyWithImpl<$Res, _$_ScheduleCategory> + implements _$$_ScheduleCategoryCopyWith<$Res> { + __$$_ScheduleCategoryCopyWithImpl( + _$_ScheduleCategory _value, $Res Function(_$_ScheduleCategory) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? timeOfDayHeader = freezed, + Object? schedules = null, + }) { + return _then(_$_ScheduleCategory( + timeOfDayHeader: freezed == timeOfDayHeader + ? _value.timeOfDayHeader + : timeOfDayHeader // ignore: cast_nullable_to_non_nullable + as TimeOfDay?, + schedules: null == schedules + ? _value._schedules + : schedules // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$_ScheduleCategory implements _ScheduleCategory { + _$_ScheduleCategory( + {this.timeOfDayHeader, + final List schedules = const []}) + : _schedules = schedules; + + @override + final TimeOfDay? timeOfDayHeader; + final List _schedules; + @override + @JsonKey() + List get schedules { + if (_schedules is EqualUnmodifiableListView) return _schedules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_schedules); + } + + @override + String toString() { + return 'ScheduleCategory(timeOfDayHeader: $timeOfDayHeader, schedules: $schedules)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_ScheduleCategory && + (identical(other.timeOfDayHeader, timeOfDayHeader) || + other.timeOfDayHeader == timeOfDayHeader) && + const DeepCollectionEquality() + .equals(other._schedules, _schedules)); + } + + @override + int get hashCode => Object.hash(runtimeType, timeOfDayHeader, + const DeepCollectionEquality().hash(_schedules)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_ScheduleCategoryCopyWith<_$_ScheduleCategory> get copyWith => + __$$_ScheduleCategoryCopyWithImpl<_$_ScheduleCategory>(this, _$identity); +} + +abstract class _ScheduleCategory implements ScheduleCategory { + factory _ScheduleCategory( + {final TimeOfDay? timeOfDayHeader, + final List schedules}) = _$_ScheduleCategory; + + @override + TimeOfDay? get timeOfDayHeader; + @override + List get schedules; + @override + @JsonKey(ignore: true) + _$$_ScheduleCategoryCopyWith<_$_ScheduleCategory> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/feature/airing_schedule/bloc/schedule_page_key.dart b/lib/feature/airing_schedule/bloc/schedule_page_key.dart new file mode 100644 index 00000000..cc42f54a --- /dev/null +++ b/lib/feature/airing_schedule/bloc/schedule_page_key.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +class SchedulePageKey extends Equatable { + const SchedulePageKey({required this.dayFromNow, required this.dateTime}); + + final int dayFromNow; + final DateTime dateTime; + + bool get isToday => dayFromNow == 0; + + @override + List get props => [dayFromNow]; + + + @override + String toString() { + return 'SchedulePageKey(dayFromNow: $dayFromNow, dateTime: $dateTime)'; + } +} + +List createScheduleKeys(DateTime dateTime, + {required int daysAgo, required int daysAfter}) { + List keys = []; + for (var i = -daysAgo; i <= daysAfter; i++) { + final daysDifferent = Duration(days: i.abs()); + final newDateTime = i.isNegative + ? dateTime.subtract(daysDifferent) + : dateTime.add(daysDifferent); + keys.add(SchedulePageKey(dayFromNow: i, dateTime: newDateTime)); + } + return keys; +} diff --git a/lib/feature/airing_schedule/bloc/schedule_page_state.dart b/lib/feature/airing_schedule/bloc/schedule_page_state.dart new file mode 100644 index 00000000..515fca6a --- /dev/null +++ b/lib/feature/airing_schedule/bloc/schedule_page_state.dart @@ -0,0 +1,50 @@ +import 'package:anime_tracker/core/data/model/airing_schedule_and_anime_model.dart'; +import 'package:anime_tracker/core/data/model/extension/airing_schedule_and_anime_model_extension.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_category.dart'; +import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; + +sealed class SchedulePageState extends Equatable { + @override + List get props => []; +} + +class SchedulePageInit extends SchedulePageState {} + +class SchedulePageLoading extends SchedulePageState {} + +class SchedulePageReady extends SchedulePageState { + SchedulePageReady({required this.schedules}); + + final List schedules; + + List get categories => _buildCategories(); + + @override + List get props => [schedules]; + + List _buildCategories() { + Map group = schedules.groupFoldBy((schedule) { + return schedule.airAtTimeOfDay.hour; + }, (previousCategory, schedule) { + final category = previousCategory ?? + ScheduleCategory( + timeOfDayHeader: schedule.airAtTimeOfDay.replacing(minute: 0), + schedules: [], + ); + + return category.copyWith(schedules: [...category.schedules, schedule]); + }); + + return group.values.toList(); + } +} + +class SchedulePageError extends SchedulePageState { + final Exception exception; + + SchedulePageError({required this.exception}); + + @override + List get props => [exception]; +} diff --git a/lib/feature/anime_list/anime_list.dart b/lib/feature/anime_list/anime_list.dart index 807d3643..024584a2 100644 --- a/lib/feature/anime_list/anime_list.dart +++ b/lib/feature/anime_list/anime_list.dart @@ -7,11 +7,11 @@ import 'package:anime_tracker/core/data/media_information_repository.dart'; import 'package:anime_tracker/core/data/ani_list_repository.dart'; import 'package:anime_tracker/core/data/auth_repository.dart'; import 'package:anime_tracker/core/design_system/widget/anime_preview_item.dart'; +import 'package:anime_tracker/core/design_system/widget/loading_indicator.dart'; import 'package:anime_tracker/feature/anime_list/bloc/anime_list_bloc.dart'; import 'package:anime_tracker/feature/anime_list/bloc/anime_list_state.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:visibility_detector/visibility_detector.dart'; import 'package:anime_tracker/core/data/model/page_loading_state.dart'; import 'package:anime_tracker/core/design_system/animetion/page_transaction_animetion.dart'; @@ -149,16 +149,13 @@ class _AnimeListPageContent extends StatelessWidget { Widget _buildPageBottomBar(BuildContext context, PagingState pagingState) { if (pagingState is PageLoading) { // Loading widget. - return SizedBox.square( + return const SizedBox.square( dimension: 64, child: Align( alignment: Alignment.center, child: SizedBox.square( dimension: 36, - child: LoadingAnimationWidget.fourRotatingDots( - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 33.0, - ), + child: LoadingIndicator(isLoading: true), ), ), ); diff --git a/lib/feature/anime_list/bloc/anime_list_bloc.dart b/lib/feature/anime_list/bloc/anime_list_bloc.dart index 6a6b3c95..3b08f21f 100644 --- a/lib/feature/anime_list/bloc/anime_list_bloc.dart +++ b/lib/feature/anime_list/bloc/anime_list_bloc.dart @@ -42,7 +42,7 @@ class AnimeListBloc extends Bloc { required AuthRepository authRepository, required MediaInformationRepository aniListRepository, required AniListRepository animeTrackListRepository, - }) : _aniListRepository = aniListRepository, + }) : _mediaInfoRepository = aniListRepository, _authRepository = authRepository, _animeTrackListRepository = animeTrackListRepository, super(AnimeListState()) { @@ -56,7 +56,7 @@ class AnimeListBloc extends Bloc { } final AnimeCategory category; - final MediaInformationRepository _aniListRepository; + final MediaInformationRepository _mediaInfoRepository; final AniListRepository _animeTrackListRepository; final AuthRepository _authRepository; @@ -105,7 +105,7 @@ class AnimeListBloc extends Bloc { } Future _createLoadAnimePageTask({required int page}) async { - final LoadResult result = await _aniListRepository.getAnimePageByCategory( + final LoadResult result = await _mediaInfoRepository.getAnimePageByCategory( category: category, page: page, perPage: Config.defaultPerPageCount); switch (result) { case LoadSuccess(data: final data): diff --git a/lib/feature/anime_track/anime_track.dart b/lib/feature/anime_track/anime_track.dart index f840f7c4..6bc0d395 100644 --- a/lib/feature/anime_track/anime_track.dart +++ b/lib/feature/anime_track/anime_track.dart @@ -1,15 +1,14 @@ import 'package:anime_tracker/app/navigation/ani_flow_router.dart'; -import 'package:anime_tracker/core/common/util/global_static_constants.dart'; import 'package:anime_tracker/core/data/model/anime_list_item_model.dart'; import 'package:anime_tracker/core/design_system/widget/af_toogle_button.dart'; import 'package:anime_tracker/core/design_system/widget/anime_track_item.dart'; +import 'package:anime_tracker/core/design_system/widget/loading_indicator.dart'; import 'package:anime_tracker/feature/anime_track/bloc/track_bloc.dart'; import 'package:anime_tracker/feature/anime_track/bloc/track_ui_state.dart'; import 'package:anime_tracker/feature/anime_track/bloc/user_anime_list_load_state.dart'; import 'package:flutter/material.dart'; import 'package:anime_tracker/core/design_system/animetion/page_transaction_animetion.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:loading_animation_widget/loading_animation_widget.dart'; class AnimeTrackPage extends Page { const AnimeTrackPage({super.key}); @@ -68,8 +67,8 @@ class _AnimeTrackPageContent extends StatelessWidget { }); } - List _buildTrackSectionContents( - BuildContext context, TrackUiState state) { + List _buildTrackSectionContents(BuildContext context, + TrackUiState state) { final animeLoadState = state.animeLoadState; if (animeLoadState is UserAnimeNoUser) { return [ @@ -88,7 +87,8 @@ class _AnimeTrackPageContent extends StatelessWidget { ), SliverList( delegate: SliverChildBuilderDelegate( - (context, index) => _buildAnimeListItem(context, animeList[index]), + (context, index) => + _buildAnimeListItem(context, animeList[index]), childCount: animeList.length, ), ) @@ -137,23 +137,20 @@ class _AnimeTrackPageContent extends StatelessWidget { ); } - Widget _buildAppBar(BuildContext context, TrackUiState state) => SliverAppBar( + Widget _buildAppBar(BuildContext context, TrackUiState state) => + SliverAppBar( title: const Text('Track'), actions: [ - AnimatedOpacity( - opacity: state.isLoading ? 1.0 : 0.0, - duration: Config.defaultAnimationDuration, - child: LoadingAnimationWidget.fourRotatingDots( - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 33.0, - ), - ), + LoadingIndicator(isLoading: state.isLoading,), const SizedBox(width: 10), Padding( padding: const EdgeInsets.only(right: 12.0), child: IconButton( - icon: const Icon(Icons.schedule_rounded), - onPressed: () {}, + icon: const Icon(Icons.calendar_month_rounded), + onPressed: () { + AnimeTrackerRouterDelegate.of(context) + .navigateToAiringSchedule(); + }, ), ), ], diff --git a/lib/feature/detail_anime/detail_anime.dart b/lib/feature/detail_anime/detail_anime.dart index f36fc125..86443bb7 100644 --- a/lib/feature/detail_anime/detail_anime.dart +++ b/lib/feature/detail_anime/detail_anime.dart @@ -14,6 +14,7 @@ import 'package:anime_tracker/app/local/util/anime_model_extension.dart'; import 'package:anime_tracker/core/design_system/widget/af_network_image.dart'; import 'package:anime_tracker/core/design_system/widget/anime_character_and_voice_actor.dart'; import 'package:anime_tracker/core/design_system/widget/anime_staff_item.dart'; +import 'package:anime_tracker/core/design_system/widget/loading_indicator.dart'; import 'package:anime_tracker/core/design_system/widget/trailer_preview.dart'; import 'package:anime_tracker/core/design_system/widget/twitter_hashtag_widget.dart'; import 'package:anime_tracker/core/design_system/widget/vertical_animated_scale_switcher.dart'; @@ -23,7 +24,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:anime_tracker/core/design_system/animetion/page_transaction_animetion.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:sprintf/sprintf.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -111,14 +111,7 @@ class _DetailAnimePageContent extends StatelessWidget { ), title: Text(model.title!.getLocalTitle(context)), actions: [ - AnimatedOpacity( - opacity: isLoading ? 1.0 : 0.0, - duration: Config.defaultAnimationDuration, - child: LoadingAnimationWidget.fourRotatingDots( - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 33.0, - ), - ), + LoadingIndicator(isLoading: isLoading), const SizedBox(width: 10), ], ), diff --git a/lib/feature/discover/discover.dart b/lib/feature/discover/discover.dart index 25156d3c..7735877c 100644 --- a/lib/feature/discover/discover.dart +++ b/lib/feature/discover/discover.dart @@ -4,6 +4,7 @@ import 'package:anime_tracker/core/data/model/page_loading_state.dart'; import 'package:anime_tracker/core/design_system/animetion/page_transaction_animetion.dart'; import 'package:anime_tracker/core/design_system/widget/anime_preview_item.dart'; import 'package:anime_tracker/core/design_system/widget/avatar_icon.dart'; +import 'package:anime_tracker/core/design_system/widget/loading_indicator.dart'; import 'package:anime_tracker/feature/discover/bloc/discover_bloc.dart'; import 'package:anime_tracker/feature/discover/bloc/discover_ui_state.dart'; import 'package:flutter/material.dart'; @@ -13,7 +14,6 @@ import 'package:anime_tracker/app/local/ani_flow_localizations.dart'; import 'package:anime_tracker/app/navigation/ani_flow_router.dart'; import 'package:anime_tracker/feature/auth/auth_dialog.dart'; import 'package:anime_tracker/core/common/util/global_static_constants.dart'; -import 'package:loading_animation_widget/loading_animation_widget.dart'; class DiscoverPage extends Page { const DiscoverPage({super.key}); @@ -70,14 +70,7 @@ class DiscoverScreen extends StatelessWidget { title: Text(AFLocalizations.of(context).discover), pinned: true, actions: [ - AnimatedOpacity( - opacity: isLoading ? 1.0 : 0.0, - duration: Config.defaultAnimationDuration, - child: LoadingAnimationWidget.fourRotatingDots( - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 33.0, - ), - ), + LoadingIndicator(isLoading: isLoading), const SizedBox(width: 10), Padding( padding: const EdgeInsets.only(right: 12.0), diff --git a/lib/main.dart b/lib/main.dart index ebfda523..cd5b5c0b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,7 @@ void main() async { /// run app after core instance initialized. runApp(MultiRepositoryProvider(providers: [ RepositoryProvider( - create: (context) => AniListRepositoryImpl(), + create: (context) => MediaInformationRepositoryImpl(), ), RepositoryProvider( create: (context) => UserDataRepositoryImpl(), diff --git a/pubspec.lock b/pubspec.lock index 9547227f..295a7cf5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -178,7 +178,7 @@ packages: source: hosted version: "4.6.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 @@ -946,6 +946,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + timeline_tile: + dependency: "direct main" + description: + name: timeline_tile + sha256: "85ec2023c67137397c2812e3e848b2fb20b410b67cd9aff304bb5480c376fc0c" + url: "https://pub.dev" + source: hosted + version: "2.0.0" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d945a722..0a569639 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,8 @@ dependencies: sprintf: ^7.0.0 flutter_slidable: ^3.0.0 loading_animation_widget: ^1.2.0+4 + timeline_tile: ^2.0.0 + collection: ^1.17.2 dev_dependencies: flutter_test: diff --git a/test/core/data/repository/ani_lsit_repository_test.dart b/test/core/data/repository/ani_lsit_repository_test.dart index 8ddcce95..642b5ef2 100644 --- a/test/core/data/repository/ani_lsit_repository_test.dart +++ b/test/core/data/repository/ani_lsit_repository_test.dart @@ -23,7 +23,7 @@ void main() { await AniFlowPreferences().setCurrentSeason(AnimeSeason.summer); await animeDatabase.initDatabase(isTest: true); - aniListRepository = AniListRepositoryImpl(); + aniListRepository = MediaInformationRepositoryImpl(); }); tearDown(() async { diff --git a/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart b/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart new file mode 100644 index 00000000..177737ff --- /dev/null +++ b/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart @@ -0,0 +1,36 @@ +import 'package:anime_tracker/core/data/media_information_repository.dart'; +import 'package:anime_tracker/core/database/anime_database.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/airing_schedule_bloc.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_key.dart'; +import 'package:anime_tracker/feature/airing_schedule/bloc/schedule_page_state.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + +void main() { + group('airing_schedule_bloc_test', () { + late AiringScheduleBloc airingScheduleBloc; + final animeDatabase = AnimeDatabase(); + + setUp(() async { + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + + await animeDatabase.initDatabase(isTest: true); + airingScheduleBloc = AiringScheduleBloc( + mediaInfoRepository: MediaInformationRepositoryImpl()); + }); + + test('test_data_time', () async { + airingScheduleBloc.add(OnRequestScheduleData(0)); + await Future.delayed(const Duration(seconds: 4)); + expect(airingScheduleBloc.state.schedulePageMap.keys, + equals([SchedulePageKey(dayFromNow: -6, dateTime: DateTime.now())])); + + expect( + airingScheduleBloc.state.schedulePageMap[ + SchedulePageKey(dayFromNow: -6, dateTime: DateTime.now())] + is SchedulePageReady, + equals(true)); + }); + }); +} From 948e083d9e7644a0ddca2a190e42187900523e08 Mon Sep 17 00:00:00 2001 From: andannn Date: Sat, 14 Oct 2023 11:15:12 +0800 Subject: [PATCH 5/5] fix test --- .../airing_schedule/bloc/airing_schedule_bloc_test.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart b/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart index 177737ff..a68a84d3 100644 --- a/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart +++ b/test/feature/airing_schedule/bloc/airing_schedule_bloc_test.dart @@ -21,14 +21,13 @@ void main() { }); test('test_data_time', () async { - airingScheduleBloc.add(OnRequestScheduleData(0)); await Future.delayed(const Duration(seconds: 4)); expect(airingScheduleBloc.state.schedulePageMap.keys, - equals([SchedulePageKey(dayFromNow: -6, dateTime: DateTime.now())])); + equals([SchedulePageKey(dayFromNow: 0, dateTime: DateTime.now())])); expect( airingScheduleBloc.state.schedulePageMap[ - SchedulePageKey(dayFromNow: -6, dateTime: DateTime.now())] + SchedulePageKey(dayFromNow: 0, dateTime: DateTime.now())] is SchedulePageReady, equals(true)); });