From 1bb6926b5e926db09a4f11232fcd9a0b2518a42c Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 2 Dec 2024 09:33:44 -0600 Subject: [PATCH 1/9] chore(mobile): Add const linter (#14447) --- mobile/analysis_options.yaml | 1 + mobile/lib/pages/albums/albums.page.dart | 10 +++++----- .../lib/pages/common/album_options.page.dart | 2 +- .../album_shared_user_selection.page.dart | 2 +- mobile/lib/pages/library/library.page.dart | 10 +++++----- .../places/places_collection.page.dart | 2 +- mobile/lib/pages/search/search.page.dart | 20 ++++++++++--------- .../lib/repositories/auth_api.repository.dart | 4 +++- mobile/lib/services/api.service.dart | 2 +- mobile/lib/utils/immich_app_theme.dart | 8 ++++---- .../widgets/album/album_viewer_appbar.dart | 4 ++-- .../asset_viewer/bottom_gallery_bar.dart | 2 +- .../common/app_bar_dialog/app_bar_dialog.dart | 4 ++-- .../modules/map/map_theme_override_test.dart | 2 +- 14 files changed, 39 insertions(+), 34 deletions(-) diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 7a20c2a6a3f91..2b4b810f2aeb5 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -28,6 +28,7 @@ linter: use_build_context_synchronously: false require_trailing_commas: true unrelated_type_equality_checks: true + prefer_const_constructors: true # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/mobile/lib/pages/albums/albums.page.dart b/mobile/lib/pages/albums/albums.page.dart index e466149ac3ee8..6f7d99b72726b 100644 --- a/mobile/lib/pages/albums/albums.page.dart +++ b/mobile/lib/pages/albums/albums.page.dart @@ -78,7 +78,7 @@ class AlbumsPage extends HookConsumerWidget { showUploadButton: false, actions: [ IconButton( - icon: Icon( + icon: const Icon( Icons.add_rounded, size: 28, ), @@ -112,13 +112,13 @@ class AlbumsPage extends HookConsumerWidget { ], begin: Alignment.topLeft, end: Alignment.bottomRight, - transform: GradientRotation(0.5 * pi), + transform: const GradientRotation(0.5 * pi), ), ), child: TextField( autofocus: false, decoration: InputDecoration( - contentPadding: EdgeInsets.all(16), + contentPadding: const EdgeInsets.all(16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(25), borderSide: BorderSide( @@ -362,13 +362,13 @@ class SortButton extends ConsumerWidget { return MenuAnchor( style: MenuStyle( - elevation: WidgetStatePropertyAll(1), + elevation: const WidgetStatePropertyAll(1), shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), - padding: WidgetStatePropertyAll( + padding: const WidgetStatePropertyAll( EdgeInsets.all(4), ), ), diff --git a/mobile/lib/pages/common/album_options.page.dart b/mobile/lib/pages/common/album_options.page.dart index d9f8544af9c70..93dfad00c4e4c 100644 --- a/mobile/lib/pages/common/album_options.page.dart +++ b/mobile/lib/pages/common/album_options.page.dart @@ -49,7 +49,7 @@ class AlbumOptionsPage extends HookConsumerWidget { if (isSuccess) { context.navigateTo( - TabControllerRoute(children: [AlbumsRoute()]), + const TabControllerRoute(children: [AlbumsRoute()]), ); } else { showErrorMessage(); diff --git a/mobile/lib/pages/common/album_shared_user_selection.page.dart b/mobile/lib/pages/common/album_shared_user_selection.page.dart index 9dadef1a76f8a..ed8a45194deb1 100644 --- a/mobile/lib/pages/common/album_shared_user_selection.page.dart +++ b/mobile/lib/pages/common/album_shared_user_selection.page.dart @@ -33,7 +33,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { if (newAlbum != null) { ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); context.maybePop(true); - context.navigateTo(TabControllerRoute(children: [AlbumsRoute()])); + context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); } ScaffoldMessenger( diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart index 837005c175eac..92fe8cec17368 100644 --- a/mobile/lib/pages/library/library.page.dart +++ b/mobile/lib/pages/library/library.page.dart @@ -28,7 +28,7 @@ class LibraryPage extends ConsumerWidget { ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); return Scaffold( - appBar: ImmichAppBar(), + appBar: const ImmichAppBar(), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ListView( @@ -81,7 +81,7 @@ class LibraryPage extends ConsumerWidget { ], ), const SizedBox(height: 12), - QuickAccessButtons(), + const QuickAccessButtons(), const SizedBox( height: 32, ), @@ -122,8 +122,8 @@ class QuickAccessButtons extends ConsumerWidget { ListTile( shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + topLeft: const Radius.circular(20), + topRight: const Radius.circular(20), bottomLeft: Radius.circular(partners.isEmpty ? 20 : 0), bottomRight: Radius.circular(partners.isEmpty ? 20 : 0), ), @@ -173,7 +173,7 @@ class PartnerList extends ConsumerWidget { right: 18.0, ), leading: userAvatar(context, partner, radius: 16), - title: Text( + title: const Text( "partner_list_user_photos", style: TextStyle( fontWeight: FontWeight.w500, diff --git a/mobile/lib/pages/library/places/places_collection.page.dart b/mobile/lib/pages/library/places/places_collection.page.dart index 3e4f9f6a1da01..f42febc373d26 100644 --- a/mobile/lib/pages/library/places/places_collection.page.dart +++ b/mobile/lib/pages/library/places/places_collection.page.dart @@ -60,7 +60,7 @@ class PlacesCollectionPage extends HookConsumerWidget { ); }, error: (error, stask) => const Text('Error getting places'), - loading: () => Center(child: const CircularProgressIndicator()), + loading: () => const Center(child: CircularProgressIndicator()), ), ], ), diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart index 82d7c0a168bc4..9f2ddee446149 100644 --- a/mobile/lib/pages/search/search.page.dart +++ b/mobile/lib/pages/search/search.page.dart @@ -499,8 +499,8 @@ class SearchPage extends HookConsumerWidget { controller: textSearchController, decoration: InputDecoration( contentPadding: prefilter != null - ? EdgeInsets.only(left: 24) - : EdgeInsets.all(8), + ? const EdgeInsets.only(left: 24) + : const EdgeInsets.all(8), prefixIcon: prefilter != null ? null : Icon( @@ -647,7 +647,9 @@ class SearchResultGrid extends StatelessWidget { stackEnabled: false, emptyIndicator: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: !isSearching ? SearchEmptyContent() : SizedBox.shrink(), + child: !isSearching + ? const SearchEmptyContent() + : const SizedBox.shrink(), ), ), ), @@ -666,7 +668,7 @@ class SearchEmptyContent extends StatelessWidget { child: ListView( shrinkWrap: false, children: [ - SizedBox(height: 40), + const SizedBox(height: 40), Center( child: Image.asset( context.isDarkTheme @@ -675,15 +677,15 @@ class SearchEmptyContent extends StatelessWidget { height: 125, ), ), - SizedBox(height: 16), + const SizedBox(height: 16), Center( child: Text( "Search for your photos and videos", style: context.textTheme.labelLarge, ), ), - SizedBox(height: 32), - QuickLinkList(), + const SizedBox(height: 32), + const QuickLinkList(), ], ), ); @@ -725,13 +727,13 @@ class QuickLinkList extends StatelessWidget { QuickLink( title: 'videos'.tr(), icon: Icons.play_circle_outline_rounded, - onTap: () => context.pushRoute(AllVideosRoute()), + onTap: () => context.pushRoute(const AllVideosRoute()), ), QuickLink( title: 'favorites'.tr(), icon: Icons.favorite_border_rounded, isBottom: true, - onTap: () => context.pushRoute(FavoritesRoute()), + onTap: () => context.pushRoute(const FavoritesRoute()), ), ], ), diff --git a/mobile/lib/repositories/auth_api.repository.dart b/mobile/lib/repositories/auth_api.repository.dart index faa2916adb1be..f3a1d52de37da 100644 --- a/mobile/lib/repositories/auth_api.repository.dart +++ b/mobile/lib/repositories/auth_api.repository.dart @@ -39,7 +39,9 @@ class AuthApiRepository extends ApiRepository implements IAuthApiRepository { @override Future logout() async { - await _apiService.authenticationApi.logout().timeout(Duration(seconds: 7)); + await _apiService.authenticationApi + .logout() + .timeout(const Duration(seconds: 7)); } _mapLoginReponse(LoginResponseDto dto) { diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index bd754ac214547..63cd3f9f8cf74 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -104,7 +104,7 @@ class ApiService implements Authentication { try { await setEndpoint(serverUrl); - await serverInfoApi.pingServer().timeout(Duration(seconds: 5)); + await serverInfoApi.pingServer().timeout(const Duration(seconds: 5)); } on TimeoutException catch (_) { return false; } on SocketException catch (_) { diff --git a/mobile/lib/utils/immich_app_theme.dart b/mobile/lib/utils/immich_app_theme.dart index da4749065154e..2ca4fe3aff003 100644 --- a/mobile/lib/utils/immich_app_theme.dart +++ b/mobile/lib/utils/immich_app_theme.dart @@ -198,7 +198,7 @@ ThemeData getThemeData({ scrolledUnderElevation: 0, centerTitle: true, ), - textTheme: TextTheme( + textTheme: const TextTheme( displayLarge: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -211,15 +211,15 @@ ThemeData getThemeData({ fontSize: 12, fontWeight: FontWeight.bold, ), - titleSmall: const TextStyle( + titleSmall: TextStyle( fontSize: 16.0, fontWeight: FontWeight.bold, ), - titleMedium: const TextStyle( + titleMedium: TextStyle( fontSize: 18.0, fontWeight: FontWeight.bold, ), - titleLarge: const TextStyle( + titleLarge: TextStyle( fontSize: 26.0, fontWeight: FontWeight.bold, ), diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart index 89528cc4da365..525bfa1242e00 100644 --- a/mobile/lib/widgets/album/album_viewer_appbar.dart +++ b/mobile/lib/widgets/album/album_viewer_appbar.dart @@ -46,7 +46,7 @@ class AlbumViewerAppbar extends HookConsumerWidget final bool success; if (album.shared) { success = await ref.watch(albumProvider.notifier).deleteAlbum(album); - context.navigateTo(TabControllerRoute(children: [AlbumsRoute()])); + context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); } else { success = await ref.watch(albumProvider.notifier).deleteAlbum(album); context @@ -113,7 +113,7 @@ class AlbumViewerAppbar extends HookConsumerWidget await ref.watch(albumProvider.notifier).leaveAlbum(album); if (isSuccess) { - context.navigateTo(TabControllerRoute(children: [AlbumsRoute()])); + context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); } else { context.pop(); ImmichToast.show( diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index f698e866ad8da..82ca295d8abbf 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -338,7 +338,7 @@ class BottomGalleryBar extends ConsumerWidget { ), position: DecorationPosition.background, child: Padding( - padding: EdgeInsets.only(top: 40.0), + padding: const EdgeInsets.only(top: 40.0), child: Column( children: [ if (showVideoPlayerControls) const VideoControls(), diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index a83afc00b3482..218e17cbe1046 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -143,9 +143,9 @@ class ImmichAppBarDialog extends HookConsumerWidget { ); }, trailing: isLoggingOut.value - ? SizedBox.square( + ? const SizedBox.square( dimension: 20, - child: const CircularProgressIndicator(strokeWidth: 2), + child: CircularProgressIndicator(strokeWidth: 2), ) : null, ); diff --git a/mobile/test/modules/map/map_theme_override_test.dart b/mobile/test/modules/map/map_theme_override_test.dart index b462ede4c5360..c21f9bf166984 100644 --- a/mobile/test/modules/map/map_theme_override_test.dart +++ b/mobile/test/modules/map/map_theme_override_test.dart @@ -27,7 +27,7 @@ void main() { mapStateNotifier = MockMapStateNotifier(mapState); overrides = [ mapStateNotifierProvider.overrideWith(() => mapStateNotifier), - localeProvider.overrideWithValue(Locale("en")), + localeProvider.overrideWithValue(const Locale("en")), ]; }); From 6e1e7a9d4a19eba0f22bcdc2f3329891b2338b13 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Tue, 26 Nov 2024 18:00:30 +0700 Subject: [PATCH 2/9] WIP --- mobile/lib/styles/immich_app_theme_v2.dart | 312 +++++++++++++++++++++ mobile/lib/styles/immich_colors_v2.dart | 114 ++++++++ 2 files changed, 426 insertions(+) create mode 100644 mobile/lib/styles/immich_app_theme_v2.dart create mode 100644 mobile/lib/styles/immich_colors_v2.dart diff --git a/mobile/lib/styles/immich_app_theme_v2.dart b/mobile/lib/styles/immich_app_theme_v2.dart new file mode 100644 index 0000000000000..638920b339231 --- /dev/null +++ b/mobile/lib/styles/immich_app_theme_v2.dart @@ -0,0 +1,312 @@ +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/locales.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; + +class ImmichTheme { + final ColorScheme light; + final ColorScheme dark; + + const ImmichTheme({required this.light, required this.dark}); +} + +ImmichTheme? _immichDynamicTheme; +bool get isDynamicThemeAvailable => _immichDynamicTheme != null; + +final immichThemeModeProvider = StateProvider((ref) { + var themeMode = ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.themeMode); + + debugPrint("Current themeMode $themeMode"); + + if (themeMode == "light") { + return ThemeMode.light; + } else if (themeMode == "dark") { + return ThemeMode.dark; + } else { + return ThemeMode.system; + } +}); + +final immichThemePresetProvider = StateProvider((ref) { + var appSettingsProvider = ref.watch(appSettingsServiceProvider); + var primaryColorName = + appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); + + debugPrint("Current theme preset $primaryColorName"); + + try { + return ImmichColorPreset.values + .firstWhere((e) => e.name == primaryColorName); + } catch (e) { + debugPrint( + "Theme preset $primaryColorName not found. Applying default preset.", + ); + appSettingsProvider.setSetting( + AppSettingsEnum.primaryColor, + defaultColorPreset.name, + ); + return defaultColorPreset; + } +}); + +final dynamicThemeSettingProvider = StateProvider((ref) { + return ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.dynamicTheme); +}); + +final colorfulInterfaceSettingProvider = StateProvider((ref) { + return ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.colorfulInterface); +}); + +// Provider for current selected theme +final immichThemeProvider = StateProvider((ref) { + var primaryColor = ref.read(immichThemePresetProvider); + var useSystemColor = ref.watch(dynamicThemeSettingProvider); + var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); + + dynamic currentTheme = (useSystemColor && _immichDynamicTheme != null) + ? _immichDynamicTheme! + : primaryColor.getTheme(); + + return useColorfulInterface + ? currentTheme + : _decolorizeSurfaces(theme: currentTheme); +}); + +// Method to fetch dynamic system colors +Future fetchSystemPalette() async { + try { + final corePalette = await DynamicColorPlugin.getCorePalette(); + if (corePalette != null) { + final primaryColor = corePalette.toColorScheme().primary; + debugPrint('dynamic_color: Core palette detected.'); + + // Some palettes do not generate surface container colors accurately, + // so we regenerate all colors using the primary color + _immichDynamicTheme = ImmichTheme( + light: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.light, + ), + dark: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.dark, + ), + ); + } + } catch (e) { + debugPrint('dynamic_color: Failed to obtain core palette.'); + } +} + +// This method replaces all surface shades in ImmichTheme to a static ones +// as we are creating the colorscheme through seedColor the default surfaces are +// tinted with primary color +ImmichTheme _decolorizeSurfaces({ + required ImmichTheme theme, +}) { + return ImmichTheme( + light: theme.light.copyWith( + surface: const Color(0xFFf9f9f9), + onSurface: const Color(0xFF1b1b1b), + surfaceContainerLowest: const Color(0xFFffffff), + surfaceContainerLow: const Color(0xFFf3f3f3), + surfaceContainer: const Color(0xFFeeeeee), + surfaceContainerHigh: const Color(0xFFe8e8e8), + surfaceContainerHighest: const Color(0xFFe2e2e2), + surfaceDim: const Color(0xFFdadada), + surfaceBright: const Color(0xFFf9f9f9), + onSurfaceVariant: const Color(0xFF4c4546), + inverseSurface: const Color(0xFF303030), + onInverseSurface: const Color(0xFFf1f1f1), + ), + dark: theme.dark.copyWith( + surface: const Color(0xFF131313), + onSurface: const Color(0xFFE2E2E2), + surfaceContainerLowest: const Color(0xFF0E0E0E), + surfaceContainerLow: const Color(0xFF1B1B1B), + surfaceContainer: const Color(0xFF1F1F1F), + surfaceContainerHigh: const Color(0xFF242424), + surfaceContainerHighest: const Color(0xFF2E2E2E), + surfaceDim: const Color(0xFF131313), + surfaceBright: const Color(0xFF353535), + onSurfaceVariant: const Color(0xFFCfC4C5), + inverseSurface: const Color(0xFFE2E2E2), + onInverseSurface: const Color(0xFF303030), + ), + ); +} + +String? getFontFamilyFromLocale(Locale locale) { + if (localesNotSupportedByOverpass.contains(locale)) { + // Let Flutter use the default font + return null; + } + return 'Manrope'; +} + +ThemeData getThemeData({ + required ColorScheme colorScheme, + required Locale locale, +}) { + var isDark = colorScheme.brightness == Brightness.dark; + var primaryColor = colorScheme.primary; + + return ThemeData( + useMaterial3: true, + brightness: colorScheme.brightness, + colorScheme: colorScheme, + primaryColor: primaryColor, + hintColor: colorScheme.onSurfaceSecondary, + focusColor: primaryColor, + scaffoldBackgroundColor: colorScheme.surface, + splashColor: primaryColor.withOpacity(0.1), + highlightColor: primaryColor.withOpacity(0.1), + dialogBackgroundColor: colorScheme.surfaceContainer, + bottomSheetTheme: BottomSheetThemeData( + backgroundColor: colorScheme.surfaceContainer, + ), + fontFamily: getFontFamilyFromLocale(locale), + snackBarTheme: SnackBarThemeData( + contentTextStyle: TextStyle( + fontFamily: getFontFamilyFromLocale(locale), + color: primaryColor, + fontWeight: FontWeight.bold, + ), + backgroundColor: colorScheme.surfaceContainerHighest, + ), + appBarTheme: AppBarTheme( + titleTextStyle: TextStyle( + color: primaryColor, + fontFamily: getFontFamilyFromLocale(locale), + fontWeight: FontWeight.bold, + fontSize: 18, + ), + backgroundColor: + isDark ? colorScheme.surfaceContainer : colorScheme.surface, + foregroundColor: primaryColor, + elevation: 0, + scrolledUnderElevation: 0, + centerTitle: true, + ), + textTheme: TextTheme( + displayLarge: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + ), + displayMedium: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + displaySmall: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + titleSmall: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + titleMedium: const TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + titleLarge: const TextStyle( + fontSize: 26.0, + fontWeight: FontWeight.bold, + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: isDark ? Colors.black87 : Colors.white, + ), + ), + chipTheme: const ChipThemeData( + side: BorderSide.none, + ), + sliderTheme: const SliderThemeData( + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7), + trackHeight: 2.0, + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + type: BottomNavigationBarType.fixed, + ), + popupMenuTheme: const PopupMenuThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), + navigationBarTheme: NavigationBarThemeData( + backgroundColor: + isDark ? colorScheme.surfaceContainer : colorScheme.surface, + labelTextStyle: const WidgetStatePropertyAll( + TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: primaryColor, + ), + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: colorScheme.outlineVariant, + ), + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + labelStyle: TextStyle( + color: primaryColor, + ), + hintStyle: const TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + ), + ), + textSelectionTheme: TextSelectionThemeData( + cursorColor: primaryColor, + ), + dropdownMenuTheme: DropdownMenuThemeData( + menuStyle: MenuStyle( + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + ), + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: primaryColor, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: colorScheme.outlineVariant, + ), + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + labelStyle: TextStyle( + color: primaryColor, + ), + hintStyle: const TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + ), + ), + ), + ); +} diff --git a/mobile/lib/styles/immich_colors_v2.dart b/mobile/lib/styles/immich_colors_v2.dart new file mode 100644 index 0000000000000..44d79dd4098df --- /dev/null +++ b/mobile/lib/styles/immich_colors_v2.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/utils/immich_app_theme.dart'; + +enum ImmichColorPreset { + indigo, + deepPurple, + pink, + red, + orange, + yellow, + lime, + green, + cyan, + slateGray +} + +final ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; +const String defaultColorPresetName = 'indigo'; + +const Color immichBrandColorLight = Color(0xFF4150AF); +const Color immichBrandColorDark = Color(0xFFACCBFA); +const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); +const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0); +const Color red400 = Color(0xFFEF5350); + +final Map _themePresetsMap = { + ImmichColorPreset.indigo: ImmichTheme( + light: ColorScheme.fromSeed( + seedColor: immichBrandColorLight, + ).copyWith( + primary: immichBrandColorLight, + onSurface: const Color.fromARGB(255, 34, 31, 32), + ), + dark: ColorScheme.fromSeed( + seedColor: immichBrandColorDark, + brightness: Brightness.dark, + ).copyWith(primary: immichBrandColorDark), + ), + ImmichColorPreset.deepPurple: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFF6F43C0)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFFD3BBFF), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.pink: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFFED79B5)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFFED79B5), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.red: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFFC51C16)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFFD3302F), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.orange: ImmichTheme( + light: ColorScheme.fromSeed( + seedColor: const Color(0xffff5b01), + dynamicSchemeVariant: DynamicSchemeVariant.fidelity, + ), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFFCC6D08), + brightness: Brightness.dark, + dynamicSchemeVariant: DynamicSchemeVariant.fidelity, + ), + ), + ImmichColorPreset.yellow: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFFFFB400)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFFFFB400), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.lime: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFFCDDC39)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFFCDDC39), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.green: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFF18C249)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFF18C249), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.cyan: ImmichTheme( + light: ColorScheme.fromSeed(seedColor: const Color(0xFF00BCD4)), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xFF00BCD4), + brightness: Brightness.dark, + ), + ), + ImmichColorPreset.slateGray: ImmichTheme( + light: ColorScheme.fromSeed( + seedColor: const Color(0xFF696969), + dynamicSchemeVariant: DynamicSchemeVariant.neutral, + ), + dark: ColorScheme.fromSeed( + seedColor: const Color(0xff696969), + brightness: Brightness.dark, + dynamicSchemeVariant: DynamicSchemeVariant.neutral, + ), + ), +}; + +extension ImmichColorModeExtension on ImmichColorPreset { + ImmichTheme getTheme() => _themePresetsMap[this]!; +} From eff557679d30f6971fb4857b6b5f292e3257f728 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Sat, 30 Nov 2024 21:33:34 +0700 Subject: [PATCH 3/9] split file and refactor theme --- mobile/lib/constants/colors.dart | 25 ++ mobile/lib/main.dart | 36 +- mobile/lib/pages/search/map/map.page.dart | 2 +- .../search/map/map_location_picker.page.dart | 2 +- mobile/lib/providers/theme.provider.dart | 74 +++++ mobile/lib/services/app_settings.service.dart | 2 +- mobile/lib/styles/immich_colors_v2.dart | 114 ------- .../color_scheme.dart} | 27 +- mobile/lib/theme/dynamic_theme.dart | 38 +++ .../theme_data.dart} | 227 ++++--------- mobile/lib/utils/immich_app_theme.dart | 312 ------------------ .../asset_viewer/bottom_gallery_bar.dart | 2 +- .../widgets/asset_viewer/video_position.dart | 2 +- mobile/lib/widgets/backup/error_chip.dart | 2 +- .../lib/widgets/backup/error_chip_text.dart | 2 +- .../lib/widgets/map/map_theme_override.dart | 11 +- mobile/lib/widgets/map/map_thumbnail.dart | 2 +- .../primary_color_setting.dart | 16 +- .../preference_settings/theme_setting.dart | 2 +- .../modules/map/map_theme_override_test.dart | 12 +- 20 files changed, 253 insertions(+), 657 deletions(-) create mode 100644 mobile/lib/constants/colors.dart create mode 100644 mobile/lib/providers/theme.provider.dart delete mode 100644 mobile/lib/styles/immich_colors_v2.dart rename mobile/lib/{constants/immich_colors.dart => theme/color_scheme.dart} (81%) create mode 100644 mobile/lib/theme/dynamic_theme.dart rename mobile/lib/{styles/immich_app_theme_v2.dart => theme/theme_data.dart} (59%) delete mode 100644 mobile/lib/utils/immich_app_theme.dart diff --git a/mobile/lib/constants/colors.dart b/mobile/lib/constants/colors.dart new file mode 100644 index 0000000000000..d71d5c702e6d0 --- /dev/null +++ b/mobile/lib/constants/colors.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +enum ImmichColorPreset { + indigo, + deepPurple, + pink, + red, + orange, + yellow, + lime, + green, + cyan, + slateGray +} + +const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; +const String defaultColorPresetName = "indigo"; + +const Color immichBrandColorLight = Color(0xFF4150AF); +const Color immichBrandColorDark = Color(0xFFACCBFA); +const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); +const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0); +const Color red400 = Color(0xFFEF5350); + + diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 7729972aa21e8..807212fc655eb 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -4,23 +4,26 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:timezone/data/latest.dart'; +import 'package:isar/isar.dart'; +import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/locale_provider.dart'; -import 'package:immich_mobile/utils/download.dart'; -import 'package:intl/date_symbol_data_local.dart'; -import 'package:timezone/data/latest.dart'; import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/services/background.service.dart'; -import 'package:immich_mobile/entities/backup_album.entity.dart'; -import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; +import 'package:immich_mobile/providers/locale_provider.dart'; +import 'package:immich_mobile/providers/theme.provider.dart'; +import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/tab_navigation_observer.dart'; -import 'package:immich_mobile/utils/cache/widgets_binding.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; @@ -30,16 +33,15 @@ import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/logger_message.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; -import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; -import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/immich_logger.service.dart'; import 'package:immich_mobile/services/local_notification.service.dart'; -import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; -import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/migration.dart'; -import 'package:isar/isar.dart'; -import 'package:logging/logging.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:immich_mobile/utils/download.dart'; +import 'package:immich_mobile/utils/cache/widgets_binding.dart'; +import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; +import 'package:immich_mobile/theme/theme_data.dart'; +import 'package:immich_mobile/theme/dynamic_theme.dart'; void main() async { ImmichWidgetsBinding(); @@ -69,12 +71,12 @@ Future initApp() async { } } - await fetchSystemPalette(); + await DynamicTheme.fetchSystemPalette(); // Initialize Immich Logger Service ImmichLogger(); - var log = Logger("ImmichErrorLogger"); + final log = Logger("ImmichErrorLogger"); FlutterError.onError = (details) { FlutterError.presentError(details); diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart index 8000c7e339282..7cab83380d628 100644 --- a/mobile/lib/pages/search/map/map.page.dart +++ b/mobile/lib/pages/search/map/map.page.dart @@ -255,7 +255,7 @@ class MapPage extends HookConsumerWidget { selectedAssets.value = selected ? selection : {}; } - return MapThemeOveride( + return MapThemeOverride( mapBuilder: (style) => context.isMobile // Single-column ? Scaffold( diff --git a/mobile/lib/pages/search/map/map_location_picker.page.dart b/mobile/lib/pages/search/map/map_location_picker.page.dart index 2fd1e1ee9edde..487de69a1ee9e 100644 --- a/mobile/lib/pages/search/map/map_location_picker.page.dart +++ b/mobile/lib/pages/search/map/map_location_picker.page.dart @@ -58,7 +58,7 @@ class MapLocationPickerPage extends HookConsumerWidget { controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); } - return MapThemeOveride( + return MapThemeOverride( mapBuilder: (style) => Builder( builder: (ctx) => Scaffold( backgroundColor: ctx.themeData.cardColor, diff --git a/mobile/lib/providers/theme.provider.dart b/mobile/lib/providers/theme.provider.dart new file mode 100644 index 0000000000000..73623bd026743 --- /dev/null +++ b/mobile/lib/providers/theme.provider.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/theme/color_scheme.dart'; +import 'package:immich_mobile/theme/theme_data.dart'; +import 'package:immich_mobile/theme/dynamic_theme.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; + +final immichThemeModeProvider = StateProvider((ref) { + final themeMode = ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.themeMode); + + debugPrint("Current themeMode $themeMode"); + + if (themeMode == ThemeMode.light.name) { + return ThemeMode.light; + } else if (themeMode == ThemeMode.dark.name) { + return ThemeMode.dark; + } else { + return ThemeMode.system; + } +}); + +final immichThemePresetProvider = StateProvider((ref) { + final appSettingsProvider = ref.watch(appSettingsServiceProvider); + final primaryColorPreset = + appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); + + debugPrint("Current theme preset $primaryColorPreset"); + + try { + return ImmichColorPreset.values + .firstWhere((e) => e.name == primaryColorPreset); + } catch (e) { + debugPrint( + "Theme preset $primaryColorPreset not found. Applying default preset.", + ); + appSettingsProvider.setSetting( + AppSettingsEnum.primaryColor, + defaultColorPresetName, + ); + return defaultColorPreset; + } +}); + +final dynamicThemeSettingProvider = StateProvider((ref) { + return ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.dynamicTheme); +}); + +final colorfulInterfaceSettingProvider = StateProvider((ref) { + return ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.colorfulInterface); +}); + +// Provider for current selected theme +final immichThemeProvider = StateProvider((ref) { + final primaryColorPreset = ref.read(immichThemePresetProvider); + final useSystemColor = ref.watch(dynamicThemeSettingProvider); + final useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); + final ImmichTheme? dynamicTheme = DynamicTheme.theme; + final currentTheme = (useSystemColor && dynamicTheme != null) + ? dynamicTheme + : primaryColorPreset.themeOfPreset; + + return useColorfulInterface + ? currentTheme + : decolorizeSurfaces(theme: currentTheme); +}); diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 8f773e1bb33a9..cc9612d30a36d 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -1,4 +1,4 @@ -import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/entities/store.entity.dart'; enum AppSettingsEnum { diff --git a/mobile/lib/styles/immich_colors_v2.dart b/mobile/lib/styles/immich_colors_v2.dart deleted file mode 100644 index 44d79dd4098df..0000000000000 --- a/mobile/lib/styles/immich_colors_v2.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_mobile/utils/immich_app_theme.dart'; - -enum ImmichColorPreset { - indigo, - deepPurple, - pink, - red, - orange, - yellow, - lime, - green, - cyan, - slateGray -} - -final ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; -const String defaultColorPresetName = 'indigo'; - -const Color immichBrandColorLight = Color(0xFF4150AF); -const Color immichBrandColorDark = Color(0xFFACCBFA); -const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); -const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0); -const Color red400 = Color(0xFFEF5350); - -final Map _themePresetsMap = { - ImmichColorPreset.indigo: ImmichTheme( - light: ColorScheme.fromSeed( - seedColor: immichBrandColorLight, - ).copyWith( - primary: immichBrandColorLight, - onSurface: const Color.fromARGB(255, 34, 31, 32), - ), - dark: ColorScheme.fromSeed( - seedColor: immichBrandColorDark, - brightness: Brightness.dark, - ).copyWith(primary: immichBrandColorDark), - ), - ImmichColorPreset.deepPurple: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFF6F43C0)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFFD3BBFF), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.pink: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFFED79B5)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFFED79B5), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.red: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFFC51C16)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFFD3302F), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.orange: ImmichTheme( - light: ColorScheme.fromSeed( - seedColor: const Color(0xffff5b01), - dynamicSchemeVariant: DynamicSchemeVariant.fidelity, - ), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFFCC6D08), - brightness: Brightness.dark, - dynamicSchemeVariant: DynamicSchemeVariant.fidelity, - ), - ), - ImmichColorPreset.yellow: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFFFFB400)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFFFFB400), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.lime: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFFCDDC39)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFFCDDC39), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.green: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFF18C249)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFF18C249), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.cyan: ImmichTheme( - light: ColorScheme.fromSeed(seedColor: const Color(0xFF00BCD4)), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xFF00BCD4), - brightness: Brightness.dark, - ), - ), - ImmichColorPreset.slateGray: ImmichTheme( - light: ColorScheme.fromSeed( - seedColor: const Color(0xFF696969), - dynamicSchemeVariant: DynamicSchemeVariant.neutral, - ), - dark: ColorScheme.fromSeed( - seedColor: const Color(0xff696969), - brightness: Brightness.dark, - dynamicSchemeVariant: DynamicSchemeVariant.neutral, - ), - ), -}; - -extension ImmichColorModeExtension on ImmichColorPreset { - ImmichTheme getTheme() => _themePresetsMap[this]!; -} diff --git a/mobile/lib/constants/immich_colors.dart b/mobile/lib/theme/color_scheme.dart similarity index 81% rename from mobile/lib/constants/immich_colors.dart rename to mobile/lib/theme/color_scheme.dart index a49e783602b4d..68fc31356a6e3 100644 --- a/mobile/lib/constants/immich_colors.dart +++ b/mobile/lib/theme/color_scheme.dart @@ -1,27 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/utils/immich_app_theme.dart'; - -enum ImmichColorPreset { - indigo, - deepPurple, - pink, - red, - orange, - yellow, - lime, - green, - cyan, - slateGray -} - -const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; -const String defaultColorPresetName = "indigo"; - -const Color immichBrandColorLight = Color(0xFF4150AF); -const Color immichBrandColorDark = Color(0xFFACCBFA); -const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); -const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0); -const Color red400 = Color(0xFFEF5350); +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/theme/theme_data.dart'; final Map _themePresetsMap = { ImmichColorPreset.indigo: ImmichTheme( @@ -110,5 +89,5 @@ final Map _themePresetsMap = { }; extension ImmichColorModeExtension on ImmichColorPreset { - ImmichTheme getTheme() => _themePresetsMap[this]!; + ImmichTheme get themeOfPreset => _themePresetsMap[this]!; } diff --git a/mobile/lib/theme/dynamic_theme.dart b/mobile/lib/theme/dynamic_theme.dart new file mode 100644 index 0000000000000..b66a2305d225f --- /dev/null +++ b/mobile/lib/theme/dynamic_theme.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:dynamic_color/dynamic_color.dart'; + +import 'package:immich_mobile/theme/theme_data.dart'; + +class DynamicTheme { + DynamicTheme._(); + + static ImmichTheme? _theme; + // Method to fetch dynamic system colors + static Future fetchSystemPalette() async { + try { + final corePalette = await DynamicColorPlugin.getCorePalette(); + if (corePalette != null) { + final primaryColor = corePalette.toColorScheme().primary; + debugPrint('dynamic_color: Core palette detected.'); + + // Some palettes do not generate surface container colors accurately, + // so we regenerate all colors using the primary color + _theme = ImmichTheme( + light: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.light, + ), + dark: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.dark, + ), + ); + } + } catch (error) { + debugPrint('dynamic_color: Failed to obtain core palette: $error'); + } + } + + static ImmichTheme? get theme => _theme; + static bool get isAvailable => _theme != null; +} diff --git a/mobile/lib/styles/immich_app_theme_v2.dart b/mobile/lib/theme/theme_data.dart similarity index 59% rename from mobile/lib/styles/immich_app_theme_v2.dart rename to mobile/lib/theme/theme_data.dart index 638920b339231..c1f0b1942e84e 100644 --- a/mobile/lib/styles/immich_app_theme_v2.dart +++ b/mobile/lib/theme/theme_data.dart @@ -1,11 +1,7 @@ -import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; import 'package:immich_mobile/constants/locales.dart'; + import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; class ImmichTheme { final ColorScheme light; @@ -14,186 +10,45 @@ class ImmichTheme { const ImmichTheme({required this.light, required this.dark}); } -ImmichTheme? _immichDynamicTheme; -bool get isDynamicThemeAvailable => _immichDynamicTheme != null; - -final immichThemeModeProvider = StateProvider((ref) { - var themeMode = ref - .watch(appSettingsServiceProvider) - .getSetting(AppSettingsEnum.themeMode); - - debugPrint("Current themeMode $themeMode"); - - if (themeMode == "light") { - return ThemeMode.light; - } else if (themeMode == "dark") { - return ThemeMode.dark; - } else { - return ThemeMode.system; - } -}); - -final immichThemePresetProvider = StateProvider((ref) { - var appSettingsProvider = ref.watch(appSettingsServiceProvider); - var primaryColorName = - appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); - - debugPrint("Current theme preset $primaryColorName"); - - try { - return ImmichColorPreset.values - .firstWhere((e) => e.name == primaryColorName); - } catch (e) { - debugPrint( - "Theme preset $primaryColorName not found. Applying default preset.", - ); - appSettingsProvider.setSetting( - AppSettingsEnum.primaryColor, - defaultColorPreset.name, - ); - return defaultColorPreset; - } -}); - -final dynamicThemeSettingProvider = StateProvider((ref) { - return ref - .watch(appSettingsServiceProvider) - .getSetting(AppSettingsEnum.dynamicTheme); -}); - -final colorfulInterfaceSettingProvider = StateProvider((ref) { - return ref - .watch(appSettingsServiceProvider) - .getSetting(AppSettingsEnum.colorfulInterface); -}); - -// Provider for current selected theme -final immichThemeProvider = StateProvider((ref) { - var primaryColor = ref.read(immichThemePresetProvider); - var useSystemColor = ref.watch(dynamicThemeSettingProvider); - var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); - - dynamic currentTheme = (useSystemColor && _immichDynamicTheme != null) - ? _immichDynamicTheme! - : primaryColor.getTheme(); - - return useColorfulInterface - ? currentTheme - : _decolorizeSurfaces(theme: currentTheme); -}); - -// Method to fetch dynamic system colors -Future fetchSystemPalette() async { - try { - final corePalette = await DynamicColorPlugin.getCorePalette(); - if (corePalette != null) { - final primaryColor = corePalette.toColorScheme().primary; - debugPrint('dynamic_color: Core palette detected.'); - - // Some palettes do not generate surface container colors accurately, - // so we regenerate all colors using the primary color - _immichDynamicTheme = ImmichTheme( - light: ColorScheme.fromSeed( - seedColor: primaryColor, - brightness: Brightness.light, - ), - dark: ColorScheme.fromSeed( - seedColor: primaryColor, - brightness: Brightness.dark, - ), - ); - } - } catch (e) { - debugPrint('dynamic_color: Failed to obtain core palette.'); - } -} - -// This method replaces all surface shades in ImmichTheme to a static ones -// as we are creating the colorscheme through seedColor the default surfaces are -// tinted with primary color -ImmichTheme _decolorizeSurfaces({ - required ImmichTheme theme, -}) { - return ImmichTheme( - light: theme.light.copyWith( - surface: const Color(0xFFf9f9f9), - onSurface: const Color(0xFF1b1b1b), - surfaceContainerLowest: const Color(0xFFffffff), - surfaceContainerLow: const Color(0xFFf3f3f3), - surfaceContainer: const Color(0xFFeeeeee), - surfaceContainerHigh: const Color(0xFFe8e8e8), - surfaceContainerHighest: const Color(0xFFe2e2e2), - surfaceDim: const Color(0xFFdadada), - surfaceBright: const Color(0xFFf9f9f9), - onSurfaceVariant: const Color(0xFF4c4546), - inverseSurface: const Color(0xFF303030), - onInverseSurface: const Color(0xFFf1f1f1), - ), - dark: theme.dark.copyWith( - surface: const Color(0xFF131313), - onSurface: const Color(0xFFE2E2E2), - surfaceContainerLowest: const Color(0xFF0E0E0E), - surfaceContainerLow: const Color(0xFF1B1B1B), - surfaceContainer: const Color(0xFF1F1F1F), - surfaceContainerHigh: const Color(0xFF242424), - surfaceContainerHighest: const Color(0xFF2E2E2E), - surfaceDim: const Color(0xFF131313), - surfaceBright: const Color(0xFF353535), - onSurfaceVariant: const Color(0xFFCfC4C5), - inverseSurface: const Color(0xFFE2E2E2), - onInverseSurface: const Color(0xFF303030), - ), - ); -} - -String? getFontFamilyFromLocale(Locale locale) { - if (localesNotSupportedByOverpass.contains(locale)) { - // Let Flutter use the default font - return null; - } - return 'Manrope'; -} - ThemeData getThemeData({ required ColorScheme colorScheme, required Locale locale, }) { - var isDark = colorScheme.brightness == Brightness.dark; - var primaryColor = colorScheme.primary; + final isDark = colorScheme.brightness == Brightness.dark; return ThemeData( useMaterial3: true, brightness: colorScheme.brightness, colorScheme: colorScheme, - primaryColor: primaryColor, + primaryColor: colorScheme.primary, hintColor: colorScheme.onSurfaceSecondary, - focusColor: primaryColor, + focusColor: colorScheme.primary, scaffoldBackgroundColor: colorScheme.surface, - splashColor: primaryColor.withOpacity(0.1), - highlightColor: primaryColor.withOpacity(0.1), + splashColor: colorScheme.primary.withOpacity(0.1), + highlightColor: colorScheme.primary.withOpacity(0.1), dialogBackgroundColor: colorScheme.surfaceContainer, bottomSheetTheme: BottomSheetThemeData( backgroundColor: colorScheme.surfaceContainer, ), - fontFamily: getFontFamilyFromLocale(locale), + fontFamily: _getFontFamilyFromLocale(locale), snackBarTheme: SnackBarThemeData( contentTextStyle: TextStyle( - fontFamily: getFontFamilyFromLocale(locale), - color: primaryColor, + fontFamily: _getFontFamilyFromLocale(locale), + color: colorScheme.primary, fontWeight: FontWeight.bold, ), backgroundColor: colorScheme.surfaceContainerHighest, ), appBarTheme: AppBarTheme( titleTextStyle: TextStyle( - color: primaryColor, - fontFamily: getFontFamilyFromLocale(locale), + color: colorScheme.primary, + fontFamily: _getFontFamilyFromLocale(locale), fontWeight: FontWeight.bold, fontSize: 18, ), backgroundColor: isDark ? colorScheme.surfaceContainer : colorScheme.surface, - foregroundColor: primaryColor, + foregroundColor: colorScheme.primary, elevation: 0, scrolledUnderElevation: 0, centerTitle: true, @@ -226,7 +81,7 @@ ThemeData getThemeData({ ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, + backgroundColor: colorScheme.primary, foregroundColor: isDark ? Colors.black87 : Colors.white, ), ), @@ -258,7 +113,7 @@ ThemeData getThemeData({ inputDecorationTheme: InputDecorationTheme( focusedBorder: OutlineInputBorder( borderSide: BorderSide( - color: primaryColor, + color: colorScheme.primary, ), borderRadius: const BorderRadius.all(Radius.circular(15)), ), @@ -269,7 +124,7 @@ ThemeData getThemeData({ borderRadius: const BorderRadius.all(Radius.circular(15)), ), labelStyle: TextStyle( - color: primaryColor, + color: colorScheme.primary, ), hintStyle: const TextStyle( fontSize: 14.0, @@ -277,7 +132,7 @@ ThemeData getThemeData({ ), ), textSelectionTheme: TextSelectionThemeData( - cursorColor: primaryColor, + cursorColor: colorScheme.primary, ), dropdownMenuTheme: DropdownMenuThemeData( menuStyle: MenuStyle( @@ -290,7 +145,7 @@ ThemeData getThemeData({ inputDecorationTheme: InputDecorationTheme( focusedBorder: OutlineInputBorder( borderSide: BorderSide( - color: primaryColor, + color: colorScheme.primary, ), ), enabledBorder: OutlineInputBorder( @@ -300,7 +155,7 @@ ThemeData getThemeData({ borderRadius: const BorderRadius.all(Radius.circular(15)), ), labelStyle: TextStyle( - color: primaryColor, + color: colorScheme.primary, ), hintStyle: const TextStyle( fontSize: 14.0, @@ -310,3 +165,49 @@ ThemeData getThemeData({ ), ); } + +// This method replaces all surface shades in ImmichTheme to a static ones +// as we are creating the colorscheme through seedColor the default surfaces are +// tinted with primary color +ImmichTheme decolorizeSurfaces({ + required ImmichTheme theme, +}) { + return ImmichTheme( + light: theme.light.copyWith( + surface: const Color(0xFFf9f9f9), + onSurface: const Color(0xFF1b1b1b), + surfaceContainerLowest: const Color(0xFFffffff), + surfaceContainerLow: const Color(0xFFf3f3f3), + surfaceContainer: const Color(0xFFeeeeee), + surfaceContainerHigh: const Color(0xFFe8e8e8), + surfaceContainerHighest: const Color(0xFFe2e2e2), + surfaceDim: const Color(0xFFdadada), + surfaceBright: const Color(0xFFf9f9f9), + onSurfaceVariant: const Color(0xFF4c4546), + inverseSurface: const Color(0xFF303030), + onInverseSurface: const Color(0xFFf1f1f1), + ), + dark: theme.dark.copyWith( + surface: const Color(0xFF131313), + onSurface: const Color(0xFFE2E2E2), + surfaceContainerLowest: const Color(0xFF0E0E0E), + surfaceContainerLow: const Color(0xFF1B1B1B), + surfaceContainer: const Color(0xFF1F1F1F), + surfaceContainerHigh: const Color(0xFF242424), + surfaceContainerHighest: const Color(0xFF2E2E2E), + surfaceDim: const Color(0xFF131313), + surfaceBright: const Color(0xFF353535), + onSurfaceVariant: const Color(0xFFCfC4C5), + inverseSurface: const Color(0xFFE2E2E2), + onInverseSurface: const Color(0xFF303030), + ), + ); +} + +String? _getFontFamilyFromLocale(Locale locale) { + if (localesNotSupportedByOverpass.contains(locale)) { + // Let Flutter use the default font + return null; + } + return 'Overpass'; +} diff --git a/mobile/lib/utils/immich_app_theme.dart b/mobile/lib/utils/immich_app_theme.dart deleted file mode 100644 index 2ca4fe3aff003..0000000000000 --- a/mobile/lib/utils/immich_app_theme.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'package:dynamic_color/dynamic_color.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; -import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; - -class ImmichTheme { - final ColorScheme light; - final ColorScheme dark; - - const ImmichTheme({required this.light, required this.dark}); -} - -ImmichTheme? _immichDynamicTheme; -bool get isDynamicThemeAvailable => _immichDynamicTheme != null; - -final immichThemeModeProvider = StateProvider((ref) { - var themeMode = ref - .watch(appSettingsServiceProvider) - .getSetting(AppSettingsEnum.themeMode); - - debugPrint("Current themeMode $themeMode"); - - if (themeMode == "light") { - return ThemeMode.light; - } else if (themeMode == "dark") { - return ThemeMode.dark; - } else { - return ThemeMode.system; - } -}); - -final immichThemePresetProvider = StateProvider((ref) { - var appSettingsProvider = ref.watch(appSettingsServiceProvider); - var primaryColorName = - appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); - - debugPrint("Current theme preset $primaryColorName"); - - try { - return ImmichColorPreset.values - .firstWhere((e) => e.name == primaryColorName); - } catch (e) { - debugPrint( - "Theme preset $primaryColorName not found. Applying default preset.", - ); - appSettingsProvider.setSetting( - AppSettingsEnum.primaryColor, - defaultColorPresetName, - ); - return defaultColorPreset; - } -}); - -final dynamicThemeSettingProvider = StateProvider((ref) { - return ref - .watch(appSettingsServiceProvider) - .getSetting(AppSettingsEnum.dynamicTheme); -}); - -final colorfulInterfaceSettingProvider = StateProvider((ref) { - return ref - .watch(appSettingsServiceProvider) - .getSetting(AppSettingsEnum.colorfulInterface); -}); - -// Provider for current selected theme -final immichThemeProvider = StateProvider((ref) { - var primaryColor = ref.read(immichThemePresetProvider); - var useSystemColor = ref.watch(dynamicThemeSettingProvider); - var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); - - var currentTheme = (useSystemColor && _immichDynamicTheme != null) - ? _immichDynamicTheme! - : primaryColor.getTheme(); - - return useColorfulInterface - ? currentTheme - : _decolorizeSurfaces(theme: currentTheme); -}); - -// Method to fetch dynamic system colors -Future fetchSystemPalette() async { - try { - final corePalette = await DynamicColorPlugin.getCorePalette(); - if (corePalette != null) { - final primaryColor = corePalette.toColorScheme().primary; - debugPrint('dynamic_color: Core palette detected.'); - - // Some palettes do not generate surface container colors accurately, - // so we regenerate all colors using the primary color - _immichDynamicTheme = ImmichTheme( - light: ColorScheme.fromSeed( - seedColor: primaryColor, - brightness: Brightness.light, - ), - dark: ColorScheme.fromSeed( - seedColor: primaryColor, - brightness: Brightness.dark, - ), - ); - } - } catch (e) { - debugPrint('dynamic_color: Failed to obtain core palette.'); - } -} - -// This method replaces all surface shades in ImmichTheme to a static ones -// as we are creating the colorscheme through seedColor the default surfaces are -// tinted with primary color -ImmichTheme _decolorizeSurfaces({ - required ImmichTheme theme, -}) { - return ImmichTheme( - light: theme.light.copyWith( - surface: const Color(0xFFf9f9f9), - onSurface: const Color(0xFF1b1b1b), - surfaceContainerLowest: const Color(0xFFffffff), - surfaceContainerLow: const Color(0xFFf3f3f3), - surfaceContainer: const Color(0xFFeeeeee), - surfaceContainerHigh: const Color(0xFFe8e8e8), - surfaceContainerHighest: const Color(0xFFe2e2e2), - surfaceDim: const Color(0xFFdadada), - surfaceBright: const Color(0xFFf9f9f9), - onSurfaceVariant: const Color(0xFF4c4546), - inverseSurface: const Color(0xFF303030), - onInverseSurface: const Color(0xFFf1f1f1), - ), - dark: theme.dark.copyWith( - surface: const Color(0xFF131313), - onSurface: const Color(0xFFE2E2E2), - surfaceContainerLowest: const Color(0xFF0E0E0E), - surfaceContainerLow: const Color(0xFF1B1B1B), - surfaceContainer: const Color(0xFF1F1F1F), - surfaceContainerHigh: const Color(0xFF242424), - surfaceContainerHighest: const Color(0xFF2E2E2E), - surfaceDim: const Color(0xFF131313), - surfaceBright: const Color(0xFF353535), - onSurfaceVariant: const Color(0xFFCfC4C5), - inverseSurface: const Color(0xFFE2E2E2), - onInverseSurface: const Color(0xFF303030), - ), - ); -} - -String? getFontFamilyFromLocale(Locale locale) { - if (localesNotSupportedByOverpass.contains(locale)) { - // Let Flutter use the default font - return null; - } - return 'Overpass'; -} - -ThemeData getThemeData({ - required ColorScheme colorScheme, - required Locale locale, -}) { - var isDark = colorScheme.brightness == Brightness.dark; - var primaryColor = colorScheme.primary; - - return ThemeData( - useMaterial3: true, - brightness: colorScheme.brightness, - colorScheme: colorScheme, - primaryColor: primaryColor, - hintColor: colorScheme.onSurfaceSecondary, - focusColor: primaryColor, - scaffoldBackgroundColor: colorScheme.surface, - splashColor: primaryColor.withOpacity(0.1), - highlightColor: primaryColor.withOpacity(0.1), - dialogBackgroundColor: colorScheme.surfaceContainer, - bottomSheetTheme: BottomSheetThemeData( - backgroundColor: colorScheme.surfaceContainer, - ), - fontFamily: getFontFamilyFromLocale(locale), - snackBarTheme: SnackBarThemeData( - contentTextStyle: TextStyle( - fontFamily: getFontFamilyFromLocale(locale), - color: primaryColor, - fontWeight: FontWeight.bold, - ), - backgroundColor: colorScheme.surfaceContainerHighest, - ), - appBarTheme: AppBarTheme( - titleTextStyle: TextStyle( - color: primaryColor, - fontFamily: getFontFamilyFromLocale(locale), - fontWeight: FontWeight.bold, - fontSize: 18, - ), - backgroundColor: - isDark ? colorScheme.surfaceContainer : colorScheme.surface, - foregroundColor: primaryColor, - elevation: 0, - scrolledUnderElevation: 0, - centerTitle: true, - ), - textTheme: const TextTheme( - displayLarge: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - ), - displayMedium: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - displaySmall: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - titleSmall: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - ), - titleMedium: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - ), - titleLarge: TextStyle( - fontSize: 26.0, - fontWeight: FontWeight.bold, - ), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, - foregroundColor: isDark ? Colors.black87 : Colors.white, - ), - ), - chipTheme: const ChipThemeData( - side: BorderSide.none, - ), - sliderTheme: const SliderThemeData( - thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7), - trackHeight: 2.0, - ), - bottomNavigationBarTheme: const BottomNavigationBarThemeData( - type: BottomNavigationBarType.fixed, - ), - popupMenuTheme: const PopupMenuThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - ), - navigationBarTheme: NavigationBarThemeData( - backgroundColor: - isDark ? colorScheme.surfaceContainer : colorScheme.surface, - labelTextStyle: const WidgetStatePropertyAll( - TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - inputDecorationTheme: InputDecorationTheme( - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: primaryColor, - ), - borderRadius: const BorderRadius.all(Radius.circular(15)), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - borderRadius: const BorderRadius.all(Radius.circular(15)), - ), - labelStyle: TextStyle( - color: primaryColor, - ), - hintStyle: const TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.normal, - ), - ), - textSelectionTheme: TextSelectionThemeData( - cursorColor: primaryColor, - ), - dropdownMenuTheme: DropdownMenuThemeData( - menuStyle: MenuStyle( - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - ), - ), - inputDecorationTheme: InputDecorationTheme( - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: primaryColor, - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: colorScheme.outlineVariant, - ), - borderRadius: const BorderRadius.all(Radius.circular(15)), - ), - labelStyle: TextStyle( - color: primaryColor, - ), - hintStyle: const TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.normal, - ), - ), - ), - ); -} diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index 82ca295d8abbf..6372a42eeda36 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -5,7 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; diff --git a/mobile/lib/widgets/asset_viewer/video_position.dart b/mobile/lib/widgets/asset_viewer/video_position.dart index ef309b9c8561a..9fca2e7756522 100644 --- a/mobile/lib/widgets/asset_viewer/video_position.dart +++ b/mobile/lib/widgets/asset_viewer/video_position.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart'; diff --git a/mobile/lib/widgets/backup/error_chip.dart b/mobile/lib/widgets/backup/error_chip.dart index 4bbc040d4d908..4df3e50f64df0 100644 --- a/mobile/lib/widgets/backup/error_chip.dart +++ b/mobile/lib/widgets/backup/error_chip.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/backup/error_chip_text.dart'; diff --git a/mobile/lib/widgets/backup/error_chip_text.dart b/mobile/lib/widgets/backup/error_chip_text.dart index 94148da1761c7..540e136722f78 100644 --- a/mobile/lib/widgets/backup/error_chip_text.dart +++ b/mobile/lib/widgets/backup/error_chip_text.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; class BackupErrorChipText extends ConsumerWidget { diff --git a/mobile/lib/widgets/map/map_theme_override.dart b/mobile/lib/widgets/map/map_theme_override.dart index 68a2146bfbce3..65425f9e78748 100644 --- a/mobile/lib/widgets/map/map_theme_override.dart +++ b/mobile/lib/widgets/map/map_theme_override.dart @@ -3,21 +3,22 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart'; -import 'package:immich_mobile/utils/immich_app_theme.dart'; +import 'package:immich_mobile/providers/theme.provider.dart'; +import 'package:immich_mobile/theme/theme_data.dart'; /// Overrides the theme below the widget tree to use the theme data based on the /// map settings instead of the one from the app settings -class MapThemeOveride extends StatefulHookConsumerWidget { +class MapThemeOverride extends StatefulHookConsumerWidget { final ThemeMode? themeMode; final Widget Function(AsyncValue style) mapBuilder; - const MapThemeOveride({required this.mapBuilder, this.themeMode, super.key}); + const MapThemeOverride({required this.mapBuilder, this.themeMode, super.key}); @override - ConsumerState createState() => _MapThemeOverideState(); + ConsumerState createState() => _MapThemeOverrideState(); } -class _MapThemeOverideState extends ConsumerState +class _MapThemeOverrideState extends ConsumerState with WidgetsBindingObserver { late ThemeMode _theme; bool _isDarkTheme = false; diff --git a/mobile/lib/widgets/map/map_thumbnail.dart b/mobile/lib/widgets/map/map_thumbnail.dart index d02c01679169e..b856f097877f4 100644 --- a/mobile/lib/widgets/map/map_thumbnail.dart +++ b/mobile/lib/widgets/map/map_thumbnail.dart @@ -62,7 +62,7 @@ class MapThumbnail extends HookConsumerWidget { } } - return MapThemeOveride( + return MapThemeOverride( themeMode: themeMode, mapBuilder: (style) => SizedBox( height: height, diff --git a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart index 1c7cd1f2070cd..119407ccad942 100644 --- a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart @@ -2,12 +2,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/immich_colors.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/theme.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/theme/color_scheme.dart'; +import 'package:immich_mobile/theme/dynamic_theme.dart'; class PrimaryColorSetting extends HookConsumerWidget { const PrimaryColorSetting({ @@ -124,7 +126,7 @@ class PrimaryColorSetting extends HookConsumerWidget { style: context.textTheme.titleLarge, ), ), - if (isDynamicThemeAvailable) + if (DynamicTheme.isAvailable) Container( padding: const EdgeInsets.symmetric(horizontal: 20), margin: const EdgeInsets.only(top: 10), @@ -153,16 +155,16 @@ class PrimaryColorSetting extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 20), child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, - children: ImmichColorPreset.values.map((themePreset) { - var theme = themePreset.getTheme(); + children: ImmichColorPreset.values.map((preset) { + final theme = preset.themeOfPreset; return GestureDetector( - onTap: () => onPrimaryColorChange(themePreset), + onTap: () => onPrimaryColorChange(preset), child: buildPrimaryColorTile( topColor: theme.light.primary, bottomColor: theme.dark.primary, tileSize: tileSize, - showSelector: currentPreset.value == themePreset && + showSelector: currentPreset.value == preset && !systemPrimaryColorSetting.value, ), ); diff --git a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart index 3e1f388e84d66..b9ba7aa7b7fee 100644 --- a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart @@ -3,12 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/theme.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; -import 'package:immich_mobile/utils/immich_app_theme.dart'; class ThemeSetting extends HookConsumerWidget { const ThemeSetting({ diff --git a/mobile/test/modules/map/map_theme_override_test.dart b/mobile/test/modules/map/map_theme_override_test.dart index c21f9bf166984..bd000c8715f70 100644 --- a/mobile/test/modules/map/map_theme_override_test.dart +++ b/mobile/test/modules/map/map_theme_override_test.dart @@ -35,7 +35,7 @@ void main() { (tester) async { AsyncValue? mapStyle; await tester.pumpConsumerWidget( - MapThemeOveride( + MapThemeOverride( mapBuilder: (AsyncValue style) { mapStyle = style; return const Text("Mock"); @@ -53,7 +53,7 @@ void main() { testWidgets("Return error when style is not fetched", (tester) async { AsyncValue? mapStyle; await tester.pumpConsumerWidget( - MapThemeOveride( + MapThemeOverride( mapBuilder: (AsyncValue style) { mapStyle = style; return const Text("Mock"); @@ -73,7 +73,7 @@ void main() { (tester) async { AsyncValue? mapStyle; await tester.pumpConsumerWidget( - MapThemeOveride( + MapThemeOverride( mapBuilder: (AsyncValue style) { mapStyle = style; return const Text("Mock"); @@ -94,7 +94,7 @@ void main() { testWidgets("Return dark theme style when system is dark", (tester) async { AsyncValue? mapStyle; await tester.pumpConsumerWidget( - MapThemeOveride( + MapThemeOverride( mapBuilder: (AsyncValue style) { mapStyle = style; return const Text("Mock"); @@ -118,7 +118,7 @@ void main() { (tester) async { AsyncValue? mapStyle; await tester.pumpConsumerWidget( - MapThemeOveride( + MapThemeOverride( mapBuilder: (AsyncValue style) { mapStyle = style; return const Text("Mock"); @@ -142,7 +142,7 @@ void main() { (tester) async { AsyncValue? mapStyle; await tester.pumpConsumerWidget( - MapThemeOveride( + MapThemeOverride( mapBuilder: (AsyncValue style) { mapStyle = style; return const Text("Mock"); From e1390c3983c68f8b1c10ff875c60acc2cdf157f2 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Sat, 30 Nov 2024 21:39:10 +0700 Subject: [PATCH 4/9] remove white row --- mobile/lib/constants/colors.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/lib/constants/colors.dart b/mobile/lib/constants/colors.dart index d71d5c702e6d0..a9ec8b67ac01f 100644 --- a/mobile/lib/constants/colors.dart +++ b/mobile/lib/constants/colors.dart @@ -21,5 +21,3 @@ const Color immichBrandColorDark = Color(0xFFACCBFA); const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0); const Color red400 = Color(0xFFEF5350); - - From cae362215186093c82bf3958ee059f4d75324da2 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Sat, 30 Nov 2024 22:45:54 +0700 Subject: [PATCH 5/9] clean --- mobile/lib/theme/theme_data.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/theme/theme_data.dart b/mobile/lib/theme/theme_data.dart index c1f0b1942e84e..fe87f6bfc6fb6 100644 --- a/mobile/lib/theme/theme_data.dart +++ b/mobile/lib/theme/theme_data.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/constants/locales.dart'; +import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; class ImmichTheme { From 27616f8f4f004883a87a2e2c0ae14a4fe02b94c7 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Mon, 2 Dec 2024 03:04:07 +0700 Subject: [PATCH 6/9] rename variable --- mobile/lib/theme/color_scheme.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/lib/theme/color_scheme.dart b/mobile/lib/theme/color_scheme.dart index 68fc31356a6e3..c01b7cfa5a553 100644 --- a/mobile/lib/theme/color_scheme.dart +++ b/mobile/lib/theme/color_scheme.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/theme/theme_data.dart'; -final Map _themePresetsMap = { +final Map _themePresets = { ImmichColorPreset.indigo: ImmichTheme( light: ColorScheme.fromSeed( seedColor: immichBrandColorLight, @@ -89,5 +89,5 @@ final Map _themePresetsMap = { }; extension ImmichColorModeExtension on ImmichColorPreset { - ImmichTheme get themeOfPreset => _themePresetsMap[this]!; + ImmichTheme get themeOfPreset => _themePresets[this]!; } From 68739c6c62433248e86413fdd8866127e9373dc0 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Mon, 2 Dec 2024 11:35:37 +0700 Subject: [PATCH 7/9] add const --- mobile/lib/theme/theme_data.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/theme/theme_data.dart b/mobile/lib/theme/theme_data.dart index fe87f6bfc6fb6..442ba0acb308f 100644 --- a/mobile/lib/theme/theme_data.dart +++ b/mobile/lib/theme/theme_data.dart @@ -138,7 +138,7 @@ ThemeData getThemeData({ menuStyle: MenuStyle( shape: WidgetStatePropertyAll( RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), + borderRadius: const BorderRadius.all(Radius.circular(15)), ), ), ), From 29512973e63844602ddd09925d62ec7a3aec17dc Mon Sep 17 00:00:00 2001 From: dvbthien Date: Tue, 3 Dec 2024 00:13:09 +0700 Subject: [PATCH 8/9] fix conflict --- mobile/lib/theme/theme_data.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/lib/theme/theme_data.dart b/mobile/lib/theme/theme_data.dart index 442ba0acb308f..de96e12c5d2c3 100644 --- a/mobile/lib/theme/theme_data.dart +++ b/mobile/lib/theme/theme_data.dart @@ -53,7 +53,7 @@ ThemeData getThemeData({ scrolledUnderElevation: 0, centerTitle: true, ), - textTheme: TextTheme( + textTheme: const TextTheme( displayLarge: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -66,15 +66,15 @@ ThemeData getThemeData({ fontSize: 12, fontWeight: FontWeight.bold, ), - titleSmall: const TextStyle( + titleSmall: TextStyle( fontSize: 16.0, fontWeight: FontWeight.bold, ), - titleMedium: const TextStyle( + titleMedium: TextStyle( fontSize: 18.0, fontWeight: FontWeight.bold, ), - titleLarge: const TextStyle( + titleLarge: TextStyle( fontSize: 26.0, fontWeight: FontWeight.bold, ), @@ -135,10 +135,10 @@ ThemeData getThemeData({ cursorColor: colorScheme.primary, ), dropdownMenuTheme: DropdownMenuThemeData( - menuStyle: MenuStyle( + menuStyle: const MenuStyle( shape: WidgetStatePropertyAll( RoundedRectangleBorder( - borderRadius: const BorderRadius.all(Radius.circular(15)), + borderRadius: BorderRadius.all(Radius.circular(15)), ), ), ), From ea887cbc097fc9c939ad2fe7f09bbee9ae5fd2bb Mon Sep 17 00:00:00 2001 From: dvbthien Date: Tue, 3 Dec 2024 11:32:46 +0700 Subject: [PATCH 9/9] update code for review --- mobile/lib/theme/dynamic_theme.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/theme/dynamic_theme.dart b/mobile/lib/theme/dynamic_theme.dart index b66a2305d225f..39d6b6ee45e46 100644 --- a/mobile/lib/theme/dynamic_theme.dart +++ b/mobile/lib/theme/dynamic_theme.dart @@ -3,7 +3,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:immich_mobile/theme/theme_data.dart'; -class DynamicTheme { +abstract final class DynamicTheme { DynamicTheme._(); static ImmichTheme? _theme;