feat(meta): Cleanup
This commit is contained in:
		
							parent
							
								
									8b17f68eed
								
							
						
					
					
						commit
						8712dbb9de
					
				| @ -1,29 +1,14 @@ | ||||
| # This file configures the analyzer, which statically analyzes Dart code to | ||||
| # check for errors, warnings, and lints. | ||||
| # | ||||
| # The issues identified by the analyzer are surfaced in the UI of Dart-enabled | ||||
| # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be | ||||
| # invoked from the command line by running `flutter analyze`. | ||||
| 
 | ||||
| # The following line activates a set of recommended lints for Flutter apps, | ||||
| # packages, and plugins designed to encourage good coding practices. | ||||
| include: package:flutter_lints/flutter.yaml | ||||
| 
 | ||||
| include: package:very_good_analysis/analysis_options.yaml | ||||
| linter: | ||||
|   # The lint rules applied to this project can be customized in the | ||||
|   # section below to disable rules from the `package:flutter_lints/flutter.yaml` | ||||
|   # included above or to enable additional rules. A list of all available lints | ||||
|   # and their documentation is published at | ||||
|   # https://dart-lang.github.io/linter/lints/index.html. | ||||
|   # | ||||
|   # Instead of disabling a lint rule for the entire project in the | ||||
|   # section below, it can also be suppressed for a single line of code | ||||
|   # or a specific dart file by using the `// ignore: name_of_lint` and | ||||
|   # `// ignore_for_file: name_of_lint` syntax on the line or in the file | ||||
|   # producing the lint. | ||||
|   rules: | ||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||
|     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||
|     public_member_api_docs: false | ||||
|     lines_longer_than_80_chars: false | ||||
|     use_setters_to_change_properties: false | ||||
|     avoid_positional_boolean_parameters: false | ||||
|     avoid_bool_literals_in_conditional_expressions: false | ||||
| 
 | ||||
