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 | include: package:very_good_analysis/analysis_options.yaml | ||||||
| # 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 |  | ||||||
| 
 |  | ||||||
| linter: | 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: |   rules: | ||||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule |     public_member_api_docs: false | ||||||
|     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule |     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 | analyzer: | ||||||
| # https://dart.dev/guides/language/analysis-options |   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_list_bloc.dart'; | ||||||
| import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | ||||||
| import 'package:anitrack/src/ui/bloc/details_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_list.dart'; | ||||||
| import 'package:anitrack/src/ui/pages/anime_search.dart'; | import 'package:anitrack/src/ui/pages/anime_search.dart'; | ||||||
| import 'package:anitrack/src/ui/pages/details.dart'; | import 'package:anitrack/src/ui/pages/details.dart'; | ||||||
| import 'package:anitrack/src/service/database.dart'; |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | import 'package:flutter_bloc/flutter_bloc.dart'; | ||||||
| import 'package:get_it/get_it.dart'; | import 'package:get_it/get_it.dart'; | ||||||
| @ -66,7 +66,6 @@ class MyApp extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return MaterialApp( |     return MaterialApp( | ||||||
|       title: 'AniTrack', |       title: 'AniTrack', | ||||||
|       themeMode: ThemeMode.system, |  | ||||||
|       theme: ThemeData( |       theme: ThemeData( | ||||||
|         brightness: Brightness.light, |         brightness: Brightness.light, | ||||||
|         primarySwatch: Colors.blue, |         primarySwatch: Colors.blue, | ||||||
|  | |||||||
| @ -1,62 +1,9 @@ | |||||||
|  | import 'package:anitrack/src/data/type.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| 
 | 
 | ||||||
| part 'anime.freezed.dart'; | part 'anime.freezed.dart'; | ||||||
| part 'anime.g.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 | /// Data about a tracked anime | ||||||
| @freezed | @freezed | ||||||
| class AnimeTrackingData with _$AnimeTrackingData{ | class AnimeTrackingData with _$AnimeTrackingData{ | ||||||
| @ -64,7 +11,7 @@ class AnimeTrackingData with _$AnimeTrackingData{ | |||||||
|     /// The ID of the anime |     /// The ID of the anime | ||||||
|     String id, |     String id, | ||||||
|     /// The state of the anime |     /// The state of the anime | ||||||
|     @AnimeTrackingStateConverter() AnimeTrackingState state, |     @MediumTrackingStateConverter() MediumTrackingState state, | ||||||
|     /// The title of the anime |     /// The title of the anime | ||||||
|     String title, |     String title, | ||||||
|     /// Episodes in total. |     /// Episodes in total. | ||||||
|  | |||||||
| @ -24,8 +24,8 @@ mixin _$AnimeTrackingData { | |||||||
|   String get id => throw _privateConstructorUsedError; |   String get id => throw _privateConstructorUsedError; | ||||||
| 
 | 
 | ||||||
|   /// The state of the anime |   /// The state of the anime | ||||||
|   @AnimeTrackingStateConverter() |   @MediumTrackingStateConverter() | ||||||
|   AnimeTrackingState get state => throw _privateConstructorUsedError; |   MediumTrackingState get state => throw _privateConstructorUsedError; | ||||||
| 
 | 
 | ||||||
|   /// The title of the anime |   /// The title of the anime | ||||||
|   String get title => throw _privateConstructorUsedError; |   String get title => throw _privateConstructorUsedError; | ||||||
| @ -52,7 +52,7 @@ abstract class $AnimeTrackingDataCopyWith<$Res> { | |||||||
|       _$AnimeTrackingDataCopyWithImpl<$Res>; |       _$AnimeTrackingDataCopyWithImpl<$Res>; | ||||||
|   $Res call( |   $Res call( | ||||||
|       {String id, |       {String id, | ||||||
|       @AnimeTrackingStateConverter() AnimeTrackingState state, |       @MediumTrackingStateConverter() MediumTrackingState state, | ||||||
|       String title, |       String title, | ||||||
|       int episodesWatched, |       int episodesWatched, | ||||||
|       int? episodesTotal, |       int? episodesTotal, | ||||||
| @ -85,7 +85,7 @@ class _$AnimeTrackingDataCopyWithImpl<$Res> | |||||||
|       state: state == freezed |       state: state == freezed | ||||||
|           ? _value.state |           ? _value.state | ||||||
|           : state // ignore: cast_nullable_to_non_nullable |           : state // ignore: cast_nullable_to_non_nullable | ||||||
|               as AnimeTrackingState, |               as MediumTrackingState, | ||||||
|       title: title == freezed |       title: title == freezed | ||||||
|           ? _value.title |           ? _value.title | ||||||
|           : title // ignore: cast_nullable_to_non_nullable |           : title // ignore: cast_nullable_to_non_nullable | ||||||
| @ -115,7 +115,7 @@ abstract class _$$_AnimeTrackingDataCopyWith<$Res> | |||||||
|   @override |   @override | ||||||
|   $Res call( |   $Res call( | ||||||
|       {String id, |       {String id, | ||||||
|       @AnimeTrackingStateConverter() AnimeTrackingState state, |       @MediumTrackingStateConverter() MediumTrackingState state, | ||||||
|       String title, |       String title, | ||||||
|       int episodesWatched, |       int episodesWatched, | ||||||
|       int? episodesTotal, |       int? episodesTotal, | ||||||
| @ -150,7 +150,7 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res> | |||||||
|       state == freezed |       state == freezed | ||||||
|           ? _value.state |           ? _value.state | ||||||
|           : state // ignore: cast_nullable_to_non_nullable |           : state // ignore: cast_nullable_to_non_nullable | ||||||
|               as AnimeTrackingState, |               as MediumTrackingState, | ||||||
|       title == freezed |       title == freezed | ||||||
|           ? _value.title |           ? _value.title | ||||||
|           : title // ignore: cast_nullable_to_non_nullable |           : title // ignore: cast_nullable_to_non_nullable | ||||||
| @ -174,7 +174,7 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res> | |||||||
| /// @nodoc | /// @nodoc | ||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
| class _$_AnimeTrackingData implements _AnimeTrackingData { | class _$_AnimeTrackingData implements _AnimeTrackingData { | ||||||
|   _$_AnimeTrackingData(this.id, @AnimeTrackingStateConverter() this.state, |   _$_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, | ||||||
|       this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl); |       this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl); | ||||||
| 
 | 
 | ||||||
|   factory _$_AnimeTrackingData.fromJson(Map<String, dynamic> json) => |   factory _$_AnimeTrackingData.fromJson(Map<String, dynamic> json) => | ||||||
| @ -186,8 +186,8 @@ class _$_AnimeTrackingData implements _AnimeTrackingData { | |||||||
| 
 | 
 | ||||||
|   /// The state of the anime |   /// The state of the anime | ||||||
|   @override |   @override | ||||||
|   @AnimeTrackingStateConverter() |   @MediumTrackingStateConverter() | ||||||
|   final AnimeTrackingState state; |   final MediumTrackingState state; | ||||||
| 
 | 
 | ||||||
|   /// The title of the anime |   /// The title of the anime | ||||||
|   @override |   @override | ||||||
| @ -254,7 +254,7 @@ class _$_AnimeTrackingData implements _AnimeTrackingData { | |||||||
| abstract class _AnimeTrackingData implements AnimeTrackingData { | abstract class _AnimeTrackingData implements AnimeTrackingData { | ||||||
|   factory _AnimeTrackingData( |   factory _AnimeTrackingData( | ||||||
|       final String id, |       final String id, | ||||||
|       @AnimeTrackingStateConverter() final AnimeTrackingState state, |       @MediumTrackingStateConverter() final MediumTrackingState state, | ||||||
|       final String title, |       final String title, | ||||||
|       final int episodesWatched, |       final int episodesWatched, | ||||||
|       final int? episodesTotal, |       final int? episodesTotal, | ||||||
| @ -270,8 +270,8 @@ abstract class _AnimeTrackingData implements AnimeTrackingData { | |||||||
|   @override |   @override | ||||||
| 
 | 
 | ||||||
|   /// The state of the anime |   /// The state of the anime | ||||||
|   @AnimeTrackingStateConverter() |   @MediumTrackingStateConverter() | ||||||
|   AnimeTrackingState get state; |   MediumTrackingState get state; | ||||||
|   @override |   @override | ||||||
| 
 | 
 | ||||||
|   /// The title of the anime |   /// The title of the anime | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ part of 'anime.dart'; | |||||||
| _$_AnimeTrackingData _$$_AnimeTrackingDataFromJson(Map<String, dynamic> json) => | _$_AnimeTrackingData _$$_AnimeTrackingDataFromJson(Map<String, dynamic> json) => | ||||||
|     _$_AnimeTrackingData( |     _$_AnimeTrackingData( | ||||||
|       json['id'] as String, |       json['id'] as String, | ||||||
|       const AnimeTrackingStateConverter().fromJson(json['state'] as int), |       const MediumTrackingStateConverter().fromJson(json['state'] as int), | ||||||
|       json['title'] as String, |       json['title'] as String, | ||||||
|       json['episodesWatched'] as int, |       json['episodesWatched'] as int, | ||||||
|       json['episodesTotal'] as int?, |       json['episodesTotal'] as int?, | ||||||
| @ -20,7 +20,7 @@ Map<String, dynamic> _$$_AnimeTrackingDataToJson( | |||||||
|         _$_AnimeTrackingData instance) => |         _$_AnimeTrackingData instance) => | ||||||
|     <String, dynamic>{ |     <String, dynamic>{ | ||||||
|       'id': instance.id, |       'id': instance.id, | ||||||
|       'state': const AnimeTrackingStateConverter().toJson(instance.state), |       'state': const MediumTrackingStateConverter().toJson(instance.state), | ||||||
|       'title': instance.title, |       'title': instance.title, | ||||||
|       'episodesWatched': instance.episodesWatched, |       'episodesWatched': instance.episodesWatched, | ||||||
|       'episodesTotal': instance.episodesTotal, |       'episodesTotal': instance.episodesTotal, | ||||||
|  | |||||||
| @ -1,62 +1,9 @@ | |||||||
|  | import 'package:anitrack/src/data/type.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| 
 | 
 | ||||||
| part 'manga.freezed.dart'; | part 'manga.freezed.dart'; | ||||||
| part 'manga.g.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 | /// Data about a tracked anime | ||||||
| @freezed | @freezed | ||||||
| class MangaTrackingData with _$MangaTrackingData{ | class MangaTrackingData with _$MangaTrackingData{ | ||||||
| @ -64,7 +11,7 @@ class MangaTrackingData with _$MangaTrackingData{ | |||||||
|     /// The ID of the manga |     /// The ID of the manga | ||||||
|     String id, |     String id, | ||||||
|     /// The state of the manga |     /// The state of the manga | ||||||
|     @MangaTrackingStateConverter() MangaTrackingState state, |     @MediumTrackingStateConverter() MediumTrackingState state, | ||||||
|     /// The title of the manga |     /// The title of the manga | ||||||
|     String title, |     String title, | ||||||
|     /// Chapters read. |     /// Chapters read. | ||||||
|  | |||||||
| @ -24,8 +24,8 @@ mixin _$MangaTrackingData { | |||||||
|   String get id => throw _privateConstructorUsedError; |   String get id => throw _privateConstructorUsedError; | ||||||
| 
 | 
 | ||||||
|   /// The state of the manga |   /// The state of the manga | ||||||
|   @MangaTrackingStateConverter() |   @MediumTrackingStateConverter() | ||||||
|   MangaTrackingState get state => throw _privateConstructorUsedError; |   MediumTrackingState get state => throw _privateConstructorUsedError; | ||||||
| 
 | 
 | ||||||
|   /// The title of the manga |   /// The title of the manga | ||||||
|   String get title => throw _privateConstructorUsedError; |   String get title => throw _privateConstructorUsedError; | ||||||
| @ -55,7 +55,7 @@ abstract class $MangaTrackingDataCopyWith<$Res> { | |||||||
|       _$MangaTrackingDataCopyWithImpl<$Res>; |       _$MangaTrackingDataCopyWithImpl<$Res>; | ||||||
|   $Res call( |   $Res call( | ||||||
|       {String id, |       {String id, | ||||||
|       @MangaTrackingStateConverter() MangaTrackingState state, |       @MediumTrackingStateConverter() MediumTrackingState state, | ||||||
|       String title, |       String title, | ||||||
|       int chaptersRead, |       int chaptersRead, | ||||||
|       int volumesOwned, |       int volumesOwned, | ||||||
| @ -90,7 +90,7 @@ class _$MangaTrackingDataCopyWithImpl<$Res> | |||||||
|       state: state == freezed |       state: state == freezed | ||||||
|           ? _value.state |           ? _value.state | ||||||
|           : state // ignore: cast_nullable_to_non_nullable |           : state // ignore: cast_nullable_to_non_nullable | ||||||
|               as MangaTrackingState, |               as MediumTrackingState, | ||||||
|       title: title == freezed |       title: title == freezed | ||||||
|           ? _value.title |           ? _value.title | ||||||
|           : title // ignore: cast_nullable_to_non_nullable |           : title // ignore: cast_nullable_to_non_nullable | ||||||
| @ -124,7 +124,7 @@ abstract class _$$_MangaTrackingDataCopyWith<$Res> | |||||||
|   @override |   @override | ||||||
|   $Res call( |   $Res call( | ||||||
|       {String id, |       {String id, | ||||||
|       @MangaTrackingStateConverter() MangaTrackingState state, |       @MediumTrackingStateConverter() MediumTrackingState state, | ||||||
|       String title, |       String title, | ||||||
|       int chaptersRead, |       int chaptersRead, | ||||||
|       int volumesOwned, |       int volumesOwned, | ||||||
| @ -161,7 +161,7 @@ class __$$_MangaTrackingDataCopyWithImpl<$Res> | |||||||
|       state == freezed |       state == freezed | ||||||
|           ? _value.state |           ? _value.state | ||||||
|           : state // ignore: cast_nullable_to_non_nullable |           : state // ignore: cast_nullable_to_non_nullable | ||||||
|               as MangaTrackingState, |               as MediumTrackingState, | ||||||
|       title == freezed |       title == freezed | ||||||
|           ? _value.title |           ? _value.title | ||||||
|           : title // ignore: cast_nullable_to_non_nullable |           : title // ignore: cast_nullable_to_non_nullable | ||||||
| @ -191,7 +191,7 @@ class __$$_MangaTrackingDataCopyWithImpl<$Res> | |||||||
| class _$_MangaTrackingData implements _MangaTrackingData { | class _$_MangaTrackingData implements _MangaTrackingData { | ||||||
|   _$_MangaTrackingData( |   _$_MangaTrackingData( | ||||||
|       this.id, |       this.id, | ||||||
|       @MangaTrackingStateConverter() this.state, |       @MediumTrackingStateConverter() this.state, | ||||||
|       this.title, |       this.title, | ||||||
|       this.chaptersRead, |       this.chaptersRead, | ||||||
|       this.volumesOwned, |       this.volumesOwned, | ||||||
| @ -207,8 +207,8 @@ class _$_MangaTrackingData implements _MangaTrackingData { | |||||||
| 
 | 
 | ||||||
|   /// The state of the manga |   /// The state of the manga | ||||||
|   @override |   @override | ||||||
|   @MangaTrackingStateConverter() |   @MediumTrackingStateConverter() | ||||||
|   final MangaTrackingState state; |   final MediumTrackingState state; | ||||||
| 
 | 
 | ||||||
|   /// The title of the manga |   /// The title of the manga | ||||||
|   @override |   @override | ||||||
| @ -282,7 +282,7 @@ class _$_MangaTrackingData implements _MangaTrackingData { | |||||||
| abstract class _MangaTrackingData implements MangaTrackingData { | abstract class _MangaTrackingData implements MangaTrackingData { | ||||||
|   factory _MangaTrackingData( |   factory _MangaTrackingData( | ||||||
|       final String id, |       final String id, | ||||||
|       @MangaTrackingStateConverter() final MangaTrackingState state, |       @MediumTrackingStateConverter() final MediumTrackingState state, | ||||||
|       final String title, |       final String title, | ||||||
|       final int chaptersRead, |       final int chaptersRead, | ||||||
|       final int volumesOwned, |       final int volumesOwned, | ||||||
| @ -299,8 +299,8 @@ abstract class _MangaTrackingData implements MangaTrackingData { | |||||||
|   @override |   @override | ||||||
| 
 | 
 | ||||||
|   /// The state of the manga |   /// The state of the manga | ||||||
|   @MangaTrackingStateConverter() |   @MediumTrackingStateConverter() | ||||||
|   MangaTrackingState get state; |   MediumTrackingState get state; | ||||||
|   @override |   @override | ||||||
| 
 | 
 | ||||||
|   /// The title of the manga |   /// The title of the manga | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ part of 'manga.dart'; | |||||||
| _$_MangaTrackingData _$$_MangaTrackingDataFromJson(Map<String, dynamic> json) => | _$_MangaTrackingData _$$_MangaTrackingDataFromJson(Map<String, dynamic> json) => | ||||||
|     _$_MangaTrackingData( |     _$_MangaTrackingData( | ||||||
|       json['id'] as String, |       json['id'] as String, | ||||||
|       const MangaTrackingStateConverter().fromJson(json['state'] as int), |       const MediumTrackingStateConverter().fromJson(json['state'] as int), | ||||||
|       json['title'] as String, |       json['title'] as String, | ||||||
|       json['chaptersRead'] as int, |       json['chaptersRead'] as int, | ||||||
|       json['volumesOwned'] as int, |       json['volumesOwned'] as int, | ||||||
| @ -21,7 +21,7 @@ Map<String, dynamic> _$$_MangaTrackingDataToJson( | |||||||
|         _$_MangaTrackingData instance) => |         _$_MangaTrackingData instance) => | ||||||
|     <String, dynamic>{ |     <String, dynamic>{ | ||||||
|       'id': instance.id, |       'id': instance.id, | ||||||
|       'state': const MangaTrackingStateConverter().toJson(instance.state), |       'state': const MediumTrackingStateConverter().toJson(instance.state), | ||||||
|       'title': instance.title, |       'title': instance.title, | ||||||
|       'chaptersRead': instance.chaptersRead, |       'chaptersRead': instance.chaptersRead, | ||||||
|       'volumesOwned': instance.volumesOwned, |       '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. | /// The type of medium we are tracking. Useful for UI stuff. | ||||||
| enum TrackingMediumType { | enum TrackingMediumType { | ||||||
|   anime, |   anime, | ||||||
|   manga, |   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 |     return animes | ||||||
|       .cast<Map<String, dynamic>>() |       .cast<Map<String, dynamic>>() | ||||||
|       .map((Map<String, dynamic> anime) => AnimeTrackingData.fromJson(anime)) |       .map(AnimeTrackingData.fromJson) | ||||||
|       .toList(); |       .toList(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -56,7 +56,7 @@ class DatabaseService { | |||||||
| 
 | 
 | ||||||
|     return mangas |     return mangas | ||||||
|       .cast<Map<String, dynamic>>() |       .cast<Map<String, dynamic>>() | ||||||
|       .map((Map<String, dynamic> manga) => MangaTrackingData.fromJson(manga)) |       .map(MangaTrackingData.fromJson) | ||||||
|       .toList(); |       .toList(); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| import 'dart:math'; |  | ||||||
| import 'package:anitrack/src/data/anime.dart'; | import 'package:anitrack/src/data/anime.dart'; | ||||||
| import 'package:anitrack/src/data/manga.dart'; | import 'package:anitrack/src/data/manga.dart'; | ||||||
| import 'package:anitrack/src/data/type.dart'; | import 'package:anitrack/src/data/type.dart'; | ||||||
|  | |||||||
| @ -18,8 +18,10 @@ final _privateConstructorUsedError = UnsupportedError( | |||||||
| mixin _$AnimeListState { | mixin _$AnimeListState { | ||||||
|   List<AnimeTrackingData> get animes => throw _privateConstructorUsedError; |   List<AnimeTrackingData> get animes => throw _privateConstructorUsedError; | ||||||
|   List<MangaTrackingData> get mangas => throw _privateConstructorUsedError; |   List<MangaTrackingData> get mangas => throw _privateConstructorUsedError; | ||||||
|   AnimeTrackingState get animeFilterState => throw _privateConstructorUsedError; |   MediumTrackingState get animeFilterState => | ||||||
|   MangaTrackingState get mangaFilterState => throw _privateConstructorUsedError; |       throw _privateConstructorUsedError; | ||||||
|  |   MediumTrackingState get mangaFilterState => | ||||||
|  |       throw _privateConstructorUsedError; | ||||||
|   TrackingMediumType get trackingType => throw _privateConstructorUsedError; |   TrackingMediumType get trackingType => throw _privateConstructorUsedError; | ||||||
| 
 | 
 | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
| @ -35,8 +37,8 @@ abstract class $AnimeListStateCopyWith<$Res> { | |||||||
|   $Res call( |   $Res call( | ||||||
|       {List<AnimeTrackingData> animes, |       {List<AnimeTrackingData> animes, | ||||||
|       List<MangaTrackingData> mangas, |       List<MangaTrackingData> mangas, | ||||||
|       AnimeTrackingState animeFilterState, |       MediumTrackingState animeFilterState, | ||||||
|       MangaTrackingState mangaFilterState, |       MediumTrackingState mangaFilterState, | ||||||
|       TrackingMediumType trackingType}); |       TrackingMediumType trackingType}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -69,11 +71,11 @@ class _$AnimeListStateCopyWithImpl<$Res> | |||||||
|       animeFilterState: animeFilterState == freezed |       animeFilterState: animeFilterState == freezed | ||||||
|           ? _value.animeFilterState |           ? _value.animeFilterState | ||||||
|           : animeFilterState // ignore: cast_nullable_to_non_nullable |           : animeFilterState // ignore: cast_nullable_to_non_nullable | ||||||
|               as AnimeTrackingState, |               as MediumTrackingState, | ||||||
|       mangaFilterState: mangaFilterState == freezed |       mangaFilterState: mangaFilterState == freezed | ||||||
|           ? _value.mangaFilterState |           ? _value.mangaFilterState | ||||||
|           : mangaFilterState // ignore: cast_nullable_to_non_nullable |           : mangaFilterState // ignore: cast_nullable_to_non_nullable | ||||||
|               as MangaTrackingState, |               as MediumTrackingState, | ||||||
|       trackingType: trackingType == freezed |       trackingType: trackingType == freezed | ||||||
|           ? _value.trackingType |           ? _value.trackingType | ||||||
|           : trackingType // ignore: cast_nullable_to_non_nullable |           : trackingType // ignore: cast_nullable_to_non_nullable | ||||||
| @ -92,8 +94,8 @@ abstract class _$$_AnimeListStateCopyWith<$Res> | |||||||
|   $Res call( |   $Res call( | ||||||
|       {List<AnimeTrackingData> animes, |       {List<AnimeTrackingData> animes, | ||||||
|       List<MangaTrackingData> mangas, |       List<MangaTrackingData> mangas, | ||||||
|       AnimeTrackingState animeFilterState, |       MediumTrackingState animeFilterState, | ||||||
|       MangaTrackingState mangaFilterState, |       MediumTrackingState mangaFilterState, | ||||||
|       TrackingMediumType trackingType}); |       TrackingMediumType trackingType}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -128,11 +130,11 @@ class __$$_AnimeListStateCopyWithImpl<$Res> | |||||||
|       animeFilterState: animeFilterState == freezed |       animeFilterState: animeFilterState == freezed | ||||||
|           ? _value.animeFilterState |           ? _value.animeFilterState | ||||||
|           : animeFilterState // ignore: cast_nullable_to_non_nullable |           : animeFilterState // ignore: cast_nullable_to_non_nullable | ||||||
|               as AnimeTrackingState, |               as MediumTrackingState, | ||||||
|       mangaFilterState: mangaFilterState == freezed |       mangaFilterState: mangaFilterState == freezed | ||||||
|           ? _value.mangaFilterState |           ? _value.mangaFilterState | ||||||
|           : mangaFilterState // ignore: cast_nullable_to_non_nullable |           : mangaFilterState // ignore: cast_nullable_to_non_nullable | ||||||
|               as MangaTrackingState, |               as MediumTrackingState, | ||||||
|       trackingType: trackingType == freezed |       trackingType: trackingType == freezed | ||||||
|           ? _value.trackingType |           ? _value.trackingType | ||||||
|           : trackingType // ignore: cast_nullable_to_non_nullable |           : trackingType // ignore: cast_nullable_to_non_nullable | ||||||
| @ -147,8 +149,8 @@ class _$_AnimeListState implements _AnimeListState { | |||||||
|   _$_AnimeListState( |   _$_AnimeListState( | ||||||
|       {final List<AnimeTrackingData> animes = const [], |       {final List<AnimeTrackingData> animes = const [], | ||||||
|       final List<MangaTrackingData> mangas = const [], |       final List<MangaTrackingData> mangas = const [], | ||||||
|       this.animeFilterState = AnimeTrackingState.watching, |       this.animeFilterState = MediumTrackingState.ongoing, | ||||||
|       this.mangaFilterState = MangaTrackingState.reading, |       this.mangaFilterState = MediumTrackingState.ongoing, | ||||||
|       this.trackingType = TrackingMediumType.anime}) |       this.trackingType = TrackingMediumType.anime}) | ||||||
|       : _animes = animes, |       : _animes = animes, | ||||||
|         _mangas = mangas; |         _mangas = mangas; | ||||||
| @ -171,10 +173,10 @@ class _$_AnimeListState implements _AnimeListState { | |||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   @JsonKey() |   @JsonKey() | ||||||
|   final AnimeTrackingState animeFilterState; |   final MediumTrackingState animeFilterState; | ||||||
|   @override |   @override | ||||||
|   @JsonKey() |   @JsonKey() | ||||||
|   final MangaTrackingState mangaFilterState; |   final MediumTrackingState mangaFilterState; | ||||||
|   @override |   @override | ||||||
|   @JsonKey() |   @JsonKey() | ||||||
|   final TrackingMediumType trackingType; |   final TrackingMediumType trackingType; | ||||||
| @ -218,8 +220,8 @@ abstract class _AnimeListState implements AnimeListState { | |||||||
|   factory _AnimeListState( |   factory _AnimeListState( | ||||||
|       {final List<AnimeTrackingData> animes, |       {final List<AnimeTrackingData> animes, | ||||||
|       final List<MangaTrackingData> mangas, |       final List<MangaTrackingData> mangas, | ||||||
|       final AnimeTrackingState animeFilterState, |       final MediumTrackingState animeFilterState, | ||||||
|       final MangaTrackingState mangaFilterState, |       final MediumTrackingState mangaFilterState, | ||||||
|       final TrackingMediumType trackingType}) = _$_AnimeListState; |       final TrackingMediumType trackingType}) = _$_AnimeListState; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
| @ -227,9 +229,9 @@ abstract class _AnimeListState implements AnimeListState { | |||||||
|   @override |   @override | ||||||
|   List<MangaTrackingData> get mangas; |   List<MangaTrackingData> get mangas; | ||||||
|   @override |   @override | ||||||
|   AnimeTrackingState get animeFilterState; |   MediumTrackingState get animeFilterState; | ||||||
|   @override |   @override | ||||||
|   MangaTrackingState get mangaFilterState; |   MediumTrackingState get mangaFilterState; | ||||||
|   @override |   @override | ||||||
|   TrackingMediumType get trackingType; |   TrackingMediumType get trackingType; | ||||||
|   @override |   @override | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ class AnimeFilterChangedEvent extends AnimeListEvent { | |||||||
|   AnimeFilterChangedEvent(this.filterState); |   AnimeFilterChangedEvent(this.filterState); | ||||||
| 
 | 
 | ||||||
|   /// The state to filter |   /// The state to filter | ||||||
|   final AnimeTrackingState filterState; |   final MediumTrackingState filterState; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Triggered when the view is changed from the anime or the manga view | /// Triggered when the view is changed from the anime or the manga view | ||||||
| @ -60,7 +60,7 @@ class MangaFilterChangedEvent extends AnimeListEvent { | |||||||
|   MangaFilterChangedEvent(this.filterState); |   MangaFilterChangedEvent(this.filterState); | ||||||
| 
 | 
 | ||||||
|   /// The state to filter |   /// The state to filter | ||||||
|   final MangaTrackingState filterState; |   final MediumTrackingState filterState; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class MangaChapterIncrementedEvent extends AnimeListEvent { | class MangaChapterIncrementedEvent extends AnimeListEvent { | ||||||
|  | |||||||
| @ -5,8 +5,8 @@ class AnimeListState with _$AnimeListState { | |||||||
|   factory AnimeListState({ |   factory AnimeListState({ | ||||||
|     @Default([]) List<AnimeTrackingData> animes, |     @Default([]) List<AnimeTrackingData> animes, | ||||||
|     @Default([]) List<MangaTrackingData> mangas, |     @Default([]) List<MangaTrackingData> mangas, | ||||||
|     @Default(AnimeTrackingState.watching) AnimeTrackingState animeFilterState, |     @Default(MediumTrackingState.ongoing) MediumTrackingState animeFilterState, | ||||||
|     @Default(MangaTrackingState.reading) MangaTrackingState mangaFilterState, |     @Default(MediumTrackingState.ongoing) MediumTrackingState mangaFilterState, | ||||||
|     @Default(TrackingMediumType.anime) TrackingMediumType trackingType, |     @Default(TrackingMediumType.anime) TrackingMediumType trackingType, | ||||||
|   }) = _AnimeListState; |   }) = _AnimeListState; | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,9 +2,9 @@ import 'package:anitrack/src/data/anime.dart'; | |||||||
| import 'package:anitrack/src/data/manga.dart'; | import 'package:anitrack/src/data/manga.dart'; | ||||||
| import 'package:anitrack/src/data/search_result.dart'; | import 'package:anitrack/src/data/search_result.dart'; | ||||||
| import 'package:anitrack/src/data/type.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/anime_list_bloc.dart' as list; | ||||||
| import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; | import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; | ||||||
|  | import 'package:anitrack/src/ui/constants.dart'; | ||||||
| import 'package:bloc/bloc.dart'; | import 'package:bloc/bloc.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:get_it/get_it.dart'; | import 'package:get_it/get_it.dart'; | ||||||
| @ -34,7 +34,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | |||||||
| 
 | 
 | ||||||
|     GetIt.I.get<NavigationBloc>().add( |     GetIt.I.get<NavigationBloc>().add( | ||||||
|       PushedNamedEvent( |       PushedNamedEvent( | ||||||
|         NavigationDestination(animeSearchRoute), |         const NavigationDestination(animeSearchRoute), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @ -101,7 +101,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | |||||||
|         list.AnimeAddedEvent( |         list.AnimeAddedEvent( | ||||||
|           AnimeTrackingData( |           AnimeTrackingData( | ||||||
|             event.result.id, |             event.result.id, | ||||||
|             AnimeTrackingState.watching, |             MediumTrackingState.ongoing, | ||||||
|             event.result.title, |             event.result.title, | ||||||
|             0, |             0, | ||||||
|             event.result.total, |             event.result.total, | ||||||
| @ -111,14 +111,14 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> { | |||||||
|         list.MangaAddedEvent( |         list.MangaAddedEvent( | ||||||
|           MangaTrackingData( |           MangaTrackingData( | ||||||
|             event.result.id, |             event.result.id, | ||||||
|             MangaTrackingState.reading, |             MediumTrackingState.ongoing, | ||||||
|             event.result.title, |             event.result.title, | ||||||
|             0, |             0, | ||||||
|             0, |             0, | ||||||
|             event.result.total, |             event.result.total, | ||||||
|             event.result.thumbnailUrl, |             event.result.thumbnailUrl, | ||||||
|           ), |           ), | ||||||
|         ) |         ), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     GetIt.I.get<NavigationBloc>().add( |     GetIt.I.get<NavigationBloc>().add( | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| import 'package:anitrack/src/data/anime.dart'; | import 'package:anitrack/src/data/anime.dart'; | ||||||
| import 'package:anitrack/src/data/manga.dart'; | import 'package:anitrack/src/data/manga.dart'; | ||||||
| import 'package:anitrack/src/data/type.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/anime_list_bloc.dart'; | ||||||
| import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; | import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; | ||||||
| import 'package:anitrack/src/ui/constants.dart'; | import 'package:anitrack/src/ui/constants.dart'; | ||||||
| import 'package:anitrack/src/service/database.dart'; |  | ||||||
| import 'package:bloc/bloc.dart'; | import 'package:bloc/bloc.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:get_it/get_it.dart'; | import 'package:get_it/get_it.dart'; | ||||||
| @ -30,9 +30,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> { | |||||||
| 
 | 
 | ||||||
|     GetIt.I.get<NavigationBloc>().add( |     GetIt.I.get<NavigationBloc>().add( | ||||||
|       PushedNamedEvent( |       PushedNamedEvent( | ||||||
|         NavigationDestination( |         const NavigationDestination(detailsRoute), | ||||||
|           detailsRoute, |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @ -47,9 +45,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> { | |||||||
| 
 | 
 | ||||||
|     GetIt.I.get<NavigationBloc>().add( |     GetIt.I.get<NavigationBloc>().add( | ||||||
|       PushedNamedEvent( |       PushedNamedEvent( | ||||||
|         NavigationDestination( |         const NavigationDestination(detailsRoute), | ||||||
|           detailsRoute, |  | ||||||
|         ), |  | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import 'package:bloc/bloc.dart'; | import 'package:bloc/bloc.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; |  | ||||||
| 
 | 
 | ||||||
| part 'navigation_event.dart'; | part 'navigation_event.dart'; | ||||||
| part 'navigation_state.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/data/type.dart'; | ||||||
| import 'package:anitrack/src/ui/bloc/anime_list_bloc.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/anime_search_bloc.dart'; | ||||||
| @ -11,6 +9,10 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | import 'package:flutter_bloc/flutter_bloc.dart'; | ||||||
| 
 | 
 | ||||||
| class AnimeListPage extends StatelessWidget { | class AnimeListPage extends StatelessWidget { | ||||||
|  |   AnimeListPage({ | ||||||
|  |     super.key, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( |   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( | ||||||
|     builder: (_) => AnimeListPage(), |     builder: (_) => AnimeListPage(), | ||||||
|     settings: const RouteSettings( |     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) { |   Widget _getPopupButton(BuildContext context, AnimeListState state) { | ||||||
|     switch (state.trackingType) { |     switch (state.trackingType) { | ||||||
|       case TrackingMediumType.anime: |       case TrackingMediumType.anime: | ||||||
|         return PopupMenuButton( |         return PopupMenuButton( | ||||||
|           icon: Icon( |           icon: const Icon( | ||||||
|             Icons.filter_list, |             Icons.filter_list, | ||||||
|           ), |           ), | ||||||
|           initialValue: state.animeFilterState, |           initialValue: state.animeFilterState, | ||||||
| @ -40,32 +67,11 @@ class AnimeListPage extends StatelessWidget { | |||||||
|               AnimeFilterChangedEvent(filterState), |               AnimeFilterChangedEvent(filterState), | ||||||
|             ); |             ); | ||||||
|           }, |           }, | ||||||
|           itemBuilder: (_) => [ |           itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime), | ||||||
|             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'), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ); |         ); | ||||||
|       case TrackingMediumType.manga: |       case TrackingMediumType.manga: | ||||||
|         return PopupMenuButton( |         return PopupMenuButton( | ||||||
|           icon: Icon( |           icon: const Icon( | ||||||
|             Icons.filter_list, |             Icons.filter_list, | ||||||
|           ), |           ), | ||||||
|           initialValue: state.mangaFilterState, |           initialValue: state.mangaFilterState, | ||||||
| @ -74,28 +80,7 @@ class AnimeListPage extends StatelessWidget { | |||||||
|               MangaFilterChangedEvent(filterState), |               MangaFilterChangedEvent(filterState), | ||||||
|             ); |             ); | ||||||
|           }, |           }, | ||||||
|           itemBuilder: (_) => [ |           itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga), | ||||||
|             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'), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -107,7 +92,7 @@ class AnimeListPage extends StatelessWidget { | |||||||
|         return Scaffold( |         return Scaffold( | ||||||
|           appBar: AppBar( |           appBar: AppBar( | ||||||
|             title: Text( |             title: Text( | ||||||
|               _getPageTitle(state.trackingType) |               _getPageTitle(state.trackingType), | ||||||
|             ), |             ), | ||||||
|             actions: [ |             actions: [ | ||||||
|               _getPopupButton(context, state), |               _getPopupButton(context, state), | ||||||
| @ -120,7 +105,7 @@ class AnimeListPage extends StatelessWidget { | |||||||
|                 itemCount: state.animes.length, |                 itemCount: state.animes.length, | ||||||
|                 itemBuilder: (context, index) { |                 itemBuilder: (context, index) { | ||||||
|                   final anime = state.animes[index]; |                   final anime = state.animes[index]; | ||||||
|                   if (state.animeFilterState != AnimeTrackingState.all) { |                   if (state.animeFilterState != MediumTrackingState.all) { | ||||||
|                     if (anime.state != state.animeFilterState) return Container(); |                     if (anime.state != state.animeFilterState) return Container(); | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
| @ -157,7 +142,7 @@ class AnimeListPage extends StatelessWidget { | |||||||
|                 itemCount: state.mangas.length, |                 itemCount: state.mangas.length, | ||||||
|                 itemBuilder: (context, index) { |                 itemBuilder: (context, index) { | ||||||
|                   final manga = state.mangas[index]; |                   final manga = state.mangas[index]; | ||||||
|                   if (state.mangaFilterState != MangaTrackingState.all) { |                   if (state.mangaFilterState != MediumTrackingState.all) { | ||||||
|                     if (manga.state != state.mangaFilterState) return Container(); |                     if (manga.state != state.mangaFilterState) return Container(); | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
| @ -216,7 +201,7 @@ class AnimeListPage extends StatelessWidget { | |||||||
| 
 | 
 | ||||||
|               _controller.jumpToPage(index); |               _controller.jumpToPage(index); | ||||||
|             }, |             }, | ||||||
|             items: <BottomBarItem>[ |             items: const [ | ||||||
|               BottomBarItem( |               BottomBarItem( | ||||||
|                 icon: Icon(Icons.tv), |                 icon: Icon(Icons.tv), | ||||||
|                 title: Text('Anime'), |                 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/data/type.dart'; | ||||||
| import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; | ||||||
| import 'package:anitrack/src/ui/constants.dart'; | import 'package:anitrack/src/ui/constants.dart'; | ||||||
| import 'package:anitrack/src/ui/widgets/list_item.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 { | class AnimeSearchPage extends StatelessWidget { | ||||||
|  |   const AnimeSearchPage({ | ||||||
|  |     super.key, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( |   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( | ||||||
|     builder: (_) => AnimeSearchPage(), |     builder: (_) => const AnimeSearchPage(), | ||||||
|     settings: const RouteSettings( |     settings: const RouteSettings( | ||||||
|       name: animeSearchRoute, |       name: animeSearchRoute, | ||||||
|     ), |     ), | ||||||
| @ -32,7 +34,7 @@ class AnimeSearchPage extends StatelessWidget { | |||||||
|               Padding( |               Padding( | ||||||
|                 padding: const EdgeInsets.all(8), |                 padding: const EdgeInsets.all(8), | ||||||
|                 child: TextField( |                 child: TextField( | ||||||
|                   decoration: InputDecoration( |                   decoration: const InputDecoration( | ||||||
|                     border: OutlineInputBorder(), |                     border: OutlineInputBorder(), | ||||||
|                     labelText: 'Search query', |                     labelText: 'Search query', | ||||||
|                   ), |                   ), | ||||||
| @ -50,9 +52,8 @@ class AnimeSearchPage extends StatelessWidget { | |||||||
|               ), |               ), | ||||||
| 
 | 
 | ||||||
|               if (state.working) |               if (state.working) | ||||||
|                 Expanded( |                 const Expanded( | ||||||
|                   child: Align( |                   child: Align( | ||||||
|                     alignment: Alignment.center, |  | ||||||
|                     child: CircularProgressIndicator(), |                     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/data/type.dart'; | ||||||
| import 'package:anitrack/src/ui/bloc/details_bloc.dart'; | import 'package:anitrack/src/ui/bloc/details_bloc.dart'; | ||||||
| import 'package:anitrack/src/ui/constants.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/image.dart'; | ||||||
| import 'package:anitrack/src/ui/widgets/list_item.dart'; |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_bloc/flutter_bloc.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 { | class DetailsPage extends StatelessWidget { | ||||||
|  |   const DetailsPage({ | ||||||
|  |     super.key, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( |   static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( | ||||||
|     builder: (_) => DetailsPage(), |     builder: (_) => const DetailsPage(), | ||||||
|     settings: const RouteSettings( |     settings: const RouteSettings( | ||||||
|       name: detailsRoute, |       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 |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text('Details'), |         title: const Text('Details'), | ||||||
|       ), |       ), | ||||||
|       body: BlocBuilder<DetailsBloc, DetailsState>( |       body: BlocBuilder<DetailsBloc, DetailsState>( | ||||||
|         builder: (context, state) { |         builder: (context, state) { | ||||||
| @ -69,101 +53,63 @@ class DetailsPage extends StatelessWidget { | |||||||
|                         children: [ |                         children: [ | ||||||
|                           Text( |                           Text( | ||||||
|                             state.trackingType == TrackingMediumType.anime ? |                             state.trackingType == TrackingMediumType.anime ? | ||||||
|                               (state.data as AnimeTrackingData).title : |                             (state.data as AnimeTrackingData).title : | ||||||
|                               (state.data as MangaTrackingData).title, |                             (state.data as MangaTrackingData).title, | ||||||
|                             textAlign: TextAlign.left, |                             textAlign: TextAlign.left, | ||||||
|                             style: Theme.of(context).textTheme.titleLarge, |                             style: Theme.of(context).textTheme.titleLarge, | ||||||
|                             maxLines: 2, |                             maxLines: 2, | ||||||
|                             softWrap: true, |                             softWrap: true, | ||||||
|                             overflow: TextOverflow.ellipsis, |                             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. |   /// The URL to the cover image. | ||||||
|   final String url; |   final String url; | ||||||
| 
 | 
 | ||||||
|  |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return ClipRRect( |     return ClipRRect( | ||||||
|       borderRadius: BorderRadius.circular(8), |       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/image.dart'; | ||||||
| import 'package:anitrack/src/ui/widgets/swipe_icon.dart'; | import 'package:anitrack/src/ui/widgets/swipe_icon.dart'; | ||||||
| import 'package:flutter/material.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 | /// A widget for displaying simple data about an anime in the listview | ||||||
| class ListItem extends StatelessWidget { | class ListItem extends StatelessWidget { | ||||||
|   ListItem({ |   const ListItem({ | ||||||
|     required this.thumbnailUrl, |     required this.thumbnailUrl, | ||||||
|     required this.title, |     required this.title, | ||||||
|     this.onLeftSwipe, |     this.onLeftSwipe, | ||||||
| @ -42,14 +41,14 @@ class ListItem extends StatelessWidget { | |||||||
|       key: UniqueKey(), |       key: UniqueKey(), | ||||||
|       backgroundBuilder: (_, direction, __) { |       backgroundBuilder: (_, direction, __) { | ||||||
|         if (direction == SwipeDirection.endToStart) { |         if (direction == SwipeDirection.endToStart) { | ||||||
|           return Align( |           return const Align( | ||||||
|             alignment: Alignment.centerRight, |             alignment: Alignment.centerRight, | ||||||
|             child: SwipeIcon( |             child: SwipeIcon( | ||||||
|               Icons.add, |               Icons.add, | ||||||
|             ), |             ), | ||||||
|           ); |           ); | ||||||
|         } else if (direction == SwipeDirection.startToEnd) { |         } else if (direction == SwipeDirection.startToEnd) { | ||||||
|           return Align( |           return const Align( | ||||||
|             alignment: Alignment.centerLeft, |             alignment: Alignment.centerLeft, | ||||||
|             child: SwipeIcon( |             child: SwipeIcon( | ||||||
|               Icons.remove, |               Icons.remove, | ||||||
|  | |||||||
| @ -576,6 +576,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.2" |     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: |   watcher: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|  | |||||||
| @ -23,12 +23,13 @@ dependencies: | |||||||
| 
 | 
 | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   build_runner: ^2.1.11 |   build_runner: ^2.1.11 | ||||||
|   flutter_test: |  | ||||||
|     sdk: flutter |  | ||||||
|   flutter_launcher_icons: ^0.11.0 |   flutter_launcher_icons: ^0.11.0 | ||||||
|   flutter_lints: ^2.0.0 |   flutter_lints: ^2.0.0 | ||||||
|  |   flutter_test: | ||||||
|  |     sdk: flutter | ||||||
|   freezed: ^2.1.0+1 |   freezed: ^2.1.0+1 | ||||||
|   json_serializable: ^6.3.1 |   json_serializable: ^6.3.1 | ||||||
|  |   very_good_analysis: ^3.0.1 | ||||||
| 
 | 
 | ||||||
| flutter: | flutter: | ||||||
|   uses-material-design: true |   uses-material-design: true | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user