diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 176cf6d..e87760d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,9 @@
android:label="Linkdy"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
+
{
- en(languageCode: 'en', build: Translations.build),
- es(languageCode: 'es', build: _StringsEs.build);
+ en(languageCode: 'en', build: Translations.build),
+ es(languageCode: 'es', build: _StringsEs.build);
- const AppLocale(
- {required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element
+ 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 build;
+ @override final String languageCode;
+ @override final String? scriptCode;
+ @override final String? countryCode;
+ @override final TranslationBuilder build;
- /// Gets current instance managed by [LocaleSettings].
- Translations get translations => LocaleSettings.instance.translationMap[this]!;
+ /// Gets current instance managed by [LocaleSettings].
+ Translations get translations => LocaleSettings.instance.translationMap[this]!;
}
/// Method A: Simple
@@ -71,10 +66,9 @@ Translations get t => LocaleSettings.instance.currentTranslations;
/// String a = t.someKey.anotherKey; // Use t variable.
/// String b = t['someKey.anotherKey']; // Only for edge cases!
class TranslationProvider extends BaseTranslationProvider {
- TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
+ TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
- static InheritedLocaleData of(BuildContext context) =>
- InheritedLocaleData.of(context);
+ static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context);
}
/// Method B shorthand via [BuildContext] extension method.
@@ -83,436 +77,356 @@ class TranslationProvider extends BaseTranslationProvider TranslationProvider.of(this).translations;
+ Translations get t => TranslationProvider.of(this).translations;
}
/// Manages all translation instances and the current locale
class LocaleSettings extends BaseFlutterLocaleSettings {
- LocaleSettings._() : super(utils: AppLocaleUtils.instance);
+ LocaleSettings._() : super(utils: AppLocaleUtils.instance);
- static final instance = LocaleSettings._();
+ static final instance = LocaleSettings._();
- // static aliases (checkout base methods for documentation)
- static AppLocale get currentLocale => instance.currentLocale;
- static Stream 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 get supportedLocales => instance.supportedLocales;
- @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]')
- static List 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,
- );
+ // static aliases (checkout base methods for documentation)
+ static AppLocale get currentLocale => instance.currentLocale;
+ static Stream 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 get supportedLocales => instance.supportedLocales;
+ @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List 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 {
- AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values);
+ AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values);
- static final instance = AppLocaleUtils._();
+ 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 get supportedLocales => instance.supportedLocales;
- static List get supportedLocalesRaw => instance.supportedLocalesRaw;
+ // 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 get supportedLocales => instance.supportedLocales;
+ static List get supportedLocalesRaw => instance.supportedLocalesRaw;
}
// translations
// Path:
class Translations implements BaseTranslations {
- /// Returns the current translations of the given [context].
- ///
- /// Usage:
- /// final t = Translations.of(context);
- static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations;
+ /// Returns the current translations of the given [context].
+ ///
+ /// Usage:
+ /// final t = Translations.of(context);
+ static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations;
- /// You can call this constructor and build your own translation instance of this locale.
- /// Constructing via the enum [AppLocale.build] is preferred.
- Translations.build({Map? 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);
- }
+ /// You can call this constructor and build your own translation instance of this locale.
+ /// Constructing via the enum [AppLocale.build] is preferred.
+ Translations.build({Map? 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 .
- @override
- final TranslationMetadata $meta;
+ /// Metadata for the translations of .
+ @override final TranslationMetadata $meta;
- /// Access flat map
- dynamic operator [](String key) => $meta.getTranslation(key);
+ /// Access flat map
+ dynamic operator[](String key) => $meta.getTranslation(key);
- late final Translations _root = this; // ignore: unused_field
+ late final Translations _root = this; // ignore: unused_field
- // Translations
- late final _StringsOnboardingEn onboarding = _StringsOnboardingEn._(_root);
- late final _StringsLinksEn links = _StringsLinksEn._(_root);
- late final _StringsSearchEn search = _StringsSearchEn._(_root);
- late final _StringsSettingsEn settings = _StringsSettingsEn._(_root);
+ // Translations
+ late final _StringsOnboardingEn onboarding = _StringsOnboardingEn._(_root);
+ late final _StringsLinksEn links = _StringsLinksEn._(_root);
+ late final _StringsSearchEn search = _StringsSearchEn._(_root);
+ late final _StringsSettingsEn settings = _StringsSettingsEn._(_root);
+ late final _StringsWebviewEn webview = _StringsWebviewEn._(_root);
}
// Path: onboarding
class _StringsOnboardingEn {
- _StringsOnboardingEn._(this._root);
+ _StringsOnboardingEn._(this._root);
- final Translations _root; // ignore: unused_field
+ final Translations _root; // ignore: unused_field
- // Translations
- String get title => 'Welcome to Linkdy';
- String get subtitle => 'An application to manage your bookmarks.';
- String get start => 'Start';
- String get next => 'Next';
- String get previous => 'Previous';
- String get serverRequired => 'Servidor requerido';
- String get serverRequiredDescription =>
- 'Linkdy it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.';
- String get installationInstructions => 'Check the installation instructions on the official GitHub repository.';
- String get serverRunningConfirmation => 'I confirm that I have an instance of the Linkding server already running.';
- String get createConnection => 'Create a connection';
- String get createConnectionSubtitle => 'Enter all the required details to create a connection to your server.';
- String get ipAddressOrDomain => 'IP address or domain';
- String get port => 'Port';
- String get token => 'Token';
- String get required => 'Required';
- String get serverDetails => 'Server details';
- String get authentication => 'Authentication';
- String get testConnectionUrl => 'Test connection url';
- String get connect => 'Connect';
- String get connecting => 'Connecting...';
- String get cannotConnectToServer => 'Cannot connect to the server.';
+ // Translations
+ String get title => 'Welcome to Linkdy';
+ String get subtitle => 'An application to manage your bookmarks.';
+ String get start => 'Start';
+ String get next => 'Next';
+ String get previous => 'Previous';
+ String get serverRequired => 'Servidor requerido';
+ String get serverRequiredDescription => 'Linkdy it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.';
+ String get installationInstructions => 'Check the installation instructions on the official GitHub repository.';
+ String get serverRunningConfirmation => 'I confirm that I have an instance of the Linkding server already running.';
+ String get createConnection => 'Create a connection';
+ String get createConnectionSubtitle => 'Enter all the required details to create a connection to your server.';
+ String get ipAddressOrDomain => 'IP address or domain';
+ String get port => 'Port';
+ String get token => 'Token';
+ String get required => 'Required';
+ String get serverDetails => 'Server details';
+ String get authentication => 'Authentication';
+ String get testConnectionUrl => 'Test connection url';
+ String get connect => 'Connect';
+ String get connecting => 'Connecting...';
+ String get cannotConnectToServer => 'Cannot connect to the server.';
}
// Path: links
class _StringsLinksEn {
- _StringsLinksEn._(this._root);
+ _StringsLinksEn._(this._root);
- final Translations _root; // ignore: unused_field
+ final Translations _root; // ignore: unused_field
- // Translations
- String get links => 'Links';
- late final _StringsLinksDatesEn dates = _StringsLinksDatesEn._(_root);
+ // Translations
+ String get links => 'Links';
+ late final _StringsLinksDatesEn dates = _StringsLinksDatesEn._(_root);
}
// Path: search
class _StringsSearchEn {
- _StringsSearchEn._(this._root);
+ _StringsSearchEn._(this._root);
- final Translations _root; // ignore: unused_field
+ final Translations _root; // ignore: unused_field
- // Translations
- String get search => 'Search';
+ // Translations
+ String get search => 'Search';
}
// Path: settings
class _StringsSettingsEn {
- _StringsSettingsEn._(this._root);
+ _StringsSettingsEn._(this._root);
- final Translations _root; // ignore: unused_field
+ final Translations _root; // ignore: unused_field
- // Translations
- String get settings => 'Settings';
- String get disconnectFromServer => 'Disconnect from server';
+ // Translations
+ String get settings => 'Settings';
+ String get disconnectFromServer => 'Disconnect from server';
+}
+
+// Path: webview
+class _StringsWebviewEn {
+ _StringsWebviewEn._(this._root);
+
+ final Translations _root; // ignore: unused_field
+
+ // Translations
+ String get goBack => 'Go back';
+ String get goForward => 'Go forward';
}
// Path: links.dates
class _StringsLinksDatesEn {
- _StringsLinksDatesEn._(this._root);
+ _StringsLinksDatesEn._(this._root);
- final Translations _root; // ignore: unused_field
+ final Translations _root; // ignore: unused_field
- // Translations
- String todayAt({required Object time}) => 'Today, ${time}';
- String yesterdayAt({required Object time}) => 'Yesterday, ${time}';
+ // Translations
+ String todayAt({required Object time}) => 'Today, ${time}';
+ String yesterdayAt({required Object time}) => 'Yesterday, ${time}';
}
// Path:
class _StringsEs implements Translations {
- /// You can call this constructor and build your own translation instance of this locale.
- /// Constructing via the enum [AppLocale.build] is preferred.
- _StringsEs.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver})
- : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'),
- $meta = TranslationMetadata(
- locale: AppLocale.es,
- overrides: overrides ?? {},
- cardinalResolver: cardinalResolver,
- ordinalResolver: ordinalResolver,
- ) {
- $meta.setFlatMapFunction(_flatMapFunction);
- }
+ /// You can call this constructor and build your own translation instance of this locale.
+ /// Constructing via the enum [AppLocale.build] is preferred.
+ _StringsEs.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver})
+ : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'),
+ $meta = TranslationMetadata(
+ locale: AppLocale.es,
+ overrides: overrides ?? {},
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver,
+ ) {
+ $meta.setFlatMapFunction(_flatMapFunction);
+ }
- /// Metadata for the translations of .
- @override
- final TranslationMetadata $meta;
+ /// Metadata for the translations of .
+ @override final TranslationMetadata $meta;
- /// Access flat map
- @override
- dynamic operator [](String key) => $meta.getTranslation(key);
+ /// Access flat map
+ @override dynamic operator[](String key) => $meta.getTranslation(key);
- @override
- late final _StringsEs _root = this; // ignore: unused_field
+ @override late final _StringsEs _root = this; // ignore: unused_field
- // Translations
- @override
- late final _StringsOnboardingEs onboarding = _StringsOnboardingEs._(_root);
- @override
- late final _StringsLinksEs links = _StringsLinksEs._(_root);
- @override
- late final _StringsSearchEs search = _StringsSearchEs._(_root);
- @override
- late final _StringsSettingsEs settings = _StringsSettingsEs._(_root);
+ // Translations
+ @override late final _StringsOnboardingEs onboarding = _StringsOnboardingEs._(_root);
+ @override late final _StringsLinksEs links = _StringsLinksEs._(_root);
+ @override late final _StringsSearchEs search = _StringsSearchEs._(_root);
+ @override late final _StringsSettingsEs settings = _StringsSettingsEs._(_root);
+ @override late final _StringsWebviewEs webview = _StringsWebviewEs._(_root);
}
// Path: onboarding
class _StringsOnboardingEs implements _StringsOnboardingEn {
- _StringsOnboardingEs._(this._root);
+ _StringsOnboardingEs._(this._root);
- @override
- final _StringsEs _root; // ignore: unused_field
+ @override final _StringsEs _root; // ignore: unused_field
- // Translations
- @override
- String get title => 'Bienvenido a Linkdy';
- @override
- String get subtitle => 'Una aplicación para gestionar tus enlaces favoritos.';
- @override
- String get start => 'Comenzar';
- @override
- String get next => 'Siguiente';
- @override
- String get previous => 'Anterior';
- @override
- String get serverRequired => 'Servidor requerido';
- @override
- String get serverRequiredDescription =>
- 'Linkdy no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.';
- @override
- String get installationInstructions => 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.';
- @override
- String get serverRunningConfirmation =>
- 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.';
- @override
- String get createConnection => 'Crear una conexión';
- @override
- String get createConnectionSubtitle =>
- 'Introduce todos los detalles requeridos para crear una conexión con el servidor';
- @override
- String get ipAddressOrDomain => 'Dirección IP o dominio';
- @override
- String get port => 'Puerto';
- @override
- String get token => 'Token';
- @override
- String get required => 'Requerido';
- @override
- String get serverDetails => 'Detalles del servidor';
- @override
- String get authentication => 'Autenticación';
- @override
- String get testConnectionUrl => 'Probar URL de conexión';
- @override
- String get connect => 'Conectar';
- @override
- String get connecting => 'Conectando...';
- @override
- String get cannotConnectToServer => 'No se puede conectar con el servidor.';
+ // Translations
+ @override String get title => 'Bienvenido a Linkdy';
+ @override String get subtitle => 'Una aplicación para gestionar tus enlaces favoritos.';
+ @override String get start => 'Comenzar';
+ @override String get next => 'Siguiente';
+ @override String get previous => 'Anterior';
+ @override String get serverRequired => 'Servidor requerido';
+ @override String get serverRequiredDescription => 'Linkdy no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.';
+ @override String get installationInstructions => 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.';
+ @override String get serverRunningConfirmation => 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.';
+ @override String get createConnection => 'Crear una conexión';
+ @override String get createConnectionSubtitle => 'Introduce todos los detalles requeridos para crear una conexión con el servidor';
+ @override String get ipAddressOrDomain => 'Dirección IP o dominio';
+ @override String get port => 'Puerto';
+ @override String get token => 'Token';
+ @override String get required => 'Requerido';
+ @override String get serverDetails => 'Detalles del servidor';
+ @override String get authentication => 'Autenticación';
+ @override String get testConnectionUrl => 'Probar URL de conexión';
+ @override String get connect => 'Conectar';
+ @override String get connecting => 'Conectando...';
+ @override String get cannotConnectToServer => 'No se puede conectar con el servidor.';
}
// Path: links
class _StringsLinksEs implements _StringsLinksEn {
- _StringsLinksEs._(this._root);
+ _StringsLinksEs._(this._root);
- @override
- final _StringsEs _root; // ignore: unused_field
+ @override final _StringsEs _root; // ignore: unused_field
- // Translations
- @override
- String get links => 'Enlaces';
- @override
- late final _StringsLinksDatesEs dates = _StringsLinksDatesEs._(_root);
+ // Translations
+ @override String get links => 'Enlaces';
+ @override late final _StringsLinksDatesEs dates = _StringsLinksDatesEs._(_root);
}
// Path: search
class _StringsSearchEs implements _StringsSearchEn {
- _StringsSearchEs._(this._root);
+ _StringsSearchEs._(this._root);
- @override
- final _StringsEs _root; // ignore: unused_field
+ @override final _StringsEs _root; // ignore: unused_field
- // Translations
- @override
- String get search => 'Buscar';
+ // Translations
+ @override String get search => 'Buscar';
}
// Path: settings
class _StringsSettingsEs implements _StringsSettingsEn {
- _StringsSettingsEs._(this._root);
+ _StringsSettingsEs._(this._root);
- @override
- final _StringsEs _root; // ignore: unused_field
+ @override final _StringsEs _root; // ignore: unused_field
- // Translations
- @override
- String get settings => 'Ajustes';
- @override
- String get disconnectFromServer => 'Desconectar del servidor';
+ // Translations
+ @override String get settings => 'Ajustes';
+ @override String get disconnectFromServer => 'Desconectar del servidor';
+}
+
+// Path: webview
+class _StringsWebviewEs implements _StringsWebviewEn {
+ _StringsWebviewEs._(this._root);
+
+ @override final _StringsEs _root; // ignore: unused_field
+
+ // Translations
+ @override String get goBack => 'Ir atrás';
+ @override String get goForward => 'Ir adelante';
}
// Path: links.dates
class _StringsLinksDatesEs implements _StringsLinksDatesEn {
- _StringsLinksDatesEs._(this._root);
+ _StringsLinksDatesEs._(this._root);
- @override
- final _StringsEs _root; // ignore: unused_field
+ @override final _StringsEs _root; // ignore: unused_field
- // Translations
- @override
- String todayAt({required Object time}) => 'Hoy, ${time}';
- @override
- String yesterdayAt({required Object time}) => 'Ayer, ${time}';
+ // Translations
+ @override String todayAt({required Object time}) => 'Hoy, ${time}';
+ @override String yesterdayAt({required Object time}) => 'Ayer, ${time}';
}
/// Flat map(s) containing all translations.
/// Only for edge cases! For simple maps, use the map function of this library.
extension on Translations {
- dynamic _flatMapFunction(String path) {
- switch (path) {
- case 'onboarding.title':
- return 'Welcome to Linkdy';
- case 'onboarding.subtitle':
- return 'An application to manage your bookmarks.';
- case 'onboarding.start':
- return 'Start';
- case 'onboarding.next':
- return 'Next';
- case 'onboarding.previous':
- return 'Previous';
- case 'onboarding.serverRequired':
- return 'Servidor requerido';
- case 'onboarding.serverRequiredDescription':
- return 'Linkdy it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.';
- case 'onboarding.installationInstructions':
- return 'Check the installation instructions on the official GitHub repository.';
- case 'onboarding.serverRunningConfirmation':
- return 'I confirm that I have an instance of the Linkding server already running.';
- case 'onboarding.createConnection':
- return 'Create a connection';
- case 'onboarding.createConnectionSubtitle':
- return 'Enter all the required details to create a connection to your server.';
- case 'onboarding.ipAddressOrDomain':
- return 'IP address or domain';
- case 'onboarding.port':
- return 'Port';
- case 'onboarding.token':
- return 'Token';
- case 'onboarding.required':
- return 'Required';
- case 'onboarding.serverDetails':
- return 'Server details';
- case 'onboarding.authentication':
- return 'Authentication';
- case 'onboarding.testConnectionUrl':
- return 'Test connection url';
- case 'onboarding.connect':
- return 'Connect';
- case 'onboarding.connecting':
- return 'Connecting...';
- case 'onboarding.cannotConnectToServer':
- return 'Cannot connect to the server.';
- case 'links.links':
- return 'Links';
- case 'links.dates.todayAt':
- return ({required Object time}) => 'Today, ${time}';
- case 'links.dates.yesterdayAt':
- return ({required Object time}) => 'Yesterday, ${time}';
- case 'search.search':
- return 'Search';
- case 'settings.settings':
- return 'Settings';
- case 'settings.disconnectFromServer':
- return 'Disconnect from server';
- default:
- return null;
- }
- }
+ dynamic _flatMapFunction(String path) {
+ switch (path) {
+ case 'onboarding.title': return 'Welcome to Linkdy';
+ case 'onboarding.subtitle': return 'An application to manage your bookmarks.';
+ case 'onboarding.start': return 'Start';
+ case 'onboarding.next': return 'Next';
+ case 'onboarding.previous': return 'Previous';
+ case 'onboarding.serverRequired': return 'Servidor requerido';
+ case 'onboarding.serverRequiredDescription': return 'Linkdy it\'s not an standalone app, it requires the Linkding server to work.\nIn order to use this application, you must deploy Linkding on your home server, VPS or any other computer.';
+ case 'onboarding.installationInstructions': return 'Check the installation instructions on the official GitHub repository.';
+ case 'onboarding.serverRunningConfirmation': return 'I confirm that I have an instance of the Linkding server already running.';
+ case 'onboarding.createConnection': return 'Create a connection';
+ case 'onboarding.createConnectionSubtitle': return 'Enter all the required details to create a connection to your server.';
+ case 'onboarding.ipAddressOrDomain': return 'IP address or domain';
+ case 'onboarding.port': return 'Port';
+ case 'onboarding.token': return 'Token';
+ case 'onboarding.required': return 'Required';
+ case 'onboarding.serverDetails': return 'Server details';
+ case 'onboarding.authentication': return 'Authentication';
+ case 'onboarding.testConnectionUrl': return 'Test connection url';
+ case 'onboarding.connect': return 'Connect';
+ case 'onboarding.connecting': return 'Connecting...';
+ case 'onboarding.cannotConnectToServer': return 'Cannot connect to the server.';
+ case 'links.links': return 'Links';
+ case 'links.dates.todayAt': return ({required Object time}) => 'Today, ${time}';
+ case 'links.dates.yesterdayAt': return ({required Object time}) => 'Yesterday, ${time}';
+ case 'search.search': return 'Search';
+ case 'settings.settings': return 'Settings';
+ case 'settings.disconnectFromServer': return 'Disconnect from server';
+ case 'webview.goBack': return 'Go back';
+ case 'webview.goForward': return 'Go forward';
+ default: return null;
+ }
+ }
}
extension on _StringsEs {
- dynamic _flatMapFunction(String path) {
- switch (path) {
- case 'onboarding.title':
- return 'Bienvenido a Linkdy';
- case 'onboarding.subtitle':
- return 'Una aplicación para gestionar tus enlaces favoritos.';
- case 'onboarding.start':
- return 'Comenzar';
- case 'onboarding.next':
- return 'Siguiente';
- case 'onboarding.previous':
- return 'Anterior';
- case 'onboarding.serverRequired':
- return 'Servidor requerido';
- case 'onboarding.serverRequiredDescription':
- return 'Linkdy no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.';
- case 'onboarding.installationInstructions':
- return 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.';
- case 'onboarding.serverRunningConfirmation':
- return 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.';
- case 'onboarding.createConnection':
- return 'Crear una conexión';
- case 'onboarding.createConnectionSubtitle':
- return 'Introduce todos los detalles requeridos para crear una conexión con el servidor';
- case 'onboarding.ipAddressOrDomain':
- return 'Dirección IP o dominio';
- case 'onboarding.port':
- return 'Puerto';
- case 'onboarding.token':
- return 'Token';
- case 'onboarding.required':
- return 'Requerido';
- case 'onboarding.serverDetails':
- return 'Detalles del servidor';
- case 'onboarding.authentication':
- return 'Autenticación';
- case 'onboarding.testConnectionUrl':
- return 'Probar URL de conexión';
- case 'onboarding.connect':
- return 'Conectar';
- case 'onboarding.connecting':
- return 'Conectando...';
- case 'onboarding.cannotConnectToServer':
- return 'No se puede conectar con el servidor.';
- case 'links.links':
- return 'Enlaces';
- case 'links.dates.todayAt':
- return ({required Object time}) => 'Hoy, ${time}';
- case 'links.dates.yesterdayAt':
- return ({required Object time}) => 'Ayer, ${time}';
- case 'search.search':
- return 'Buscar';
- case 'settings.settings':
- return 'Ajustes';
- case 'settings.disconnectFromServer':
- return 'Desconectar del servidor';
- default:
- return null;
- }
- }
+ dynamic _flatMapFunction(String path) {
+ switch (path) {
+ case 'onboarding.title': return 'Bienvenido a Linkdy';
+ case 'onboarding.subtitle': return 'Una aplicación para gestionar tus enlaces favoritos.';
+ case 'onboarding.start': return 'Comenzar';
+ case 'onboarding.next': return 'Siguiente';
+ case 'onboarding.previous': return 'Anterior';
+ case 'onboarding.serverRequired': return 'Servidor requerido';
+ case 'onboarding.serverRequiredDescription': return 'Linkdy no es una aplicación independiente, requiere el servidor Linkding para funcionar.\nPara utilizar esta aplicación, debe instalar Linkding en su servidor doméstico, VPS o cualquier otro ordenador.';
+ case 'onboarding.installationInstructions': return 'Mira las instrucciones de instalación en el repositorio oficial en GitHub.';
+ case 'onboarding.serverRunningConfirmation': return 'Confirmo que tengo una instancia del servidor Linkding ya en funcionamiento.';
+ case 'onboarding.createConnection': return 'Crear una conexión';
+ case 'onboarding.createConnectionSubtitle': return 'Introduce todos los detalles requeridos para crear una conexión con el servidor';
+ case 'onboarding.ipAddressOrDomain': return 'Dirección IP o dominio';
+ case 'onboarding.port': return 'Puerto';
+ case 'onboarding.token': return 'Token';
+ case 'onboarding.required': return 'Requerido';
+ case 'onboarding.serverDetails': return 'Detalles del servidor';
+ case 'onboarding.authentication': return 'Autenticación';
+ case 'onboarding.testConnectionUrl': return 'Probar URL de conexión';
+ case 'onboarding.connect': return 'Conectar';
+ case 'onboarding.connecting': return 'Conectando...';
+ case 'onboarding.cannotConnectToServer': return 'No se puede conectar con el servidor.';
+ case 'links.links': return 'Enlaces';
+ case 'links.dates.todayAt': return ({required Object time}) => 'Hoy, ${time}';
+ case 'links.dates.yesterdayAt': return ({required Object time}) => 'Ayer, ${time}';
+ case 'search.search': return 'Buscar';
+ case 'settings.settings': return 'Ajustes';
+ case 'settings.disconnectFromServer': return 'Desconectar del servidor';
+ case 'webview.goBack': return 'Ir atrás';
+ case 'webview.goForward': return 'Ir adelante';
+ default: return null;
+ }
+ }
}
diff --git a/lib/i18n/strings_en.i18n.json b/lib/i18n/strings_en.i18n.json
index f3a85fa..ed35a23 100644
--- a/lib/i18n/strings_en.i18n.json
+++ b/lib/i18n/strings_en.i18n.json
@@ -35,5 +35,9 @@
"settings": {
"settings": "Settings",
"disconnectFromServer": "Disconnect from server"
+ },
+ "webview": {
+ "goBack": "Go back",
+ "goForward": "Go forward"
}
}
\ No newline at end of file
diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json
index 6dd3b5a..1e2720a 100644
--- a/lib/i18n/strings_es.i18n.json
+++ b/lib/i18n/strings_es.i18n.json
@@ -35,5 +35,9 @@
"settings": {
"settings": "Ajustes",
"disconnectFromServer": "Desconectar del servidor"
+ },
+ "webview": {
+ "goBack": "Ir atrás",
+ "goForward": "Ir adelante"
}
}
\ No newline at end of file
diff --git a/lib/router/routes.dart b/lib/router/routes.dart
index 8038e66..608df96 100644
--- a/lib/router/routes.dart
+++ b/lib/router/routes.dart
@@ -21,6 +21,10 @@ final List appRoutes = [
path: RoutesPaths.onboarding,
builder: (context, state) => const Onboarding(),
),
+ GoRoute(
+ path: RoutesPaths.webview,
+ builder: (context, state) => WebView(bookmark: state.extra as Bookmark),
+ ),
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) => Layout(
navigationShell: navigationShell,
@@ -34,10 +38,6 @@ final List appRoutes = [
path: RoutesPaths.links,
builder: (context, state) => const Links(),
),
- GoRoute(
- path: RoutesPaths.webview,
- builder: (context, state) => WebView(bookmark: state.extra as Bookmark),
- ),
],
),
StatefulShellBranch(
diff --git a/lib/screens/onboarding/model/onboarding.model.dart b/lib/screens/onboarding/model/onboarding.model.dart
index 3fb0d7a..1fe5fba 100644
--- a/lib/screens/onboarding/model/onboarding.model.dart
+++ b/lib/screens/onboarding/model/onboarding.model.dart
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
class OnboardingModel {
final PageController pageController;
- final bool confirmServerRunning;
+ bool confirmServerRunning;
OnboardingModel({
required this.pageController,
- required this.confirmServerRunning,
+ this.confirmServerRunning = false,
});
OnboardingModel toggleConfirmServerRunning(bool newValue) => OnboardingModel(
diff --git a/lib/screens/onboarding/provider/onboarding.provider.dart b/lib/screens/onboarding/provider/onboarding.provider.dart
index e1da35a..783e59a 100644
--- a/lib/screens/onboarding/provider/onboarding.provider.dart
+++ b/lib/screens/onboarding/provider/onboarding.provider.dart
@@ -30,6 +30,7 @@ class Onboarding extends _$Onboarding {
}
void confirmServerRunning() {
- state = state.toggleConfirmServerRunning(!state.confirmServerRunning);
+ state.confirmServerRunning = !state.confirmServerRunning;
+ ref.notifyListeners();
}
}
diff --git a/lib/screens/onboarding/provider/onboarding.provider.g.dart b/lib/screens/onboarding/provider/onboarding.provider.g.dart
index 7e1feff..d31fd60 100644
--- a/lib/screens/onboarding/provider/onboarding.provider.g.dart
+++ b/lib/screens/onboarding/provider/onboarding.provider.g.dart
@@ -6,7 +6,7 @@ part of 'onboarding.provider.dart';
// RiverpodGenerator
// **************************************************************************
-String _$onboardingHash() => r'5c876d53acbca92ab15630516099700609aa4f89';
+String _$onboardingHash() => r'06f4c2d233d6babdd1d8fef05e5a6285e19ec453';
/// See also [Onboarding].
@ProviderFor(Onboarding)
diff --git a/lib/screens/webview/model/webview.model.dart b/lib/screens/webview/model/webview.model.dart
index b81c2e8..735a31b 100644
--- a/lib/screens/webview/model/webview.model.dart
+++ b/lib/screens/webview/model/webview.model.dart
@@ -1,9 +1,15 @@
-import 'package:webview_flutter/webview_flutter.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class WebViewModel {
- final WebViewController webViewController;
+ InAppWebViewController? inAppWebViewController;
+ int loadProgress;
+ bool canGoBack;
+ bool canGoForward;
WebViewModel({
- required this.webViewController,
+ this.inAppWebViewController,
+ this.loadProgress = 0,
+ this.canGoBack = false,
+ this.canGoForward = false,
});
}
diff --git a/lib/screens/webview/provider/webview.provider.dart b/lib/screens/webview/provider/webview.provider.dart
index 9b138c2..34eb384 100644
--- a/lib/screens/webview/provider/webview.provider.dart
+++ b/lib/screens/webview/provider/webview.provider.dart
@@ -1,5 +1,5 @@
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
-import 'package:webview_flutter/webview_flutter.dart';
import 'package:linkdy/screens/webview/model/webview.model.dart';
@@ -9,25 +9,25 @@ part 'webview.provider.g.dart';
class WebView extends _$WebView {
@override
WebViewModel build() {
- return WebViewModel(
- webViewController: WebViewController(
- onPermissionRequest: (request) => request.deny(),
- )
- ..setJavaScriptMode(JavaScriptMode.unrestricted)
- ..setNavigationDelegate(
- NavigationDelegate(
- onProgress: (int progress) {
- // Update loading bar.
- },
- onPageStarted: (String url) {},
- onPageFinished: (String url) {},
- onWebResourceError: (WebResourceError error) {},
- ),
- ),
- );
+ return WebViewModel();
}
- void initialize(String url) {
- state.webViewController.loadRequest(Uri.parse(url));
+ void initializeController(InAppWebViewController controller) {
+ state.inAppWebViewController = controller;
+ }
+
+ void setLoadProgress(int progress) {
+ state.loadProgress = progress;
+ ref.notifyListeners();
+ }
+
+ void setCanGoBack(bool value) {
+ state.canGoBack = value;
+ ref.notifyListeners();
+ }
+
+ void setCanGoForward(bool value) {
+ state.canGoForward = value;
+ ref.notifyListeners();
}
}
diff --git a/lib/screens/webview/provider/webview.provider.g.dart b/lib/screens/webview/provider/webview.provider.g.dart
index 8e9dc92..b69dd30 100644
--- a/lib/screens/webview/provider/webview.provider.g.dart
+++ b/lib/screens/webview/provider/webview.provider.g.dart
@@ -6,7 +6,7 @@ part of 'webview.provider.dart';
// RiverpodGenerator
// **************************************************************************
-String _$webViewHash() => r'9eb114ec97e04b92aa1e2ec9fc8948b1443c948c';
+String _$webViewHash() => r'55d815688ade5764e1b2b4c862f81df45d98ae3b';
/// See also [WebView].
@ProviderFor(WebView)
diff --git a/lib/screens/webview/ui/webview.dart b/lib/screens/webview/ui/webview.dart
index 014ce77..a9eefc6 100644
--- a/lib/screens/webview/ui/webview.dart
+++ b/lib/screens/webview/ui/webview.dart
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:linkdy/models/data/bookmarks.dart';
import 'package:linkdy/screens/webview/provider/webview.provider.dart';
-import 'package:webview_flutter/webview_flutter.dart';
+
+import 'package:linkdy/models/data/bookmarks.dart';
+import 'package:linkdy/i18n/strings.g.dart';
class WebView extends ConsumerWidget {
final Bookmark bookmark;
@@ -15,17 +17,92 @@ class WebView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- ref.read(webViewProvider.notifier).initialize(bookmark.url!);
- });
-
- final webViewController = ref.watch(webViewProvider).webViewController;
-
return Scaffold(
appBar: AppBar(
- title: Text(bookmark.title != "" ? bookmark.title! : bookmark.websiteTitle!),
+ elevation: 3,
+ title: Column(
+ children: [
+ Text(
+ bookmark.title != "" ? bookmark.title! : bookmark.websiteTitle!,
+ style: const TextStyle(fontSize: 14),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ bookmark.url!,
+ style: TextStyle(
+ fontSize: 12,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ ],
+ ),
+ bottom: PreferredSize(
+ preferredSize: const Size(double.maxFinite, 0),
+ child: ref.watch(webViewProvider).loadProgress < 100
+ ? LinearProgressIndicator(
+ value: ref.watch(webViewProvider).loadProgress.toDouble(),
+ )
+ : const SizedBox(
+ height: 4,
+ ),
+ ),
+ ),
+ body: SafeArea(
+ child: Column(
+ children: [
+ Expanded(
+ child: InAppWebView(
+ initialUrlRequest: URLRequest(url: WebUri.uri(Uri.parse(bookmark.url!))),
+ onWebViewCreated: (controller) {
+ ref.read(webViewProvider).inAppWebViewController = controller;
+ },
+ onPermissionRequest: (controller, request) async {
+ return PermissionResponse(
+ resources: request.resources,
+ action: PermissionResponseAction.DENY,
+ );
+ },
+ onProgressChanged: (controller, progress) {
+ ref.read(webViewProvider.notifier).setLoadProgress(progress);
+ },
+ initialSettings: InAppWebViewSettings(
+ allowContentAccess: false,
+ allowFileAccess: false,
+ applePayAPIEnabled: false,
+ cacheEnabled: false,
+ isInspectable: false,
+ ),
+ onUpdateVisitedHistory: (controller, url, androidIsReload) {
+ controller.canGoBack().then((value) => ref.read(webViewProvider.notifier).setCanGoBack(value));
+ controller.canGoForward().then((value) => ref.read(webViewProvider.notifier).setCanGoForward(value));
+ },
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ children: [
+ IconButton(
+ onPressed: ref.watch(webViewProvider).canGoBack == true
+ ? () => ref.watch(webViewProvider).inAppWebViewController?.goBack()
+ : null,
+ icon: const Icon(Icons.arrow_back_rounded),
+ tooltip: t.webview.goForward,
+ ),
+ const SizedBox(width: 8),
+ IconButton(
+ onPressed: ref.watch(webViewProvider).canGoForward == true
+ ? () => ref.watch(webViewProvider).inAppWebViewController?.goForward()
+ : null,
+ icon: const Icon(Icons.arrow_forward_rounded),
+ tooltip: t.webview.goForward,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
),
- body: webViewController != null ? WebViewWidget(controller: ref.watch(webViewProvider).webViewController!) : null,
);
}
}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 8d7da96..440eb3c 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,11 +6,13 @@ import FlutterMacOS
import Foundation
import dynamic_color
+import flutter_inappwebview_macos
import shared_preferences_foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
+ InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index 4314346..d27ac5f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -358,6 +358,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
+ flutter_inappwebview:
+ dependency: "direct main"
+ description:
+ name: flutter_inappwebview
+ sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.0"
+ flutter_inappwebview_android:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_android
+ sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.13"
+ flutter_inappwebview_internal_annotations:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_internal_annotations
+ sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ flutter_inappwebview_ios:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_ios
+ sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.13"
+ flutter_inappwebview_macos:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_macos
+ sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.11"
+ flutter_inappwebview_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_platform_interface
+ sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.10"
+ flutter_inappwebview_web:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_web
+ sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
flutter_lints:
dependency: "direct dev"
description:
@@ -375,10 +431,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_native_splash
- sha256: "45e2c0986d749c070509e03d6c7ad6c8bd1f7b1dad7d11dd8750a5e4fe3e2c0b"
+ sha256: "558f10070f03ee71f850a78f7136ab239a67636a294a44a06b6b7345178edb1e"
url: "https://pub.dev"
source: hosted
- version: "2.3.11"
+ version: "2.3.10"
flutter_riverpod:
dependency: "direct main"
description:
@@ -513,10 +569,10 @@ packages:
dependency: transitive
description:
name: js
- sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
- version: "0.7.1"
+ version: "0.6.7"
json2yaml:
dependency: transitive
description:
@@ -1082,38 +1138,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
- webview_flutter:
- dependency: "direct main"
- description:
- name: webview_flutter
- sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
- url: "https://pub.dev"
- source: hosted
- version: "4.7.0"
- webview_flutter_android:
- dependency: transitive
- description:
- name: webview_flutter_android
- sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0"
- url: "https://pub.dev"
- source: hosted
- version: "3.15.0"
- webview_flutter_platform_interface:
- dependency: transitive
- description:
- name: webview_flutter_platform_interface
- sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
- url: "https://pub.dev"
- source: hosted
- version: "2.10.0"
- webview_flutter_wkwebview:
- dependency: transitive
- description:
- name: webview_flutter_wkwebview
- sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86"
- url: "https://pub.dev"
- source: hosted
- version: "3.12.0"
win32:
dependency: transitive
description:
@@ -1148,4 +1172,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.2.6 <4.0.0"
- flutter: ">=3.16.6"
+ flutter: ">=3.16.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index b2cdd34..130665b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -47,7 +47,7 @@ dependencies:
slang_flutter: ^3.29.0
flutter_svg: ^2.0.9
flutter_custom_tabs: ^2.0.0+1
- webview_flutter: ^4.7.0
+ flutter_inappwebview: ^6.0.0
dev_dependencies:
build_runner: ^2.4.8