| # Additional information about this file can be found at | ||||
| # https://dart.dev/guides/language/analysis-options | ||||
| analyzer: | ||||
|   exclude: | ||||
|     - "**/*.g.dart" | ||||
|     - "**/*.freezed.dart" | ||||
|     - "test/" | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'package:anitrack/src/service/database.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/details_bloc.dart'; | ||||
| @ -6,7 +7,6 @@ import 'package:anitrack/src/ui/constants.dart'; | ||||
| import 'package:anitrack/src/ui/pages/anime_list.dart'; | ||||
| import 'package:anitrack/src/ui/pages/anime_search.dart'; | ||||
| import 'package:anitrack/src/ui/pages/details.dart'; | ||||
| import 'package:anitrack/src/service/database.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| @ -66,7 +66,6 @@ class MyApp extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     return MaterialApp( | ||||
|       title: 'AniTrack', | ||||
|       themeMode: ThemeMode.system, | ||||
|       theme: ThemeData( | ||||
|         brightness: Brightness.light, | ||||
|         primarySwatch: Colors.blue, | ||||
|  | ||||
| @ -1,62 +1,9 @@ | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| 
 | ||||
| part 'anime.freezed.dart'; | ||||
| part 'anime.g.dart'; | ||||
| 
 | ||||
| /// The watch state of an anime | ||||
| enum AnimeTrackingState { | ||||
|   watching,    // 0 | ||||
|   completed,   // 1 | ||||
|   planToWatch, // 2 | ||||
|   dropped,     // 3 | ||||
|   /// This is a pseudo state, i.e. it should never be set | ||||
|   all,         // -1 | ||||
| } | ||||
| 
 | ||||
| extension AnimeTrackStateExtension on AnimeTrackingState { | ||||
|   int toInteger() { | ||||
|     assert(this != AnimeTrackingState.all, 'AnimeTrackingState.all must not be serialized'); | ||||
|     switch (this) { | ||||
|       case AnimeTrackingState.watching: return 0; | ||||
|       case AnimeTrackingState.completed: return 1; | ||||
|       case AnimeTrackingState.planToWatch: return 2; | ||||
|       case AnimeTrackingState.dropped: return 3; | ||||
|       case AnimeTrackingState.all: return -1; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   String toNameString() { | ||||
|     assert(this != AnimeTrackingState.all, 'AnimeTrackingState.all must not be stringified'); | ||||
| 
 | ||||
|     switch (this) { | ||||
|       case AnimeTrackingState.watching: return 'Watching'; | ||||
|       case AnimeTrackingState.completed: return 'Completed'; | ||||
|       case AnimeTrackingState.planToWatch: return 'Plan to watch'; | ||||
|       case AnimeTrackingState.dropped: return 'Dropped'; | ||||
|       case AnimeTrackingState.all: return 'All'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class AnimeTrackingStateConverter implements JsonConverter<AnimeTrackingState, int> { | ||||
|   const AnimeTrackingStateConverter(); | ||||
| 
 | ||||
|   @override | ||||
|   AnimeTrackingState fromJson(int json) { | ||||
|     switch (json) { | ||||
|       case 0: return AnimeTrackingState.watching; | ||||
|       case 1: return AnimeTrackingState.completed; | ||||
|       case 2: return AnimeTrackingState.planToWatch; | ||||
|       case 3: return AnimeTrackingState.dropped; | ||||
|     } | ||||
| 
 | ||||
|     return AnimeTrackingState.planToWatch; | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   int toJson(AnimeTrackingState state) => state.toInteger(); | ||||
| } | ||||
| 
 | ||||
| /// Data about a tracked anime | ||||
| @freezed | ||||
| class AnimeTrackingData with _$AnimeTrackingData{ | ||||
| @ -64,7 +11,7 @@ class AnimeTrackingData with _$AnimeTrackingData{ | ||||
|     /// The ID of the anime | ||||
|     String id, | ||||
|     /// The state of the anime | ||||
|     @AnimeTrackingStateConverter() AnimeTrackingState state, | ||||
|     @MediumTrackingStateConverter() MediumTrackingState state, | ||||
|     /// The title of the anime | ||||
|     String title, | ||||
|     /// Episodes in total. | ||||
|  | ||||
| @ -24,8 +24,8 @@ mixin _$AnimeTrackingData { | ||||
|   String get id => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// The state of the anime | ||||
|   @AnimeTrackingStateConverter() | ||||
|   AnimeTrackingState get state => throw _privateConstructorUsedError; | ||||
|   @MediumTrackingStateConverter() | ||||
|   MediumTrackingState get state => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// The title of the anime | ||||
|   String get title => throw _privateConstructorUsedError; | ||||
| @ -52,7 +52,7 @@ abstract class $AnimeTrackingDataCopyWith<$Res> { | ||||
|       _$AnimeTrackingDataCopyWithImpl<$Res>; | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       @AnimeTrackingStateConverter() AnimeTrackingState state, | ||||
|       @MediumTrackingStateConverter() MediumTrackingState state, | ||||
|       String title, | ||||
|       int episodesWatched, | ||||
|       int? episodesTotal, | ||||
| @ -85,7 +85,7 @@ class _$AnimeTrackingDataCopyWithImpl<$Res> | ||||
|       state: state == freezed | ||||
|           ? _value.state | ||||
|           : state // ignore: cast_nullable_to_non_nullable | ||||
|               as AnimeTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       title: title == freezed | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
| @ -115,7 +115,7 @@ abstract class _$$_AnimeTrackingDataCopyWith<$Res> | ||||
|   @override | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       @AnimeTrackingStateConverter() AnimeTrackingState state, | ||||
|       @MediumTrackingStateConverter() MediumTrackingState state, | ||||
|       String title, | ||||
|       int episodesWatched, | ||||
|       int? episodesTotal, | ||||
| @ -150,7 +150,7 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res> | ||||
|       state == freezed | ||||
|           ? _value.state | ||||
|           : state // ignore: cast_nullable_to_non_nullable | ||||
|               as AnimeTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       title == freezed | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
| @ -174,7 +174,7 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res> | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_AnimeTrackingData implements _AnimeTrackingData { | ||||
|   _$_AnimeTrackingData(this.id, @AnimeTrackingStateConverter() this.state, | ||||
|   _$_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, | ||||
|       this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl); | ||||
| 
 | ||||
|   factory _$_AnimeTrackingData.fromJson(Map<String, dynamic> json) => | ||||
| @ -186,8 +186,8 @@ class _$_AnimeTrackingData implements _AnimeTrackingData { | ||||
| 
 | ||||
|   /// The state of the anime | ||||
|   @override | ||||
|   @AnimeTrackingStateConverter() | ||||
|   final AnimeTrackingState state; | ||||
|   @MediumTrackingStateConverter() | ||||
|   final MediumTrackingState state; | ||||
| 
 | ||||
|   /// The title of the anime | ||||
|   @override | ||||
| @ -254,7 +254,7 @@ class _$_AnimeTrackingData implements _AnimeTrackingData { | ||||
| abstract class _AnimeTrackingData implements AnimeTrackingData { | ||||
|   factory _AnimeTrackingData( | ||||
|       final String id, | ||||
|       @AnimeTrackingStateConverter() final AnimeTrackingState state, | ||||
|       @MediumTrackingStateConverter() final MediumTrackingState state, | ||||
|       final String title, | ||||
|       final int episodesWatched, | ||||
|       final int? episodesTotal, | ||||
| @ -270,8 +270,8 @@ abstract class _AnimeTrackingData implements AnimeTrackingData { | ||||
|   @override | ||||
| 
 | ||||
|   /// The state of the anime | ||||
|   @AnimeTrackingStateConverter() | ||||
|   AnimeTrackingState get state; | ||||
|   @MediumTrackingStateConverter() | ||||
|   MediumTrackingState get state; | ||||
|   @override | ||||
| 
 | ||||
|   /// The title of the anime | ||||
|  | ||||
| @ -9,7 +9,7 @@ part of 'anime.dart'; | ||||
| _$_AnimeTrackingData _$$_AnimeTrackingDataFromJson(Map<String, dynamic> json) => | ||||
|     _$_AnimeTrackingData( | ||||
|       json['id'] as String, | ||||
|       const AnimeTrackingStateConverter().fromJson(json['state'] as int), | ||||
|       const MediumTrackingStateConverter().fromJson(json['state'] as int), | ||||
|       json['title'] as String, | ||||
|       json['episodesWatched'] as int, | ||||
|       json['episodesTotal'] as int?, | ||||
| @ -20,7 +20,7 @@ Map<String, dynamic> _$$_AnimeTrackingDataToJson( | ||||
|         _$_AnimeTrackingData instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'state': const AnimeTrackingStateConverter().toJson(instance.state), | ||||
|       'state': const MediumTrackingStateConverter().toJson(instance.state), | ||||
|       'title': instance.title, | ||||
|       'episodesWatched': instance.episodesWatched, | ||||
|       'episodesTotal': instance.episodesTotal, | ||||
|  | ||||
| @ -1,62 +1,9 @@ | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| 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 | ||||
|   planToRead, // 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.planToRead: return 2; | ||||
|       case MangaTrackingState.dropped: return 3; | ||||
|       case MangaTrackingState.all: return -1; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   String toNameString() { | ||||
|     assert(this != MangaTrackingState.all, 'MangaTrackingState.all must not be stringified'); | ||||
| 
 | ||||
|     switch (this) { | ||||
|       case MangaTrackingState.reading: return 'Reading'; | ||||
|       case MangaTrackingState.completed: return 'Completed'; | ||||
|       case MangaTrackingState.planToRead: return 'Plan to read'; | ||||
|       case MangaTrackingState.dropped: return 'Dropped'; | ||||
|       case MangaTrackingState.all: return 'All'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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.planToRead; | ||||
|       case 3: return MangaTrackingState.dropped; | ||||
|     } | ||||
| 
 | ||||
|     return MangaTrackingState.planToRead; | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   int toJson(MangaTrackingState state) => state.toInteger(); | ||||
| } | ||||
| 
 | ||||
| /// Data about a tracked anime | ||||
| @freezed | ||||
| class MangaTrackingData with _$MangaTrackingData{ | ||||
| @ -64,7 +11,7 @@ class MangaTrackingData with _$MangaTrackingData{ | ||||
|     /// The ID of the manga | ||||
|     String id, | ||||
|     /// The state of the manga | ||||
|     @MangaTrackingStateConverter() MangaTrackingState state, | ||||
|     @MediumTrackingStateConverter() MediumTrackingState state, | ||||
|     /// The title of the manga | ||||
|     String title, | ||||
|     /// Chapters read. | ||||
|  | ||||
| @ -24,8 +24,8 @@ mixin _$MangaTrackingData { | ||||
|   String get id => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// The state of the manga | ||||
|   @MangaTrackingStateConverter() | ||||
|   MangaTrackingState get state => throw _privateConstructorUsedError; | ||||
|   @MediumTrackingStateConverter() | ||||
|   MediumTrackingState get state => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   /// The title of the manga | ||||
|   String get title => throw _privateConstructorUsedError; | ||||
| @ -55,7 +55,7 @@ abstract class $MangaTrackingDataCopyWith<$Res> { | ||||
|       _$MangaTrackingDataCopyWithImpl<$Res>; | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       @MangaTrackingStateConverter() MangaTrackingState state, | ||||
|       @MediumTrackingStateConverter() MediumTrackingState state, | ||||
|       String title, | ||||
|       int chaptersRead, | ||||
|       int volumesOwned, | ||||
| @ -90,7 +90,7 @@ class _$MangaTrackingDataCopyWithImpl<$Res> | ||||
|       state: state == freezed | ||||
|           ? _value.state | ||||
|           : state // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       title: title == freezed | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
| @ -124,7 +124,7 @@ abstract class _$$_MangaTrackingDataCopyWith<$Res> | ||||
|   @override | ||||
|   $Res call( | ||||
|       {String id, | ||||
|       @MangaTrackingStateConverter() MangaTrackingState state, | ||||
|       @MediumTrackingStateConverter() MediumTrackingState state, | ||||
|       String title, | ||||
|       int chaptersRead, | ||||
|       int volumesOwned, | ||||
| @ -161,7 +161,7 @@ class __$$_MangaTrackingDataCopyWithImpl<$Res> | ||||
|       state == freezed | ||||
|           ? _value.state | ||||
|           : state // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       title == freezed | ||||
|           ? _value.title | ||||
|           : title // ignore: cast_nullable_to_non_nullable | ||||
| @ -191,7 +191,7 @@ class __$$_MangaTrackingDataCopyWithImpl<$Res> | ||||
| class _$_MangaTrackingData implements _MangaTrackingData { | ||||
|   _$_MangaTrackingData( | ||||
|       this.id, | ||||
|       @MangaTrackingStateConverter() this.state, | ||||
|       @MediumTrackingStateConverter() this.state, | ||||
|       this.title, | ||||
|       this.chaptersRead, | ||||
|       this.volumesOwned, | ||||
| @ -207,8 +207,8 @@ class _$_MangaTrackingData implements _MangaTrackingData { | ||||
| 
 | ||||
|   /// The state of the manga | ||||
|   @override | ||||
|   @MangaTrackingStateConverter() | ||||
|   final MangaTrackingState state; | ||||
|   @MediumTrackingStateConverter() | ||||
|   final MediumTrackingState state; | ||||
| 
 | ||||
|   /// The title of the manga | ||||
|   @override | ||||
| @ -282,7 +282,7 @@ class _$_MangaTrackingData implements _MangaTrackingData { | ||||
| abstract class _MangaTrackingData implements MangaTrackingData { | ||||
|   factory _MangaTrackingData( | ||||
|       final String id, | ||||
|       @MangaTrackingStateConverter() final MangaTrackingState state, | ||||
|       @MediumTrackingStateConverter() final MediumTrackingState state, | ||||
|       final String title, | ||||
|       final int chaptersRead, | ||||
|       final int volumesOwned, | ||||
| @ -299,8 +299,8 @@ abstract class _MangaTrackingData implements MangaTrackingData { | ||||
|   @override | ||||
| 
 | ||||
|   /// The state of the manga | ||||
|   @MangaTrackingStateConverter() | ||||
|   MangaTrackingState get state; | ||||
|   @MediumTrackingStateConverter() | ||||
|   MediumTrackingState get state; | ||||
|   @override | ||||
| 
 | ||||
|   /// The title of the manga | ||||
|  | ||||
| @ -9,7 +9,7 @@ part of 'manga.dart'; | ||||
| _$_MangaTrackingData _$$_MangaTrackingDataFromJson(Map<String, dynamic> json) => | ||||
|     _$_MangaTrackingData( | ||||
|       json['id'] as String, | ||||
|       const MangaTrackingStateConverter().fromJson(json['state'] as int), | ||||
|       const MediumTrackingStateConverter().fromJson(json['state'] as int), | ||||
|       json['title'] as String, | ||||
|       json['chaptersRead'] as int, | ||||
|       json['volumesOwned'] as int, | ||||
| @ -21,7 +21,7 @@ Map<String, dynamic> _$$_MangaTrackingDataToJson( | ||||
|         _$_MangaTrackingData instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'state': const MangaTrackingStateConverter().toJson(instance.state), | ||||
|       'state': const MediumTrackingStateConverter().toJson(instance.state), | ||||
|       'title': instance.title, | ||||
|       'chaptersRead': instance.chaptersRead, | ||||
|       'volumesOwned': instance.volumesOwned, | ||||
|  | ||||
| @ -1,5 +1,68 @@ | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| 
 | ||||
| /// The type of medium we are tracking. Useful for UI stuff. | ||||
| enum TrackingMediumType { | ||||
|   anime, | ||||
|   manga, | ||||
| } | ||||
| 
 | ||||
| /// The state of the medium we're tracking, i.e. reading/watching, dropped, ... | ||||
| enum MediumTrackingState { | ||||
|   ongoing, | ||||
|   completed, | ||||
|   planned, | ||||
|   dropped, | ||||
|   all, | ||||
| } | ||||
| 
 | ||||
| extension MediumStateExtension on MediumTrackingState { | ||||
|   int toInteger() { | ||||
|     assert(this != MediumTrackingState.all, 'MediumTrackingState.all must not be serialized'); | ||||
|     switch (this) { | ||||
|       case MediumTrackingState.ongoing: return 0; | ||||
|       case MediumTrackingState.completed: return 1; | ||||
|       case MediumTrackingState.planned: return 2; | ||||
|       case MediumTrackingState.dropped: return 3; | ||||
|       case MediumTrackingState.all: return -1; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   String toNameString(TrackingMediumType type) { | ||||
|     assert(this != MediumTrackingState.all, 'MediumTrackingState.all must not be stringified'); | ||||
| 
 | ||||
|     switch (this) { | ||||
|       case MediumTrackingState.ongoing: | ||||
|         switch (type) { | ||||
|           case TrackingMediumType.anime: return 'Watching'; | ||||
|           case TrackingMediumType.manga: return 'Reading'; | ||||
|         } | ||||
|       case MediumTrackingState.completed: return 'Completed'; | ||||
|       case MediumTrackingState.planned: | ||||
|         switch (type) { | ||||
|           case TrackingMediumType.anime: return 'Plan to watch'; | ||||
|           case TrackingMediumType.manga: return 'Plan to read'; | ||||
|         } | ||||
|       case MediumTrackingState.dropped: return 'Dropped'; | ||||
|       case MediumTrackingState.all: return 'All'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class MediumTrackingStateConverter implements JsonConverter<MediumTrackingState, int> { | ||||
|   const MediumTrackingStateConverter(); | ||||
| 
 | ||||
|   @override | ||||
|   MediumTrackingState fromJson(int json) { | ||||
|     switch (json) { | ||||
|       case 0: return MediumTrackingState.ongoing; | ||||
|       case 1: return MediumTrackingState.completed; | ||||
|       case 2: return MediumTrackingState.planned; | ||||
|       case 3: return MediumTrackingState.dropped; | ||||
|     } | ||||
| 
 | ||||
|     return MediumTrackingState.planned; | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   int toJson(MediumTrackingState state) => state.toInteger(); | ||||
| } | ||||
|  | ||||
| @ -47,7 +47,7 @@ class DatabaseService { | ||||
| 
 | ||||
|     return animes | ||||
|       .cast<Map<String, dynamic>>() | ||||
|       .map((Map<String, dynamic> anime) => AnimeTrackingData.fromJson(anime)) | ||||
|       .map(AnimeTrackingData.fromJson) | ||||
|       .toList(); | ||||
|   } | ||||
| 
 | ||||
| @ -56,7 +56,7 @@ class DatabaseService { | ||||
| 
 | ||||
|     return mangas | ||||
|       .cast<Map<String, dynamic>>() | ||||
|       .map((Map<String, dynamic> manga) => MangaTrackingData.fromJson(manga)) | ||||
|       .map(MangaTrackingData.fromJson) | ||||
|       .toList(); | ||||
|   } | ||||
|    | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| import 'dart:math'; | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/data/manga.dart'; | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
|  | ||||
| @ -18,8 +18,10 @@ final _privateConstructorUsedError = UnsupportedError( | ||||
| mixin _$AnimeListState { | ||||
|   List<AnimeTrackingData> get animes => throw _privateConstructorUsedError; | ||||
|   List<MangaTrackingData> get mangas => throw _privateConstructorUsedError; | ||||
|   AnimeTrackingState get animeFilterState => throw _privateConstructorUsedError; | ||||
|   MangaTrackingState get mangaFilterState => throw _privateConstructorUsedError; | ||||
|   MediumTrackingState get animeFilterState => | ||||
|       throw _privateConstructorUsedError; | ||||
|   MediumTrackingState get mangaFilterState => | ||||
|       throw _privateConstructorUsedError; | ||||
|   TrackingMediumType get trackingType => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
| @ -35,8 +37,8 @@ abstract class $AnimeListStateCopyWith<$Res> { | ||||
|   $Res call( | ||||
|       {List<AnimeTrackingData> animes, | ||||
|       List<MangaTrackingData> mangas, | ||||
|       AnimeTrackingState animeFilterState, | ||||
|       MangaTrackingState mangaFilterState, | ||||
|       MediumTrackingState animeFilterState, | ||||
|       MediumTrackingState mangaFilterState, | ||||
|       TrackingMediumType trackingType}); | ||||
| } | ||||
| 
 | ||||
| @ -69,11 +71,11 @@ class _$AnimeListStateCopyWithImpl<$Res> | ||||
|       animeFilterState: animeFilterState == freezed | ||||
|           ? _value.animeFilterState | ||||
|           : animeFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as AnimeTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       mangaFilterState: mangaFilterState == freezed | ||||
|           ? _value.mangaFilterState | ||||
|           : mangaFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       trackingType: trackingType == freezed | ||||
|           ? _value.trackingType | ||||
|           : trackingType // ignore: cast_nullable_to_non_nullable | ||||
| @ -92,8 +94,8 @@ abstract class _$$_AnimeListStateCopyWith<$Res> | ||||
|   $Res call( | ||||
|       {List<AnimeTrackingData> animes, | ||||
|       List<MangaTrackingData> mangas, | ||||
|       AnimeTrackingState animeFilterState, | ||||
|       MangaTrackingState mangaFilterState, | ||||
|       MediumTrackingState animeFilterState, | ||||
|       MediumTrackingState mangaFilterState, | ||||
|       TrackingMediumType trackingType}); | ||||
| } | ||||
| 
 | ||||
| @ -128,11 +130,11 @@ class __$$_AnimeListStateCopyWithImpl<$Res> | ||||
|       animeFilterState: animeFilterState == freezed | ||||
|           ? _value.animeFilterState | ||||
|           : animeFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as AnimeTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       mangaFilterState: mangaFilterState == freezed | ||||
|           ? _value.mangaFilterState | ||||
|           : mangaFilterState // ignore: cast_nullable_to_non_nullable | ||||
|               as MangaTrackingState, | ||||
|               as MediumTrackingState, | ||||
|       trackingType: trackingType == freezed | ||||
|           ? _value.trackingType | ||||
|           : trackingType // ignore: cast_nullable_to_non_nullable | ||||
| @ -147,8 +149,8 @@ class _$_AnimeListState implements _AnimeListState { | ||||
|   _$_AnimeListState( | ||||
|       {final List<AnimeTrackingData> animes = const [], | ||||
|       final List<MangaTrackingData> mangas = const [], | ||||
|       this.animeFilterState = AnimeTrackingState.watching, | ||||
|       this.mangaFilterState = MangaTrackingState.reading, | ||||
|       this.animeFilterState = MediumTrackingState.ongoing, | ||||
|       this.mangaFilterState = MediumTrackingState.ongoing, | ||||
|       this.trackingType = TrackingMediumType.anime}) | ||||
|       : _animes = animes, | ||||
|         _mangas = mangas; | ||||
| @ -171,10 +173,10 @@ class _$_AnimeListState implements _AnimeListState { | ||||
| 
 | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final AnimeTrackingState animeFilterState; | ||||
|   final MediumTrackingState animeFilterState; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final MangaTrackingState mangaFilterState; | ||||
|   final MediumTrackingState mangaFilterState; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final TrackingMediumType trackingType; | ||||
| @ -218,8 +220,8 @@ abstract class _AnimeListState implements AnimeListState { | ||||
|   factory _AnimeListState( | ||||
|       {final List<AnimeTrackingData> animes, | ||||
|       final List<MangaTrackingData> mangas, | ||||
|       final AnimeTrackingState animeFilterState, | ||||
|       final MangaTrackingState mangaFilterState, | ||||
|       final MediumTrackingState animeFilterState, | ||||
|       final MediumTrackingState mangaFilterState, | ||||
|       final TrackingMediumType trackingType}) = _$_AnimeListState; | ||||
| 
 | ||||
|   @override | ||||
| @ -227,9 +229,9 @@ abstract class _AnimeListState implements AnimeListState { | ||||
|   @override | ||||
|   List<MangaTrackingData> get mangas; | ||||
|   @override | ||||
|   AnimeTrackingState get animeFilterState; | ||||
|   MediumTrackingState get animeFilterState; | ||||
|   @override | ||||
|   MangaTrackingState get mangaFilterState; | ||||
|   MediumTrackingState get mangaFilterState; | ||||
|   @override | ||||
|   TrackingMediumType get trackingType; | ||||
|   @override | ||||
|  | ||||
| @ -31,7 +31,7 @@ class AnimeFilterChangedEvent extends AnimeListEvent { | ||||
|   AnimeFilterChangedEvent(this.filterState); | ||||
| 
 | ||||
|   /// The state to filter | ||||
|   final AnimeTrackingState filterState; | ||||
|   final MediumTrackingState filterState; | ||||
| } | ||||
| 
 | ||||
| /// Triggered when the view is changed from the anime or the manga view | ||||
| @ -60,7 +60,7 @@ class MangaFilterChangedEvent extends AnimeListEvent { | ||||
|   MangaFilterChangedEvent(this.filterState); | ||||
| 
 | ||||
|   /// The state to filter | ||||
|   final MangaTrackingState filterState; | ||||
|   final MediumTrackingState filterState; | ||||
| } | ||||
| 
 | ||||
| class MangaChapterIncrementedEvent extends AnimeListEvent { | ||||
|  | ||||
| @ -5,8 +5,8 @@ class AnimeListState with _$AnimeListState { | ||||
|   factory AnimeListState({ | ||||
|     @Default([]) List<AnimeTrackingData> animes, | ||||
|     @Default([]) List<MangaTrackingData> mangas, | ||||
|     @Default(AnimeTrackingState.watching) AnimeTrackingState animeFilterState, | ||||
|     @Default(MangaTrackingState.reading) MangaTrackingState mangaFilterState, | ||||
|     @Default(MediumTrackingState.ongoing) MediumTrackingState animeFilterState, | ||||
|     @Default(MediumTrackingState.ongoing) MediumTrackingState mangaFilterState, | ||||
|     @Default(TrackingMediumType.anime) TrackingMediumType trackingType, | ||||
|   }) = _AnimeListState; | ||||
| } | ||||
|  | ||||
| @ -2,9 +2,9 @@ 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'; | ||||
| import 'package:anitrack/src/ui/constants.dart'; | ||||
| import 'package:bloc/bloc.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| @ -34,7 +34,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
| 
 | ||||
|     GetIt.I.get<NavigationBloc>().add( | ||||
|       PushedNamedEvent( | ||||
|         NavigationDestination(animeSearchRoute), | ||||
|         const NavigationDestination(animeSearchRoute), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| @ -101,7 +101,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|         list.AnimeAddedEvent( | ||||
|           AnimeTrackingData( | ||||
|             event.result.id, | ||||
|             AnimeTrackingState.watching, | ||||
|             MediumTrackingState.ongoing, | ||||
|             event.result.title, | ||||
|             0, | ||||
|             event.result.total, | ||||
| @ -111,14 +111,14 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | ||||
|         list.MangaAddedEvent( | ||||
|           MangaTrackingData( | ||||
|             event.result.id, | ||||
|             MangaTrackingState.reading, | ||||
|             MediumTrackingState.ongoing, | ||||
|             event.result.title, | ||||
|             0, | ||||
|             0, | ||||
|             event.result.total, | ||||
|             event.result.thumbnailUrl, | ||||
|           ), | ||||
|         ) | ||||
|         ), | ||||
|     ); | ||||
| 
 | ||||
|     GetIt.I.get<NavigationBloc>().add( | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| 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:anitrack/src/ui/bloc/anime_list_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/constants.dart'; | ||||
| import 'package:anitrack/src/service/database.dart'; | ||||
| import 'package:bloc/bloc.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| @ -30,9 +30,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> { | ||||
| 
 | ||||
|     GetIt.I.get<NavigationBloc>().add( | ||||
|       PushedNamedEvent( | ||||
|         NavigationDestination( | ||||
|           detailsRoute, | ||||
|         ), | ||||
|         const NavigationDestination(detailsRoute), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| @ -47,9 +45,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> { | ||||
| 
 | ||||
|     GetIt.I.get<NavigationBloc>().add( | ||||
|       PushedNamedEvent( | ||||
|         NavigationDestination( | ||||
|           detailsRoute, | ||||
|         ), | ||||
|         const NavigationDestination(detailsRoute), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import 'package:bloc/bloc.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| 
 | ||||
| part 'navigation_event.dart'; | ||||
| part 'navigation_state.dart'; | ||||
|  | ||||
| @ -1,5 +1,3 @@ | ||||
| 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'; | ||||
| @ -11,6 +9,10 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| 
 | ||||
| class AnimeListPage extends StatelessWidget { | ||||
|   AnimeListPage({ | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( | ||||
|     builder: (_) => AnimeListPage(), | ||||
|     settings: const RouteSettings( | ||||
| @ -27,11 +29,36 @@ class AnimeListPage extends StatelessWidget { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(TrackingMediumType type) { | ||||
|     return [ | ||||
|       PopupMenuItem<MediumTrackingState>( | ||||
|         value: MediumTrackingState.ongoing, | ||||
|         child: Text(MediumTrackingState.ongoing.toNameString(type)), | ||||
|       ), | ||||
|       PopupMenuItem<MediumTrackingState>( | ||||
|         value: MediumTrackingState.completed, | ||||
|         child: Text(MediumTrackingState.completed.toNameString(type)), | ||||
|       ), | ||||
|       PopupMenuItem<MediumTrackingState>( | ||||
|         value: MediumTrackingState.planned, | ||||
|         child: Text(MediumTrackingState.planned.toNameString(type)), | ||||
|       ), | ||||
|       PopupMenuItem<MediumTrackingState>( | ||||
|         value: MediumTrackingState.dropped, | ||||
|         child: Text(MediumTrackingState.dropped.toNameString(type)), | ||||
|       ), | ||||
|       const PopupMenuItem<MediumTrackingState>( | ||||
|         value: MediumTrackingState.all, | ||||
|         child: Text('All'), | ||||
|       ), | ||||
|     ]; | ||||
|   } | ||||
|    | ||||
|   Widget _getPopupButton(BuildContext context, AnimeListState state) { | ||||
|     switch (state.trackingType) { | ||||
|       case TrackingMediumType.anime: | ||||
|         return PopupMenuButton( | ||||
|           icon: Icon( | ||||
|           icon: const Icon( | ||||
|             Icons.filter_list, | ||||
|           ), | ||||
|           initialValue: state.animeFilterState, | ||||
| @ -40,32 +67,11 @@ class AnimeListPage extends StatelessWidget { | ||||
|               AnimeFilterChangedEvent(filterState), | ||||
|             ); | ||||
|           }, | ||||
|           itemBuilder: (_) => [ | ||||
|             const PopupMenuItem<AnimeTrackingState>( | ||||
|               value: AnimeTrackingState.watching, | ||||
|               child: Text('Watching'), | ||||
|             ), | ||||
|             const PopupMenuItem<AnimeTrackingState>( | ||||
|               value: AnimeTrackingState.completed, | ||||
|               child: Text('Completed'), | ||||
|             ), | ||||
|             const PopupMenuItem<AnimeTrackingState>( | ||||
|               value: AnimeTrackingState.planToWatch, | ||||
|               child: Text('Plan to watch'), | ||||
|             ), | ||||
|             const PopupMenuItem<AnimeTrackingState>( | ||||
|               value: AnimeTrackingState.dropped, | ||||
|               child: Text('Dropped'), | ||||
|             ), | ||||
|             const PopupMenuItem<AnimeTrackingState>( | ||||
|               value: AnimeTrackingState.all, | ||||
|               child: Text('All'), | ||||
|             ), | ||||
|           ], | ||||
|           itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime), | ||||
|         ); | ||||
|       case TrackingMediumType.manga: | ||||
|         return PopupMenuButton( | ||||
|           icon: Icon( | ||||
|           icon: const Icon( | ||||
|             Icons.filter_list, | ||||
|           ), | ||||
|           initialValue: state.mangaFilterState, | ||||
| @ -74,28 +80,7 @@ class AnimeListPage extends StatelessWidget { | ||||
|               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.planToRead, | ||||
|               child: Text('Plan to read'), | ||||
|             ), | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.dropped, | ||||
|               child: Text('Dropped'), | ||||
|             ), | ||||
|             const PopupMenuItem<MangaTrackingState>( | ||||
|               value: MangaTrackingState.all, | ||||
|               child: Text('All'), | ||||
|             ), | ||||
|           ], | ||||
|           itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga), | ||||
|         ); | ||||
|     } | ||||
|   } | ||||
| @ -107,7 +92,7 @@ class AnimeListPage extends StatelessWidget { | ||||
|         return Scaffold( | ||||
|           appBar: AppBar( | ||||
|             title: Text( | ||||
|               _getPageTitle(state.trackingType) | ||||
|               _getPageTitle(state.trackingType), | ||||
|             ), | ||||
|             actions: [ | ||||
|               _getPopupButton(context, state), | ||||
| @ -120,7 +105,7 @@ class AnimeListPage extends StatelessWidget { | ||||
|                 itemCount: state.animes.length, | ||||
|                 itemBuilder: (context, index) { | ||||
|                   final anime = state.animes[index]; | ||||
|                   if (state.animeFilterState != AnimeTrackingState.all) { | ||||
|                   if (state.animeFilterState != MediumTrackingState.all) { | ||||
|                     if (anime.state != state.animeFilterState) return Container(); | ||||
|                   } | ||||
| 
 | ||||
| @ -157,7 +142,7 @@ class AnimeListPage extends StatelessWidget { | ||||
|                 itemCount: state.mangas.length, | ||||
|                 itemBuilder: (context, index) { | ||||
|                   final manga = state.mangas[index]; | ||||
|                   if (state.mangaFilterState != MangaTrackingState.all) { | ||||
|                   if (state.mangaFilterState != MediumTrackingState.all) { | ||||
|                     if (manga.state != state.mangaFilterState) return Container(); | ||||
|                   } | ||||
| 
 | ||||
| @ -216,7 +201,7 @@ class AnimeListPage extends StatelessWidget { | ||||
| 
 | ||||
|               _controller.jumpToPage(index); | ||||
|             }, | ||||
|             items: <BottomBarItem>[ | ||||
|             items: const [ | ||||
|               BottomBarItem( | ||||
|                 icon: Icon(Icons.tv), | ||||
|                 title: Text('Anime'), | ||||
|  | ||||
| @ -1,15 +1,17 @@ | ||||
| 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'; | ||||
| import 'package:anitrack/src/ui/widgets/image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| 
 | ||||
| class AnimeSearchPage extends StatelessWidget { | ||||
|   const AnimeSearchPage({ | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( | ||||
|     builder: (_) => AnimeSearchPage(), | ||||
|     builder: (_) => const AnimeSearchPage(), | ||||
|     settings: const RouteSettings( | ||||
|       name: animeSearchRoute, | ||||
|     ), | ||||
| @ -32,7 +34,7 @@ class AnimeSearchPage extends StatelessWidget { | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.all(8), | ||||
|                 child: TextField( | ||||
|                   decoration: InputDecoration( | ||||
|                   decoration: const InputDecoration( | ||||
|                     border: OutlineInputBorder(), | ||||
|                     labelText: 'Search query', | ||||
|                   ), | ||||
| @ -50,9 +52,8 @@ class AnimeSearchPage extends StatelessWidget { | ||||
|               ), | ||||
| 
 | ||||
|               if (state.working) | ||||
|                 Expanded( | ||||
|                 const Expanded( | ||||
|                   child: Align( | ||||
|                     alignment: Alignment.center, | ||||
|                     child: CircularProgressIndicator(), | ||||
|                   ), | ||||
|                 ) | ||||
| @ -86,7 +87,7 @@ class AnimeSearchPage extends StatelessWidget { | ||||
|                           ], | ||||
|                         ), | ||||
|                       ); | ||||
|                     } | ||||
|                     }, | ||||
|                   ), | ||||
|                 ), | ||||
|             ], | ||||
|  | ||||
| @ -3,44 +3,28 @@ import 'package:anitrack/src/data/manga.dart'; | ||||
| import 'package:anitrack/src/data/type.dart'; | ||||
| import 'package:anitrack/src/ui/bloc/details_bloc.dart'; | ||||
| import 'package:anitrack/src/ui/constants.dart'; | ||||
| import 'package:anitrack/src/ui/widgets/dropdown.dart'; | ||||
| import 'package:anitrack/src/ui/widgets/image.dart'; | ||||
| import 'package:anitrack/src/ui/widgets/list_item.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| 
 | ||||
| Widget _makeListTile(BuildContext context, dynamic value, String text, bool selected) { | ||||
|   return ListTile( | ||||
|     title: Text(text), | ||||
|     trailing: selected ? | ||||
|       Icon(Icons.check) : | ||||
|       null, | ||||
|     onTap: () { | ||||
|       Navigator.of(context).pop(value); | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| class DetailsPage extends StatelessWidget { | ||||
|   const DetailsPage({ | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( | ||||
|     builder: (_) => DetailsPage(), | ||||
|     builder: (_) => const DetailsPage(), | ||||
|     settings: const RouteSettings( | ||||
|       name: detailsRoute, | ||||
|     ), | ||||
|   ); | ||||
| 
 | ||||
|   String _getTrackingStateText(DetailsState state) { | ||||
|     if (state.trackingType == TrackingMediumType.anime) { | ||||
|       return (state.data as AnimeTrackingData).state.toNameString(); | ||||
|     } else { | ||||
|       return (state.data as MangaTrackingData).state.toNameString(); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: Text('Details'), | ||||
|         title: const Text('Details'), | ||||
|       ), | ||||
|       body: BlocBuilder<DetailsBloc, DetailsState>( | ||||
|         builder: (context, state) { | ||||
| @ -69,101 +53,63 @@ class DetailsPage extends StatelessWidget { | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             state.trackingType == TrackingMediumType.anime ? | ||||
|                               (state.data as AnimeTrackingData).title : | ||||
|                               (state.data as MangaTrackingData).title, | ||||
|                             (state.data as AnimeTrackingData).title : | ||||
|                             (state.data as MangaTrackingData).title, | ||||
|                             textAlign: TextAlign.left, | ||||
|                             style: Theme.of(context).textTheme.titleLarge, | ||||
|                             maxLines: 2, | ||||
|                             softWrap: true, | ||||
|                             overflow: TextOverflow.ellipsis, | ||||
|                           ), | ||||
| 
 | ||||
|                           TextButton( | ||||
|                             child: Text( | ||||
|                               _getTrackingStateText(state), | ||||
|                             ), | ||||
|                             onPressed: () async { | ||||
|                               final result = await showModalBottomSheet<dynamic>( | ||||
|                                 context: context, | ||||
|                                 builder: (context) { | ||||
|                                   return ListView( | ||||
|                                     shrinkWrap: true, | ||||
|                                     children: [ | ||||
|                                       _makeListTile( | ||||
|                                         context, | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           AnimeTrackingState.watching : | ||||
|                                           MangaTrackingState.reading, | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           'Watching' : | ||||
|                                           'Reading', | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           (state.data as AnimeTrackingData).state == AnimeTrackingState.watching : | ||||
|                                           (state.data as MangaTrackingData).state == MangaTrackingState.reading, | ||||
|                                       ), | ||||
|                                       _makeListTile( | ||||
|                                         context, | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           AnimeTrackingState.completed : | ||||
|                                           MangaTrackingState.completed, | ||||
|                                         'Completed', | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           (state.data as AnimeTrackingData).state == AnimeTrackingState.completed : | ||||
|                                           (state.data as MangaTrackingData).state == MangaTrackingState.completed, | ||||
|                                       ), | ||||
|                                       _makeListTile( | ||||
|                                         context, | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           AnimeTrackingState.planToWatch : | ||||
|                                           MangaTrackingState.planToRead, | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           'Plan to watch' : | ||||
|                                           'Plan to read', | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           (state.data as AnimeTrackingData).state == AnimeTrackingState.planToWatch : | ||||
|                                           (state.data as MangaTrackingData).state == MangaTrackingState.planToRead, | ||||
|                                       ), | ||||
|                                       _makeListTile( | ||||
|                                         context, | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           AnimeTrackingState.dropped : | ||||
|                                           MangaTrackingState.dropped, | ||||
|                                         'Dropped', | ||||
|                                         state.trackingType == TrackingMediumType.anime ? | ||||
|                                           (state.data as AnimeTrackingData).state == AnimeTrackingState.dropped : | ||||
|                                           (state.data as MangaTrackingData).state == MangaTrackingState.dropped, | ||||
|                                       ), | ||||
|                                     ], | ||||
|                                   ); | ||||
|                                 }, | ||||
|                               ); | ||||
| 
 | ||||
|                               if (result == null) return; | ||||
| 
 | ||||
|                               if (state.trackingType == TrackingMediumType.anime) { | ||||
|                                 context.read<DetailsBloc>().add( | ||||
|                                   DetailsUpdatedEvent( | ||||
|                                     (state.data as AnimeTrackingData).copyWith( | ||||
|                                       state: result as AnimeTrackingState, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ); | ||||
|                               } else { | ||||
|                                 context.read<DetailsBloc>().add( | ||||
|                                   DetailsUpdatedEvent( | ||||
|                                     (state.data as MangaTrackingData).copyWith( | ||||
|                                       state: result as MangaTrackingState, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ); | ||||
|                               } | ||||
|                             }, | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
| 
 | ||||
|                 DropdownSelector<MediumTrackingState>( | ||||
|                   title: 'Watchstate', | ||||
|                   onChanged: (MediumTrackingState newState) { | ||||
|                     if (state.trackingType == TrackingMediumType.anime) { | ||||
|                       context.read<DetailsBloc>().add( | ||||
|                         DetailsUpdatedEvent( | ||||
|                           (state.data as AnimeTrackingData).copyWith( | ||||
|                             state: newState, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ); | ||||
|                     } else if (state.trackingType == TrackingMediumType.manga) { | ||||
|                       context.read<DetailsBloc>().add( | ||||
|                         DetailsUpdatedEvent( | ||||
|                           (state.data as MangaTrackingData).copyWith( | ||||
|                             state: newState, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ); | ||||
|                     } | ||||
|                   }, | ||||
|                   values: [ | ||||
|                     SelectorItem( | ||||
|                       MediumTrackingState.ongoing, | ||||
|                       MediumTrackingState.ongoing.toNameString(state.trackingType), | ||||
|                     ), | ||||
|                     SelectorItem( | ||||
|                       MediumTrackingState.completed, | ||||
|                       MediumTrackingState.completed.toNameString(state.trackingType), | ||||
|                     ), | ||||
|                     SelectorItem( | ||||
|                       MediumTrackingState.planned, | ||||
|                       MediumTrackingState.planned.toNameString(state.trackingType), | ||||
|                     ), | ||||
|                     SelectorItem( | ||||
|                       MediumTrackingState.dropped, | ||||
|                       MediumTrackingState.dropped.toNameString(state.trackingType), | ||||
|                     ), | ||||
|                   ], | ||||
|                   initialValue: state.trackingType == TrackingMediumType.anime ? | ||||
|                   (state.data as AnimeTrackingData).state : | ||||
|                   (state.data as MangaTrackingData).state, | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ); | ||||
|  | ||||
							
								
								
									
										129
									
								
								lib/src/ui/widgets/dropdown.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								lib/src/ui/widgets/dropdown.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| class SelectorItem<T> { | ||||
|   const SelectorItem(this.value, this.text); | ||||
| 
 | ||||
|   final T value; | ||||
| 
 | ||||
|   final String text; | ||||
| } | ||||
| 
 | ||||
| class DropdownSelector<T> extends StatefulWidget { | ||||
|   const DropdownSelector({ | ||||
|     required this.title, | ||||
|     required this.onChanged, | ||||
|     required this.values, | ||||
|     required this.initialValue, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   /// The title of the dropdown | ||||
|   final String title; | ||||
| 
 | ||||
|   final List<SelectorItem<T>> values; | ||||
| 
 | ||||
|   final T initialValue; | ||||
|    | ||||
|   /// Called when the selection has changed | ||||
|   final void Function(T) onChanged; | ||||
|    | ||||
|   @override | ||||
|   DropdownSelectorState<T> createState() => DropdownSelectorState<T>(); | ||||
| } | ||||
| 
 | ||||
| class DropdownSelectorDialog<T> extends StatelessWidget { | ||||
|   const DropdownSelectorDialog({ | ||||
|     required this.values, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   final List<SelectorItem<T>> values; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SafeArea( | ||||
|       child: Material( | ||||
|         color: Theme.of(context).scaffoldBackgroundColor, | ||||
|         child: ListView.builder( | ||||
|           itemCount: values.length, | ||||
|           itemBuilder: (context, index) => InkWell( | ||||
|             onTap: () { | ||||
|               Navigator.of(context).pop(values[index].value); | ||||
|             }, | ||||
|             child: Row( | ||||
|               mainAxisAlignment: MainAxisAlignment.center, | ||||
|               children: [ | ||||
|                 Padding( | ||||
|                   padding: const EdgeInsets.all(16), | ||||
|                   child: Text( | ||||
|                     values[index].text, | ||||
|                     style: Theme.of(context).textTheme.titleLarge, | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class DropdownSelectorState<T> extends State<DropdownSelector<T>> { | ||||
|   late int index; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
| 
 | ||||
|     index = widget.values.indexWhere((item) => item.value == widget.initialValue); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Row( | ||||
|         children: [ | ||||
|           Expanded( | ||||
|             child: Card( | ||||
|               clipBehavior: Clip.hardEdge, | ||||
|               child: InkWell( | ||||
|                 onTap: () async { | ||||
|                   final result = await showDialog<T>( | ||||
|                     context: context, | ||||
|                     builder: (context) => DropdownSelectorDialog<T>( | ||||
|                       values: widget.values.cast<SelectorItem<T>>(), | ||||
|                     ), | ||||
|                   ); | ||||
| 
 | ||||
|                   if (result == null) return; | ||||
|                   if (result == widget.values[index].value) return; | ||||
| 
 | ||||
|                   setState(() { | ||||
|                     index = widget.values.indexWhere((item) => item.value == result); | ||||
|                   }); | ||||
| 
 | ||||
|                   widget.onChanged(result); | ||||
|                 }, | ||||
|                 child: Padding( | ||||
|                   padding: const EdgeInsets.all(12), | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         widget.title, | ||||
|                         style: Theme.of(context).textTheme.titleSmall, | ||||
|                       ), | ||||
|                       Text( | ||||
|                         widget.values[index].text, | ||||
|                         style: Theme.of(context).textTheme.bodyLarge, | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -9,6 +9,7 @@ class AnimeCoverImage extends StatelessWidget { | ||||
|   /// The URL to the cover image. | ||||
|   final String url; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ClipRRect( | ||||
|       borderRadius: BorderRadius.circular(8), | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| import 'package:anitrack/src/data/anime.dart'; | ||||
| import 'package:anitrack/src/ui/widgets/image.dart'; | ||||
| import 'package:anitrack/src/ui/widgets/swipe_icon.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -6,7 +5,7 @@ import 'package:swipeable_tile/swipeable_tile.dart'; | ||||
| 
 | ||||
| /// A widget for displaying simple data about an anime in the listview | ||||
| class ListItem extends StatelessWidget { | ||||
|   ListItem({ | ||||
|   const ListItem({ | ||||
|     required this.thumbnailUrl, | ||||
|     required this.title, | ||||
|     this.onLeftSwipe, | ||||
| @ -42,14 +41,14 @@ class ListItem extends StatelessWidget { | ||||
|       key: UniqueKey(), | ||||
|       backgroundBuilder: (_, direction, __) { | ||||
|         if (direction == SwipeDirection.endToStart) { | ||||
|           return Align( | ||||
|           return const Align( | ||||
|             alignment: Alignment.centerRight, | ||||
|             child: SwipeIcon( | ||||
|               Icons.add, | ||||
|             ), | ||||
|           ); | ||||
|         } else if (direction == SwipeDirection.startToEnd) { | ||||
|           return Align( | ||||
|           return const Align( | ||||
|             alignment: Alignment.centerLeft, | ||||
|             child: SwipeIcon( | ||||
|               Icons.remove, | ||||
|  | ||||
| @ -576,6 +576,13 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.2" | ||||
|   very_good_analysis: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
|       name: very_good_analysis | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.1.0" | ||||
|   watcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | ||||
| @ -23,12 +23,13 @@ dependencies: | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   build_runner: ^2.1.11 | ||||
|   flutter_test: | ||||
|     sdk: flutter | ||||
|   flutter_launcher_icons: ^0.11.0 | ||||
|   flutter_lints: ^2.0.0 | ||||
|   flutter_test: | ||||
|     sdk: flutter | ||||
|   freezed: ^2.1.0+1 | ||||
|   json_serializable: ^6.3.1 | ||||
|   very_good_analysis: ^3.0.1 | ||||
| 
 | ||||
| flutter: | ||||
|   uses-material-design: true | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user