diff --git a/lib/config/shared_preferences_keys.dart b/lib/config/shared_preferences_keys.dart deleted file mode 100644 index 01f4128..0000000 --- a/lib/config/shared_preferences_keys.dart +++ /dev/null @@ -1,3 +0,0 @@ -class SharedPreferencesKeys { - static const serversInstances = "serversInstances"; -} diff --git a/lib/constants/enums.dart b/lib/constants/enums.dart index 437b86e..958ba94 100644 --- a/lib/constants/enums.dart +++ b/lib/constants/enums.dart @@ -1,3 +1,5 @@ enum LoadStatus { loading, loaded, error } enum ConnectionMethod { http, https } + +enum SelectedTheme { system, light, dark } diff --git a/lib/constants/shared_preferences_keys.dart b/lib/constants/shared_preferences_keys.dart new file mode 100644 index 0000000..8631d43 --- /dev/null +++ b/lib/constants/shared_preferences_keys.dart @@ -0,0 +1,6 @@ +class SharedPreferencesKeys { + static const serversInstances = "serversInstances"; + static const selectedTheme = "selectedTheme"; + static const useDynamicTheme = "useDynamicTheme"; + static const selectedColor = "selectedColor"; +} diff --git a/lib/constants/strings.dart b/lib/constants/strings.dart new file mode 100644 index 0000000..c53c24c --- /dev/null +++ b/lib/constants/strings.dart @@ -0,0 +1,3 @@ +class Strings { + static const createdBy = "JGeek00"; +} diff --git a/lib/constants/urls.dart b/lib/constants/urls.dart index a8e7da7..82c74dc 100644 --- a/lib/constants/urls.dart +++ b/lib/constants/urls.dart @@ -1,4 +1,5 @@ class Urls { static const linkdingInstallationInstructions = "https://github.com/sissbruecker/linkding?tab=readme-ov-file#installation"; + static const gitHubRepo = "https://github.com/JGeek00/linkdy"; } diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 67624a1..4ea6e4c 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 68 (34 per locale) +/// Strings: 122 (61 per locale) /// -/// Built on 2024-02-22 at 21:21 UTC +/// Built on 2024-02-23 at 00:06 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -153,6 +153,7 @@ class Translations implements BaseTranslations { late final _StringsSearchEn search = _StringsSearchEn._(_root); late final _StringsSettingsEn settings = _StringsSettingsEn._(_root); late final _StringsWebviewEn webview = _StringsWebviewEn._(_root); + late final _StringsColorsEn colors = _StringsColorsEn._(_root); } // Path: onboarding @@ -214,7 +215,17 @@ class _StringsSettingsEn { // Translations String get settings => 'Settings'; + String get appSettings => 'App settings'; String get disconnectFromServer => 'Disconnect from server'; + String get aboutApp => 'About the app'; + String get appVersion => 'App version'; + String get createdBy => 'Created by'; + String get visitGooglePlay => 'Visit the application page at Google Play'; + String get visitGitHubRepo => 'Visit the application repository at GitHub'; + String get customizationDescription => 'Set the app theme and color palette.'; + String get generalSettings => 'General settings'; + String get generalSettingsDescription => 'General tweaks for the application.'; + late final _StringsSettingsCustomizationEn customization = _StringsSettingsCustomizationEn._(_root); } // Path: webview @@ -233,6 +244,26 @@ class _StringsWebviewEn { String get share => 'Share'; } +// Path: colors +class _StringsColorsEn { + _StringsColorsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get red => 'Red'; + String get green => 'Green'; + String get blue => 'Blue'; + String get yellow => 'Yellow'; + String get orange => 'Orange'; + String get brown => 'Brown'; + String get cyan => 'Cyan'; + String get purple => 'Purple'; + String get pink => 'Pink'; + String get deepOrange => 'Deep orange'; + String get indigo => 'Indigo'; +} + // Path: links.dates class _StringsLinksDatesEn { _StringsLinksDatesEn._(this._root); @@ -244,6 +275,22 @@ class _StringsLinksDatesEn { String yesterdayAt({required Object time}) => 'Yesterday, ${time}'; } +// Path: settings.customization +class _StringsSettingsCustomizationEn { + _StringsSettingsCustomizationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get customization => 'Customization'; + String get theme => 'Theme'; + String get light => 'Light'; + String get dark => 'Dark'; + String get systemDefined => 'System defined'; + String get color => 'Color'; + String get useDynamicTheme => 'Use dynamic theme'; +} + // Path: class _StringsEs implements Translations { /// You can call this constructor and build your own translation instance of this locale. @@ -273,6 +320,7 @@ class _StringsEs implements Translations { @override late final _StringsSearchEs search = _StringsSearchEs._(_root); @override late final _StringsSettingsEs settings = _StringsSettingsEs._(_root); @override late final _StringsWebviewEs webview = _StringsWebviewEs._(_root); + @override late final _StringsColorsEs colors = _StringsColorsEs._(_root); } // Path: onboarding @@ -334,7 +382,17 @@ class _StringsSettingsEs implements _StringsSettingsEn { // Translations @override String get settings => 'Ajustes'; + @override String get appSettings => 'Ajustes de la aplicación'; @override String get disconnectFromServer => 'Desconectar del servidor'; + @override String get aboutApp => 'Sobre la aplicación'; + @override String get appVersion => 'Versión de la aplicación'; + @override String get createdBy => 'Creado por'; + @override String get visitGooglePlay => 'Visita la página de la aplicación en Google Play'; + @override String get visitGitHubRepo => 'Visita el repositorio de la aplicación en GitHub'; + @override String get customizationDescription => 'Configura el tema y la paleta de colores.'; + @override String get generalSettings => 'Ajustes generales'; + @override String get generalSettingsDescription => 'Ajustes generales para la aplicación.'; + @override late final _StringsSettingsCustomizationEs customization = _StringsSettingsCustomizationEs._(_root); } // Path: webview @@ -353,6 +411,26 @@ class _StringsWebviewEs implements _StringsWebviewEn { @override String get share => 'Compartir'; } +// Path: colors +class _StringsColorsEs implements _StringsColorsEn { + _StringsColorsEs._(this._root); + + @override final _StringsEs _root; // ignore: unused_field + + // Translations + @override String get red => 'Rojo'; + @override String get green => 'Verde'; + @override String get blue => 'Azul'; + @override String get yellow => 'Amarillo'; + @override String get orange => 'Naranja'; + @override String get brown => 'Marron'; + @override String get cyan => 'Cian'; + @override String get purple => 'Morado'; + @override String get pink => 'Rosa'; + @override String get deepOrange => 'Naranja oscuro'; + @override String get indigo => 'Índigo'; +} + // Path: links.dates class _StringsLinksDatesEs implements _StringsLinksDatesEn { _StringsLinksDatesEs._(this._root); @@ -364,6 +442,22 @@ class _StringsLinksDatesEs implements _StringsLinksDatesEn { @override String yesterdayAt({required Object time}) => 'Ayer, ${time}'; } +// Path: settings.customization +class _StringsSettingsCustomizationEs implements _StringsSettingsCustomizationEn { + _StringsSettingsCustomizationEs._(this._root); + + @override final _StringsEs _root; // ignore: unused_field + + // Translations + @override String get customization => 'Personalización'; + @override String get theme => 'Tema'; + @override String get light => 'Claro'; + @override String get dark => 'Oscuro'; + @override String get systemDefined => 'Definido por el sistema'; + @override String get color => 'Color'; + @override String get useDynamicTheme => 'Usar tema dinámico'; +} + /// Flat map(s) containing all translations. /// Only for edge cases! For simple maps, use the map function of this library. @@ -396,7 +490,23 @@ extension on Translations { case 'links.dates.yesterdayAt': return ({required Object time}) => 'Yesterday, ${time}'; case 'search.search': return 'Search'; case 'settings.settings': return 'Settings'; + case 'settings.appSettings': return 'App settings'; case 'settings.disconnectFromServer': return 'Disconnect from server'; + case 'settings.aboutApp': return 'About the app'; + case 'settings.appVersion': return 'App version'; + case 'settings.createdBy': return 'Created by'; + case 'settings.visitGooglePlay': return 'Visit the application page at Google Play'; + case 'settings.visitGitHubRepo': return 'Visit the application repository at GitHub'; + case 'settings.customizationDescription': return 'Set the app theme and color palette.'; + case 'settings.generalSettings': return 'General settings'; + case 'settings.generalSettingsDescription': return 'General tweaks for the application.'; + case 'settings.customization.customization': return 'Customization'; + case 'settings.customization.theme': return 'Theme'; + case 'settings.customization.light': return 'Light'; + case 'settings.customization.dark': return 'Dark'; + case 'settings.customization.systemDefined': return 'System defined'; + case 'settings.customization.color': return 'Color'; + case 'settings.customization.useDynamicTheme': return 'Use dynamic theme'; case 'webview.goBack': return 'Go back'; case 'webview.goForward': return 'Go forward'; case 'webview.reload': return 'Reload'; @@ -404,6 +514,17 @@ extension on Translations { case 'webview.copyLinkClipboard': return 'Copy link to clipboard'; case 'webview.linkCopiedClipboard': return 'Link copied to the clipboard.'; case 'webview.share': return 'Share'; + case 'colors.red': return 'Red'; + case 'colors.green': return 'Green'; + case 'colors.blue': return 'Blue'; + case 'colors.yellow': return 'Yellow'; + case 'colors.orange': return 'Orange'; + case 'colors.brown': return 'Brown'; + case 'colors.cyan': return 'Cyan'; + case 'colors.purple': return 'Purple'; + case 'colors.pink': return 'Pink'; + case 'colors.deepOrange': return 'Deep orange'; + case 'colors.indigo': return 'Indigo'; default: return null; } } @@ -438,7 +559,23 @@ extension on _StringsEs { case 'links.dates.yesterdayAt': return ({required Object time}) => 'Ayer, ${time}'; case 'search.search': return 'Buscar'; case 'settings.settings': return 'Ajustes'; + case 'settings.appSettings': return 'Ajustes de la aplicación'; case 'settings.disconnectFromServer': return 'Desconectar del servidor'; + case 'settings.aboutApp': return 'Sobre la aplicación'; + case 'settings.appVersion': return 'Versión de la aplicación'; + case 'settings.createdBy': return 'Creado por'; + case 'settings.visitGooglePlay': return 'Visita la página de la aplicación en Google Play'; + case 'settings.visitGitHubRepo': return 'Visita el repositorio de la aplicación en GitHub'; + case 'settings.customizationDescription': return 'Configura el tema y la paleta de colores.'; + case 'settings.generalSettings': return 'Ajustes generales'; + case 'settings.generalSettingsDescription': return 'Ajustes generales para la aplicación.'; + case 'settings.customization.customization': return 'Personalización'; + case 'settings.customization.theme': return 'Tema'; + case 'settings.customization.light': return 'Claro'; + case 'settings.customization.dark': return 'Oscuro'; + case 'settings.customization.systemDefined': return 'Definido por el sistema'; + case 'settings.customization.color': return 'Color'; + case 'settings.customization.useDynamicTheme': return 'Usar tema dinámico'; case 'webview.goBack': return 'Ir atrás'; case 'webview.goForward': return 'Ir adelante'; case 'webview.reload': return 'Recargar'; @@ -446,6 +583,17 @@ extension on _StringsEs { case 'webview.copyLinkClipboard': return 'Copiar enlace al portapapeles'; case 'webview.linkCopiedClipboard': return 'Enlace copiado al portapapeles.'; case 'webview.share': return 'Compartir'; + case 'colors.red': return 'Rojo'; + case 'colors.green': return 'Verde'; + case 'colors.blue': return 'Azul'; + case 'colors.yellow': return 'Amarillo'; + case 'colors.orange': return 'Naranja'; + case 'colors.brown': return 'Marron'; + case 'colors.cyan': return 'Cian'; + case 'colors.purple': return 'Morado'; + case 'colors.pink': return 'Rosa'; + case 'colors.deepOrange': return 'Naranja oscuro'; + case 'colors.indigo': return 'Índigo'; default: return null; } } diff --git a/lib/i18n/strings_en.i18n.json b/lib/i18n/strings_en.i18n.json index 16f8fed..3e88726 100644 --- a/lib/i18n/strings_en.i18n.json +++ b/lib/i18n/strings_en.i18n.json @@ -34,7 +34,25 @@ }, "settings": { "settings": "Settings", - "disconnectFromServer": "Disconnect from server" + "appSettings": "App settings", + "disconnectFromServer": "Disconnect from server", + "aboutApp": "About the app", + "appVersion": "App version", + "createdBy": "Created by", + "visitGooglePlay": "Visit the application page at Google Play", + "visitGitHubRepo": "Visit the application repository at GitHub", + "customizationDescription": "Set the app theme and color palette.", + "generalSettings": "General settings", + "generalSettingsDescription": "General tweaks for the application.", + "customization": { + "customization": "Customization", + "theme": "Theme", + "light": "Light", + "dark": "Dark", + "systemDefined": "System defined", + "color": "Color", + "useDynamicTheme": "Use dynamic theme" + } }, "webview": { "goBack": "Go back", @@ -44,5 +62,18 @@ "copyLinkClipboard": "Copy link to clipboard", "linkCopiedClipboard": "Link copied to the clipboard.", "share": "Share" + }, + "colors": { + "red": "Red", + "green": "Green", + "blue": "Blue", + "yellow": "Yellow", + "orange": "Orange", + "brown": "Brown", + "cyan": "Cyan", + "purple": "Purple", + "pink": "Pink", + "deepOrange": "Deep orange", + "indigo": "Indigo" } } \ No newline at end of file diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json index 9c6a14f..e9473fd 100644 --- a/lib/i18n/strings_es.i18n.json +++ b/lib/i18n/strings_es.i18n.json @@ -34,7 +34,25 @@ }, "settings": { "settings": "Ajustes", - "disconnectFromServer": "Desconectar del servidor" + "appSettings": "Ajustes de la aplicación", + "disconnectFromServer": "Desconectar del servidor", + "aboutApp": "Sobre la aplicación", + "appVersion": "Versión de la aplicación", + "createdBy": "Creado por", + "visitGooglePlay": "Visita la página de la aplicación en Google Play", + "visitGitHubRepo": "Visita el repositorio de la aplicación en GitHub", + "customizationDescription": "Configura el tema y la paleta de colores.", + "generalSettings": "Ajustes generales", + "generalSettingsDescription": "Ajustes generales para la aplicación.", + "customization": { + "customization": "Personalización", + "theme": "Tema", + "light": "Claro", + "dark": "Oscuro", + "systemDefined": "Definido por el sistema", + "color": "Color", + "useDynamicTheme": "Usar tema dinámico" + } }, "webview": { "goBack": "Ir atrás", @@ -44,5 +62,18 @@ "copyLinkClipboard": "Copiar enlace al portapapeles", "linkCopiedClipboard": "Enlace copiado al portapapeles.", "share": "Compartir" + }, + "colors": { + "red": "Rojo", + "green": "Verde", + "blue": "Azul", + "yellow": "Amarillo", + "orange": "Naranja", + "brown": "Marron", + "cyan": "Cian", + "purple": "Morado", + "pink": "Rosa", + "deepOrange": "Naranja oscuro", + "indigo": "Índigo" } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index fd043f8..0126911 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,8 +6,11 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:linkdy/constants/global_keys.dart'; import 'package:dynamic_color/dynamic_color.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:linkdy/providers/app_status_provider.dart'; +import 'package:linkdy/providers/app_info_provider.dart'; import 'package:linkdy/config/theme.dart'; import 'package:linkdy/constants/colors.dart'; import 'package:linkdy/i18n/strings.g.dart'; @@ -25,11 +28,14 @@ void main() async { final sharedPreferences = await SharedPreferences.getInstance(); + final appInfo = await PackageInfo.fromPlatform(); + runApp( TranslationProvider( child: ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(sharedPreferences), + appInfoProvider.overrideWithValue(appInfo), ], child: const MyApp(), ), @@ -42,18 +48,33 @@ class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final selectedColor = ref.watch(appStatusProvider).selectedColor; + final useDynamicColor = ref.watch(appStatusProvider).useDynamicTheme; + return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) => MaterialApp.router( - title: 'Linkdy', - theme: lightDynamic != null ? lightTheme(lightDynamic) : lightThemeOldVersions(colors[0]), - darkTheme: darkDynamic != null ? darkTheme(darkDynamic) : darkThemeOldVersions(colors[0]), - locale: TranslationProvider.of(context).flutterLocale, - supportedLocales: AppLocaleUtils.supportedLocales, - localizationsDelegates: GlobalMaterialLocalizations.delegates, - debugShowCheckedModeBanner: false, - scaffoldMessengerKey: scaffoldMessengerGlobalKey, - routerConfig: ref.watch(routerProvider), - ), + builder: (lightDynamic, darkDynamic) { + ref.read(appStatusProvider.notifier).setSupportsDynamicTheme(lightDynamic != null && darkDynamic != null); + return MaterialApp.router( + title: 'Linkdy', + theme: lightDynamic != null + ? useDynamicColor == true + ? lightTheme(lightDynamic) + : lightThemeOldVersions(colors[selectedColor]) + : lightThemeOldVersions(colors[selectedColor]), + darkTheme: darkDynamic != null + ? useDynamicColor == true + ? darkTheme(darkDynamic) + : darkThemeOldVersions(colors[selectedColor]) + : darkThemeOldVersions(colors[selectedColor]), + themeMode: ref.watch(selectedThemeProvider), + locale: TranslationProvider.of(context).flutterLocale, + supportedLocales: AppLocaleUtils.supportedLocales, + localizationsDelegates: GlobalMaterialLocalizations.delegates, + debugShowCheckedModeBanner: false, + scaffoldMessengerKey: scaffoldMessengerGlobalKey, + routerConfig: ref.watch(routerProvider), + ); + }, ); } } diff --git a/lib/models/app_status.dart b/lib/models/app_status.dart index 8d736f8..baf28a4 100644 --- a/lib/models/app_status.dart +++ b/lib/models/app_status.dart @@ -1,12 +1,15 @@ -import 'package:linkdy/models/server_instance.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:linkdy/constants/enums.dart'; -class AppStatus { - AppStatus(); +class AppStatusModel { + SelectedTheme selectedTheme; + bool supportsDynamicTheme; + bool useDynamicTheme; + int selectedColor; - AppStatus copyWith({ - SharedPreferences? sharedPreferences, - ServerInstance? serverInstance, - }) => - AppStatus(); + AppStatusModel({ + this.selectedTheme = SelectedTheme.system, + this.supportsDynamicTheme = false, + this.useDynamicTheme = true, + this.selectedColor = 0, + }); } diff --git a/lib/providers/app_info_provider.dart b/lib/providers/app_info_provider.dart new file mode 100644 index 0000000..00e1124 --- /dev/null +++ b/lib/providers/app_info_provider.dart @@ -0,0 +1,9 @@ +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'app_info_provider.g.dart'; + +@Riverpod(keepAlive: true) +PackageInfo appInfo(AppInfoRef ref) { + throw UnimplementedError(); +} diff --git a/lib/providers/app_info_provider.g.dart b/lib/providers/app_info_provider.g.dart new file mode 100644 index 0000000..4a6be4b --- /dev/null +++ b/lib/providers/app_info_provider.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_info_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$appInfoHash() => r'c1b99fe8e0ba1b1ba04aa2c1ae066a3de9f1dac1'; + +/// See also [appInfo]. +@ProviderFor(appInfo) +final appInfoProvider = Provider.internal( + appInfo, + name: r'appInfoProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appInfoHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AppInfoRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/providers/app_status_provider.dart b/lib/providers/app_status_provider.dart index 9ccfa45..38e38a1 100644 --- a/lib/providers/app_status_provider.dart +++ b/lib/providers/app_status_provider.dart @@ -1,18 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:linkdy/constants/shared_preferences_keys.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:linkdy/providers/shared_preferences_provider.dart'; +import 'package:linkdy/constants/enums.dart'; import 'package:linkdy/models/app_status.dart'; -import 'package:shared_preferences/shared_preferences.dart'; part 'app_status_provider.g.dart'; @Riverpod(keepAlive: true) -class AppStatusProvider extends _$AppStatusProvider { +class AppStatus extends _$AppStatus { @override - AppStatus build() { - return AppStatus(); + AppStatusModel build() { + final sharedPreferences = ref.watch(sharedPreferencesProvider); + return AppStatusModel( + selectedColor: sharedPreferences.getInt(SharedPreferencesKeys.selectedColor) ?? 0, + selectedTheme: SelectedTheme.values[sharedPreferences.getInt(SharedPreferencesKeys.selectedTheme) ?? 0], + useDynamicTheme: sharedPreferences.getBool(SharedPreferencesKeys.useDynamicTheme) ?? true, + ); } - void setSharedPreferencesInstance(SharedPreferences instance) { - state = state.copyWith(sharedPreferences: instance); + void setTheme(SelectedTheme theme) { + state.selectedTheme = theme; + ref.read(sharedPreferencesProvider).setInt(SharedPreferencesKeys.selectedTheme, theme.index); + ref.notifyListeners(); + } + + void setSupportsDynamicTheme(bool value) { + state.supportsDynamicTheme = value; + } + + void setUseDynamicTheme(bool value) { + state.useDynamicTheme = value; + ref.read(sharedPreferencesProvider).setBool(SharedPreferencesKeys.useDynamicTheme, value); + ref.notifyListeners(); + } + + void setSelectedColor(int value) { + state.selectedColor = value; + ref.read(sharedPreferencesProvider).setInt(SharedPreferencesKeys.selectedColor, value); + ref.notifyListeners(); + } +} + +@riverpod +ThemeMode selectedTheme(SelectedThemeRef ref) { + switch (ref.watch(appStatusProvider).selectedTheme) { + case SelectedTheme.system: + return SchedulerBinding.instance.window.platformBrightness == Brightness.light ? ThemeMode.light : ThemeMode.dark; + + case SelectedTheme.light: + return ThemeMode.light; + + case SelectedTheme.dark: + return ThemeMode.dark; + + default: + return ThemeMode.light; } } diff --git a/lib/providers/app_status_provider.g.dart b/lib/providers/app_status_provider.g.dart index 5182e56..1cfaef1 100644 --- a/lib/providers/app_status_provider.g.dart +++ b/lib/providers/app_status_provider.g.dart @@ -6,21 +6,34 @@ part of 'app_status_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$appStatusProviderHash() => r'ba585a0cf3cf46f88f5895eacec3e992bf0cd42f'; +String _$selectedThemeHash() => r'b820484a59ed72419c5b4cad0b894a030b54c702'; -/// See also [AppStatusProvider]. -@ProviderFor(AppStatusProvider) -final appStatusProviderProvider = - NotifierProvider.internal( - AppStatusProvider.new, - name: r'appStatusProviderProvider', +/// See also [selectedTheme]. +@ProviderFor(selectedTheme) +final selectedThemeProvider = AutoDisposeProvider.internal( + selectedTheme, + name: r'selectedThemeProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$appStatusProviderHash, + : _$selectedThemeHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$AppStatusProvider = Notifier; +typedef SelectedThemeRef = AutoDisposeProviderRef; +String _$appStatusHash() => r'cde4567157dd5a89f842a2818efc199db3585232'; + +/// See also [AppStatus]. +@ProviderFor(AppStatus) +final appStatusProvider = NotifierProvider.internal( + AppStatus.new, + name: r'appStatusProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appStatusHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AppStatus = Notifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/providers/server_instances_provider.dart b/lib/providers/server_instances_provider.dart index 0ba9082..d6b37c0 100644 --- a/lib/providers/server_instances_provider.dart +++ b/lib/providers/server_instances_provider.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:linkdy/config/shared_preferences_keys.dart'; +import 'package:linkdy/constants/shared_preferences_keys.dart'; import 'package:linkdy/providers/shared_preferences_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/providers/shared_preferences_provider.dart b/lib/providers/shared_preferences_provider.dart index 8ef1e3f..853e617 100644 --- a/lib/providers/shared_preferences_provider.dart +++ b/lib/providers/shared_preferences_provider.dart @@ -1,6 +1,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; -final sharedPreferencesProvider = Provider((ref) { +part 'shared_preferences_provider.g.dart'; + +@Riverpod(keepAlive: true) +SharedPreferences sharedPreferences(SharedPreferencesRef ref) { throw UnimplementedError(); -}); +} diff --git a/lib/providers/shared_preferences_provider.g.dart b/lib/providers/shared_preferences_provider.g.dart new file mode 100644 index 0000000..7956f4b --- /dev/null +++ b/lib/providers/shared_preferences_provider.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'shared_preferences_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$sharedPreferencesHash() => r'3a9f8412df34c1653d08100c9826aa2125b80f7f'; + +/// See also [sharedPreferences]. +@ProviderFor(sharedPreferences) +final sharedPreferencesProvider = Provider.internal( + sharedPreferences, + name: r'sharedPreferencesProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$sharedPreferencesHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef SharedPreferencesRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/router/paths.dart b/lib/router/paths.dart index d1b1598..673e9ed 100644 --- a/lib/router/paths.dart +++ b/lib/router/paths.dart @@ -4,4 +4,5 @@ class RoutesPaths { static const webview = "/webview"; static const search = "/search"; static const settings = "/settings"; + static const customization = "/settings/customization"; } diff --git a/lib/router/routes.dart b/lib/router/routes.dart index 608df96..7bcf536 100644 --- a/lib/router/routes.dart +++ b/lib/router/routes.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:linkdy/screens/links/ui/links.dart'; import 'package:linkdy/screens/onboarding/ui/onboarding.dart'; import 'package:linkdy/screens/search/ui/search.dart'; +import 'package:linkdy/screens/settings/ui/customization/customization.dart'; import 'package:linkdy/screens/settings/ui/settings.dart'; import 'package:linkdy/screens/webview/ui/webview.dart'; import 'package:linkdy/widgets/layout.dart'; @@ -58,6 +59,10 @@ final List appRoutes = [ path: RoutesPaths.settings, builder: (context, state) => const Settings(), ), + GoRoute( + path: RoutesPaths.customization, + builder: (context, state) => const Customization(), + ), ], ), ], diff --git a/lib/screens/settings/model/customization.model.dart b/lib/screens/settings/model/customization.model.dart new file mode 100644 index 0000000..ff3a593 --- /dev/null +++ b/lib/screens/settings/model/customization.model.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +class CustomizationModel { + ScrollController? scrollController; + + CustomizationModel({ + this.scrollController, + }); +} diff --git a/lib/screens/settings/provider/customization.provider.dart b/lib/screens/settings/provider/customization.provider.dart new file mode 100644 index 0000000..dffbc2c --- /dev/null +++ b/lib/screens/settings/provider/customization.provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:linkdy/screens/settings/model/customization.model.dart'; + +part 'customization.provider.g.dart'; + +@riverpod +class Customization extends _$Customization { + @override + CustomizationModel build() { + return CustomizationModel( + scrollController: ScrollController(), + ); + } +} diff --git a/lib/screens/settings/provider/customization.provider.g.dart b/lib/screens/settings/provider/customization.provider.g.dart new file mode 100644 index 0000000..4e68ffa --- /dev/null +++ b/lib/screens/settings/provider/customization.provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'customization.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$customizationHash() => r'c4cf4b566abbe697ec14c5024e1576f230d3e9fb'; + +/// See also [Customization]. +@ProviderFor(Customization) +final customizationProvider = + AutoDisposeNotifierProvider.internal( + Customization.new, + name: r'customizationProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$customizationHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Customization = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/screens/settings/ui/customization/color_item.dart b/lib/screens/settings/ui/customization/color_item.dart new file mode 100644 index 0000000..d753bf5 --- /dev/null +++ b/lib/screens/settings/ui/customization/color_item.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; + +class ColorItem extends StatelessWidget { + final int index; + final int total; + final Color color; + final int numericValue; + final int? selectedValue; + final void Function(int) onChanged; + + const ColorItem({ + super.key, + required this.index, + required this.total, + required this.color, + required this.numericValue, + required this.selectedValue, + required this.onChanged + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: index == 0 + ? const EdgeInsets.only(top: 10, right: 10, bottom: 10) + : index == total-1 + ? const EdgeInsets.only(top: 10, bottom: 10, left: 10) + : const EdgeInsets.all(10), + child: Material( + borderRadius: BorderRadius.circular(50), + child: InkWell( + onTap: () => onChanged(numericValue), + borderRadius: BorderRadius.circular(50), + overlayColor: const MaterialStatePropertyAll(Colors.grey), + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(50) + ), + child: AnimatedOpacity( + opacity: numericValue == selectedValue ? 1 : 0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + child: Icon( + Icons.check, + size: 30, + color: color.computeLuminance() > 0.5 ? Colors.black : Colors.white, + ), + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/ui/customization/customization.dart b/lib/screens/settings/ui/customization/customization.dart new file mode 100644 index 0000000..ac6fc88 --- /dev/null +++ b/lib/screens/settings/ui/customization/customization.dart @@ -0,0 +1,156 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:linkdy/screens/settings/provider/customization.provider.dart'; +import 'package:linkdy/screens/settings/ui/customization/color_item.dart'; +import 'package:linkdy/screens/settings/ui/customization/theme_mode_button.dart'; +import 'package:linkdy/utils/color_translation.dart'; +import 'package:linkdy/widgets/custom_switch_list_tile.dart'; +import 'package:linkdy/widgets/section_label.dart'; + +import 'package:linkdy/constants/colors.dart'; +import 'package:linkdy/constants/enums.dart'; +import 'package:linkdy/i18n/strings.g.dart'; +import 'package:linkdy/providers/app_status_provider.dart'; + +class Customization extends ConsumerWidget { + const Customization({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final width = MediaQuery.of(context).size.width; + + return Scaffold( + appBar: AppBar( + title: Text(t.settings.customization.customization), + centerTitle: false, + ), + body: SafeArea( + child: ListView( + children: [ + SectionLabel( + label: t.settings.customization.theme, + padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 5), + ), + Column( + children: [ + CustomSwitchListTile( + value: ref.watch(appStatusProvider).selectedTheme == SelectedTheme.system ? true : false, + onChanged: (value) => ref + .read(appStatusProvider.notifier) + .setTheme(value == true ? SelectedTheme.system : SelectedTheme.light), + title: t.settings.customization.systemDefined, + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ThemeModeButton( + icon: Icons.light_mode, + value: 1, + selected: ref.watch(appStatusProvider).selectedTheme.index, + label: t.settings.customization.light, + onChanged: (value) => ref.read(appStatusProvider.notifier).setTheme(SelectedTheme.values[value]), + disabled: ref.watch(appStatusProvider).selectedTheme == SelectedTheme.system ? true : false, + ), + ThemeModeButton( + icon: Icons.dark_mode, + value: 2, + selected: ref.watch(appStatusProvider).selectedTheme.index, + label: t.settings.customization.dark, + onChanged: (value) => ref.read(appStatusProvider.notifier).setTheme(SelectedTheme.values[value]), + disabled: ref.watch(appStatusProvider).selectedTheme == SelectedTheme.system ? true : false, + ), + ], + ), + ], + ), + SectionLabel( + label: t.settings.customization.color, + padding: const EdgeInsets.only(top: 45, left: 16, right: 16, bottom: 5), + ), + if (ref.watch(appStatusProvider).supportsDynamicTheme) + CustomSwitchListTile( + value: ref.watch(appStatusProvider).useDynamicTheme, + onChanged: ref.read(appStatusProvider.notifier).setUseDynamicTheme, + title: t.settings.customization.useDynamicTheme, + ), + if (ref.watch(appStatusProvider).supportsDynamicTheme == false || + (ref.watch(appStatusProvider).supportsDynamicTheme == true && + ref.watch(appStatusProvider).useDynamicTheme == false)) + Padding( + padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16), + child: Scrollbar( + controller: ref.watch(customizationProvider).scrollController, + thumbVisibility: Platform.isMacOS || Platform.isLinux || Platform.isWindows, + interactive: Platform.isMacOS || Platform.isLinux || Platform.isWindows, + thickness: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 8 : 0, + child: Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width, + height: 70, + child: ListView.builder( + controller: ref.watch(customizationProvider).scrollController, + scrollDirection: Axis.horizontal, + itemCount: colors.length, + padding: const EdgeInsets.all(0), + itemBuilder: (context, index) { + if (index == 0) { + return Row( + children: [ + ColorItem( + index: index, + total: colors.length, + color: colors[index], + numericValue: index, + selectedValue: ref.watch(appStatusProvider).selectedColor, + onChanged: ref.read(appStatusProvider.notifier).setSelectedColor, + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + width: 1, + height: 60, + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(1), + ), + ), + ], + ); + } else { + return ColorItem( + index: index, + total: colors.length, + color: colors[index], + numericValue: index, + selectedValue: ref.watch(appStatusProvider).selectedColor, + onChanged: ref.read(appStatusProvider.notifier).setSelectedColor, + ); + } + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Text( + colorTranslation(ref.watch(appStatusProvider).selectedColor), + style: TextStyle(color: Theme.of(context).listTileTheme.iconColor, fontSize: 16), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/settings/ui/customization/theme_mode_button.dart b/lib/screens/settings/ui/customization/theme_mode_button.dart new file mode 100644 index 0000000..df3f8d1 --- /dev/null +++ b/lib/screens/settings/ui/customization/theme_mode_button.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +class ThemeModeButton extends StatelessWidget { + final IconData icon; + final int value; + final int selected; + final String label; + final void Function(int) onChanged; + final bool? disabled; + + const ThemeModeButton({ + Key? key, + required this.icon, + required this.value, + required this.selected, + required this.label, + required this.onChanged, + this.disabled + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final Color greyBackgroundColor = Theme.of(context).brightness == Brightness.light + ?const Color.fromRGBO(200, 200, 200, 1) + :const Color.fromRGBO(50, 50, 50, 1); + final Color greyIconColor = Theme.of(context).brightness == Brightness.light + ? const Color.fromRGBO(130, 130, 130, 1) + : const Color.fromRGBO(100, 100, 100, 1); + + return ElevatedButton( + onPressed: disabled == null || disabled == false + ? () => onChanged(value) + : null, + style: ButtonStyle( + elevation: MaterialStateProperty.all(0), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ) + ), + backgroundColor: MaterialStateProperty.all( + value == selected + ? disabled == null || disabled == false + ? Theme.of(context).colorScheme.primary + : greyBackgroundColor + : disabled == null || disabled == false + ? Theme.of(context).colorScheme.surfaceVariant + : greyBackgroundColor, + ) + ), + child: AnimatedContainer( + width: 118, + height: 150, + curve: Curves.easeInOut, + duration: const Duration(milliseconds: 200), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + icon, + color: value == selected + ? disabled == null || disabled == false + ? Theme.of(context).colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white + : greyIconColor + : disabled == null || disabled == false + ? Theme.of(context).colorScheme.primary + : greyIconColor, + size: 30, + ), + Text( + label, + style: TextStyle( + color: value == selected + ? disabled == null || disabled == false + ? Theme.of(context).colorScheme.primary.computeLuminance() > 0.5 ? Colors.black : Colors.white + : greyIconColor + : disabled == null || disabled == false + ? Theme.of(context).colorScheme.primary + : greyIconColor, + fontSize: 18 + ), + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/ui/settings.dart b/lib/screens/settings/ui/settings.dart index 7a2daad..67d3a4f 100644 --- a/lib/screens/settings/ui/settings.dart +++ b/lib/screens/settings/ui/settings.dart @@ -1,6 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:linkdy/providers/router_provider.dart'; +import 'package:linkdy/router/paths.dart'; +import 'package:linkdy/widgets/section_label.dart'; +import 'package:linkdy/widgets/custom_list_tile.dart'; + +import 'package:linkdy/providers/app_info_provider.dart'; +import 'package:linkdy/utils/open_url.dart'; +import 'package:linkdy/constants/strings.dart'; +import 'package:linkdy/constants/urls.dart'; import 'package:linkdy/i18n/strings.g.dart'; import 'package:linkdy/providers/api_client_provider.dart'; @@ -15,6 +25,18 @@ class Settings extends ConsumerWidget { ), body: ListView( children: [ + SectionLabel(label: t.settings.appSettings), + CustomListTile( + icon: Icons.palette_rounded, + title: t.settings.customization.customization, + subtitle: t.settings.customizationDescription, + onTap: () => ref.read(routerProvider).push(RoutesPaths.customization), + ), + CustomListTile( + icon: Icons.settings_rounded, + title: t.settings.generalSettings, + subtitle: t.settings.generalSettingsDescription, + ), FilledButton.icon( onPressed: () => ref.read(apiClientProviderProvider.notifier).disconnectApiClient(), icon: const Icon(Icons.clear_rounded), @@ -22,6 +44,44 @@ class Settings extends ConsumerWidget { t.settings.disconnectFromServer, ), ), + SectionLabel(label: t.settings.aboutApp), + CustomListTile( + title: t.settings.appVersion, + subtitle: ref.watch(appInfoProvider).version, + ), + CustomListTile( + title: t.settings.createdBy, + subtitle: Strings.createdBy, + ), + Padding( + padding: const EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // if (Platform.isAndroid) + // IconButton( + // onPressed: () => openUrl(Urls.playStore), + // icon: SvgPicture.asset( + // 'assets/resources/google-play.svg', + // color: Theme.of(context).colorScheme.onSurfaceVariant, + // width: 30, + // height: 30, + // ), + // tooltip: t.settings.visitGooglePlay, + // ), + IconButton( + onPressed: () => openUrl(Urls.gitHubRepo), + icon: SvgPicture.asset( + 'assets/resources/github.svg', + color: Theme.of(context).colorScheme.onSurfaceVariant, + width: 30, + height: 30, + ), + tooltip: t.settings.visitGitHubRepo, + ), + ], + ), + ), ], ), ); diff --git a/lib/utils/color_translation.dart b/lib/utils/color_translation.dart new file mode 100644 index 0000000..fb17e29 --- /dev/null +++ b/lib/utils/color_translation.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import 'package:linkdy/i18n/strings.g.dart'; + +String colorTranslation(int index) { + // This indexes has to be in sync with colors array at constants/colors.dart + List translations = [ + "Linkding", + t.colors.red, + t.colors.green, + t.colors.blue, + t.colors.yellow, + t.colors.orange, + t.colors.brown, + t.colors.cyan, + t.colors.purple, + t.colors.pink, + t.colors.deepOrange, + t.colors.indigo, + ]; + return translations[index]; +} diff --git a/lib/widgets/custom_list_tile.dart b/lib/widgets/custom_list_tile.dart new file mode 100644 index 0000000..30c9653 --- /dev/null +++ b/lib/widgets/custom_list_tile.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; + +class CustomListTile extends StatelessWidget { + final String title; + final String? subtitle; + final Widget? subtitleWidget; + final void Function()? onTap; + final IconData? icon; + final Widget? trailing; + final EdgeInsets? padding; + final void Function()? onLongPress; + final bool? disabled; + final void Function(bool)? onHover; + final Color? color; + + const CustomListTile({ + Key? key, + required this.title, + this.subtitle, + this.subtitleWidget, + this.onTap, + this.icon, + this.trailing, + this.padding, + this.onLongPress, + this.disabled, + this.onHover, + this.color, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: color ?? Colors.transparent, + child: InkWell( + onTap: onTap, + onHover: onHover, + onLongPress: onLongPress, + child: Padding( + padding: padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon( + icon, + size: 24, + color: disabled == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.38) + : Theme.of(context).listTileTheme.iconColor, + ), + const SizedBox(width: 16), + ], + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: disabled == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.38) + : Theme.of(context).colorScheme.onSurface, + ), + ), + if (subtitle != null || subtitleWidget != null) ...[ + const SizedBox(height: 5), + if (subtitle == null && subtitleWidget != null) subtitleWidget!, + if (subtitle != null && subtitleWidget == null) + Text( + subtitle!, + style: TextStyle( + color: disabled == true + ? Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.38) + : Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ] + ], + ), + ) + ], + ), + ), + if (trailing != null) ...[const SizedBox(width: 16), trailing!] + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/custom_switch_list_tile.dart b/lib/widgets/custom_switch_list_tile.dart new file mode 100644 index 0000000..84e65c5 --- /dev/null +++ b/lib/widgets/custom_switch_list_tile.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +class CustomSwitchListTile extends StatelessWidget { + final bool value; + final void Function(bool) onChanged; + final String title; + final String? subtitle; + final bool? disabled; + final EdgeInsets? padding; + final IconData? leadingIcon; + + const CustomSwitchListTile({ + super.key, + required this.value, + required this.onChanged, + required this.title, + this.disabled, + this.subtitle, + this.padding, + this.leadingIcon, + }); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: disabled != null && disabled == true + ? null + : () => onChanged(!value), + child: Padding( + padding: padding ?? const EdgeInsets.only( + top: 12, left: 16, right: 18, bottom: 16 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (leadingIcon != null) ...[ + Icon( + leadingIcon, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + color: disabled != null && disabled == true + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.38) + : Theme.of(context).colorScheme.onSurface, + ), + ), + if (subtitle != null) ... [ + const SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width-110, + child: Text( + subtitle!, + style: TextStyle( + fontSize: 14, + color: disabled != null && disabled == true + ? Theme.of(context).listTileTheme.textColor!.withOpacity(0.38) + : Theme.of(context).listTileTheme.textColor + ), + ), + ), + ] + ], + ), + ), + const SizedBox(width: 16), + Switch( + value: value, + onChanged: disabled != null && disabled == true + ? null + : onChanged, + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 17c0f0f..8ac780d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import dynamic_color import flutter_inappwebview_macos +import package_info_plus import path_provider_foundation import share_plus import shared_preferences_foundation @@ -15,6 +16,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index e8ea23e..9116bdf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -661,6 +661,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 527fc3a..a9219b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: flutter_custom_tabs: ^2.0.0+1 flutter_inappwebview: ^6.0.0 share_plus: ^7.2.2 + package_info_plus: ^5.0.1 dev_dependencies: build_runner: ^2.4.8