diff --git a/mobile/lib/constants/colors.dart b/mobile/lib/constants/colors.dart
new file mode 100644
index 0000000000..ade878d6f6
--- /dev/null
+++ b/mobile/lib/constants/colors.dart
@@ -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);
diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart
index 7729972aa2..807212fc65 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<void> 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/common/settings.page.dart b/mobile/lib/pages/common/settings.page.dart
index ba3150c046..3cbded1787 100644
--- a/mobile/lib/pages/common/settings.page.dart
+++ b/mobile/lib/pages/common/settings.page.dart
@@ -133,6 +133,7 @@ class _MobileLayout extends StatelessWidget {
                   ).tr(),
                   subtitle: Text(
                     setting.subtitle,
+                    style: context.textTheme.labelLarge,
                   ).tr(),
                   onTap: () =>
                       context.pushRoute(SettingsSubRoute(section: setting)),
diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart
index 10fe8de541..52ce13f958 100644
--- a/mobile/lib/pages/search/map/map.page.dart
+++ b/mobile/lib/pages/search/map/map.page.dart
@@ -264,7 +264,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 2fd1e1ee9e..487de69a1e 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 0000000000..73623bd026
--- /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<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);
+});
diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart
index 14d800a4ef..c3fde894d5 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<T> {
diff --git a/mobile/lib/constants/immich_colors.dart b/mobile/lib/theme/color_scheme.dart
similarity index 80%
rename from mobile/lib/constants/immich_colors.dart
rename to mobile/lib/theme/color_scheme.dart
index 847887de8c..c01b7cfa5a 100644
--- a/mobile/lib/constants/immich_colors.dart
+++ b/mobile/lib/theme/color_scheme.dart
@@ -1,29 +1,8 @@
 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 {
-  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 = {
+final Map<ImmichColorPreset, ImmichTheme> _themePresets = {
   ImmichColorPreset.indigo: ImmichTheme(
     light: ColorScheme.fromSeed(
       seedColor: immichBrandColorLight,
@@ -110,5 +89,5 @@ final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
 };
 
 extension ImmichColorModeExtension on ImmichColorPreset {
-  ImmichTheme getTheme() => _themePresetsMap[this]!;
+  ImmichTheme get themeOfPreset => _themePresets[this]!;
 }
diff --git a/mobile/lib/theme/dynamic_theme.dart b/mobile/lib/theme/dynamic_theme.dart
new file mode 100644
index 0000000000..39d6b6ee45
--- /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';
+
+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;
+}
diff --git a/mobile/lib/utils/immich_app_theme.dart b/mobile/lib/theme/theme_data.dart
similarity index 58%
rename from mobile/lib/utils/immich_app_theme.dart
rename to mobile/lib/theme/theme_data.dart
index 2ca4fe3aff..de96e12c5d 100644
--- a/mobile/lib/utils/immich_app_theme.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,104 +10,166 @@ class ImmichTheme {
   const ImmichTheme({required this.light, required this.dark});
 }
 
-ImmichTheme? _immichDynamicTheme;
-bool get isDynamicThemeAvailable => _immichDynamicTheme != null;
+ThemeData getThemeData({
+  required ColorScheme colorScheme,
+  required Locale locale,
+}) {
+  final isDark = colorScheme.brightness == Brightness.dark;
 
-final immichThemeModeProvider = StateProvider<ThemeMode>((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<ImmichColorPreset>((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<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) {
-  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<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
-      _immichDynamicTheme = ImmichTheme(
-        light: ColorScheme.fromSeed(
-          seedColor: primaryColor,
-          brightness: Brightness.light,
+  return ThemeData(
+    useMaterial3: true,
+    brightness: colorScheme.brightness,
+    colorScheme: colorScheme,
+    primaryColor: colorScheme.primary,
+    hintColor: colorScheme.onSurfaceSecondary,
+    focusColor: colorScheme.primary,
+    scaffoldBackgroundColor: colorScheme.surface,
+    splashColor: colorScheme.primary.withOpacity(0.1),
+    highlightColor: colorScheme.primary.withOpacity(0.1),
+    dialogBackgroundColor: colorScheme.surfaceContainer,
+    bottomSheetTheme: BottomSheetThemeData(
+      backgroundColor: colorScheme.surfaceContainer,
+    ),
+    fontFamily: _getFontFamilyFromLocale(locale),
+    snackBarTheme: SnackBarThemeData(
+      contentTextStyle: TextStyle(
+        fontFamily: _getFontFamilyFromLocale(locale),
+        color: colorScheme.primary,
+        fontWeight: FontWeight.bold,
+      ),
+      backgroundColor: colorScheme.surfaceContainerHighest,
+    ),
+    appBarTheme: AppBarTheme(
+      titleTextStyle: TextStyle(
+        color: colorScheme.primary,
+        fontFamily: _getFontFamilyFromLocale(locale),
+        fontWeight: FontWeight.bold,
+        fontSize: 18,
+      ),
+      backgroundColor:
+          isDark ? colorScheme.surfaceContainer : colorScheme.surface,
+      foregroundColor: colorScheme.primary,
+      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: colorScheme.primary,
+        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,
         ),
-        dark: ColorScheme.fromSeed(
-          seedColor: primaryColor,
-          brightness: Brightness.dark,
+      ),
+    ),
+    inputDecorationTheme: InputDecorationTheme(
+      focusedBorder: OutlineInputBorder(
+        borderSide: BorderSide(
+          color: colorScheme.primary,
         ),
-      );
-    }
-  } catch (e) {
-    debugPrint('dynamic_color: Failed to obtain core palette.');
-  }
+        borderRadius: const BorderRadius.all(Radius.circular(15)),
+      ),
+      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,
+      ),
+    ),
+    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
 // as we are creating the colorscheme through seedColor the default surfaces are
 // tinted with primary color
-ImmichTheme _decolorizeSurfaces({
+ImmichTheme decolorizeSurfaces({
   required ImmichTheme theme,
 }) {
   return ImmichTheme(
@@ -146,167 +204,10 @@ ImmichTheme _decolorizeSurfaces({
   );
 }
 
-String? getFontFamilyFromLocale(Locale locale) {
+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<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,
-        ),
-      ),
-    ),
-  );
-}
diff --git a/mobile/lib/widgets/asset_viewer/motion_photo_button.dart b/mobile/lib/widgets/asset_viewer/motion_photo_button.dart
index e4dd355554..f5479ab86e 100644
--- a/mobile/lib/widgets/asset_viewer/motion_photo_button.dart
+++ b/mobile/lib/widgets/asset_viewer/motion_photo_button.dart
@@ -1,6 +1,6 @@
 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/asset_viewer/is_motion_video_playing.provider.dart';
 
 class MotionPhotoButton extends ConsumerWidget {
diff --git a/mobile/lib/widgets/asset_viewer/video_position.dart b/mobile/lib/widgets/asset_viewer/video_position.dart
index b1f70b8686..4d0e7aa17f 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 4bbc040d4d..4df3e50f64 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 94148da176..540e136722 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 68a2146bfb..65425f9e78 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<String> 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<MapThemeOveride>
+class _MapThemeOverrideState extends ConsumerState<MapThemeOverride>
     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 d02c016791..b856f09787 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 1c7cd1f207..119407ccad 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 3e1f388e84..b9ba7aa7b7 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 c21f9bf166..bd000c8715 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<String>? mapStyle;
     await tester.pumpConsumerWidget(
-      MapThemeOveride(
+      MapThemeOverride(
         mapBuilder: (AsyncValue<String> style) {
           mapStyle = style;
           return const Text("Mock");
@@ -53,7 +53,7 @@ void main() {
   testWidgets("Return error when style is not fetched", (tester) async {
     AsyncValue<String>? mapStyle;
     await tester.pumpConsumerWidget(
-      MapThemeOveride(
+      MapThemeOverride(
         mapBuilder: (AsyncValue<String> style) {
           mapStyle = style;
           return const Text("Mock");
@@ -73,7 +73,7 @@ void main() {
       (tester) async {
     AsyncValue<String>? mapStyle;
     await tester.pumpConsumerWidget(
-      MapThemeOveride(
+      MapThemeOverride(
         mapBuilder: (AsyncValue<String> 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<String>? mapStyle;
       await tester.pumpConsumerWidget(
-        MapThemeOveride(
+        MapThemeOverride(
           mapBuilder: (AsyncValue<String> style) {
             mapStyle = style;
             return const Text("Mock");
@@ -118,7 +118,7 @@ void main() {
         (tester) async {
       AsyncValue<String>? mapStyle;
       await tester.pumpConsumerWidget(
-        MapThemeOveride(
+        MapThemeOverride(
           mapBuilder: (AsyncValue<String> style) {
             mapStyle = style;
             return const Text("Mock");
@@ -142,7 +142,7 @@ void main() {
         (tester) async {
       AsyncValue<String>? mapStyle;
       await tester.pumpConsumerWidget(
-        MapThemeOveride(
+        MapThemeOverride(
           mapBuilder: (AsyncValue<String> style) {
             mapStyle = style;
             return const Text("Mock");