feat: Add i18n using slang
This commit is contained in:
		
							parent
							
								
									7530fe5b80
								
							
						
					
					
						commit
						13ed7144cb
					
				
							
								
								
									
										56
									
								
								assets/i18n/strings.i18n.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								assets/i18n/strings.i18n.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "settings": {
 | 
				
			||||||
 | 
					        "title": "Settings",
 | 
				
			||||||
 | 
					        "importAnime": "Import anime list",
 | 
				
			||||||
 | 
					        "importAnimeDesc": "Import anime list exported from MyAnimeList.",
 | 
				
			||||||
 | 
					        "invalidAnimeListTitle": "Invalid anime list",
 | 
				
			||||||
 | 
					        "invalidAnimeListBody": "The selected file is not a MAL anime list. It lacks the \".xml.gz\" suffix.",
 | 
				
			||||||
 | 
					        "importManga": "Import manga list",
 | 
				
			||||||
 | 
					        "importMangaDesc": "Import manga list exported from MyAnimeList.",
 | 
				
			||||||
 | 
					        "invalidMangaListTitle": "Invalid manga list",
 | 
				
			||||||
 | 
					        "invalidMangaListBody": "The selected file is not a MAL manga list. It lacks the \".xml.gz\" suffix.",
 | 
				
			||||||
 | 
					        "importIndicator": "$current of $total"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "about": {
 | 
				
			||||||
 | 
					        "title": "About",
 | 
				
			||||||
 | 
					        "source": "Source code"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "tooltips": {
 | 
				
			||||||
 | 
					        "addNewItem": "Add new item"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "content": {
 | 
				
			||||||
 | 
					        "anime": "Anime",
 | 
				
			||||||
 | 
					        "manga": "Manga"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "search": {
 | 
				
			||||||
 | 
					        "anime": "Anime search",
 | 
				
			||||||
 | 
					        "manga": "Manga search",
 | 
				
			||||||
 | 
					        "query": "Search query"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "details": {
 | 
				
			||||||
 | 
					        "title": "Details",
 | 
				
			||||||
 | 
					        "removeTitle": "Remove $title?",
 | 
				
			||||||
 | 
					        "removeBody": "Are you sure you want to remove \"$title\" from the list?",
 | 
				
			||||||
 | 
					        "removeButton": "Remove",
 | 
				
			||||||
 | 
					        "cancelButton": "Cancel",
 | 
				
			||||||
 | 
					        "watchState": "Watch state",
 | 
				
			||||||
 | 
					        "readState": "Read state",
 | 
				
			||||||
 | 
					        "episodes": "Episodes",
 | 
				
			||||||
 | 
					        "chapters": "Chapters",
 | 
				
			||||||
 | 
					        "volumesOwned": "Volumes owned"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "data": {
 | 
				
			||||||
 | 
					        "ongoing": {
 | 
				
			||||||
 | 
					            "anime": "Watching",
 | 
				
			||||||
 | 
					            "manga": "Reading"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "completed": "Completed",
 | 
				
			||||||
 | 
					        "planned": {
 | 
				
			||||||
 | 
					            "anime": "Plan to watch",
 | 
				
			||||||
 | 
					            "manga": "Plan to read"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "dropped": "Dropped",
 | 
				
			||||||
 | 
					        "paused": "Paused",
 | 
				
			||||||
 | 
					        "all": "All"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								build.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								build.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					targets:
 | 
				
			||||||
 | 
					  $default:
 | 
				
			||||||
 | 
					    builders:
 | 
				
			||||||
 | 
					      slang_build_runner:
 | 
				
			||||||
 | 
					        options:
 | 
				
			||||||
 | 
					          input_directory: assets/i18n
 | 
				
			||||||
 | 
					          output_directory: lib/i18n
 | 
				
			||||||
							
								
								
									
										24
									
								
								flake.lock
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								flake.lock
									
									
									
									
									
								
							@ -1,15 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "nodes": {
 | 
					  "nodes": {
 | 
				
			||||||
    "flake-utils": {
 | 
					    "flake-utils": {
 | 
				
			||||||
      "inputs": {
 | 
					 | 
				
			||||||
        "systems": "systems"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "locked": {
 | 
					      "locked": {
 | 
				
			||||||
        "lastModified": 1681202837,
 | 
					        "lastModified": 1667395993,
 | 
				
			||||||
        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
 | 
					        "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
 | 
				
			||||||
        "owner": "numtide",
 | 
					        "owner": "numtide",
 | 
				
			||||||
        "repo": "flake-utils",
 | 
					        "repo": "flake-utils",
 | 
				
			||||||
        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
 | 
					        "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
 | 
				
			||||||
        "type": "github"
 | 
					        "type": "github"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "original": {
 | 
					      "original": {
 | 
				
			||||||
@ -39,21 +36,6 @@
 | 
				
			|||||||
        "flake-utils": "flake-utils",
 | 
					        "flake-utils": "flake-utils",
 | 
				
			||||||
        "nixpkgs": "nixpkgs"
 | 
					        "nixpkgs": "nixpkgs"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "systems": {
 | 
					 | 
				
			||||||
      "locked": {
 | 
					 | 
				
			||||||
        "lastModified": 1681028828,
 | 
					 | 
				
			||||||
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
 | 
					 | 
				
			||||||
        "owner": "nix-systems",
 | 
					 | 
				
			||||||
        "repo": "default",
 | 
					 | 
				
			||||||
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
 | 
					 | 
				
			||||||
        "type": "github"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "original": {
 | 
					 | 
				
			||||||
        "owner": "nix-systems",
 | 
					 | 
				
			||||||
        "repo": "default",
 | 
					 | 
				
			||||||
        "type": "github"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "root": "root",
 | 
					  "root": "root",
 | 
				
			||||||
 | 
				
			|||||||
@ -38,9 +38,8 @@
 | 
				
			|||||||
    devShell = pkgs.mkShell {
 | 
					    devShell = pkgs.mkShell {
 | 
				
			||||||
      buildInputs = with pkgs; [
 | 
					      buildInputs = with pkgs; [
 | 
				
			||||||
        flutter pinnedJDK android.platform-tools dart scrcpy # Flutter/Android
 | 
					        flutter pinnedJDK android.platform-tools dart scrcpy # Flutter/Android
 | 
				
			||||||
	pythonEnv gnumake # Build scripts
 | 
						      gitlint jq # Code hygiene
 | 
				
			||||||
	gitlint jq # Code hygiene
 | 
					      	ripgrep # General utilities
 | 
				
			||||||
	ripgrep # General utilities
 | 
					 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      JAVA_HOME = pinnedJDK;
 | 
					      JAVA_HOME = pinnedJDK;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										322
									
								
								lib/i18n/strings.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								lib/i18n/strings.g.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,322 @@
 | 
				
			|||||||
 | 
					/// Generated file. Do not edit.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Locales: 1
 | 
				
			||||||
 | 
					/// Strings: 36
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Built on 2023-06-21 at 20:03 UTC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// coverage:ignore-file
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/widgets.dart';
 | 
				
			||||||
 | 
					import 'package:slang/builder/model/node.dart';
 | 
				
			||||||
 | 
					import 'package:slang_flutter/slang_flutter.dart';
 | 
				
			||||||
 | 
					export 'package:slang_flutter/slang_flutter.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AppLocale _baseLocale = AppLocale.en;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Supported locales, see extension methods below.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Usage:
 | 
				
			||||||
 | 
					/// - LocaleSettings.setLocale(AppLocale.en) // set locale
 | 
				
			||||||
 | 
					/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum
 | 
				
			||||||
 | 
					/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check
 | 
				
			||||||
 | 
					enum AppLocale with BaseAppLocale<AppLocale, _StringsEn> {
 | 
				
			||||||
 | 
						en(languageCode: 'en', build: _StringsEn.build);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const AppLocale({required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@override final String languageCode;
 | 
				
			||||||
 | 
						@override final String? scriptCode;
 | 
				
			||||||
 | 
						@override final String? countryCode;
 | 
				
			||||||
 | 
						@override final TranslationBuilder<AppLocale, _StringsEn> build;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Gets current instance managed by [LocaleSettings].
 | 
				
			||||||
 | 
						_StringsEn get translations => LocaleSettings.instance.translationMap[this]!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Method A: Simple
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// No rebuild after locale change.
 | 
				
			||||||
 | 
					/// Translation happens during initialization of the widget (call of t).
 | 
				
			||||||
 | 
					/// Configurable via 'translate_var'.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Usage:
 | 
				
			||||||
 | 
					/// String a = t.someKey.anotherKey;
 | 
				
			||||||
 | 
					/// String b = t['someKey.anotherKey']; // Only for edge cases!
 | 
				
			||||||
 | 
					_StringsEn get t => LocaleSettings.instance.currentTranslations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Method B: Advanced
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// All widgets using this method will trigger a rebuild when locale changes.
 | 
				
			||||||
 | 
					/// Use this if you have e.g. a settings page where the user can select the locale during runtime.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Step 1:
 | 
				
			||||||
 | 
					/// wrap your App with
 | 
				
			||||||
 | 
					/// TranslationProvider(
 | 
				
			||||||
 | 
					/// 	child: MyApp()
 | 
				
			||||||
 | 
					/// );
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Step 2:
 | 
				
			||||||
 | 
					/// final t = Translations.of(context); // Get t variable.
 | 
				
			||||||
 | 
					/// String a = t.someKey.anotherKey; // Use t variable.
 | 
				
			||||||
 | 
					/// String b = t['someKey.anotherKey']; // Only for edge cases!
 | 
				
			||||||
 | 
					class Translations {
 | 
				
			||||||
 | 
						Translations._(); // no constructor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static _StringsEn of(BuildContext context) => InheritedLocaleData.of<AppLocale, _StringsEn>(context).translations;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The provider for method B
 | 
				
			||||||
 | 
					class TranslationProvider extends BaseTranslationProvider<AppLocale, _StringsEn> {
 | 
				
			||||||
 | 
						TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static InheritedLocaleData<AppLocale, _StringsEn> of(BuildContext context) => InheritedLocaleData.of<AppLocale, _StringsEn>(context);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Method B shorthand via [BuildContext] extension method.
 | 
				
			||||||
 | 
					/// Configurable via 'translate_var'.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Usage (e.g. in a widget's build method):
 | 
				
			||||||
 | 
					/// context.t.someKey.anotherKey
 | 
				
			||||||
 | 
					extension BuildContextTranslationsExtension on BuildContext {
 | 
				
			||||||
 | 
						_StringsEn get t => TranslationProvider.of(this).translations;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Manages all translation instances and the current locale
 | 
				
			||||||
 | 
					class LocaleSettings extends BaseFlutterLocaleSettings<AppLocale, _StringsEn> {
 | 
				
			||||||
 | 
						LocaleSettings._() : super(utils: AppLocaleUtils.instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static final instance = LocaleSettings._();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// static aliases (checkout base methods for documentation)
 | 
				
			||||||
 | 
						static AppLocale get currentLocale => instance.currentLocale;
 | 
				
			||||||
 | 
						static Stream<AppLocale> getLocaleStream() => instance.getLocaleStream();
 | 
				
			||||||
 | 
						static AppLocale setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale);
 | 
				
			||||||
 | 
						static AppLocale setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
 | 
				
			||||||
 | 
						static AppLocale useDeviceLocale() => instance.useDeviceLocale();
 | 
				
			||||||
 | 
						@Deprecated('Use [AppLocaleUtils.supportedLocales]') static List<Locale> get supportedLocales => instance.supportedLocales;
 | 
				
			||||||
 | 
						@Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List<String> get supportedLocalesRaw => instance.supportedLocalesRaw;
 | 
				
			||||||
 | 
						static void setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver(
 | 
				
			||||||
 | 
							language: language,
 | 
				
			||||||
 | 
							locale: locale,
 | 
				
			||||||
 | 
							cardinalResolver: cardinalResolver,
 | 
				
			||||||
 | 
							ordinalResolver: ordinalResolver,
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Provides utility functions without any side effects.
 | 
				
			||||||
 | 
					class AppLocaleUtils extends BaseAppLocaleUtils<AppLocale, _StringsEn> {
 | 
				
			||||||
 | 
						AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static final instance = AppLocaleUtils._();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// static aliases (checkout base methods for documentation)
 | 
				
			||||||
 | 
						static AppLocale parse(String rawLocale) => instance.parse(rawLocale);
 | 
				
			||||||
 | 
						static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode);
 | 
				
			||||||
 | 
						static AppLocale findDeviceLocale() => instance.findDeviceLocale();
 | 
				
			||||||
 | 
						static List<Locale> get supportedLocales => instance.supportedLocales;
 | 
				
			||||||
 | 
						static List<String> get supportedLocalesRaw => instance.supportedLocalesRaw;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// translations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: <root>
 | 
				
			||||||
 | 
					class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// You can call this constructor and build your own translation instance of this locale.
 | 
				
			||||||
 | 
						/// Constructing via the enum [AppLocale.build] is preferred.
 | 
				
			||||||
 | 
						_StringsEn.build({Map<String, Node>? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver})
 | 
				
			||||||
 | 
							: assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'),
 | 
				
			||||||
 | 
							  $meta = TranslationMetadata(
 | 
				
			||||||
 | 
							    locale: AppLocale.en,
 | 
				
			||||||
 | 
							    overrides: overrides ?? {},
 | 
				
			||||||
 | 
							    cardinalResolver: cardinalResolver,
 | 
				
			||||||
 | 
							    ordinalResolver: ordinalResolver,
 | 
				
			||||||
 | 
							  ) {
 | 
				
			||||||
 | 
							$meta.setFlatMapFunction(_flatMapFunction);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Metadata for the translations of <en>.
 | 
				
			||||||
 | 
						@override final TranslationMetadata<AppLocale, _StringsEn> $meta;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Access flat map
 | 
				
			||||||
 | 
						dynamic operator[](String key) => $meta.getTranslation(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						late final _StringsEn _root = this; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						late final _StringsSettingsEn settings = _StringsSettingsEn._(_root);
 | 
				
			||||||
 | 
						late final _StringsAboutEn about = _StringsAboutEn._(_root);
 | 
				
			||||||
 | 
						late final _StringsTooltipsEn tooltips = _StringsTooltipsEn._(_root);
 | 
				
			||||||
 | 
						late final _StringsContentEn content = _StringsContentEn._(_root);
 | 
				
			||||||
 | 
						late final _StringsSearchEn search = _StringsSearchEn._(_root);
 | 
				
			||||||
 | 
						late final _StringsDetailsEn details = _StringsDetailsEn._(_root);
 | 
				
			||||||
 | 
						late final _StringsDataEn data = _StringsDataEn._(_root);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: settings
 | 
				
			||||||
 | 
					class _StringsSettingsEn {
 | 
				
			||||||
 | 
						_StringsSettingsEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get title => 'Settings';
 | 
				
			||||||
 | 
						String get importAnime => 'Import anime list';
 | 
				
			||||||
 | 
						String get importAnimeDesc => 'Import anime list exported from MyAnimeList.';
 | 
				
			||||||
 | 
						String get invalidAnimeListTitle => 'Invalid anime list';
 | 
				
			||||||
 | 
						String get invalidAnimeListBody => 'The selected file is not a MAL anime list. It lacks the ".xml.gz" suffix.';
 | 
				
			||||||
 | 
						String get importManga => 'Import manga list';
 | 
				
			||||||
 | 
						String get importMangaDesc => 'Import manga list exported from MyAnimeList.';
 | 
				
			||||||
 | 
						String get invalidMangaListTitle => 'Invalid manga list';
 | 
				
			||||||
 | 
						String get invalidMangaListBody => 'The selected file is not a MAL manga list. It lacks the ".xml.gz" suffix.';
 | 
				
			||||||
 | 
						String importIndicator({required Object current, required Object total}) => '${current} of ${total}';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: about
 | 
				
			||||||
 | 
					class _StringsAboutEn {
 | 
				
			||||||
 | 
						_StringsAboutEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get title => 'About';
 | 
				
			||||||
 | 
						String get source => 'Source code';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: tooltips
 | 
				
			||||||
 | 
					class _StringsTooltipsEn {
 | 
				
			||||||
 | 
						_StringsTooltipsEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get addNewItem => 'Add new item';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: content
 | 
				
			||||||
 | 
					class _StringsContentEn {
 | 
				
			||||||
 | 
						_StringsContentEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get anime => 'Anime';
 | 
				
			||||||
 | 
						String get manga => 'Manga';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: search
 | 
				
			||||||
 | 
					class _StringsSearchEn {
 | 
				
			||||||
 | 
						_StringsSearchEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get anime => 'Anime search';
 | 
				
			||||||
 | 
						String get manga => 'Manga search';
 | 
				
			||||||
 | 
						String get query => 'Search query';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: details
 | 
				
			||||||
 | 
					class _StringsDetailsEn {
 | 
				
			||||||
 | 
						_StringsDetailsEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get title => 'Details';
 | 
				
			||||||
 | 
						String removeTitle({required Object title}) => 'Remove ${title}?';
 | 
				
			||||||
 | 
						String removeBody({required Object title}) => 'Are you sure you want to remove "${title}" from the list?';
 | 
				
			||||||
 | 
						String get removeButton => 'Remove';
 | 
				
			||||||
 | 
						String get cancelButton => 'Cancel';
 | 
				
			||||||
 | 
						String get watchState => 'Watch state';
 | 
				
			||||||
 | 
						String get readState => 'Read state';
 | 
				
			||||||
 | 
						String get episodes => 'Episodes';
 | 
				
			||||||
 | 
						String get chapters => 'Chapters';
 | 
				
			||||||
 | 
						String get volumesOwned => 'Volumes owned';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: data
 | 
				
			||||||
 | 
					class _StringsDataEn {
 | 
				
			||||||
 | 
						_StringsDataEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						late final _StringsDataOngoingEn ongoing = _StringsDataOngoingEn._(_root);
 | 
				
			||||||
 | 
						String get completed => 'Completed';
 | 
				
			||||||
 | 
						late final _StringsDataPlannedEn planned = _StringsDataPlannedEn._(_root);
 | 
				
			||||||
 | 
						String get dropped => 'Dropped';
 | 
				
			||||||
 | 
						String get paused => 'Paused';
 | 
				
			||||||
 | 
						String get all => 'All';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: data.ongoing
 | 
				
			||||||
 | 
					class _StringsDataOngoingEn {
 | 
				
			||||||
 | 
						_StringsDataOngoingEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get anime => 'Watching';
 | 
				
			||||||
 | 
						String get manga => 'Reading';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path: data.planned
 | 
				
			||||||
 | 
					class _StringsDataPlannedEn {
 | 
				
			||||||
 | 
						_StringsDataPlannedEn._(this._root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final _StringsEn _root; // ignore: unused_field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Translations
 | 
				
			||||||
 | 
						String get anime => 'Plan to watch';
 | 
				
			||||||
 | 
						String get manga => 'Plan to read';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Flat map(s) containing all translations.
 | 
				
			||||||
 | 
					/// Only for edge cases! For simple maps, use the map function of this library.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension on _StringsEn {
 | 
				
			||||||
 | 
						dynamic _flatMapFunction(String path) {
 | 
				
			||||||
 | 
							switch (path) {
 | 
				
			||||||
 | 
								case 'settings.title': return 'Settings';
 | 
				
			||||||
 | 
								case 'settings.importAnime': return 'Import anime list';
 | 
				
			||||||
 | 
								case 'settings.importAnimeDesc': return 'Import anime list exported from MyAnimeList.';
 | 
				
			||||||
 | 
								case 'settings.invalidAnimeListTitle': return 'Invalid anime list';
 | 
				
			||||||
 | 
								case 'settings.invalidAnimeListBody': return 'The selected file is not a MAL anime list. It lacks the ".xml.gz" suffix.';
 | 
				
			||||||
 | 
								case 'settings.importManga': return 'Import manga list';
 | 
				
			||||||
 | 
								case 'settings.importMangaDesc': return 'Import manga list exported from MyAnimeList.';
 | 
				
			||||||
 | 
								case 'settings.invalidMangaListTitle': return 'Invalid manga list';
 | 
				
			||||||
 | 
								case 'settings.invalidMangaListBody': return 'The selected file is not a MAL manga list. It lacks the ".xml.gz" suffix.';
 | 
				
			||||||
 | 
								case 'settings.importIndicator': return ({required Object current, required Object total}) => '${current} of ${total}';
 | 
				
			||||||
 | 
								case 'about.title': return 'About';
 | 
				
			||||||
 | 
								case 'about.source': return 'Source code';
 | 
				
			||||||
 | 
								case 'tooltips.addNewItem': return 'Add new item';
 | 
				
			||||||
 | 
								case 'content.anime': return 'Anime';
 | 
				
			||||||
 | 
								case 'content.manga': return 'Manga';
 | 
				
			||||||
 | 
								case 'search.anime': return 'Anime search';
 | 
				
			||||||
 | 
								case 'search.manga': return 'Manga search';
 | 
				
			||||||
 | 
								case 'search.query': return 'Search query';
 | 
				
			||||||
 | 
								case 'details.title': return 'Details';
 | 
				
			||||||
 | 
								case 'details.removeTitle': return ({required Object title}) => 'Remove ${title}?';
 | 
				
			||||||
 | 
								case 'details.removeBody': return ({required Object title}) => 'Are you sure you want to remove "${title}" from the list?';
 | 
				
			||||||
 | 
								case 'details.removeButton': return 'Remove';
 | 
				
			||||||
 | 
								case 'details.cancelButton': return 'Cancel';
 | 
				
			||||||
 | 
								case 'details.watchState': return 'Watch state';
 | 
				
			||||||
 | 
								case 'details.readState': return 'Read state';
 | 
				
			||||||
 | 
								case 'details.episodes': return 'Episodes';
 | 
				
			||||||
 | 
								case 'details.chapters': return 'Chapters';
 | 
				
			||||||
 | 
								case 'details.volumesOwned': return 'Volumes owned';
 | 
				
			||||||
 | 
								case 'data.ongoing.anime': return 'Watching';
 | 
				
			||||||
 | 
								case 'data.ongoing.manga': return 'Reading';
 | 
				
			||||||
 | 
								case 'data.completed': return 'Completed';
 | 
				
			||||||
 | 
								case 'data.planned.anime': return 'Plan to watch';
 | 
				
			||||||
 | 
								case 'data.planned.manga': return 'Plan to read';
 | 
				
			||||||
 | 
								case 'data.dropped': return 'Dropped';
 | 
				
			||||||
 | 
								case 'data.paused': return 'Paused';
 | 
				
			||||||
 | 
								case 'data.all': return 'All';
 | 
				
			||||||
 | 
								default: return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.dart';
 | 
				
			||||||
import 'package:anitrack/src/service/database.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/anime_search_bloc.dart';
 | 
					import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
 | 
				
			||||||
@ -37,6 +38,9 @@ void main() async {
 | 
				
			|||||||
        AnimesLoadedEvent(),
 | 
					        AnimesLoadedEvent(),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WidgetsFlutterBinding.ensureInitialized();
 | 
				
			||||||
 | 
					  LocaleSettings.useDeviceLocale();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  runApp(
 | 
					  runApp(
 | 
				
			||||||
    MultiBlocProvider(
 | 
					    MultiBlocProvider(
 | 
				
			||||||
      providers: [
 | 
					      providers: [
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.dart';
 | 
				
			||||||
import 'package:json_annotation/json_annotation.dart';
 | 
					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.
 | 
				
			||||||
@ -74,25 +75,25 @@ extension MediumStateExtension on MediumTrackingState {
 | 
				
			|||||||
      case MediumTrackingState.ongoing:
 | 
					      case MediumTrackingState.ongoing:
 | 
				
			||||||
        switch (type) {
 | 
					        switch (type) {
 | 
				
			||||||
          case TrackingMediumType.anime:
 | 
					          case TrackingMediumType.anime:
 | 
				
			||||||
            return 'Watching';
 | 
					            return t.data.ongoing.anime;
 | 
				
			||||||
          case TrackingMediumType.manga:
 | 
					          case TrackingMediumType.manga:
 | 
				
			||||||
            return 'Reading';
 | 
					            return t.data.ongoing.manga;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      case MediumTrackingState.completed:
 | 
					      case MediumTrackingState.completed:
 | 
				
			||||||
        return 'Completed';
 | 
					        return t.data.completed;
 | 
				
			||||||
      case MediumTrackingState.planned:
 | 
					      case MediumTrackingState.planned:
 | 
				
			||||||
        switch (type) {
 | 
					        switch (type) {
 | 
				
			||||||
          case TrackingMediumType.anime:
 | 
					          case TrackingMediumType.anime:
 | 
				
			||||||
            return 'Plan to watch';
 | 
					            return t.data.planned.anime;
 | 
				
			||||||
          case TrackingMediumType.manga:
 | 
					          case TrackingMediumType.manga:
 | 
				
			||||||
            return 'Plan to read';
 | 
					            return t.data.planned.manga;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      case MediumTrackingState.dropped:
 | 
					      case MediumTrackingState.dropped:
 | 
				
			||||||
        return 'Dropped';
 | 
					        return t.data.dropped;
 | 
				
			||||||
      case MediumTrackingState.paused:
 | 
					      case MediumTrackingState.paused:
 | 
				
			||||||
        return 'Paused';
 | 
					        return t.data.paused;
 | 
				
			||||||
      case MediumTrackingState.all:
 | 
					      case MediumTrackingState.all:
 | 
				
			||||||
        return 'All';
 | 
					        return t.data.all;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.dart';
 | 
				
			||||||
import 'package:anitrack/licenses.g.dart';
 | 
					import 'package:anitrack/licenses.g.dart';
 | 
				
			||||||
import 'package:anitrack/src/ui/constants.dart';
 | 
					import 'package:anitrack/src/ui/constants.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
@ -19,7 +20,7 @@ class AboutPage extends StatelessWidget {
 | 
				
			|||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        title: const Text('About'),
 | 
					        title: Text(t.about.title),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      body: ListView.builder(
 | 
					      body: ListView.builder(
 | 
				
			||||||
        itemCount: ossLicenses.length + 1,
 | 
					        itemCount: ossLicenses.length + 1,
 | 
				
			||||||
@ -41,7 +42,7 @@ class AboutPage extends StatelessWidget {
 | 
				
			|||||||
                        mode: LaunchMode.externalApplication,
 | 
					                        mode: LaunchMode.externalApplication,
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    child: const Text('Source'),
 | 
					                    child: Text(t.about.source),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.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';
 | 
				
			||||||
@ -58,9 +59,9 @@ class AnimeListPageState extends State<AnimeListPage> {
 | 
				
			|||||||
  String _getPageTitle(TrackingMediumType type) {
 | 
					  String _getPageTitle(TrackingMediumType type) {
 | 
				
			||||||
    switch (type) {
 | 
					    switch (type) {
 | 
				
			||||||
      case TrackingMediumType.anime:
 | 
					      case TrackingMediumType.anime:
 | 
				
			||||||
        return 'Anime';
 | 
					        return t.content.anime;
 | 
				
			||||||
      case TrackingMediumType.manga:
 | 
					      case TrackingMediumType.manga:
 | 
				
			||||||
        return 'Manga';
 | 
					        return t.content.manga;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -156,14 +157,14 @@ class AnimeListPageState extends State<AnimeListPage> {
 | 
				
			|||||||
                ),
 | 
					                ),
 | 
				
			||||||
                ListTile(
 | 
					                ListTile(
 | 
				
			||||||
                  leading: const Icon(Icons.settings),
 | 
					                  leading: const Icon(Icons.settings),
 | 
				
			||||||
                  title: const Text('Settings'),
 | 
					                  title: Text(t.settings.title),
 | 
				
			||||||
                  onTap: () {
 | 
					                  onTap: () {
 | 
				
			||||||
                    Navigator.of(context).pushNamed(settingsRoute);
 | 
					                    Navigator.of(context).pushNamed(settingsRoute);
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                ListTile(
 | 
					                ListTile(
 | 
				
			||||||
                  leading: const Icon(Icons.info),
 | 
					                  leading: const Icon(Icons.info),
 | 
				
			||||||
                  title: const Text('About'),
 | 
					                  title: Text(t.about.title),
 | 
				
			||||||
                  onTap: () {
 | 
					                  onTap: () {
 | 
				
			||||||
                    Navigator.of(context).pushNamed(aboutRoute);
 | 
					                    Navigator.of(context).pushNamed(aboutRoute);
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
@ -294,7 +295,7 @@ class AnimeListPageState extends State<AnimeListPage> {
 | 
				
			|||||||
                          AnimeSearchRequestedEvent(state.trackingType),
 | 
					                          AnimeSearchRequestedEvent(state.trackingType),
 | 
				
			||||||
                        );
 | 
					                        );
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  tooltip: 'Add new item',
 | 
					                  tooltip: t.tooltips.addNewItem,
 | 
				
			||||||
                  child: const Icon(Icons.add),
 | 
					                  child: const Icon(Icons.add),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
@ -314,15 +315,15 @@ class AnimeListPageState extends State<AnimeListPage> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
              _controller.jumpToPage(index);
 | 
					              _controller.jumpToPage(index);
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            items: const [
 | 
					            items: [
 | 
				
			||||||
              BottomBarItem(
 | 
					              BottomBarItem(
 | 
				
			||||||
                icon: Icon(Icons.tv),
 | 
					                icon: const Icon(Icons.tv),
 | 
				
			||||||
                title: Text('Anime'),
 | 
					                title: Text(t.content.anime),
 | 
				
			||||||
                activeColor: Colors.blue,
 | 
					                activeColor: Colors.blue,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              BottomBarItem(
 | 
					              BottomBarItem(
 | 
				
			||||||
                icon: Icon(Icons.auto_stories),
 | 
					                icon: const Icon(Icons.auto_stories),
 | 
				
			||||||
                title: Text('Manga'),
 | 
					                title: Text(t.content.manga),
 | 
				
			||||||
                activeColor: Colors.red,
 | 
					                activeColor: Colors.red,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.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';
 | 
				
			||||||
@ -25,8 +26,8 @@ class AnimeSearchPage extends StatelessWidget {
 | 
				
			|||||||
          appBar: AppBar(
 | 
					          appBar: AppBar(
 | 
				
			||||||
            title: Text(
 | 
					            title: Text(
 | 
				
			||||||
              state.trackingType == TrackingMediumType.anime
 | 
					              state.trackingType == TrackingMediumType.anime
 | 
				
			||||||
                  ? 'Anime Search'
 | 
					                  ? t.search.anime
 | 
				
			||||||
                  : 'Manga Search',
 | 
					                  : t.search.manga,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          body: Column(
 | 
					          body: Column(
 | 
				
			||||||
@ -34,9 +35,9 @@ class AnimeSearchPage extends StatelessWidget {
 | 
				
			|||||||
              Padding(
 | 
					              Padding(
 | 
				
			||||||
                padding: const EdgeInsets.all(8),
 | 
					                padding: const EdgeInsets.all(8),
 | 
				
			||||||
                child: TextField(
 | 
					                child: TextField(
 | 
				
			||||||
                  decoration: const InputDecoration(
 | 
					                  decoration: InputDecoration(
 | 
				
			||||||
                    border: OutlineInputBorder(),
 | 
					                    border: const OutlineInputBorder(),
 | 
				
			||||||
                    labelText: 'Search query',
 | 
					                    labelText: t.search.query,
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  onSubmitted: (_) {
 | 
					                  onSubmitted: (_) {
 | 
				
			||||||
                    context.read<AnimeSearchBloc>().add(
 | 
					                    context.read<AnimeSearchBloc>().add(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.dart';
 | 
				
			||||||
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';
 | 
				
			||||||
@ -26,7 +27,7 @@ class DetailsPage extends StatelessWidget {
 | 
				
			|||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        title: const Text('Details'),
 | 
					        title: Text(t.details.title),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      body: BlocBuilder<DetailsBloc, DetailsState>(
 | 
					      body: BlocBuilder<DetailsBloc, DetailsState>(
 | 
				
			||||||
        builder: (context, state) {
 | 
					        builder: (context, state) {
 | 
				
			||||||
@ -68,10 +69,10 @@ class DetailsPage extends StatelessWidget {
 | 
				
			|||||||
                                        builder: (context) {
 | 
					                                        builder: (context) {
 | 
				
			||||||
                                          return AlertDialog(
 | 
					                                          return AlertDialog(
 | 
				
			||||||
                                            title: Text(
 | 
					                                            title: Text(
 | 
				
			||||||
                                              'Remove "${state.data!.title}"?',
 | 
					                                              t.details.removeTitle(title: state.data!.title),
 | 
				
			||||||
                                            ),
 | 
					                                            ),
 | 
				
			||||||
                                            content: Text(
 | 
					                                            content: Text(
 | 
				
			||||||
                                              'Are you sure you want to remove "${state.data!.title}" from the list?',
 | 
					                                              t.details.removeBody(title: state.data!.title),
 | 
				
			||||||
                                            ),
 | 
					                                            ),
 | 
				
			||||||
                                            actions: [
 | 
					                                            actions: [
 | 
				
			||||||
                                              TextButton(
 | 
					                                              TextButton(
 | 
				
			||||||
@ -82,14 +83,14 @@ class DetailsPage extends StatelessWidget {
 | 
				
			|||||||
                                                style: TextButton.styleFrom(
 | 
					                                                style: TextButton.styleFrom(
 | 
				
			||||||
                                                  foregroundColor: Colors.red,
 | 
					                                                  foregroundColor: Colors.red,
 | 
				
			||||||
                                                ),
 | 
					                                                ),
 | 
				
			||||||
                                                child: const Text('Remove'),
 | 
					                                                child: Text(t.details.removeButton),
 | 
				
			||||||
                                              ),
 | 
					                                              ),
 | 
				
			||||||
                                              TextButton(
 | 
					                                              TextButton(
 | 
				
			||||||
                                                onPressed: () {
 | 
					                                                onPressed: () {
 | 
				
			||||||
                                                  Navigator.of(context)
 | 
					                                                  Navigator.of(context)
 | 
				
			||||||
                                                      .pop(false);
 | 
					                                                      .pop(false);
 | 
				
			||||||
                                                },
 | 
					                                                },
 | 
				
			||||||
                                                child: const Text('Cancel'),
 | 
					                                                child: Text(t.details.cancelButton),
 | 
				
			||||||
                                              ),
 | 
					                                              ),
 | 
				
			||||||
                                            ],
 | 
					                                            ],
 | 
				
			||||||
                                          );
 | 
					                                          );
 | 
				
			||||||
@ -120,8 +121,8 @@ class DetailsPage extends StatelessWidget {
 | 
				
			|||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                        child: DropdownSelector<MediumTrackingState>(
 | 
					                        child: DropdownSelector<MediumTrackingState>(
 | 
				
			||||||
                          title: state.trackingType == TrackingMediumType.anime
 | 
					                          title: state.trackingType == TrackingMediumType.anime
 | 
				
			||||||
                              ? 'Watch state'
 | 
					                              ? t.details.watchState
 | 
				
			||||||
                              : 'Read state',
 | 
					                              : t.details.readState,
 | 
				
			||||||
                          onChanged: (MediumTrackingState newState) {
 | 
					                          onChanged: (MediumTrackingState newState) {
 | 
				
			||||||
                            if (state.trackingType ==
 | 
					                            if (state.trackingType ==
 | 
				
			||||||
                                TrackingMediumType.anime) {
 | 
					                                TrackingMediumType.anime) {
 | 
				
			||||||
@ -182,8 +183,8 @@ class DetailsPage extends StatelessWidget {
 | 
				
			|||||||
                        child: IntegerInput(
 | 
					                        child: IntegerInput(
 | 
				
			||||||
                          labelText:
 | 
					                          labelText:
 | 
				
			||||||
                              state.trackingType == TrackingMediumType.anime
 | 
					                              state.trackingType == TrackingMediumType.anime
 | 
				
			||||||
                                  ? 'Episodes'
 | 
					                                  ? t.details.episodes
 | 
				
			||||||
                                  : 'Chapters',
 | 
					                                  : t.details.chapters,
 | 
				
			||||||
                          onChanged: (value) {
 | 
					                          onChanged: (value) {
 | 
				
			||||||
                            switch (state.trackingType) {
 | 
					                            switch (state.trackingType) {
 | 
				
			||||||
                              case TrackingMediumType.anime:
 | 
					                              case TrackingMediumType.anime:
 | 
				
			||||||
@ -221,7 +222,7 @@ class DetailsPage extends StatelessWidget {
 | 
				
			|||||||
                            vertical: 8,
 | 
					                            vertical: 8,
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                          child: IntegerInput(
 | 
					                          child: IntegerInput(
 | 
				
			||||||
                            labelText: 'Volumes owned',
 | 
					                            labelText: t.details.volumesOwned,
 | 
				
			||||||
                            onChanged: (value) {
 | 
					                            onChanged: (value) {
 | 
				
			||||||
                              final data = state.data! as MangaTrackingData;
 | 
					                              final data = state.data! as MangaTrackingData;
 | 
				
			||||||
                              context.read<DetailsBloc>().add(
 | 
					                              context.read<DetailsBloc>().add(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:anitrack/i18n/strings.g.dart';
 | 
				
			||||||
import 'package:anitrack/src/ui/bloc/settings_bloc.dart';
 | 
					import 'package:anitrack/src/ui/bloc/settings_bloc.dart';
 | 
				
			||||||
import 'package:anitrack/src/ui/constants.dart';
 | 
					import 'package:anitrack/src/ui/constants.dart';
 | 
				
			||||||
import 'package:file_picker/file_picker.dart';
 | 
					import 'package:file_picker/file_picker.dart';
 | 
				
			||||||
@ -32,15 +33,13 @@ class SettingsPage extends StatelessWidget {
 | 
				
			|||||||
                bottom: 0,
 | 
					                bottom: 0,
 | 
				
			||||||
                child: Scaffold(
 | 
					                child: Scaffold(
 | 
				
			||||||
                  appBar: AppBar(
 | 
					                  appBar: AppBar(
 | 
				
			||||||
                    title: const Text('Settings'),
 | 
					                    title: Text(t.settings.title),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  body: ListView(
 | 
					                  body: ListView(
 | 
				
			||||||
                    children: [
 | 
					                    children: [
 | 
				
			||||||
                      ListTile(
 | 
					                      ListTile(
 | 
				
			||||||
                        title: const Text('Import anime list'),
 | 
					                        title: Text(t.settings.importAnime),
 | 
				
			||||||
                        subtitle: const Text(
 | 
					                        subtitle: Text(t.settings.importAnimeDesc),
 | 
				
			||||||
                          'Import anime list exported from MyAnimeList.',
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        onTap: () async {
 | 
					                        onTap: () async {
 | 
				
			||||||
                          // Pick the file
 | 
					                          // Pick the file
 | 
				
			||||||
                          final result = await FilePicker.platform.pickFiles();
 | 
					                          final result = await FilePicker.platform.pickFiles();
 | 
				
			||||||
@ -49,11 +48,9 @@ class SettingsPage extends StatelessWidget {
 | 
				
			|||||||
                          if (!result.files.first.path!.endsWith('.xml.gz')) {
 | 
					                          if (!result.files.first.path!.endsWith('.xml.gz')) {
 | 
				
			||||||
                            await showDialog<void>(
 | 
					                            await showDialog<void>(
 | 
				
			||||||
                              context: context,
 | 
					                              context: context,
 | 
				
			||||||
                              builder: (_) => const AlertDialog(
 | 
					                              builder: (_) => AlertDialog(
 | 
				
			||||||
                                title: Text('Invalid anime list'),
 | 
					                                title: Text(t.settings.invalidAnimeListTitle),
 | 
				
			||||||
                                content: Text(
 | 
					                                content: Text(t.settings.invalidAnimeListBody),
 | 
				
			||||||
                                  'The selected file is not a MAL anime list. It lacks the ".xml.gz" suffix.',
 | 
					 | 
				
			||||||
                                ),
 | 
					 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            );
 | 
					                            );
 | 
				
			||||||
                            return;
 | 
					                            return;
 | 
				
			||||||
@ -68,10 +65,8 @@ class SettingsPage extends StatelessWidget {
 | 
				
			|||||||
                        },
 | 
					                        },
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                      ListTile(
 | 
					                      ListTile(
 | 
				
			||||||
                        title: const Text('Import manga list'),
 | 
					                        title: Text(t.settings.importManga),
 | 
				
			||||||
                        subtitle: const Text(
 | 
					                        subtitle: Text(t.settings.importMangaDesc),
 | 
				
			||||||
                          'Import manga list exported from MyAnimeList.',
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        onTap: () async {
 | 
					                        onTap: () async {
 | 
				
			||||||
                          // Pick the file
 | 
					                          // Pick the file
 | 
				
			||||||
                          final result = await FilePicker.platform.pickFiles();
 | 
					                          final result = await FilePicker.platform.pickFiles();
 | 
				
			||||||
@ -80,11 +75,9 @@ class SettingsPage extends StatelessWidget {
 | 
				
			|||||||
                          if (!result.files.first.path!.endsWith('.xml.gz')) {
 | 
					                          if (!result.files.first.path!.endsWith('.xml.gz')) {
 | 
				
			||||||
                            await showDialog<void>(
 | 
					                            await showDialog<void>(
 | 
				
			||||||
                              context: context,
 | 
					                              context: context,
 | 
				
			||||||
                              builder: (_) => const AlertDialog(
 | 
					                              builder: (_) => AlertDialog(
 | 
				
			||||||
                                title: Text('Invalid manga list'),
 | 
					                                title: Text(t.settings.invalidMangaListTitle),
 | 
				
			||||||
                                content: Text(
 | 
					                                content: Text(t.settings.invalidMangaListBody),
 | 
				
			||||||
                                  'The selected file is not a MAL manga list. It lacks the ".xml.gz" suffix.',
 | 
					 | 
				
			||||||
                                ),
 | 
					 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            );
 | 
					                            );
 | 
				
			||||||
                            return;
 | 
					                            return;
 | 
				
			||||||
@ -137,7 +130,10 @@ class SettingsPage extends StatelessWidget {
 | 
				
			|||||||
                              child: CircularProgressIndicator(),
 | 
					                              child: CircularProgressIndicator(),
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            Text(
 | 
					                            Text(
 | 
				
			||||||
                              '${state.importCurrent} of ${state.importTotal}',
 | 
					                              t.settings.importIndicator(
 | 
				
			||||||
 | 
					                                current: state.importCurrent,
 | 
				
			||||||
 | 
					                                total: state.importTotal,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
                              style: Theme.of(context).textTheme.bodyMedium,
 | 
					                              style: Theme.of(context).textTheme.bodyMedium,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                          ],
 | 
					                          ],
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										40
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								pubspec.lock
									
									
									
									
									
								
							@ -217,6 +217,14 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "3.0.2"
 | 
					    version: "3.0.2"
 | 
				
			||||||
 | 
					  csv:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: csv
 | 
				
			||||||
 | 
					      sha256: "016b31a51a913744a0a1655c74ff13c9379e1200e246a03d96c81c5d9ed297b5"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "5.0.2"
 | 
				
			||||||
  cupertino_icons:
 | 
					  cupertino_icons:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -456,6 +464,14 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.6.5"
 | 
					    version: "0.6.5"
 | 
				
			||||||
 | 
					  json2yaml:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: json2yaml
 | 
				
			||||||
 | 
					      sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.0.1"
 | 
				
			||||||
  json_annotation:
 | 
					  json_annotation:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -709,6 +725,30 @@ packages:
 | 
				
			|||||||
    description: flutter
 | 
					    description: flutter
 | 
				
			||||||
    source: sdk
 | 
					    source: sdk
 | 
				
			||||||
    version: "0.0.99"
 | 
					    version: "0.0.99"
 | 
				
			||||||
 | 
					  slang:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: slang
 | 
				
			||||||
 | 
					      sha256: "187e35d220765ff22be030b30506d1b8e0e94842a2c1801a6a2941c95db5a9eb"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.19.0"
 | 
				
			||||||
 | 
					  slang_build_runner:
 | 
				
			||||||
 | 
					    dependency: "direct dev"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: slang_build_runner
 | 
				
			||||||
 | 
					      sha256: "3c48c91736704879b767552bf9e7ba38f1974dd06f44b5e15981cadfde06d760"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.19.0"
 | 
				
			||||||
 | 
					  slang_flutter:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: slang_flutter
 | 
				
			||||||
 | 
					      sha256: c6c58162ef66fe88be0313d8062a39e98ae9b539dde7b35f59fa206eb4db2030
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.19.0"
 | 
				
			||||||
  source_gen:
 | 
					  source_gen:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,8 @@ dependencies:
 | 
				
			|||||||
  get_it: ^7.2.0
 | 
					  get_it: ^7.2.0
 | 
				
			||||||
  jikan_api: ^2.0.0
 | 
					  jikan_api: ^2.0.0
 | 
				
			||||||
  json_annotation: 4.6.0
 | 
					  json_annotation: 4.6.0
 | 
				
			||||||
 | 
					  slang: 3.19.0
 | 
				
			||||||
 | 
					  slang_flutter: 3.19.0
 | 
				
			||||||
  sqflite: ^2.2.4+1
 | 
					  sqflite: ^2.2.4+1
 | 
				
			||||||
  swipeable_tile: ^2.0.0+3
 | 
					  swipeable_tile: ^2.0.0+3
 | 
				
			||||||
  url_launcher: ^6.1.8
 | 
					  url_launcher: ^6.1.8
 | 
				
			||||||
@ -36,6 +38,7 @@ dev_dependencies:
 | 
				
			|||||||
    sdk: flutter
 | 
					    sdk: flutter
 | 
				
			||||||
  freezed: ^2.1.0+1
 | 
					  freezed: ^2.1.0+1
 | 
				
			||||||
  json_serializable: ^6.3.1
 | 
					  json_serializable: ^6.3.1
 | 
				
			||||||
 | 
					  slang_build_runner: 3.19.0
 | 
				
			||||||
  very_good_analysis: ^3.0.1
 | 
					  very_good_analysis: ^3.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
flutter:
 | 
					flutter:
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user