1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 06:31:58 +00:00

refactor(mobile): refactor theme management (#14415)

This commit is contained in:
dvbthien 2024-12-11 23:30:56 +07:00 committed by GitHub
parent 5814a1b223
commit 11f585d0ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 343 additions and 322 deletions

View file

@ -0,0 +1,23 @@
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 red400 = Color(0xFFEF5350);
const Color grey200 = Color(0xFFEEEEEE);

View file

@ -4,23 +4,26 @@ import 'dart:io';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.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/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.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/constants/locales.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/providers/locale_provider.dart';
import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/providers/theme.provider.dart';
import 'package:immich_mobile/entities/duplicated_asset.entity.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/router.dart';
import 'package:immich_mobile/routing/tab_navigation_observer.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/album.entity.dart';
import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/android_device_asset.entity.dart';
import 'package:immich_mobile/entities/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/logger_message.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/services/immich_logger.service.dart'; import 'package:immich_mobile/services/immich_logger.service.dart';
import 'package:immich_mobile/services/local_notification.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:immich_mobile/utils/migration.dart';
import 'package:isar/isar.dart'; import 'package:immich_mobile/utils/download.dart';
import 'package:logging/logging.dart'; import 'package:immich_mobile/utils/cache/widgets_binding.dart';
import 'package:path_provider/path_provider.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 { void main() async {
ImmichWidgetsBinding(); ImmichWidgetsBinding();
@ -69,12 +71,12 @@ Future<void> initApp() async {
} }
} }
await fetchSystemPalette(); await DynamicTheme.fetchSystemPalette();
// Initialize Immich Logger Service // Initialize Immich Logger Service
ImmichLogger(); ImmichLogger();
var log = Logger("ImmichErrorLogger"); final log = Logger("ImmichErrorLogger");
FlutterError.onError = (details) { FlutterError.onError = (details) {
FlutterError.presentError(details); FlutterError.presentError(details);

View file

@ -133,6 +133,7 @@ class _MobileLayout extends StatelessWidget {
).tr(), ).tr(),
subtitle: Text( subtitle: Text(
setting.subtitle, setting.subtitle,
style: context.textTheme.labelLarge,
).tr(), ).tr(),
onTap: () => onTap: () =>
context.pushRoute(SettingsSubRoute(section: setting)), context.pushRoute(SettingsSubRoute(section: setting)),

View file

@ -264,7 +264,7 @@ class MapPage extends HookConsumerWidget {
selectedAssets.value = selected ? selection : {}; selectedAssets.value = selected ? selection : {};
} }
return MapThemeOveride( return MapThemeOverride(
mapBuilder: (style) => context.isMobile mapBuilder: (style) => context.isMobile
// Single-column // Single-column
? Scaffold( ? Scaffold(

View file

@ -58,7 +58,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng));
} }
return MapThemeOveride( return MapThemeOverride(
mapBuilder: (style) => Builder( mapBuilder: (style) => Builder(
builder: (ctx) => Scaffold( builder: (ctx) => Scaffold(
backgroundColor: ctx.themeData.cardColor, backgroundColor: ctx.themeData.cardColor,

View file

@ -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<ThemeMode>((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<ImmichColorPreset>((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<bool>((ref) {
return ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.dynamicTheme);
});
final colorfulInterfaceSettingProvider = StateProvider<bool>((ref) {
return ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.colorfulInterface);
});
// Provider for current selected theme
final immichThemeProvider = StateProvider<ImmichTheme>((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);
});

View file

@ -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'; import 'package:immich_mobile/entities/store.entity.dart';
enum AppSettingsEnum<T> { enum AppSettingsEnum<T> {

View file

@ -1,29 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/theme/theme_data.dart';
enum ImmichColorPreset { final Map<ImmichColorPreset, ImmichTheme> _themePresets = {
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 red400 = Color(0xFFEF5350);
const Color grey200 = Color(0xFFEEEEEE);
final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
ImmichColorPreset.indigo: ImmichTheme( ImmichColorPreset.indigo: ImmichTheme(
light: ColorScheme.fromSeed( light: ColorScheme.fromSeed(
seedColor: immichBrandColorLight, seedColor: immichBrandColorLight,
@ -110,5 +89,5 @@ final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
}; };
extension ImmichColorModeExtension on ImmichColorPreset { extension ImmichColorModeExtension on ImmichColorPreset {
ImmichTheme getTheme() => _themePresetsMap[this]!; ImmichTheme get themeOfPreset => _themePresets[this]!;
} }

View file

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:immich_mobile/theme/theme_data.dart';
abstract final class DynamicTheme {
DynamicTheme._();
static ImmichTheme? _theme;
// Method to fetch dynamic system colors
static Future<void> 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;
}

View file

@ -1,11 +1,7 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.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/constants/locales.dart';
import 'package:immich_mobile/extensions/theme_extensions.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 { class ImmichTheme {
final ColorScheme light; final ColorScheme light;
@ -14,104 +10,166 @@ class ImmichTheme {
const ImmichTheme({required this.light, required this.dark}); const ImmichTheme({required this.light, required this.dark});
} }
ImmichTheme? _immichDynamicTheme; ThemeData getThemeData({
bool get isDynamicThemeAvailable => _immichDynamicTheme != null; required ColorScheme colorScheme,
required Locale locale,
}) {
final isDark = colorScheme.brightness == Brightness.dark;
final immichThemeModeProvider = StateProvider<ThemeMode>((ref) { return ThemeData(
var themeMode = ref useMaterial3: true,
.watch(appSettingsServiceProvider) brightness: colorScheme.brightness,
.getSetting(AppSettingsEnum.themeMode); colorScheme: colorScheme,
primaryColor: colorScheme.primary,
debugPrint("Current themeMode $themeMode"); hintColor: colorScheme.onSurfaceSecondary,
focusColor: colorScheme.primary,
if (themeMode == "light") { scaffoldBackgroundColor: colorScheme.surface,
return ThemeMode.light; splashColor: colorScheme.primary.withOpacity(0.1),
} else if (themeMode == "dark") { highlightColor: colorScheme.primary.withOpacity(0.1),
return ThemeMode.dark; dialogBackgroundColor: colorScheme.surfaceContainer,
} else { bottomSheetTheme: BottomSheetThemeData(
return ThemeMode.system; backgroundColor: colorScheme.surfaceContainer,
} ),
}); fontFamily: _getFontFamilyFromLocale(locale),
snackBarTheme: SnackBarThemeData(
final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) { contentTextStyle: TextStyle(
var appSettingsProvider = ref.watch(appSettingsServiceProvider); fontFamily: _getFontFamilyFromLocale(locale),
var primaryColorName = color: colorScheme.primary,
appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); fontWeight: FontWeight.bold,
),
debugPrint("Current theme preset $primaryColorName"); backgroundColor: colorScheme.surfaceContainerHighest,
),
try { appBarTheme: AppBarTheme(
return ImmichColorPreset.values titleTextStyle: TextStyle(
.firstWhere((e) => e.name == primaryColorName); color: colorScheme.primary,
} catch (e) { fontFamily: _getFontFamilyFromLocale(locale),
debugPrint( fontWeight: FontWeight.bold,
"Theme preset $primaryColorName not found. Applying default preset.", fontSize: 18,
); ),
appSettingsProvider.setSetting( backgroundColor:
AppSettingsEnum.primaryColor, isDark ? colorScheme.surfaceContainer : colorScheme.surface,
defaultColorPresetName, foregroundColor: colorScheme.primary,
); elevation: 0,
return defaultColorPreset; scrolledUnderElevation: 0,
} centerTitle: true,
}); ),
textTheme: const TextTheme(
final dynamicThemeSettingProvider = StateProvider<bool>((ref) { displayLarge: TextStyle(
return ref fontSize: 26,
.watch(appSettingsServiceProvider) fontWeight: FontWeight.bold,
.getSetting(AppSettingsEnum.dynamicTheme); ),
}); displayMedium: TextStyle(
fontSize: 14,
final colorfulInterfaceSettingProvider = StateProvider<bool>((ref) { fontWeight: FontWeight.bold,
return ref ),
.watch(appSettingsServiceProvider) displaySmall: TextStyle(
.getSetting(AppSettingsEnum.colorfulInterface); fontSize: 12,
}); fontWeight: FontWeight.bold,
),
// Provider for current selected theme titleSmall: TextStyle(
final immichThemeProvider = StateProvider<ImmichTheme>((ref) { fontSize: 16.0,
var primaryColor = ref.read(immichThemePresetProvider); fontWeight: FontWeight.bold,
var useSystemColor = ref.watch(dynamicThemeSettingProvider); ),
var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); titleMedium: TextStyle(
fontSize: 18.0,
var currentTheme = (useSystemColor && _immichDynamicTheme != null) fontWeight: FontWeight.bold,
? _immichDynamicTheme! ),
: primaryColor.getTheme(); titleLarge: TextStyle(
fontSize: 26.0,
return useColorfulInterface fontWeight: FontWeight.bold,
? currentTheme ),
: _decolorizeSurfaces(theme: currentTheme); ),
}); elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
// Method to fetch dynamic system colors backgroundColor: colorScheme.primary,
Future<void> fetchSystemPalette() async { foregroundColor: isDark ? Colors.black87 : Colors.white,
try { ),
final corePalette = await DynamicColorPlugin.getCorePalette(); ),
if (corePalette != null) { chipTheme: const ChipThemeData(
final primaryColor = corePalette.toColorScheme().primary; side: BorderSide.none,
debugPrint('dynamic_color: Core palette detected.'); ),
sliderTheme: const SliderThemeData(
// Some palettes do not generate surface container colors accurately, thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
// so we regenerate all colors using the primary color trackHeight: 2.0,
_immichDynamicTheme = ImmichTheme( ),
light: ColorScheme.fromSeed( bottomNavigationBarTheme: const BottomNavigationBarThemeData(
seedColor: primaryColor, type: BottomNavigationBarType.fixed,
brightness: Brightness.light, ),
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,
), ),
dark: ColorScheme.fromSeed( ),
seedColor: primaryColor, ),
brightness: Brightness.dark, inputDecorationTheme: InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: colorScheme.primary,
), ),
); borderRadius: const BorderRadius.all(Radius.circular(15)),
} ),
} catch (e) { enabledBorder: OutlineInputBorder(
debugPrint('dynamic_color: Failed to obtain core palette.'); borderSide: BorderSide(
} color: colorScheme.outlineVariant,
),
borderRadius: const BorderRadius.all(Radius.circular(15)),
),
labelStyle: TextStyle(
color: colorScheme.primary,
),
hintStyle: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
textSelectionTheme: TextSelectionThemeData(
cursorColor: colorScheme.primary,
),
dropdownMenuTheme: DropdownMenuThemeData(
menuStyle: const MenuStyle(
shape: WidgetStatePropertyAll<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(15)),
),
),
),
inputDecorationTheme: InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: colorScheme.primary,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: colorScheme.outlineVariant,
),
borderRadius: const BorderRadius.all(Radius.circular(15)),
),
labelStyle: TextStyle(
color: colorScheme.primary,
),
hintStyle: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
),
);
} }
// This method replaces all surface shades in ImmichTheme to a static ones // This method replaces all surface shades in ImmichTheme to a static ones
// as we are creating the colorscheme through seedColor the default surfaces are // as we are creating the colorscheme through seedColor the default surfaces are
// tinted with primary color // tinted with primary color
ImmichTheme _decolorizeSurfaces({ ImmichTheme decolorizeSurfaces({
required ImmichTheme theme, required ImmichTheme theme,
}) { }) {
return ImmichTheme( return ImmichTheme(
@ -146,167 +204,10 @@ ImmichTheme _decolorizeSurfaces({
); );
} }
String? getFontFamilyFromLocale(Locale locale) { String? _getFontFamilyFromLocale(Locale locale) {
if (localesNotSupportedByOverpass.contains(locale)) { if (localesNotSupportedByOverpass.contains(locale)) {
// Let Flutter use the default font // Let Flutter use the default font
return null; return null;
} }
return 'Overpass'; 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<OutlinedBorder>(
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,
),
),
),
);
}

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/is_motion_video_playing.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
class MotionPhotoButton extends ConsumerWidget { class MotionPhotoButton extends ConsumerWidget {

View file

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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_controls_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart'; import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart';

View file

@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/providers/backup/error_backup_list.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/backup/error_chip_text.dart'; import 'package:immich_mobile/widgets/backup/error_chip_text.dart';

View file

@ -1,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/providers/backup/error_backup_list.provider.dart';
class BackupErrorChipText extends ConsumerWidget { class BackupErrorChipText extends ConsumerWidget {

View file

@ -3,21 +3,22 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/locale_provider.dart';
import 'package:immich_mobile/providers/map/map_state.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 /// 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 /// map settings instead of the one from the app settings
class MapThemeOveride extends StatefulHookConsumerWidget { class MapThemeOverride extends StatefulHookConsumerWidget {
final ThemeMode? themeMode; final ThemeMode? themeMode;
final Widget Function(AsyncValue<String> style) mapBuilder; final Widget Function(AsyncValue<String> style) mapBuilder;
const MapThemeOveride({required this.mapBuilder, this.themeMode, super.key}); const MapThemeOverride({required this.mapBuilder, this.themeMode, super.key});
@override @override
ConsumerState createState() => _MapThemeOverideState(); ConsumerState createState() => _MapThemeOverrideState();
} }
class _MapThemeOverideState extends ConsumerState<MapThemeOveride> class _MapThemeOverrideState extends ConsumerState<MapThemeOverride>
with WidgetsBindingObserver { with WidgetsBindingObserver {
late ThemeMode _theme; late ThemeMode _theme;
bool _isDarkTheme = false; bool _isDarkTheme = false;

View file

@ -62,7 +62,7 @@ class MapThumbnail extends HookConsumerWidget {
} }
} }
return MapThemeOveride( return MapThemeOverride(
themeMode: themeMode, themeMode: themeMode,
mapBuilder: (style) => SizedBox( mapBuilder: (style) => SizedBox(
height: height, height: height,

View file

@ -2,12 +2,14 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_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/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/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 { class PrimaryColorSetting extends HookConsumerWidget {
const PrimaryColorSetting({ const PrimaryColorSetting({
@ -124,7 +126,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
style: context.textTheme.titleLarge, style: context.textTheme.titleLarge,
), ),
), ),
if (isDynamicThemeAvailable) if (DynamicTheme.isAvailable)
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
margin: const EdgeInsets.only(top: 10), margin: const EdgeInsets.only(top: 10),
@ -153,16 +155,16 @@ class PrimaryColorSetting extends HookConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Wrap( child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
children: ImmichColorPreset.values.map((themePreset) { children: ImmichColorPreset.values.map((preset) {
var theme = themePreset.getTheme(); final theme = preset.themeOfPreset;
return GestureDetector( return GestureDetector(
onTap: () => onPrimaryColorChange(themePreset), onTap: () => onPrimaryColorChange(preset),
child: buildPrimaryColorTile( child: buildPrimaryColorTile(
topColor: theme.light.primary, topColor: theme.light.primary,
bottomColor: theme.dark.primary, bottomColor: theme.dark.primary,
tileSize: tileSize, tileSize: tileSize,
showSelector: currentPreset.value == themePreset && showSelector: currentPreset.value == preset &&
!systemPrimaryColorSetting.value, !systemPrimaryColorSetting.value,
), ),
); );

View file

@ -3,12 +3,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.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/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.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_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.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/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
class ThemeSetting extends HookConsumerWidget { class ThemeSetting extends HookConsumerWidget {
const ThemeSetting({ const ThemeSetting({

View file

@ -35,7 +35,7 @@ void main() {
(tester) async { (tester) async {
AsyncValue<String>? mapStyle; AsyncValue<String>? mapStyle;
await tester.pumpConsumerWidget( await tester.pumpConsumerWidget(
MapThemeOveride( MapThemeOverride(
mapBuilder: (AsyncValue<String> style) { mapBuilder: (AsyncValue<String> style) {
mapStyle = style; mapStyle = style;
return const Text("Mock"); return const Text("Mock");
@ -53,7 +53,7 @@ void main() {
testWidgets("Return error when style is not fetched", (tester) async { testWidgets("Return error when style is not fetched", (tester) async {
AsyncValue<String>? mapStyle; AsyncValue<String>? mapStyle;
await tester.pumpConsumerWidget( await tester.pumpConsumerWidget(
MapThemeOveride( MapThemeOverride(
mapBuilder: (AsyncValue<String> style) { mapBuilder: (AsyncValue<String> style) {
mapStyle = style; mapStyle = style;
return const Text("Mock"); return const Text("Mock");
@ -73,7 +73,7 @@ void main() {
(tester) async { (tester) async {
AsyncValue<String>? mapStyle; AsyncValue<String>? mapStyle;
await tester.pumpConsumerWidget( await tester.pumpConsumerWidget(
MapThemeOveride( MapThemeOverride(
mapBuilder: (AsyncValue<String> style) { mapBuilder: (AsyncValue<String> style) {
mapStyle = style; mapStyle = style;
return const Text("Mock"); return const Text("Mock");
@ -94,7 +94,7 @@ void main() {
testWidgets("Return dark theme style when system is dark", (tester) async { testWidgets("Return dark theme style when system is dark", (tester) async {
AsyncValue<String>? mapStyle; AsyncValue<String>? mapStyle;
await tester.pumpConsumerWidget( await tester.pumpConsumerWidget(
MapThemeOveride( MapThemeOverride(
mapBuilder: (AsyncValue<String> style) { mapBuilder: (AsyncValue<String> style) {
mapStyle = style; mapStyle = style;
return const Text("Mock"); return const Text("Mock");
@ -118,7 +118,7 @@ void main() {
(tester) async { (tester) async {
AsyncValue<String>? mapStyle; AsyncValue<String>? mapStyle;
await tester.pumpConsumerWidget( await tester.pumpConsumerWidget(
MapThemeOveride( MapThemeOverride(
mapBuilder: (AsyncValue<String> style) { mapBuilder: (AsyncValue<String> style) {
mapStyle = style; mapStyle = style;
return const Text("Mock"); return const Text("Mock");
@ -142,7 +142,7 @@ void main() {
(tester) async { (tester) async {
AsyncValue<String>? mapStyle; AsyncValue<String>? mapStyle;
await tester.pumpConsumerWidget( await tester.pumpConsumerWidget(
MapThemeOveride( MapThemeOverride(
mapBuilder: (AsyncValue<String> style) { mapBuilder: (AsyncValue<String> style) {
mapStyle = style; mapStyle = style;
return const Text("Mock"); return const Text("Mock");