feat(meta): Implement manga tracking
This commit is contained in:
		
							parent
							
								
									cd1291a192
								
							
						
					
					
						commit
						7af2277bb2
					
				
							
								
								
									
										70
									
								
								lib/src/data/manga.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								lib/src/data/manga.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| 
 | ||||
| part 'manga.freezed.dart'; | ||||
| part 'manga.g.dart'; | ||||
| 
 | ||||
| /// The watch state of an manga | ||||
| enum MangaTrackingState { | ||||
|   reading,    // 0 | ||||
|   completed,   // 1 | ||||
|   planToWatch, // 2 | ||||
|   dropped,     // 3 | ||||
|   /// This is a pseudo state, i.e. it should never be set | ||||
|   all,         // -1 | ||||
| } | ||||
| 
 | ||||
| extension MangaTrackStateExtension on MangaTrackingState { | ||||
|   int toInteger() { | ||||
|     assert(this != MangaTrackingState.all, 'MangaTrackingState.all must not be serialized'); | ||||
|     switch (this) { | ||||
|       case MangaTrackingState.reading: return 0; | ||||
|       case MangaTrackingState.completed: return 1; | ||||
|       case MangaTrackingState.planToWatch: return 2; | ||||
|       case MangaTrackingState.dropped: return 3; | ||||
|       case MangaTrackingState.all: return -1; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class MangaTrackingStateConverter implements JsonConverter<MangaTrackingState, int> { | ||||
|   const MangaTrackingStateConverter(); | ||||
| 
 | ||||
|   @override | ||||
|   MangaTrackingState fromJson(int json) { | ||||
|     switch (json) { | ||||
|       case 0: return MangaTrackingState.reading; | ||||
|       case 1: return MangaTrackingState.completed; | ||||
|       case 2: return MangaTrackingState.planToWatch; | ||||
|       case 3: return MangaTrackingState.dropped; | ||||
|     } | ||||
| 
 | ||||
|     return MangaTrackingState.planToWatch; | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   int toJson(MangaTrackingState state) => state.toInteger(); | ||||
| } | ||||
| 
 | ||||
| /// Data about a tracked anime | ||||
| @freezed | ||||
| class MangaTrackingData with _$MangaTrackingData{ | ||||
|   factory MangaTrackingData( | ||||
|     /// The ID of the manga | ||||
|     String id, | ||||
|     /// The state of the manga | ||||
|     @MangaTrackingStateConverter() MangaTrackingState state, | ||||
|     /// The title of the manga | ||||
|     String title, | ||||
|     /// Chapters read. | ||||
|     int chaptersRead, | ||||
|     /// Chapters read. | ||||
|     int volumesOwned, | ||||
|     /// Episodes watched. | ||||
|     int? chaptersTotal, | ||||
|     /// URL to the thumbnail/cover art for the manga. | ||||
|     String thumbnailUrl, | ||||
|   ) = _MangaTrackingData; | ||||
| 
 | ||||
|   /// JSON | ||||
|   factory MangaTrackingData.fromJson(Map<String, dynamic> json) => _$MangaTrackingDataFromJson(json); | ||||
| } | ||||
							
								
								
									
										328
									
								
								lib/src/data/manga.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								lib/src/data/manga.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,328 @@ | ||||
| // 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 | ||||
| 
 | ||||
| part of 'manga.dart'; | ||||
| 
 | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
| 
 | ||||
| T _$identity<T>(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'); | ||||
| 
 | ||||
| MangaTrackingData _$MangaTrackingDataFromJson(Map<String, dynamic> json) { | ||||
|   return _MangaTrackingData.fromJson(json); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$MangaTrackingData { | ||||
|   /// The ID of the manga | ||||
|   String get id => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// The state of the manga | ||||
|   @MangaTrackingStateConverter() | ||||
|   MangaTrackingState get state => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// The title of the manga | ||||
|   String get title => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// Chapters read. | ||||
|   int get chaptersRead => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// Chapters read. | ||||
|   int get volumesOwned => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// Episodes watched. | ||||
|   int? get chaptersTotal => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// URL to the thumbnail/cover art for the manga. | ||||
|   String get thumbnailUrl => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|   @JsonKey(ignore: true) | ||||
|   $MangaTrackingDataCopyWith<MangaTrackingData> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class $MangaTrackingDataCopyWith<$Res> { | ||||
|   factory $MangaTrackingDataCopyWith( | ||||
|           MangaTrackingData value, $Res Function(MangaTrackingData) then) = | ||||
|       _$MangaTrackingDataCopyWithImpl<$Res>; | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       @MangaTrackingStateConverter() MangaTrackingState state, | ||||
|       String title, | ||||
|       int chaptersRead, | ||||
|       int volumesOwned, | ||||
|       int? chaptersTotal, | ||||
|       String thumbnailUrl}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class _$MangaTrackingDataCopyWithImpl<$Res> | ||||
|     implements $MangaTrackingDataCopyWith<$Res> { | ||||
|   _$MangaTrackingDataCopyWithImpl(this._value, this._then); | ||||
| 
 | ||||
|   final MangaTrackingData _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function(MangaTrackingData) _then; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? id = freezed, | ||||
|     Object? state = freezed, | ||||
|     Object? title = freezed, | ||||
|     Object? chaptersRead = freezed, | ||||
|     Object? volumesOwned = freezed, | ||||
|     Object? chaptersTotal = freezed, | ||||
|     Object? thumbnailUrl = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       id: id == freezed | ||||
|           ? _value.id | ||||
|           : id // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       state: state == freezed | ||||
|           ? _value.state | ||||
|           : state // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|       title: title == freezed | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       chaptersRead: chaptersRead == freezed | ||||
|           ? _value.chaptersRead | ||||
|           : chaptersRead // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       volumesOwned: volumesOwned == freezed | ||||
|           ? _value.volumesOwned | ||||
|           : volumesOwned // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       chaptersTotal: chaptersTotal == freezed | ||||
|           ? _value.chaptersTotal | ||||
|           : chaptersTotal // ignore: cast_nullable_to_non_nullable | ||||
|               as int?, | ||||
|       thumbnailUrl: thumbnailUrl == freezed | ||||
|           ? _value.thumbnailUrl | ||||
|           : thumbnailUrl // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class _$$_MangaTrackingDataCopyWith<$Res> | ||||
|     implements $MangaTrackingDataCopyWith<$Res> { | ||||
|   factory _$$_MangaTrackingDataCopyWith(_$_MangaTrackingData value, | ||||
|           $Res Function(_$_MangaTrackingData) then) = | ||||
|       __$$_MangaTrackingDataCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       @MangaTrackingStateConverter() MangaTrackingState state, | ||||
|       String title, | ||||
|       int chaptersRead, | ||||
|       int volumesOwned, | ||||
|       int? chaptersTotal, | ||||
|       String thumbnailUrl}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class __$$_MangaTrackingDataCopyWithImpl<$Res> | ||||
|     extends _$MangaTrackingDataCopyWithImpl<$Res> | ||||
|     implements _$$_MangaTrackingDataCopyWith<$Res> { | ||||
|   __$$_MangaTrackingDataCopyWithImpl( | ||||
|       _$_MangaTrackingData _value, $Res Function(_$_MangaTrackingData) _then) | ||||
|       : super(_value, (v) => _then(v as _$_MangaTrackingData)); | ||||
| 
 | ||||
|   @override | ||||
|   _$_MangaTrackingData get _value => super._value as _$_MangaTrackingData; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? id = freezed, | ||||
|     Object? state = freezed, | ||||
|     Object? title = freezed, | ||||
|     Object? chaptersRead = freezed, | ||||
|     Object? volumesOwned = freezed, | ||||
|     Object? chaptersTotal = freezed, | ||||
|     Object? thumbnailUrl = freezed, | ||||
|   }) { | ||||
|     return _then(_$_MangaTrackingData( | ||||
|       id == freezed | ||||
|           ? _value.id | ||||
|           : id // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       state == freezed | ||||
|           ? _value.state | ||||
|           : state // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|       title == freezed | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       chaptersRead == freezed | ||||
|           ? _value.chaptersRead | ||||
|           : chaptersRead // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       volumesOwned == freezed | ||||
|           ? _value.volumesOwned | ||||
|           : volumesOwned // ignore: cast_nullable_to_non_nullable | ||||
|               as int, | ||||
|       chaptersTotal == freezed | ||||
|           ? _value.chaptersTotal | ||||
|           : chaptersTotal // ignore: cast_nullable_to_non_nullable | ||||
|               as int?, | ||||
|       thumbnailUrl == freezed | ||||
|           ? _value.thumbnailUrl | ||||
|           : thumbnailUrl // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_MangaTrackingData implements _MangaTrackingData { | ||||
|   _$_MangaTrackingData( | ||||
|       this.id, | ||||
|       @MangaTrackingStateConverter() this.state, | ||||
|       this.title, | ||||
|       this.chaptersRead, | ||||
|       this.volumesOwned, | ||||
|       this.chaptersTotal, | ||||
|       this.thumbnailUrl); | ||||
| 
 | ||||
|   factory _$_MangaTrackingData.fromJson(Map<String, dynamic> json) => | ||||
|       _$$_MangaTrackingDataFromJson(json); | ||||
| 
 | ||||
|   /// The ID of the manga | ||||
|   @override | ||||
|   final String id; | ||||
| 
 | ||||
|   /// The state of the manga | ||||
|   @override | ||||
|   @MangaTrackingStateConverter() | ||||
|   final MangaTrackingState state; | ||||
| 
 | ||||
|   /// The title of the manga | ||||
|   @override | ||||
|   final String title; | ||||
| 
 | ||||
|   /// Chapters read. | ||||
|   @override | ||||
|   final int chaptersRead; | ||||
| 
 | ||||
|   /// Chapters read. | ||||
|   @override | ||||
|   final int volumesOwned; | ||||
| 
 | ||||
|   /// Episodes watched. | ||||
|   @override | ||||
|   final int? chaptersTotal; | ||||
| 
 | ||||
|   /// URL to the thumbnail/cover art for the manga. | ||||
|   @override | ||||
|   final String thumbnailUrl; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(dynamic other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_MangaTrackingData && | ||||
|             const DeepCollectionEquality().equals(other.id, id) && | ||||
|             const DeepCollectionEquality().equals(other.state, state) && | ||||
|             const DeepCollectionEquality().equals(other.title, title) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.chaptersRead, chaptersRead) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.volumesOwned, volumesOwned) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.chaptersTotal, chaptersTotal) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.thumbnailUrl, thumbnailUrl)); | ||||
|   } | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(id), | ||||
|       const DeepCollectionEquality().hash(state), | ||||
|       const DeepCollectionEquality().hash(title), | ||||
|       const DeepCollectionEquality().hash(chaptersRead), | ||||
|       const DeepCollectionEquality().hash(volumesOwned), | ||||
|       const DeepCollectionEquality().hash(chaptersTotal), | ||||
|       const DeepCollectionEquality().hash(thumbnailUrl)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   _$$_MangaTrackingDataCopyWith<_$_MangaTrackingData> get copyWith => | ||||
|       __$$_MangaTrackingDataCopyWithImpl<_$_MangaTrackingData>( | ||||
|           this, _$identity); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$_MangaTrackingDataToJson( | ||||
|       this, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| abstract class _MangaTrackingData implements MangaTrackingData { | ||||
|   factory _MangaTrackingData( | ||||
|       final String id, | ||||
|       @MangaTrackingStateConverter() final MangaTrackingState state, | ||||
|       final String title, | ||||
|       final int chaptersRead, | ||||
|       final int volumesOwned, | ||||
|       final int? chaptersTotal, | ||||
|       final String thumbnailUrl) = _$_MangaTrackingData; | ||||
| 
 | ||||
|   factory _MangaTrackingData.fromJson(Map<String, dynamic> json) = | ||||
|       _$_MangaTrackingData.fromJson; | ||||
| 
 | ||||
|   @override | ||||
| 
 | ||||
|   /// The ID of the manga | ||||
|   String get id; | ||||
|   @override | ||||
| 
 | ||||
|   /// The state of the manga | ||||
|   @MangaTrackingStateConverter() | ||||
|   MangaTrackingState get state; | ||||
|   @override | ||||
| 
 | ||||
|   /// The title of the manga | ||||
|   String get title; | ||||
|   @override | ||||
| 
 | ||||
|   /// Chapters read. | ||||
|   int get chaptersRead; | ||||
|   @override | ||||
| 
 | ||||
|   /// Chapters read. | ||||
|   int get volumesOwned; | ||||
|   @override | ||||
| 
 | ||||
|   /// Episodes watched. | ||||
|   int? get chaptersTotal; | ||||
|   @override | ||||
| 
 | ||||
|   /// URL to the thumbnail/cover art for the manga. | ||||
|   String get thumbnailUrl; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_MangaTrackingDataCopyWith<_$_MangaTrackingData> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
							
								
								
									
										30
									
								
								lib/src/data/manga.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/src/data/manga.g.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| 
 | ||||
| part of 'manga.dart'; | ||||
| 
 | ||||
| // ************************************************************************** | ||||
| // JsonSerializableGenerator | ||||
| // ************************************************************************** | ||||
| 
 | ||||
| _$_MangaTrackingData _$$_MangaTrackingDataFromJson(Map<String, dynamic> json) => | ||||
|     _$_MangaTrackingData( | ||||
|       json['id'] as String, | ||||
|       const MangaTrackingStateConverter().fromJson(json['state'] as int), | ||||
|       json['title'] as String, | ||||
|       json['chaptersRead'] as int, | ||||
|       json['volumesOwned'] as int, | ||||
|       json['chaptersTotal'] as int?, | ||||
|       json['thumbnailUrl'] as String, | ||||
|     ); | ||||
| 
 | ||||
| Map<String, dynamic> _$$_MangaTrackingDataToJson( | ||||
|         _$_MangaTrackingData instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'state': const MangaTrackingStateConverter().toJson(instance.state), | ||||
|       'title': instance.title, | ||||
|       'chaptersRead': instance.chaptersRead, | ||||
|       'volumesOwned': instance.volumesOwned, | ||||
|       'chaptersTotal': instance.chaptersTotal, | ||||
|       'thumbnailUrl': instance.thumbnailUrl, | ||||
|     }; | ||||
| @ -1,8 +1,8 @@ | ||||
| class AnimeSearchResult { | ||||
|   const AnimeSearchResult( | ||||
| class SearchResult { | ||||
|   const SearchResult( | ||||
|     this.title, | ||||
|     this.id, | ||||
|     this.episodesTotal, | ||||
|     this.total, | ||||
|     this.thumbnailUrl, | ||||
|     this.description, | ||||
|   ); | ||||
| @ -16,9 +16,9 @@ class AnimeSearchResult { | ||||
|   /// The URL to a thumbnail image. | ||||
|   final String thumbnailUrl; | ||||
| 
 | ||||
|   /// The amount of total episodes. If null, it means that there is not total amount | ||||
|   /// of episodes set. | ||||
|   final int? episodesTotal; | ||||
|   /// The amount of total episodes or chapters. If null, it means that there is not | ||||
|   /// total amount of episodes set. | ||||
|   final int? total; | ||||
| 
 | ||||
|   /// The description of the anime | ||||
|   final String description; | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/data/manga.dart'; | ||||
| import 'package:sqflite/sqflite.dart'; | ||||
| 
 | ||||
| const animeTable = 'Anime'; | ||||
| const mangaTable = 'Manga'; | ||||
| 
 | ||||
| Future<void> _createDatabase(Database db, int version) async { | ||||
|   await db.execute( | ||||
| @ -15,6 +17,18 @@ Future<void> _createDatabase(Database db, int version) async { | ||||
|       title TEXT NOT NULL | ||||
|     )''', | ||||
|   ); | ||||
|   await db.execute( | ||||
|     ''' | ||||
|     CREATE TABLE $mangaTable( | ||||
|       id TEXT NOT NULL PRIMARY KEY, | ||||
|       state INTEGER NOT NULL, | ||||
|       chaptersTotal INTEGER, | ||||
|       chaptersRead INTEGER NOT NULL, | ||||
|       volumesOwned INTEGER NOT NULL, | ||||
|       thumbnailUrl TEXT NOT NULL, | ||||
|       title TEXT NOT NULL | ||||
|     )''', | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| class DatabaseService { | ||||
| @ -37,6 +51,15 @@ class DatabaseService { | ||||
|       .toList(); | ||||
|   } | ||||
| 
 | ||||
|   Future<List<MangaTrackingData>> loadMangas() async { | ||||
|     final mangas = await _db.query(mangaTable); | ||||
| 
 | ||||
|     return mangas | ||||
|       .cast<Map<String, dynamic>>() | ||||
|       .map((Map<String, dynamic> manga) => MangaTrackingData.fromJson(manga)) | ||||
|       .toList(); | ||||
|   } | ||||
|    | ||||
|   Future<void> addAnime(AnimeTrackingData data) async { | ||||
|     await _db.insert( | ||||
|       animeTable, | ||||
| @ -52,4 +75,20 @@ class DatabaseService { | ||||
|       whereArgs: [data.id], | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> addManga(MangaTrackingData data) async { | ||||
|     await _db.insert( | ||||
|       mangaTable, | ||||
|       data.toJson(), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> updateManga(MangaTrackingData data) async { | ||||
|     await _db.update( | ||||
|       mangaTable, | ||||
|       data.toJson(), | ||||
|       where: 'id = ?', | ||||
|       whereArgs: [data.id], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import 'dart:math'; | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/data/manga.dart'; | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| import 'package:anitrack/src/service/database.dart'; | ||||
| import 'package:bloc/bloc.dart'; | ||||
| @ -13,11 +14,15 @@ part 'anime_list_bloc.freezed.dart'; | ||||
| class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> { | ||||
|   AnimeListBloc() : super(AnimeListState()) { | ||||
|     on<AnimeAddedEvent>(_onAnimeAdded); | ||||
|     on<AnimeEpisodeIncrementedEvent>(_onIncremented); | ||||
|     on<AnimeEpisodeDecrementedEvent>(_onDecremented); | ||||
|     on<MangaAddedEvent>(_onMangaAdded); | ||||
|     on<AnimeEpisodeIncrementedEvent>(_onAnimeIncremented); | ||||
|     on<AnimeEpisodeDecrementedEvent>(_onAnimeDecremented); | ||||
|     on<AnimesLoadedEvent>(_onAnimesLoaded); | ||||
|     on<AnimeFilterChangedEvent>(_onAnimesFiltered); | ||||
|     on<AnimeTrackingTypeChanged>(_onTrackingTypeChanged); | ||||
|     on<MangaFilterChangedEvent>(_onMangasFiltered); | ||||
|     on<MangaChapterIncrementedEvent>(_onMangaIncremented); | ||||
|     on<MangaChapterDecrementedEvent>(_onMangaDecremented); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async { | ||||
| @ -34,7 +39,21 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onIncremented(AnimeEpisodeIncrementedEvent event, Emitter<AnimeListState> emit) async { | ||||
|   Future<void> _onMangaAdded(MangaAddedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     // Add the manga to the database | ||||
|     await GetIt.I.get<DatabaseService>().addManga(event.data); | ||||
| 
 | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         mangas: List.from([ | ||||
|           ...state.mangas, | ||||
|           event.data, | ||||
|         ]), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|    | ||||
|   Future<void> _onAnimeIncremented(AnimeEpisodeIncrementedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     final index = state.animes.indexWhere((item) => item.id == event.id); | ||||
|     if (index == -1) return; | ||||
| 
 | ||||
| @ -56,7 +75,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> { | ||||
|     await GetIt.I.get<DatabaseService>().updateAnime(newAnime); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onDecremented(AnimeEpisodeDecrementedEvent event, Emitter<AnimeListState> emit) async { | ||||
|   Future<void> _onAnimeDecremented(AnimeEpisodeDecrementedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     final index = state.animes.indexWhere((item) => item.id == event.id); | ||||
|     if (index == -1) return; | ||||
| 
 | ||||
| @ -82,6 +101,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> { | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         animes: await GetIt.I.get<DatabaseService>().loadAnimes(), | ||||
|         mangas: await GetIt.I.get<DatabaseService>().loadMangas(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| @ -89,7 +109,15 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> { | ||||
|   Future<void> _onAnimesFiltered(AnimeFilterChangedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         filterState: event.filterState, | ||||
|         animeFilterState: event.filterState, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onMangasFiltered(MangaFilterChangedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         mangaFilterState: event.filterState, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| @ -101,4 +129,48 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> { | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onMangaIncremented(MangaChapterIncrementedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     final index = state.mangas.indexWhere((item) => item.id == event.id); | ||||
|     if (index == -1) return; | ||||
| 
 | ||||
|     final manga = state.mangas[index]; | ||||
|     if (manga.chaptersTotal != null && manga.chaptersRead + 1 > manga.chaptersTotal!) return; | ||||
| 
 | ||||
|     final newList = List<MangaTrackingData>.from(state.mangas); | ||||
|     final newManga = manga.copyWith( | ||||
|       chaptersRead: manga.chaptersRead + 1, | ||||
|     ); | ||||
|     newList[index] = newManga; | ||||
| 
 | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         mangas: newList, | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     await GetIt.I.get<DatabaseService>().updateManga(newManga); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onMangaDecremented(MangaChapterDecrementedEvent event, Emitter<AnimeListState> emit) async { | ||||
|     final index = state.mangas.indexWhere((item) => item.id == event.id); | ||||
|     if (index == -1) return; | ||||
| 
 | ||||
|     final manga = state.mangas[index]; | ||||
|     if (manga.chaptersRead - 1 < 0) return; | ||||
| 
 | ||||
|     final newList = List<MangaTrackingData>.from(state.mangas); | ||||
|     final newManga = manga.copyWith( | ||||
|       chaptersRead: manga.chaptersRead - 1, | ||||
|     ); | ||||
|     newList[index] = newManga; | ||||
| 
 | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         mangas: newList, | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     await GetIt.I.get<DatabaseService>().updateManga(newManga); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -17,7 +17,9 @@ final _privateConstructorUsedError = UnsupportedError( | ||||
| /// @nodoc | ||||
| mixin _$AnimeListState { | ||||
|   List<AnimeTrackingData> get animes => throw _privateConstructorUsedError; | ||||
|   AnimeTrackingState get filterState => throw _privateConstructorUsedError; | ||||
|   List<MangaTrackingData> get mangas => throw _privateConstructorUsedError; | ||||
|   AnimeTrackingState get animeFilterState => throw _privateConstructorUsedError; | ||||
|   MangaTrackingState get mangaFilterState => throw _privateConstructorUsedError; | ||||
|   TrackingMediumType get trackingType => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
| @ -32,7 +34,9 @@ abstract class $AnimeListStateCopyWith<$Res> { | ||||
|       _$AnimeListStateCopyWithImpl<$Res>; | ||||
|   $Res call( | ||||
|       {List<AnimeTrackingData> animes, | ||||
|       AnimeTrackingState filterState, | ||||
|       List<MangaTrackingData> mangas, | ||||
|       AnimeTrackingState animeFilterState, | ||||
|       MangaTrackingState mangaFilterState, | ||||
|       TrackingMediumType trackingType}); | ||||
| } | ||||
| 
 | ||||
| @ -48,7 +52,9 @@ class _$AnimeListStateCopyWithImpl<$Res> | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? animes = freezed, | ||||
|     Object? filterState = freezed, | ||||
|     Object? mangas = freezed, | ||||
|     Object? animeFilterState = freezed, | ||||
|     Object? mangaFilterState = freezed, | ||||
|     Object? trackingType = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
| @ -56,10 +62,18 @@ class _$AnimeListStateCopyWithImpl<$Res> | ||||
|           ? _value.animes | ||||
|           : animes // ignore: cast_nullable_to_non_nullable | ||||
|               as List<AnimeTrackingData>, | ||||
|       filterState: filterState == freezed | ||||
|           ? _value.filterState | ||||
|           : filterState // ignore: cast_nullable_to_non_nullable | ||||
|       mangas: mangas == freezed | ||||
|           ? _value.mangas | ||||
|           : mangas // ignore: cast_nullable_to_non_nullable | ||||
|               as List<MangaTrackingData>, | ||||
|       animeFilterState: animeFilterState == freezed | ||||
|           ? _value.animeFilterState | ||||
|           : animeFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as AnimeTrackingState, | ||||
|       mangaFilterState: mangaFilterState == freezed | ||||
|           ? _value.mangaFilterState | ||||
|           : mangaFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|       trackingType: trackingType == freezed | ||||
|           ? _value.trackingType | ||||
|           : trackingType // ignore: cast_nullable_to_non_nullable | ||||
| @ -77,7 +91,9 @@ abstract class _$$_AnimeListStateCopyWith<$Res> | ||||
|   @override | ||||
|   $Res call( | ||||
|       {List<AnimeTrackingData> animes, | ||||
|       AnimeTrackingState filterState, | ||||
|       List<MangaTrackingData> mangas, | ||||
|       AnimeTrackingState animeFilterState, | ||||
|       MangaTrackingState mangaFilterState, | ||||
|       TrackingMediumType trackingType}); | ||||
| } | ||||
| 
 | ||||
| @ -95,7 +111,9 @@ class __$$_AnimeListStateCopyWithImpl<$Res> | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? animes = freezed, | ||||
|     Object? filterState = freezed, | ||||
|     Object? mangas = freezed, | ||||
|     Object? animeFilterState = freezed, | ||||
|     Object? mangaFilterState = freezed, | ||||
|     Object? trackingType = freezed, | ||||
|   }) { | ||||
|     return _then(_$_AnimeListState( | ||||
| @ -103,10 +121,18 @@ class __$$_AnimeListStateCopyWithImpl<$Res> | ||||
|           ? _value._animes | ||||
|           : animes // ignore: cast_nullable_to_non_nullable | ||||
|               as List<AnimeTrackingData>, | ||||
|       filterState: filterState == freezed | ||||
|           ? _value.filterState | ||||
|           : filterState // ignore: cast_nullable_to_non_nullable | ||||
|       mangas: mangas == freezed | ||||
|           ? _value._mangas | ||||
|           : mangas // ignore: cast_nullable_to_non_nullable | ||||
|               as List<MangaTrackingData>, | ||||
|       animeFilterState: animeFilterState == freezed | ||||
|           ? _value.animeFilterState | ||||
|           : animeFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as AnimeTrackingState, | ||||
|       mangaFilterState: mangaFilterState == freezed | ||||
|           ? _value.mangaFilterState | ||||
|           : mangaFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|       trackingType: trackingType == freezed | ||||
|           ? _value.trackingType | ||||
|           : trackingType // ignore: cast_nullable_to_non_nullable | ||||
| @ -120,9 +146,12 @@ class __$$_AnimeListStateCopyWithImpl<$Res> | ||||
| class _$_AnimeListState implements _AnimeListState { | ||||
|   _$_AnimeListState( | ||||
|       {final List<AnimeTrackingData> animes = const [], | ||||
|       this.filterState = AnimeTrackingState.watching, | ||||
|       final List<MangaTrackingData> mangas = const [], | ||||
|       this.animeFilterState = AnimeTrackingState.watching, | ||||
|       this.mangaFilterState = MangaTrackingState.reading, | ||||
|       this.trackingType = TrackingMediumType.anime}) | ||||
|       : _animes = animes; | ||||
|       : _animes = animes, | ||||
|         _mangas = mangas; | ||||
| 
 | ||||
|   final List<AnimeTrackingData> _animes; | ||||
|   @override | ||||
| @ -132,16 +161,27 @@ class _$_AnimeListState implements _AnimeListState { | ||||
|     return EqualUnmodifiableListView(_animes); | ||||
|   } | ||||
| 
 | ||||
|   final List<MangaTrackingData> _mangas; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final AnimeTrackingState filterState; | ||||
|   List<MangaTrackingData> get mangas { | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(_mangas); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final AnimeTrackingState animeFilterState; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final MangaTrackingState mangaFilterState; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final TrackingMediumType trackingType; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'AnimeListState(animes: $animes, filterState: $filterState, trackingType: $trackingType)'; | ||||
|     return 'AnimeListState(animes: $animes, mangas: $mangas, animeFilterState: $animeFilterState, mangaFilterState: $mangaFilterState, trackingType: $trackingType)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
| @ -150,8 +190,11 @@ class _$_AnimeListState implements _AnimeListState { | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_AnimeListState && | ||||
|             const DeepCollectionEquality().equals(other._animes, _animes) && | ||||
|             const DeepCollectionEquality().equals(other._mangas, _mangas) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.filterState, filterState) && | ||||
|                 .equals(other.animeFilterState, animeFilterState) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.mangaFilterState, mangaFilterState) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.trackingType, trackingType)); | ||||
|   } | ||||
| @ -160,7 +203,9 @@ class _$_AnimeListState implements _AnimeListState { | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(_animes), | ||||
|       const DeepCollectionEquality().hash(filterState), | ||||
|       const DeepCollectionEquality().hash(_mangas), | ||||
|       const DeepCollectionEquality().hash(animeFilterState), | ||||
|       const DeepCollectionEquality().hash(mangaFilterState), | ||||
|       const DeepCollectionEquality().hash(trackingType)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
| @ -172,13 +217,19 @@ class _$_AnimeListState implements _AnimeListState { | ||||
| abstract class _AnimeListState implements AnimeListState { | ||||
|   factory _AnimeListState( | ||||
|       {final List<AnimeTrackingData> animes, | ||||
|       final AnimeTrackingState filterState, | ||||
|       final List<MangaTrackingData> mangas, | ||||
|       final AnimeTrackingState animeFilterState, | ||||
|       final MangaTrackingState mangaFilterState, | ||||
|       final TrackingMediumType trackingType}) = _$_AnimeListState; | ||||
| 
 | ||||
|   @override | ||||
|   List<AnimeTrackingData> get animes; | ||||
|   @override | ||||
|   AnimeTrackingState get filterState; | ||||
|   List<MangaTrackingData> get mangas; | ||||
|   @override | ||||
|   AnimeTrackingState get animeFilterState; | ||||
|   @override | ||||
|   MangaTrackingState get mangaFilterState; | ||||
|   @override | ||||
|   TrackingMediumType get trackingType; | ||||
|   @override | ||||
|  | ||||
| @ -26,7 +26,7 @@ class AnimeAddedEvent extends AnimeListEvent { | ||||
| /// Triggered when animes are to be loaded from the database | ||||
| class AnimesLoadedEvent extends AnimeListEvent {} | ||||
| 
 | ||||
| /// Triggered when the filter is changed | ||||
| /// Triggered when the anime filter is changed | ||||
| class AnimeFilterChangedEvent extends AnimeListEvent { | ||||
|   AnimeFilterChangedEvent(this.filterState); | ||||
| 
 | ||||
| @ -41,3 +41,32 @@ class AnimeTrackingTypeChanged extends AnimeListEvent { | ||||
|   /// The type we switched to | ||||
|   final TrackingMediumType type; | ||||
| } | ||||
| 
 | ||||
| class MangaAddedEvent extends AnimeListEvent { | ||||
|   MangaAddedEvent(this.data); | ||||
|    | ||||
|   /// The manga to add. | ||||
|   final MangaTrackingData data; | ||||
| } | ||||
| 
 | ||||
| /// Triggered when the manga filter is changed | ||||
| class MangaFilterChangedEvent extends AnimeListEvent { | ||||
|   MangaFilterChangedEvent(this.filterState); | ||||
| 
 | ||||
|   /// The state to filter | ||||
|   final MangaTrackingState filterState; | ||||
| } | ||||
| 
 | ||||
| class MangaChapterIncrementedEvent extends AnimeListEvent { | ||||
|   MangaChapterIncrementedEvent(this.id); | ||||
| 
 | ||||
|   /// The ID of the anime | ||||
|   final String id; | ||||
| } | ||||
| 
 | ||||
| class MangaChapterDecrementedEvent extends AnimeListEvent { | ||||
|   MangaChapterDecrementedEvent(this.id); | ||||
| 
 | ||||
|   /// The ID of the anime | ||||
|   final String id; | ||||
| } | ||||
|  | ||||
| @ -4,7 +4,9 @@ part of 'anime_list_bloc.dart'; | ||||
| class AnimeListState with _$AnimeListState { | ||||
|   factory AnimeListState({ | ||||
|     @Default([]) List<AnimeTrackingData> animes, | ||||
|     @Default(AnimeTrackingState.watching) AnimeTrackingState filterState, | ||||
|     @Default([]) List<MangaTrackingData> mangas, | ||||
|     @Default(AnimeTrackingState.watching) AnimeTrackingState animeFilterState, | ||||
|     @Default(MangaTrackingState.reading) MangaTrackingState mangaFilterState, | ||||
|     @Default(TrackingMediumType.anime) TrackingMediumType trackingType, | ||||
|   }) = _AnimeListState; | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/data/manga.dart'; | ||||
| import 'package:anitrack/src/data/search_result.dart'; | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| import 'package:anitrack/src/ui/constants.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart' as list; | ||||
| import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; | ||||
| @ -17,7 +19,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|     on<AnimeSearchRequestedEvent>(_onRequested); | ||||
|     on<SearchQueryChangedEvent>(_onQueryChanged); | ||||
|     on<SearchQuerySubmittedEvent>(_onQuerySubmitted); | ||||
|     on<AnimeAddedEvent>(_onAnimeAdded); | ||||
|     on<ResultTappedEvent>(_onResultTapped); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onRequested(AnimeSearchRequestedEvent event, Emitter<AnimeSearchState> emit) async { | ||||
| @ -26,6 +28,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|         searchQuery: '', | ||||
|         working: false, | ||||
|         searchResults: [], | ||||
|         trackingType: event.type, | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
| @ -53,6 +56,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     if (state.trackingType == TrackingMediumType.anime) { | ||||
|       // Anime | ||||
|       final result = await Jikan().searchAnime( | ||||
|         query: state.searchQuery, | ||||
|       ); | ||||
| @ -60,12 +65,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|       emit( | ||||
|         state.copyWith( | ||||
|           working: false, | ||||
|       ), | ||||
|     ); | ||||
|      | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         searchResults: result.map((Anime anime) => AnimeSearchResult( | ||||
|           searchResults: result.map((Anime anime) => SearchResult( | ||||
|             anime.title, | ||||
|             anime.malId.toString(), | ||||
|             anime.episodes, | ||||
| @ -74,20 +74,51 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|           ),).toList(), | ||||
|         ), | ||||
|       ); | ||||
|     } else { | ||||
|       // Manga | ||||
|       final result = await Jikan().searchManga( | ||||
|         query: state.searchQuery, | ||||
|       ); | ||||
| 
 | ||||
|       emit( | ||||
|         state.copyWith( | ||||
|           working: false, | ||||
|           searchResults: result.map((Manga manga) => SearchResult( | ||||
|             manga.title, | ||||
|             manga.malId.toString(), | ||||
|             manga.chapters, | ||||
|             manga.imageUrl, | ||||
|             manga.synopsis ?? '', | ||||
|           ),).toList(), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeSearchState> emit) async { | ||||
|   Future<void> _onResultTapped(ResultTappedEvent event, Emitter<AnimeSearchState> emit) async { | ||||
|     GetIt.I.get<list.AnimeListBloc>().add( | ||||
|       state.trackingType == TrackingMediumType.anime ? | ||||
|         list.AnimeAddedEvent( | ||||
|           AnimeTrackingData( | ||||
|             event.result.id, | ||||
|             AnimeTrackingState.watching, | ||||
|             event.result.title, | ||||
|             0, | ||||
|           event.result.episodesTotal, | ||||
|             event.result.total, | ||||
|             event.result.thumbnailUrl, | ||||
|           ), | ||||
|         ) : | ||||
|         list.MangaAddedEvent( | ||||
|           MangaTrackingData( | ||||
|             event.result.id, | ||||
|             MangaTrackingState.reading, | ||||
|             event.result.title, | ||||
|             0, | ||||
|             0, | ||||
|             event.result.total, | ||||
|             event.result.thumbnailUrl, | ||||
|           ), | ||||
|         ) | ||||
|     ); | ||||
| 
 | ||||
|     GetIt.I.get<NavigationBloc>().add( | ||||
|  | ||||
| @ -16,10 +16,10 @@ final _privateConstructorUsedError = UnsupportedError( | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$AnimeSearchState { | ||||
|   TrackingMediumType get trackingType => throw _privateConstructorUsedError; | ||||
|   String get searchQuery => throw _privateConstructorUsedError; | ||||
|   bool get working => throw _privateConstructorUsedError; | ||||
|   List<AnimeSearchResult> get searchResults => | ||||
|       throw _privateConstructorUsedError; | ||||
|   List<SearchResult> get searchResults => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   $AnimeSearchStateCopyWith<AnimeSearchState> get copyWith => | ||||
| @ -32,9 +32,10 @@ abstract class $AnimeSearchStateCopyWith<$Res> { | ||||
|           AnimeSearchState value, $Res Function(AnimeSearchState) then) = | ||||
|       _$AnimeSearchStateCopyWithImpl<$Res>; | ||||
|   $Res call( | ||||
|       {String searchQuery, | ||||
|       {TrackingMediumType trackingType, | ||||
|       String searchQuery, | ||||
|       bool working, | ||||
|       List<AnimeSearchResult> searchResults}); | ||||
|       List<SearchResult> searchResults}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @ -48,11 +49,16 @@ class _$AnimeSearchStateCopyWithImpl<$Res> | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? trackingType = freezed, | ||||
|     Object? searchQuery = freezed, | ||||
|     Object? working = freezed, | ||||
|     Object? searchResults = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       trackingType: trackingType == freezed | ||||
|           ? _value.trackingType | ||||
|           : trackingType // ignore: cast_nullable_to_non_nullable | ||||
|               as TrackingMediumType, | ||||
|       searchQuery: searchQuery == freezed | ||||
|           ? _value.searchQuery | ||||
|           : searchQuery // ignore: cast_nullable_to_non_nullable | ||||
| @ -64,7 +70,7 @@ class _$AnimeSearchStateCopyWithImpl<$Res> | ||||
|       searchResults: searchResults == freezed | ||||
|           ? _value.searchResults | ||||
|           : searchResults // ignore: cast_nullable_to_non_nullable | ||||
|               as List<AnimeSearchResult>, | ||||
|               as List<SearchResult>, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| @ -77,9 +83,10 @@ abstract class _$$_AnimeSearchStateCopyWith<$Res> | ||||
|       __$$_AnimeSearchStateCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call( | ||||
|       {String searchQuery, | ||||
|       {TrackingMediumType trackingType, | ||||
|       String searchQuery, | ||||
|       bool working, | ||||
|       List<AnimeSearchResult> searchResults}); | ||||
|       List<SearchResult> searchResults}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @ -95,11 +102,16 @@ class __$$_AnimeSearchStateCopyWithImpl<$Res> | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? trackingType = freezed, | ||||
|     Object? searchQuery = freezed, | ||||
|     Object? working = freezed, | ||||
|     Object? searchResults = freezed, | ||||
|   }) { | ||||
|     return _then(_$_AnimeSearchState( | ||||
|       trackingType: trackingType == freezed | ||||
|           ? _value.trackingType | ||||
|           : trackingType // ignore: cast_nullable_to_non_nullable | ||||
|               as TrackingMediumType, | ||||
|       searchQuery: searchQuery == freezed | ||||
|           ? _value.searchQuery | ||||
|           : searchQuery // ignore: cast_nullable_to_non_nullable | ||||
| @ -111,7 +123,7 @@ class __$$_AnimeSearchStateCopyWithImpl<$Res> | ||||
|       searchResults: searchResults == freezed | ||||
|           ? _value._searchResults | ||||
|           : searchResults // ignore: cast_nullable_to_non_nullable | ||||
|               as List<AnimeSearchResult>, | ||||
|               as List<SearchResult>, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| @ -120,28 +132,32 @@ class __$$_AnimeSearchStateCopyWithImpl<$Res> | ||||
| 
 | ||||
| class _$_AnimeSearchState implements _AnimeSearchState { | ||||
|   _$_AnimeSearchState( | ||||
|       {this.searchQuery = '', | ||||
|       {this.trackingType = TrackingMediumType.anime, | ||||
|       this.searchQuery = '', | ||||
|       this.working = false, | ||||
|       final List<AnimeSearchResult> searchResults = const []}) | ||||
|       final List<SearchResult> searchResults = const []}) | ||||
|       : _searchResults = searchResults; | ||||
| 
 | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final TrackingMediumType trackingType; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final String searchQuery; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final bool working; | ||||
|   final List<AnimeSearchResult> _searchResults; | ||||
|   final List<SearchResult> _searchResults; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   List<AnimeSearchResult> get searchResults { | ||||
|   List<SearchResult> get searchResults { | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(_searchResults); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'AnimeSearchState(searchQuery: $searchQuery, working: $working, searchResults: $searchResults)'; | ||||
|     return 'AnimeSearchState(trackingType: $trackingType, searchQuery: $searchQuery, working: $working, searchResults: $searchResults)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
| @ -149,6 +165,8 @@ class _$_AnimeSearchState implements _AnimeSearchState { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_AnimeSearchState && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.trackingType, trackingType) && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.searchQuery, searchQuery) && | ||||
|             const DeepCollectionEquality().equals(other.working, working) && | ||||
| @ -159,6 +177,7 @@ class _$_AnimeSearchState implements _AnimeSearchState { | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(trackingType), | ||||
|       const DeepCollectionEquality().hash(searchQuery), | ||||
|       const DeepCollectionEquality().hash(working), | ||||
|       const DeepCollectionEquality().hash(_searchResults)); | ||||
| @ -171,16 +190,19 @@ class _$_AnimeSearchState implements _AnimeSearchState { | ||||
| 
 | ||||
| abstract class _AnimeSearchState implements AnimeSearchState { | ||||
|   factory _AnimeSearchState( | ||||
|       {final String searchQuery, | ||||
|       {final TrackingMediumType trackingType, | ||||
|       final String searchQuery, | ||||
|       final bool working, | ||||
|       final List<AnimeSearchResult> searchResults}) = _$_AnimeSearchState; | ||||
|       final List<SearchResult> searchResults}) = _$_AnimeSearchState; | ||||
| 
 | ||||
|   @override | ||||
|   TrackingMediumType get trackingType; | ||||
|   @override | ||||
|   String get searchQuery; | ||||
|   @override | ||||
|   bool get working; | ||||
|   @override | ||||
|   List<AnimeSearchResult> get searchResults; | ||||
|   List<SearchResult> get searchResults; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_AnimeSearchStateCopyWith<_$_AnimeSearchState> get copyWith => | ||||
|  | ||||
| @ -2,7 +2,12 @@ part of 'anime_search_bloc.dart'; | ||||
| 
 | ||||
| abstract class AnimeSearchEvent {} | ||||
| 
 | ||||
| class AnimeSearchRequestedEvent extends AnimeSearchEvent {} | ||||
| class AnimeSearchRequestedEvent extends AnimeSearchEvent { | ||||
|   AnimeSearchRequestedEvent(this.type); | ||||
| 
 | ||||
|   /// The tracking type for which we want to search | ||||
|   TrackingMediumType type; | ||||
| } | ||||
| 
 | ||||
| /// Triggered when the search query is changed. | ||||
| class SearchQueryChangedEvent extends AnimeSearchEvent { | ||||
| @ -16,9 +21,9 @@ class SearchQueryChangedEvent extends AnimeSearchEvent { | ||||
| class SearchQuerySubmittedEvent extends AnimeSearchEvent {} | ||||
| 
 | ||||
| /// Triggered when an anime is added to the tracking list | ||||
| class AnimeAddedEvent extends AnimeSearchEvent { | ||||
|   AnimeAddedEvent(this.result); | ||||
| class ResultTappedEvent extends AnimeSearchEvent { | ||||
|   ResultTappedEvent(this.result); | ||||
| 
 | ||||
|   /// The search result to add. | ||||
|   final AnimeSearchResult result; | ||||
|   final SearchResult result; | ||||
| } | ||||
|  | ||||
| @ -3,8 +3,9 @@ part of 'anime_search_bloc.dart'; | ||||
| @freezed | ||||
| class AnimeSearchState with _$AnimeSearchState { | ||||
|   factory AnimeSearchState({ | ||||
|     @Default(TrackingMediumType.anime) TrackingMediumType trackingType, | ||||
|     @Default('') String searchQuery, | ||||
|     @Default(false) bool working, | ||||
|     @Default([]) List<AnimeSearchResult> searchResults, | ||||
|     @Default([]) List<SearchResult> searchResults, | ||||
|   }) = _AnimeSearchState; | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/data/manga.dart'; | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | ||||
| @ -25,21 +26,14 @@ class AnimeListPage extends StatelessWidget { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return BlocBuilder<AnimeListBloc, AnimeListState>( | ||||
|       builder: (context, state) { | ||||
|         return Scaffold( | ||||
|           appBar: AppBar( | ||||
|             title: Text( | ||||
|               _getPageTitle(state.trackingType) | ||||
|             ), | ||||
|             actions: [ | ||||
|               PopupMenuButton( | ||||
|   Widget _getPopupButton(BuildContext context, AnimeListState state) { | ||||
|     switch (state.trackingType) { | ||||
|       case TrackingMediumType.anime: | ||||
|         return PopupMenuButton( | ||||
|           icon: Icon( | ||||
|             Icons.filter_list, | ||||
|           ), | ||||
|                 initialValue: state.filterState, | ||||
|           initialValue: state.animeFilterState, | ||||
|           onSelected: (filterState) { | ||||
|             context.read<AnimeListBloc>().add( | ||||
|               AnimeFilterChangedEvent(filterState), | ||||
| @ -67,7 +61,55 @@ class AnimeListPage extends StatelessWidget { | ||||
|               child: Text('All'), | ||||
|             ), | ||||
|           ], | ||||
|         ); | ||||
|       case TrackingMediumType.manga: | ||||
|         return PopupMenuButton( | ||||
|           icon: Icon( | ||||
|             Icons.filter_list, | ||||
|           ), | ||||
|           initialValue: state.mangaFilterState, | ||||
|           onSelected: (filterState) { | ||||
|             context.read<AnimeListBloc>().add( | ||||
|               MangaFilterChangedEvent(filterState), | ||||
|             ); | ||||
|           }, | ||||
|           itemBuilder: (_) => [ | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.reading, | ||||
|               child: Text('Reading'), | ||||
|             ), | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.completed, | ||||
|               child: Text('Completed'), | ||||
|             ), | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.planToWatch, | ||||
|               child: Text('Plan to watch'), | ||||
|             ), | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.dropped, | ||||
|               child: Text('Dropped'), | ||||
|             ), | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.all, | ||||
|               child: Text('All'), | ||||
|             ), | ||||
|           ], | ||||
|         ); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return BlocBuilder<AnimeListBloc, AnimeListState>( | ||||
|       builder: (context, state) { | ||||
|         return Scaffold( | ||||
|           appBar: AppBar( | ||||
|             title: Text( | ||||
|               _getPageTitle(state.trackingType) | ||||
|             ), | ||||
|             actions: [ | ||||
|               _getPopupButton(context, state), | ||||
|             ], | ||||
|           ), | ||||
|           body: PageView( | ||||
| @ -77,8 +119,8 @@ class AnimeListPage extends StatelessWidget { | ||||
|                 itemCount: state.animes.length, | ||||
|                 itemBuilder: (context, index) { | ||||
|                   final anime = state.animes[index]; | ||||
|                   if (state.filterState != AnimeTrackingState.all) { | ||||
|                     if (anime.state != state.filterState) return Container(); | ||||
|                   if (state.animeFilterState != AnimeTrackingState.all) { | ||||
|                     if (anime.state != state.animeFilterState) return Container(); | ||||
|                   } | ||||
| 
 | ||||
|                   return ListItem( | ||||
| @ -103,13 +145,42 @@ class AnimeListPage extends StatelessWidget { | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|               Placeholder(), | ||||
|               ListView.builder( | ||||
|                 itemCount: state.mangas.length, | ||||
|                 itemBuilder: (context, index) { | ||||
|                   final manga = state.mangas[index]; | ||||
|                   if (state.mangaFilterState != MangaTrackingState.all) { | ||||
|                     if (manga.state != state.mangaFilterState) return Container(); | ||||
|                   } | ||||
| 
 | ||||
|                   return ListItem( | ||||
|                     title: manga.title, | ||||
|                     thumbnailUrl: manga.thumbnailUrl, | ||||
|                     extra: [ | ||||
|                       Text( | ||||
|                         '${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}', | ||||
|                         style: Theme.of(context).textTheme.titleMedium, | ||||
|                       ), | ||||
|                     ], | ||||
|                     onLeftSwipe: () { | ||||
|                       context.read<AnimeListBloc>().add( | ||||
|                         MangaChapterDecrementedEvent(state.mangas[index].id), | ||||
|                       ); | ||||
|                     }, | ||||
|                     onRightSwipe: () { | ||||
|                       context.read<AnimeListBloc>().add( | ||||
|                         MangaChapterIncrementedEvent(state.mangas[index].id), | ||||
|                       ); | ||||
|                     }, | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           floatingActionButton: FloatingActionButton( | ||||
|             onPressed: () { | ||||
|               context.read<AnimeSearchBloc>().add( | ||||
|                 AnimeSearchRequestedEvent(), | ||||
|                 AnimeSearchRequestedEvent(state.trackingType), | ||||
|               ); | ||||
|             }, | ||||
|             tooltip: 'Increment', | ||||
| @ -120,7 +191,6 @@ class AnimeListPage extends StatelessWidget { | ||||
|               0 : | ||||
|               1, | ||||
|             onTap: (int index) { | ||||
|               _controller.jumpToPage(index); | ||||
|               context.read<AnimeListBloc>().add( | ||||
|                 AnimeTrackingTypeChanged( | ||||
|                   index == 0 ? | ||||
| @ -128,6 +198,8 @@ class AnimeListPage extends StatelessWidget { | ||||
|                     TrackingMediumType.manga, | ||||
|                 ), | ||||
|               ); | ||||
| 
 | ||||
|               _controller.jumpToPage(index); | ||||
|             }, | ||||
|             items: <BottomBarItem>[ | ||||
|               BottomBarItem( | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/constants.dart'; | ||||
| import 'package:anitrack/src/ui/widgets/list_item.dart'; | ||||
| @ -20,7 +21,11 @@ class AnimeSearchPage extends StatelessWidget { | ||||
|       builder: (context, state) { | ||||
|         return Scaffold( | ||||
|           appBar: AppBar( | ||||
|             title: Text('Anime Search'), | ||||
|             title: Text( | ||||
|               state.trackingType == TrackingMediumType.anime ? | ||||
|                 'Anime Search' : | ||||
|                 'Manga Search', | ||||
|             ), | ||||
|           ), | ||||
|           body: Column( | ||||
|             children: [ | ||||
| @ -60,7 +65,7 @@ class AnimeSearchPage extends StatelessWidget { | ||||
|                       return InkWell( | ||||
|                         onTap: () { | ||||
|                           context.read<AnimeSearchBloc>().add( | ||||
|                             AnimeAddedEvent(item), | ||||
|                             ResultTappedEvent(item), | ||||
|                           ); | ||||
|                         }, | ||||
|                         child: ListItem( | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user