From 0eacdf93ebf32dfdeb672e7b67d2aa57e96baea4 Mon Sep 17 00:00:00 2001
From: Pruthvi Bugidi <pruthvi.bugidi@proton.me>
Date: Tue, 6 Aug 2024 19:50:27 +0530
Subject: [PATCH] feat(mobile): add support for material themes (#11560)

* feat(mobile): add support for material themes

Added support for custom theming and updated all elements accordingly.

* fix(mobile): Restored immich brand colors to default theme

* fix(mobile): make ListTile titles bold in settings main page

* feat(mobile): update bottom nav and appbar colors

* small tweaks

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
---
 mobile/assets/i18n/en-US.json                 |   7 +-
 mobile/lib/constants/immich_colors.dart       | 109 +++-
 mobile/lib/entities/store.entity.dart         |   5 +
 .../extensions/build_context_extensions.dart  |   4 +-
 mobile/lib/extensions/theme_extensions.dart   |  24 +
 mobile/lib/main.dart                          |   9 +-
 .../lib/pages/backup/album_preview.page.dart  |   4 +-
 .../backup/backup_album_selection.page.dart   |   6 +-
 .../pages/backup/backup_controller.page.dart  |   9 +-
 .../lib/pages/common/album_options.page.dart  |  11 +-
 .../lib/pages/common/album_viewer.page.dart   |   6 +-
 mobile/lib/pages/common/app_log.page.dart     |  20 +-
 .../lib/pages/common/app_log_detail.page.dart |   6 +-
 .../lib/pages/common/create_album.page.dart   |  22 +-
 mobile/lib/pages/common/settings.page.dart    |  23 +-
 mobile/lib/pages/library/library.page.dart    |  32 +-
 mobile/lib/pages/login/login.page.dart        |   5 +-
 mobile/lib/pages/search/search.page.dart      |  19 +-
 .../lib/pages/search/search_input.page.dart   |   3 +-
 .../shared_link/shared_link_edit.page.dart    |  19 +-
 mobile/lib/pages/sharing/sharing.page.dart    |  21 +-
 mobile/lib/services/app_settings.service.dart |  16 +
 mobile/lib/utils/immich_app_theme.dart        | 489 +++++++++---------
 ...n.dart => album_action_filled_button.dart} |  15 +-
 .../widgets/album/album_thumbnail_card.dart   |  18 +-
 .../widgets/album/album_title_text_field.dart |  22 +-
 .../widgets/album/album_viewer_appbar.dart    |   2 +-
 .../album/album_viewer_editable_title.dart    |  18 +-
 .../asset_grid/control_bottom_app_bar.dart    |   2 +-
 .../disable_multi_select_button.dart          |   7 +-
 .../asset_grid/group_divider_title.dart       |   5 +-
 .../asset_grid/immich_asset_grid_view.dart    |   5 +-
 .../widgets/asset_grid/thumbnail_image.dart   |   9 +-
 .../asset_grid/thumbnail_placeholder.dart     |  18 +-
 .../asset_viewer/description_input.dart       |   7 +-
 .../exif_sheet/exif_bottom_sheet.dart         |   2 +-
 .../asset_viewer/top_control_app_bar.dart     |   1 +
 .../widgets/backup/album_info_list_tile.dart  |  10 +-
 .../lib/widgets/backup/backup_info_card.dart  |   9 +-
 .../backup/current_backup_asset_info_box.dart |  27 +-
 .../common/app_bar_dialog/app_bar_dialog.dart |  14 +-
 .../app_bar_dialog/app_bar_profile_info.dart  |  11 +-
 .../app_bar_dialog/app_bar_server_info.dart   |  37 +-
 mobile/lib/widgets/common/confirm_dialog.dart |   2 +-
 mobile/lib/widgets/common/immich_app_bar.dart |   4 +-
 .../lib/widgets/common/immich_title_text.dart |   1 +
 mobile/lib/widgets/common/immich_toast.dart   |   4 +-
 .../widgets/forms/change_password_form.dart   |   5 +-
 .../lib/widgets/map/map_theme_override.dart   |   5 +-
 .../lib/widgets/memories/memory_epilogue.dart |  29 +-
 .../memories/memory_progress_indicator.dart   |   9 +-
 .../search_filter/search_filter_chip.dart     |   9 +-
 .../search/thumbnail_with_info_container.dart |  12 +-
 .../custome_proxy_headers_settings.dart       |   5 +-
 .../widgets/settings/language_settings.dart   |   4 +-
 .../settings/local_storage_settings.dart      |   7 +-
 .../preference_setting.dart                   |   5 +-
 .../primary_color_setting.dart                | 221 ++++++++
 .../preference_settings/theme_setting.dart    |  37 +-
 .../settings/settings_button_list_tile.dart   |   8 +-
 .../settings/settings_switch_list_tile.dart   |   5 +-
 .../settings/ssl_client_cert_settings.dart    |   5 +-
 .../widgets/shared_link/shared_link_item.dart |  12 +-
 mobile/pubspec.lock                           |   8 +
 mobile/pubspec.yaml                           |   2 +
 65 files changed, 944 insertions(+), 563 deletions(-)
 create mode 100644 mobile/lib/extensions/theme_extensions.dart
 rename mobile/lib/widgets/album/{album_action_outlined_button.dart => album_action_filled_button.dart} (70%)
 create mode 100644 mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart

diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json
index ad3103b002..220a73b58d 100644
--- a/mobile/assets/i18n/en-US.json
+++ b/mobile/assets/i18n/en-US.json
@@ -531,6 +531,11 @@
   "theme_setting_dark_mode_switch": "Dark mode",
   "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
   "theme_setting_image_viewer_quality_title": "Image viewer quality",
+  "theme_setting_primary_color_title": "Primary color",
+  "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
+  "theme_setting_colorful_interface_title": "Colorful interface",
+  "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
+  "theme_setting_system_primary_color_title": "Use system color",
   "theme_setting_system_theme_switch": "Automatic (Follow system setting)",
   "theme_setting_theme_subtitle": "Choose the app's theme setting",
   "theme_setting_theme_title": "Theme",
@@ -562,4 +567,4 @@
   "viewer_remove_from_stack": "Remove from Stack",
   "viewer_stack_use_as_main_asset": "Use as Main Asset",
   "viewer_unstack": "Un-Stack"
-}
\ No newline at end of file
+}
diff --git a/mobile/lib/constants/immich_colors.dart b/mobile/lib/constants/immich_colors.dart
index 598f956619..38deac3f0e 100644
--- a/mobile/lib/constants/immich_colors.dart
+++ b/mobile/lib/constants/immich_colors.dart
@@ -1,5 +1,108 @@
 import 'package:flutter/material.dart';
+import 'package:immich_mobile/utils/immich_app_theme.dart';
 
-const Color immichBackgroundColor = Color(0xFFf6f8fe);
-const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
-const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);
+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);
+
+final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
+  ImmichColorPreset.indigo: ImmichTheme(
+    light: ColorScheme.fromSeed(
+      seedColor: immichBrandColorLight,
+    ).copyWith(primary: immichBrandColorLight),
+    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/entities/store.entity.dart b/mobile/lib/entities/store.entity.dart
index baa7ff51a3..a84f980001 100644
--- a/mobile/lib/entities/store.entity.dart
+++ b/mobile/lib/entities/store.entity.dart
@@ -229,6 +229,11 @@ enum StoreKey<T> {
   mapwithPartners<bool>(125, type: bool),
   enableHapticFeedback<bool>(126, type: bool),
   customHeaders<String>(127, type: String),
+
+  // theme settings
+  primaryColor<String>(128, type: String),
+  dynamicTheme<bool>(129, type: bool),
+  colorfulInterface<bool>(130, type: bool),
   ;
 
   const StoreKey(
diff --git a/mobile/lib/extensions/build_context_extensions.dart b/mobile/lib/extensions/build_context_extensions.dart
index 6a61b00530..141a1ede15 100644
--- a/mobile/lib/extensions/build_context_extensions.dart
+++ b/mobile/lib/extensions/build_context_extensions.dart
@@ -20,10 +20,10 @@ extension ContextHelper on BuildContext {
   bool get isDarkTheme => themeData.brightness == Brightness.dark;
 
   // Returns the current Primary color of the Theme
-  Color get primaryColor => themeData.primaryColor;
+  Color get primaryColor => themeData.colorScheme.primary;
 
   // Returns the Scaffold background color of the Theme
-  Color get scaffoldBackgroundColor => themeData.scaffoldBackgroundColor;
+  Color get scaffoldBackgroundColor => colorScheme.surface;
 
   // Returns the current TextTheme
   TextTheme get textTheme => themeData.textTheme;
diff --git a/mobile/lib/extensions/theme_extensions.dart b/mobile/lib/extensions/theme_extensions.dart
new file mode 100644
index 0000000000..3e17e2b991
--- /dev/null
+++ b/mobile/lib/extensions/theme_extensions.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+
+extension ImmichColorSchemeExtensions on ColorScheme {
+  bool get _isDarkMode => brightness == Brightness.dark;
+  Color get onSurfaceSecondary => _isDarkMode
+      ? onSurface.darken(amount: .3)
+      : onSurface.lighten(amount: .3);
+}
+
+extension ColorExtensions on Color {
+  Color lighten({double amount = 0.1}) {
+    return Color.alphaBlend(
+      Colors.white.withOpacity(amount),
+      this,
+    );
+  }
+
+  Color darken({double amount = 0.1}) {
+    return Color.alphaBlend(
+      Colors.black.withOpacity(amount),
+      this,
+    );
+  }
+}
diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart
index 2340ed70d2..916c1ad3d3 100644
--- a/mobile/lib/main.dart
+++ b/mobile/lib/main.dart
@@ -65,6 +65,8 @@ Future<void> initApp() async {
     }
   }
 
+  await fetchSystemPalette();
+
   // Initialize Immich Logger Service
   ImmichLogger();
 
@@ -187,6 +189,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
   @override
   Widget build(BuildContext context) {
     var router = ref.watch(appRouterProvider);
+    var immichTheme = ref.watch(immichThemeProvider);
 
     return MaterialApp(
       localizationsDelegates: context.localizationDelegates,
@@ -196,9 +199,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
       home: MaterialApp.router(
         title: 'Immich',
         debugShowCheckedModeBanner: false,
-        themeMode: ref.watch(immichThemeProvider),
-        darkTheme: immichDarkTheme,
-        theme: immichLightTheme,
+        themeMode: ref.watch(immichThemeModeProvider),
+        darkTheme: getThemeData(colorScheme: immichTheme.dark),
+        theme: getThemeData(colorScheme: immichTheme.light),
         routeInformationParser: router.defaultRouteParser(),
         routerDelegate: router.delegate(
           navigatorObservers: () => [TabNavigationObserver(ref: ref)],
diff --git a/mobile/lib/pages/backup/album_preview.page.dart b/mobile/lib/pages/backup/album_preview.page.dart
index 218127ff43..5cb5d418a0 100644
--- a/mobile/lib/pages/backup/album_preview.page.dart
+++ b/mobile/lib/pages/backup/album_preview.page.dart
@@ -4,6 +4,8 @@ import 'package:auto_route/auto_route.dart';
 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
 import 'package:photo_manager/photo_manager.dart';
 
@@ -46,7 +48,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
                 "ID ${album.id}",
                 style: TextStyle(
                   fontSize: 10,
-                  color: Colors.grey[600],
+                  color: context.colorScheme.onSurfaceSecondary,
                   fontWeight: FontWeight.bold,
                 ),
               ),
diff --git a/mobile/lib/pages/backup/backup_album_selection.page.dart b/mobile/lib/pages/backup/backup_album_selection.page.dart
index ecfebd3cb7..9f3e387755 100644
--- a/mobile/lib/pages/backup/backup_album_selection.page.dart
+++ b/mobile/lib/pages/backup/backup_album_selection.page.dart
@@ -3,7 +3,6 @@ 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/extensions/build_context_extensions.dart';
 import 'package:immich_mobile/providers/backup/backup.provider.dart';
 import 'package:immich_mobile/widgets/backup/album_info_card.dart';
@@ -128,13 +127,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
                 album.name,
                 style: TextStyle(
                   fontSize: 12,
-                  color: isDarkTheme ? Colors.black : immichBackgroundColor,
+                  color: context.scaffoldBackgroundColor,
                   fontWeight: FontWeight.bold,
                 ),
               ),
               backgroundColor: Colors.red[300],
-              deleteIconColor:
-                  isDarkTheme ? Colors.black : immichBackgroundColor,
+              deleteIconColor: context.scaffoldBackgroundColor,
               deleteIcon: const Icon(
                 Icons.cancel_rounded,
                 size: 15,
diff --git a/mobile/lib/pages/backup/backup_controller.page.dart b/mobile/lib/pages/backup/backup_controller.page.dart
index 89384cf97a..61a6bc1bb9 100644
--- a/mobile/lib/pages/backup/backup_controller.page.dart
+++ b/mobile/lib/pages/backup/backup_controller.page.dart
@@ -7,6 +7,7 @@ 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/models/backup/backup_state.model.dart';
 import 'package:immich_mobile/providers/album/album.provider.dart';
 import 'package:immich_mobile/providers/backup/backup.provider.dart';
@@ -130,9 +131,7 @@ class BackupControllerPage extends HookConsumerWidget {
           shape: RoundedRectangleBorder(
             borderRadius: BorderRadius.circular(20),
             side: BorderSide(
-              color: context.isDarkTheme
-                  ? const Color.fromARGB(255, 56, 56, 56)
-                  : Colors.black12,
+              color: context.colorScheme.outlineVariant,
               width: 1,
             ),
           ),
@@ -151,7 +150,9 @@ class BackupControllerPage extends HookConsumerWidget {
                 children: [
                   Text(
                     "backup_controller_page_to_backup",
-                    style: context.textTheme.bodyMedium,
+                    style: context.textTheme.bodyMedium?.copyWith(
+                      color: context.colorScheme.onSurfaceSecondary,
+                    ),
                   ).tr(),
                   buildSelectedAlbumName(),
                   buildExcludedAlbumName(),
diff --git a/mobile/lib/pages/common/album_options.page.dart b/mobile/lib/pages/common/album_options.page.dart
index 1cc24af09c..3cc30af7a9 100644
--- a/mobile/lib/pages/common/album_options.page.dart
+++ b/mobile/lib/pages/common/album_options.page.dart
@@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:fluttertoast/fluttertoast.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/album/shared_album.provider.dart';
 import 'package:immich_mobile/providers/authentication.provider.dart';
 import 'package:immich_mobile/utils/immich_loading_overlay.dart';
@@ -102,7 +103,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
       }
 
       showModalBottomSheet(
-        backgroundColor: context.scaffoldBackgroundColor,
+        backgroundColor: context.colorScheme.surfaceContainer,
         isScrollControlled: false,
         context: context,
         builder: (context) {
@@ -131,7 +132,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
         ),
         subtitle: Text(
           album.owner.value?.email ?? "",
-          style: TextStyle(color: Colors.grey[600]),
+          style: TextStyle(color: context.colorScheme.onSurfaceSecondary),
         ),
         trailing: Text(
           "shared_album_section_people_owner_label",
@@ -160,7 +161,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
             ),
             subtitle: Text(
               user.email,
-              style: TextStyle(color: Colors.grey[600]),
+              style: TextStyle(
+                color: context.colorScheme.onSurfaceSecondary,
+              ),
             ),
             trailing: userId == user.id || isOwner
                 ? const Icon(Icons.more_horiz_rounded)
@@ -214,7 +217,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
               subtitle: Text(
                 "shared_album_activity_setting_subtitle",
                 style: context.textTheme.labelLarge?.copyWith(
-                  color: context.textTheme.labelLarge?.color?.withAlpha(175),
+                  color: context.colorScheme.onSurfaceSecondary,
                 ),
               ).tr(),
             ),
diff --git a/mobile/lib/pages/common/album_viewer.page.dart b/mobile/lib/pages/common/album_viewer.page.dart
index e1e0419d52..33b314f3b1 100644
--- a/mobile/lib/pages/common/album_viewer.page.dart
+++ b/mobile/lib/pages/common/album_viewer.page.dart
@@ -14,7 +14,7 @@ import 'package:immich_mobile/providers/album/current_album.provider.dart';
 import 'package:immich_mobile/providers/album/shared_album.provider.dart';
 import 'package:immich_mobile/utils/immich_loading_overlay.dart';
 import 'package:immich_mobile/services/album.service.dart';
-import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
+import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
 import 'package:immich_mobile/widgets/album/album_viewer_editable_title.dart';
 import 'package:immich_mobile/providers/multiselect.provider.dart';
 import 'package:immich_mobile/providers/authentication.provider.dart';
@@ -114,13 +114,13 @@ class AlbumViewerPage extends HookConsumerWidget {
           child: ListView(
             scrollDirection: Axis.horizontal,
             children: [
-              AlbumActionOutlinedButton(
+              AlbumActionFilledButton(
                 iconData: Icons.add_photo_alternate_outlined,
                 onPressed: () => onAddPhotosPressed(album),
                 labelText: "share_add_photos".tr(),
               ),
               if (userId == album.ownerId)
-                AlbumActionOutlinedButton(
+                AlbumActionFilledButton(
                   iconData: Icons.person_add_alt_rounded,
                   onPressed: () => onAddUsersPressed(album),
                   labelText: "album_viewer_page_share_add_users".tr(),
diff --git a/mobile/lib/pages/common/app_log.page.dart b/mobile/lib/pages/common/app_log.page.dart
index 8066835d84..fd718ee37d 100644
--- a/mobile/lib/pages/common/app_log.page.dart
+++ b/mobile/lib/pages/common/app_log.page.dart
@@ -3,6 +3,7 @@ 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/entities/logger_message.entity.dart';
 import 'package:immich_mobile/services/immich_logger.service.dart';
@@ -18,7 +19,6 @@ class AppLogPage extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     final immichLogger = ImmichLogger();
     final logMessages = useState(immichLogger.messages);
-    final isDarkTheme = context.isDarkTheme;
 
     Widget colorStatusIndicator(Color color) {
       return Column(
@@ -55,13 +55,9 @@ class AppLogPage extends HookConsumerWidget {
         case LogLevel.INFO:
           return Colors.transparent;
         case LogLevel.SEVERE:
-          return isDarkTheme
-              ? Colors.redAccent.withOpacity(0.25)
-              : Colors.redAccent.withOpacity(0.075);
+          return Colors.redAccent.withOpacity(0.25);
         case LogLevel.WARNING:
-          return isDarkTheme
-              ? Colors.orangeAccent.withOpacity(0.25)
-              : Colors.orangeAccent.withOpacity(0.075);
+          return Colors.orangeAccent.withOpacity(0.25);
         default:
           return context.primaryColor.withOpacity(0.1);
       }
@@ -120,10 +116,7 @@ class AppLogPage extends HookConsumerWidget {
       ),
       body: ListView.separated(
         separatorBuilder: (context, index) {
-          return Divider(
-            height: 0,
-            color: isDarkTheme ? Colors.white70 : Colors.grey[600],
-          );
+          return const Divider(height: 0);
         },
         itemCount: logMessages.value.length,
         itemBuilder: (context, index) {
@@ -141,8 +134,9 @@ class AppLogPage extends HookConsumerWidget {
             minLeadingWidth: 10,
             title: Text(
               truncateLogMessage(logMessage.message, 4),
-              style: const TextStyle(
+              style: TextStyle(
                 fontSize: 14.0,
+                color: context.colorScheme.onSurface,
                 fontFamily: "Inconsolata",
               ),
             ),
@@ -150,7 +144,7 @@ class AppLogPage extends HookConsumerWidget {
               "at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.context1}",
               style: TextStyle(
                 fontSize: 12.0,
-                color: Colors.grey[600],
+                color: context.colorScheme.onSurfaceSecondary,
               ),
             ),
             leading: buildLeadingIcon(logMessage.level),
diff --git a/mobile/lib/pages/common/app_log_detail.page.dart b/mobile/lib/pages/common/app_log_detail.page.dart
index 61f510c0de..1b9af6cfcf 100644
--- a/mobile/lib/pages/common/app_log_detail.page.dart
+++ b/mobile/lib/pages/common/app_log_detail.page.dart
@@ -13,8 +13,6 @@ class AppLogDetailPage extends HookConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    var isDarkTheme = context.isDarkTheme;
-
     buildTextWithCopyButton(String header, String text) {
       return Padding(
         padding: const EdgeInsets.all(8.0),
@@ -61,7 +59,7 @@ class AppLogDetailPage extends HookConsumerWidget {
             ),
             Container(
               decoration: BoxDecoration(
-                color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
+                color: context.colorScheme.surfaceContainerHigh,
                 borderRadius: BorderRadius.circular(15.0),
               ),
               child: Padding(
@@ -100,7 +98,7 @@ class AppLogDetailPage extends HookConsumerWidget {
             ),
             Container(
               decoration: BoxDecoration(
-                color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
+                color: context.colorScheme.surfaceContainerHigh,
                 borderRadius: BorderRadius.circular(15.0),
               ),
               child: Padding(
diff --git a/mobile/lib/pages/common/create_album.page.dart b/mobile/lib/pages/common/create_album.page.dart
index 053057425e..1ed6885a07 100644
--- a/mobile/lib/pages/common/create_album.page.dart
+++ b/mobile/lib/pages/common/create_album.page.dart
@@ -10,7 +10,7 @@ import 'package:immich_mobile/providers/album/album.provider.dart';
 import 'package:immich_mobile/providers/album/album_title.provider.dart';
 import 'package:immich_mobile/providers/asset.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
-import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
+import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
 import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
 import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
 
@@ -109,20 +109,16 @@ class CreateAlbumPage extends HookConsumerWidget {
       if (selectedAssets.value.isEmpty) {
         return SliverToBoxAdapter(
           child: Padding(
-            padding: const EdgeInsets.only(top: 16, left: 18, right: 18),
-            child: OutlinedButton.icon(
-              style: OutlinedButton.styleFrom(
+            padding: const EdgeInsets.only(top: 16, left: 16, right: 16),
+            child: FilledButton.icon(
+              style: FilledButton.styleFrom(
                 alignment: Alignment.centerLeft,
                 padding:
-                    const EdgeInsets.symmetric(vertical: 22, horizontal: 16),
-                side: BorderSide(
-                  color: context.isDarkTheme
-                      ? const Color.fromARGB(255, 63, 63, 63)
-                      : const Color.fromARGB(255, 129, 129, 129),
-                ),
+                    const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
                 shape: RoundedRectangleBorder(
-                  borderRadius: BorderRadius.circular(5),
+                  borderRadius: BorderRadius.circular(10),
                 ),
+                backgroundColor: context.colorScheme.surfaceContainerHighest,
               ),
               onPressed: onSelectPhotosButtonPressed,
               icon: Icon(
@@ -134,7 +130,7 @@ class CreateAlbumPage extends HookConsumerWidget {
                 child: Text(
                   'create_shared_album_page_share_select_photos',
                   style: context.textTheme.titleMedium?.copyWith(
-                    color: context.primaryColor,
+                    fontWeight: FontWeight.normal,
                   ),
                 ).tr(),
               ),
@@ -154,7 +150,7 @@ class CreateAlbumPage extends HookConsumerWidget {
           child: ListView(
             scrollDirection: Axis.horizontal,
             children: [
-              AlbumActionOutlinedButton(
+              AlbumActionFilledButton(
                 iconData: Icons.add_photo_alternate_outlined,
                 onPressed: onSelectPhotosButtonPressed,
                 labelText: "share_add_photos".tr(),
diff --git a/mobile/lib/pages/common/settings.page.dart b/mobile/lib/pages/common/settings.page.dart
index 486eeba4cd..117b0aedc0 100644
--- a/mobile/lib/pages/common/settings.page.dart
+++ b/mobile/lib/pages/common/settings.page.dart
@@ -49,10 +49,6 @@ class SettingsPage extends StatelessWidget {
     return Scaffold(
       appBar: AppBar(
         centerTitle: false,
-        bottom: const PreferredSize(
-          preferredSize: Size.fromHeight(1),
-          child: Divider(height: 1),
-        ),
         title: const Text('setting_pages_app_bar_settings').tr(),
       ),
       body: context.isMobile ? _MobileLayout() : _TabletLayout(),
@@ -67,13 +63,18 @@ class _MobileLayout extends StatelessWidget {
       children: SettingSection.values
           .map(
             (s) => ListTile(
-              title: Text(
-                s.title,
-                style: const TextStyle(
-                  fontWeight: FontWeight.bold,
-                ),
-              ).tr(),
+              contentPadding:
+                  const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
               leading: Icon(s.icon),
+              title: Padding(
+                padding: const EdgeInsets.only(left: 8.0),
+                child: Text(
+                  s.title,
+                  style: const TextStyle(
+                    fontWeight: FontWeight.bold,
+                  ),
+                ).tr(),
+              ),
               onTap: () => context.pushRoute(SettingsSubRoute(section: s)),
             ),
           )
@@ -102,7 +103,7 @@ class _TabletLayout extends HookWidget {
                       leading: Icon(s.icon),
                       selected: s.index == selectedSection.value.index,
                       selectedColor: context.primaryColor,
-                      selectedTileColor: context.primaryColor.withAlpha(50),
+                      selectedTileColor: context.themeData.highlightColor,
                       onTap: () => selectedSection.value = s,
                     ),
                   ),
diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart
index be98440349..5f03ed6871 100644
--- a/mobile/lib/pages/library/library.page.dart
+++ b/mobile/lib/pages/library/library.page.dart
@@ -20,7 +20,6 @@ class LibraryPage extends HookConsumerWidget {
     final trashEnabled =
         ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
     final albums = ref.watch(albumProvider);
-    final isDarkTheme = context.isDarkTheme;
     final albumSortOption = ref.watch(albumSortByOptionsProvider);
     final albumSortIsReverse = ref.watch(albumSortOrderProvider);
 
@@ -116,12 +115,7 @@ class LibraryPage extends HookConsumerWidget {
                     width: cardSize,
                     height: cardSize,
                     decoration: BoxDecoration(
-                      border: Border.all(
-                        color: isDarkTheme
-                            ? const Color.fromARGB(255, 53, 53, 53)
-                            : const Color.fromARGB(255, 203, 203, 203),
-                      ),
-                      color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
+                      color: context.colorScheme.surfaceContainer,
                       borderRadius: const BorderRadius.all(Radius.circular(20)),
                     ),
                     child: Center(
@@ -139,7 +133,9 @@ class LibraryPage extends HookConsumerWidget {
                     ),
                     child: Text(
                       'library_page_new_album',
-                      style: context.textTheme.labelLarge,
+                      style: context.textTheme.labelLarge?.copyWith(
+                        color: context.colorScheme.onSurface,
+                      ),
                     ).tr(),
                   ),
                 ],
@@ -156,26 +152,25 @@ class LibraryPage extends HookConsumerWidget {
       Function() onClick,
     ) {
       return Expanded(
-        child: OutlinedButton.icon(
+        child: FilledButton.icon(
           onPressed: onClick,
           label: Padding(
             padding: const EdgeInsets.only(left: 8.0),
             child: Text(
               label,
               style: TextStyle(
-                color: context.isDarkTheme
-                    ? Colors.white
-                    : Colors.black.withAlpha(200),
+                color: context.colorScheme.onSurface,
               ),
             ),
           ),
-          style: OutlinedButton.styleFrom(
-            padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
-            backgroundColor: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
-            side: BorderSide(
-              color: isDarkTheme ? Colors.grey[800]! : Colors.grey[300]!,
-            ),
+          style: FilledButton.styleFrom(
+            elevation: 0,
+            padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
+            backgroundColor: context.colorScheme.surfaceContainer,
             alignment: Alignment.centerLeft,
+            shape: const RoundedRectangleBorder(
+              borderRadius: BorderRadius.all(Radius.circular(20)),
+            ),
           ),
           icon: Icon(
             icon,
@@ -247,6 +242,7 @@ class LibraryPage extends HookConsumerWidget {
                   Text(
                     'library_page_albums',
                     style: context.textTheme.bodyLarge?.copyWith(
+                      color: context.colorScheme.onSurface,
                       fontWeight: FontWeight.w500,
                     ),
                   ).tr(),
diff --git a/mobile/lib/pages/login/login.page.dart b/mobile/lib/pages/login/login.page.dart
index 212145ed5a..b305b5fc53 100644
--- a/mobile/lib/pages/login/login.page.dart
+++ b/mobile/lib/pages/login/login.page.dart
@@ -3,6 +3,7 @@ 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/widgets/forms/login/login_form.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:package_info_plus/package_info_plus.dart';
@@ -39,8 +40,8 @@ class LoginPage extends HookConsumerWidget {
               children: [
                 Text(
                   'v${appVersion.value}',
-                  style: const TextStyle(
-                    color: Colors.grey,
+                  style: TextStyle(
+                    color: context.colorScheme.onSurfaceSecondary,
                     fontWeight: FontWeight.bold,
                     fontFamily: "Inconsolata",
                   ),
diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart
index 2c578925c1..173115185b 100644
--- a/mobile/lib/pages/search/search.page.dart
+++ b/mobile/lib/pages/search/search.page.dart
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/models/search/search_curated_content.model.dart';
 import 'package:immich_mobile/models/search/search_filter.model.dart';
 import 'package:immich_mobile/providers/search/people.provider.dart';
@@ -38,7 +39,7 @@ class SearchPage extends HookConsumerWidget {
       fontSize: 15.0,
     );
 
-    Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black;
+    Color categoryIconColor = context.colorScheme.onSurface;
 
     showNameEditModel(
       String personId,
@@ -128,13 +129,9 @@ class SearchPage extends HookConsumerWidget {
         },
         child: Card(
           elevation: 0,
+          color: context.colorScheme.surfaceContainerHigh,
           shape: RoundedRectangleBorder(
-            borderRadius: BorderRadius.circular(20),
-            side: BorderSide(
-              color: context.isDarkTheme
-                  ? Colors.grey[800]!
-                  : const Color.fromARGB(255, 225, 225, 225),
-            ),
+            borderRadius: BorderRadius.circular(50),
           ),
           margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
           child: Padding(
@@ -144,13 +141,15 @@ class SearchPage extends HookConsumerWidget {
             ),
             child: Row(
               children: [
-                Icon(Icons.search, color: context.primaryColor),
+                Icon(
+                  Icons.search,
+                  color: context.colorScheme.onSurfaceSecondary,
+                ),
                 const SizedBox(width: 16.0),
                 Text(
                   "search_bar_hint",
                   style: context.textTheme.bodyLarge?.copyWith(
-                    color:
-                        context.isDarkTheme ? Colors.white70 : Colors.black54,
+                    color: context.colorScheme.onSurfaceSecondary,
                     fontWeight: FontWeight.w400,
                   ),
                 ).tr(),
diff --git a/mobile/lib/pages/search/search_input.page.dart b/mobile/lib/pages/search/search_input.page.dart
index 1f90f2929c..acabc75aa4 100644
--- a/mobile/lib/pages/search/search_input.page.dart
+++ b/mobile/lib/pages/search/search_input.page.dart
@@ -7,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/entities/asset.entity.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/models/search/search_filter.model.dart';
 import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
 import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
@@ -509,7 +510,7 @@ class SearchInputPage extends HookConsumerWidget {
                 ? 'contextual_search'.tr()
                 : 'filename_search'.tr(),
             hintStyle: context.textTheme.bodyLarge?.copyWith(
-              color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
+              color: context.themeData.colorScheme.onSurfaceSecondary,
               fontWeight: FontWeight.w500,
             ),
             enabledBorder: const UnderlineInputBorder(
diff --git a/mobile/lib/pages/sharing/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/sharing/shared_link/shared_link_edit.page.dart
index 6223e110e1..5ed85932f8 100644
--- a/mobile/lib/pages/sharing/shared_link/shared_link_edit.page.dart
+++ b/mobile/lib/pages/sharing/shared_link/shared_link_edit.page.dart
@@ -30,6 +30,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     const padding = 20.0;
     final themeData = context.themeData;
+    final colorScheme = context.colorScheme;
     final descriptionController =
         useTextEditingController(text: existingLink?.description ?? "");
     final descriptionFocusNode = useFocusNode();
@@ -58,7 +59,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
               Text(
                 existingLink!.title,
                 style: TextStyle(
-                  color: themeData.primaryColor,
+                  color: colorScheme.primary,
                   fontWeight: FontWeight.bold,
                 ),
               ),
@@ -81,7 +82,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
                 child: Text(
                   existingLink!.description ?? "--",
                   style: TextStyle(
-                    color: themeData.primaryColor,
+                    color: colorScheme.primary,
                     fontWeight: FontWeight.bold,
                   ),
                   overflow: TextOverflow.ellipsis,
@@ -109,7 +110,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
           labelText: 'shared_link_edit_description'.tr(),
           labelStyle: TextStyle(
             fontWeight: FontWeight.bold,
-            color: themeData.primaryColor,
+            color: colorScheme.primary,
           ),
           floatingLabelBehavior: FloatingLabelBehavior.always,
           border: const OutlineInputBorder(),
@@ -135,7 +136,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
           labelText: 'shared_link_edit_password'.tr(),
           labelStyle: TextStyle(
             fontWeight: FontWeight.bold,
-            color: themeData.primaryColor,
+            color: colorScheme.primary,
           ),
           floatingLabelBehavior: FloatingLabelBehavior.always,
           border: const OutlineInputBorder(),
@@ -157,7 +158,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
         onChanged: newShareLink.value.isEmpty
             ? (value) => showMetadata.value = value
             : null,
-        activeColor: themeData.primaryColor,
+        activeColor: colorScheme.primary,
         dense: true,
         title: Text(
           "shared_link_edit_show_meta",
@@ -173,7 +174,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
         onChanged: newShareLink.value.isEmpty
             ? (value) => allowDownload.value = value
             : null,
-        activeColor: themeData.primaryColor,
+        activeColor: colorScheme.primary,
         dense: true,
         title: Text(
           "shared_link_edit_allow_download",
@@ -189,7 +190,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
         onChanged: newShareLink.value.isEmpty
             ? (value) => allowUpload.value = value
             : null,
-        activeColor: themeData.primaryColor,
+        activeColor: colorScheme.primary,
         dense: true,
         title: Text(
           "shared_link_edit_allow_upload",
@@ -205,7 +206,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
         onChanged: newShareLink.value.isEmpty
             ? (value) => editExpiry.value = value
             : null,
-        activeColor: themeData.primaryColor,
+        activeColor: colorScheme.primary,
         dense: true,
         title: Text(
           "shared_link_edit_change_expiry",
@@ -221,7 +222,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
           "shared_link_edit_expire_after",
           style: TextStyle(
             fontWeight: FontWeight.bold,
-            color: themeData.primaryColor,
+            color: colorScheme.primary,
           ),
         ).tr(),
         enableSearch: false,
diff --git a/mobile/lib/pages/sharing/sharing.page.dart b/mobile/lib/pages/sharing/sharing.page.dart
index 45148945ed..98d4cfafe9 100644
--- a/mobile/lib/pages/sharing/sharing.page.dart
+++ b/mobile/lib/pages/sharing/sharing.page.dart
@@ -4,6 +4,7 @@ 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
 import 'package:immich_mobile/providers/album/shared_album.provider.dart';
 import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
@@ -83,20 +84,24 @@ class SharingPage extends HookConsumerWidget {
                 maxLines: 1,
                 overflow: TextOverflow.ellipsis,
                 style: context.textTheme.bodyMedium?.copyWith(
-                  color: context.primaryColor,
+                  color: context.colorScheme.onSurface,
                   fontWeight: FontWeight.w500,
                 ),
               ),
               subtitle: isOwner
                   ? Text(
                       'album_thumbnail_owned'.tr(),
-                      style: context.textTheme.bodyMedium,
+                      style: context.textTheme.bodyMedium?.copyWith(
+                        color: context.colorScheme.onSurfaceSecondary,
+                      ),
                     )
                   : album.ownerName != null
                       ? Text(
                           'album_thumbnail_shared_by'
                               .tr(args: [album.ownerName!]),
-                          style: context.textTheme.bodyMedium,
+                          style: context.textTheme.bodyMedium?.copyWith(
+                            color: context.colorScheme.onSurfaceSecondary,
+                          ),
                         )
                       : null,
               onTap: () => context
@@ -166,11 +171,13 @@ class SharingPage extends HookConsumerWidget {
           padding: const EdgeInsets.all(8.0),
           child: Card(
             elevation: 0,
-            shape: const RoundedRectangleBorder(
-              borderRadius: BorderRadius.all(Radius.circular(20)),
+            shape: RoundedRectangleBorder(
+              borderRadius: const BorderRadius.all(Radius.circular(20)),
               side: BorderSide(
-                color: Colors.grey,
-                width: 0.5,
+                color: context.isDarkTheme
+                    ? const Color(0xFF383838)
+                    : Colors.black12,
+                width: 1,
               ),
             ),
             child: Padding(
diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart
index fd6c2d89a7..bd25403215 100644
--- a/mobile/lib/services/app_settings.service.dart
+++ b/mobile/lib/services/app_settings.service.dart
@@ -1,3 +1,4 @@
+import 'package:immich_mobile/constants/immich_colors.dart';
 import 'package:immich_mobile/entities/store.entity.dart';
 
 enum AppSettingsEnum<T> {
@@ -8,6 +9,21 @@ enum AppSettingsEnum<T> {
     "themeMode",
     "system",
   ), // "light","dark","system"
+  primaryColor<String>(
+    StoreKey.primaryColor,
+    "primaryColor",
+    defaultColorPresetName,
+  ),
+  dynamicTheme<bool>(
+    StoreKey.dynamicTheme,
+    "dynamicTheme",
+    false,
+  ),
+  colorfulInterface<bool>(
+    StoreKey.colorfulInterface,
+    "colorfulInterface",
+    true,
+  ),
   tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
   dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false),
   groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),
diff --git a/mobile/lib/utils/immich_app_theme.dart b/mobile/lib/utils/immich_app_theme.dart
index 32a26439d5..d61eba73b2 100644
--- a/mobile/lib/utils/immich_app_theme.dart
+++ b/mobile/lib/utils/immich_app_theme.dart
@@ -1,10 +1,22 @@
+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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/app_settings.provider.dart';
 import 'package:immich_mobile/services/app_settings.service.dart';
 
-final immichThemeProvider = StateProvider<ThemeMode>((ref) {
+class ImmichTheme {
+  ColorScheme light;
+  ColorScheme dark;
+
+  ImmichTheme({required this.light, required this.dark});
+}
+
+ImmichTheme? _immichDynamicTheme;
+bool get isDynamicThemeAvailable => _immichDynamicTheme != null;
+
+final immichThemeModeProvider = StateProvider<ThemeMode>((ref) {
   var themeMode = ref
       .watch(appSettingsServiceProvider)
       .getSetting(AppSettingsEnum.themeMode);
@@ -20,266 +32,241 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
   }
 });
 
-final ThemeData base = ThemeData(
-  chipTheme: const ChipThemeData(
-    side: BorderSide.none,
-  ),
-  sliderTheme: const SliderThemeData(
-    thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
-    trackHeight: 2.0,
-  ),
-);
+final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) {
+  var appSettingsProvider = ref.watch(appSettingsServiceProvider);
+  var primaryColorName =
+      appSettingsProvider.getSetting(AppSettingsEnum.primaryColor);
 
-final ThemeData immichLightTheme = ThemeData(
-  useMaterial3: true,
-  brightness: Brightness.light,
-  colorScheme: ColorScheme.fromSeed(
-    seedColor: Colors.indigo,
-  ),
-  primarySwatch: Colors.indigo,
-  primaryColor: Colors.indigo,
-  hintColor: Colors.indigo,
-  focusColor: Colors.indigo,
-  splashColor: Colors.indigo.withOpacity(0.15),
-  fontFamily: 'Overpass',
-  scaffoldBackgroundColor: immichBackgroundColor,
-  snackBarTheme: const SnackBarThemeData(
-    contentTextStyle: TextStyle(
-      fontFamily: 'Overpass',
-      color: Colors.indigo,
-      fontWeight: FontWeight.bold,
-    ),
-    backgroundColor: Colors.white,
-  ),
-  appBarTheme: const AppBarTheme(
-    titleTextStyle: TextStyle(
-      fontFamily: 'Overpass',
-      color: Colors.indigo,
-      fontWeight: FontWeight.bold,
-      fontSize: 18,
-    ),
-    backgroundColor: immichBackgroundColor,
-    foregroundColor: Colors.indigo,
-    elevation: 0,
-    scrolledUnderElevation: 0,
-    centerTitle: true,
-  ),
-  bottomNavigationBarTheme: const BottomNavigationBarThemeData(
-    type: BottomNavigationBarType.fixed,
-    backgroundColor: immichBackgroundColor,
-    selectedItemColor: Colors.indigo,
-  ),
-  cardTheme: const CardTheme(
-    surfaceTintColor: Colors.transparent,
-  ),
-  drawerTheme: const DrawerThemeData(
-    backgroundColor: immichBackgroundColor,
-  ),
-  textTheme: const TextTheme(
-    displayLarge: TextStyle(
-      fontSize: 26,
-      fontWeight: FontWeight.bold,
-      color: Colors.indigo,
-    ),
-    displayMedium: TextStyle(
-      fontSize: 14,
-      fontWeight: FontWeight.bold,
-      color: Colors.black87,
-    ),
-    displaySmall: TextStyle(
-      fontSize: 12,
-      fontWeight: FontWeight.bold,
-      color: Colors.indigo,
-    ),
-    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: Colors.indigo,
-      foregroundColor: Colors.white,
-    ),
-  ),
-  chipTheme: base.chipTheme,
-  sliderTheme: base.sliderTheme,
-  popupMenuTheme: const PopupMenuThemeData(
-    shape: RoundedRectangleBorder(
-      borderRadius: BorderRadius.all(Radius.circular(10)),
-    ),
-    surfaceTintColor: Colors.transparent,
-    color: Colors.white,
-  ),
-  navigationBarTheme: NavigationBarThemeData(
-    indicatorColor: Colors.indigo.withOpacity(0.15),
-    iconTheme: WidgetStatePropertyAll(
-      IconThemeData(color: Colors.grey[700]),
-    ),
-    backgroundColor: immichBackgroundColor,
-    surfaceTintColor: Colors.transparent,
-    labelTextStyle: WidgetStatePropertyAll(
-      TextStyle(
-        fontSize: 13,
-        fontWeight: FontWeight.w500,
-        color: Colors.grey[800],
-      ),
-    ),
-  ),
-  dialogTheme: const DialogTheme(
-    surfaceTintColor: Colors.transparent,
-  ),
-  inputDecorationTheme: const InputDecorationTheme(
-    focusedBorder: OutlineInputBorder(
-      borderSide: BorderSide(
-        color: Colors.indigo,
-      ),
-    ),
-    labelStyle: TextStyle(
-      color: Colors.indigo,
-    ),
-    hintStyle: TextStyle(
-      fontSize: 14.0,
-      fontWeight: FontWeight.normal,
-    ),
-  ),
-  textSelectionTheme: const TextSelectionThemeData(
-    cursorColor: Colors.indigo,
-  ),
-);
+  debugPrint("Current theme preset $primaryColorName");
 
-final ThemeData immichDarkTheme = ThemeData(
-  useMaterial3: true,
-  brightness: Brightness.dark,
-  primarySwatch: Colors.indigo,
-  primaryColor: immichDarkThemePrimaryColor,
-  colorScheme: ColorScheme.fromSeed(
-    seedColor: immichDarkThemePrimaryColor,
-    brightness: Brightness.dark,
-  ),
-  scaffoldBackgroundColor: immichDarkBackgroundColor,
-  hintColor: Colors.grey[600],
-  fontFamily: 'Overpass',
-  snackBarTheme: SnackBarThemeData(
-    contentTextStyle: const TextStyle(
-      fontFamily: 'Overpass',
-      color: immichDarkThemePrimaryColor,
-      fontWeight: FontWeight.bold,
+  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,
+        ),
+        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),
     ),
-    backgroundColor: Colors.grey[900],
-  ),
-  textButtonTheme: TextButtonThemeData(
-    style: TextButton.styleFrom(
-      foregroundColor: immichDarkThemePrimaryColor,
+    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),
     ),
-  ),
-  appBarTheme: const AppBarTheme(
-    titleTextStyle: TextStyle(
-      fontFamily: 'Overpass',
-      color: immichDarkThemePrimaryColor,
-      fontWeight: FontWeight.bold,
-      fontSize: 18,
+  );
+}
+
+ThemeData getThemeData({required ColorScheme colorScheme}) {
+  var isDark = colorScheme.brightness == Brightness.dark;
+  var primaryColor = colorScheme.primary;
+
+  return ThemeData(
+    useMaterial3: true,
+    brightness: isDark ? Brightness.dark : Brightness.light,
+    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,
     ),
-    backgroundColor: Color.fromARGB(255, 32, 33, 35),
-    foregroundColor: immichDarkThemePrimaryColor,
-    elevation: 0,
-    scrolledUnderElevation: 0,
-    centerTitle: true,
-  ),
-  bottomNavigationBarTheme: const BottomNavigationBarThemeData(
-    type: BottomNavigationBarType.fixed,
-    backgroundColor: Color.fromARGB(255, 35, 36, 37),
-    selectedItemColor: immichDarkThemePrimaryColor,
-  ),
-  drawerTheme: DrawerThemeData(
-    backgroundColor: immichDarkBackgroundColor,
-    scrimColor: Colors.white.withOpacity(0.1),
-  ),
-  textTheme: const TextTheme(
-    displayLarge: TextStyle(
-      fontSize: 26,
-      fontWeight: FontWeight.bold,
-      color: Color.fromARGB(255, 255, 255, 255),
+    fontFamily: 'Overpass',
+    snackBarTheme: SnackBarThemeData(
+      contentTextStyle: TextStyle(
+        fontFamily: 'Overpass',
+        color: primaryColor,
+        fontWeight: FontWeight.bold,
+      ),
+      backgroundColor: colorScheme.surfaceContainerHighest,
     ),
-    displayMedium: TextStyle(
-      fontSize: 14,
-      fontWeight: FontWeight.bold,
-      color: Color.fromARGB(255, 255, 255, 255),
+    appBarTheme: AppBarTheme(
+      titleTextStyle: TextStyle(
+        color: primaryColor,
+        fontFamily: 'Overpass',
+        fontWeight: FontWeight.bold,
+        fontSize: 18,
+      ),
+      backgroundColor:
+          isDark ? colorScheme.surfaceContainer : colorScheme.surface,
+      foregroundColor: primaryColor,
+      elevation: 0,
+      scrolledUnderElevation: 0,
+      centerTitle: true,
     ),
-    displaySmall: TextStyle(
-      fontSize: 12,
-      fontWeight: FontWeight.bold,
-      color: immichDarkThemePrimaryColor,
-    ),
-    titleSmall: TextStyle(
-      fontSize: 16.0,
-      fontWeight: FontWeight.bold,
-    ),
-    titleMedium: TextStyle(
-      fontSize: 18.0,
-      fontWeight: FontWeight.bold,
-    ),
-    titleLarge: TextStyle(
-      fontSize: 26.0,
-      fontWeight: FontWeight.bold,
-    ),
-  ),
-  cardColor: Colors.grey[900],
-  elevatedButtonTheme: ElevatedButtonThemeData(
-    style: ElevatedButton.styleFrom(
-      foregroundColor: Colors.black87,
-      backgroundColor: immichDarkThemePrimaryColor,
-    ),
-  ),
-  chipTheme: base.chipTheme,
-  sliderTheme: base.sliderTheme,
-  popupMenuTheme: const PopupMenuThemeData(
-    shape: RoundedRectangleBorder(
-      borderRadius: BorderRadius.all(Radius.circular(10)),
-    ),
-    surfaceTintColor: Colors.transparent,
-  ),
-  navigationBarTheme: NavigationBarThemeData(
-    indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
-    iconTheme: WidgetStatePropertyAll(
-      IconThemeData(color: Colors.grey[500]),
-    ),
-    backgroundColor: Colors.grey[900],
-    surfaceTintColor: Colors.transparent,
-    labelTextStyle: WidgetStatePropertyAll(
-      TextStyle(
-        fontSize: 13,
-        fontWeight: FontWeight.w500,
-        color: Colors.grey[300],
+    textTheme: TextTheme(
+      displayLarge: TextStyle(
+        fontSize: 26,
+        fontWeight: FontWeight.bold,
+        color: isDark ? Colors.white : primaryColor,
+      ),
+      displayMedium: TextStyle(
+        fontSize: 14,
+        fontWeight: FontWeight.bold,
+        color: isDark ? Colors.white : Colors.black87,
+      ),
+      displaySmall: TextStyle(
+        fontSize: 12,
+        fontWeight: FontWeight.bold,
+        color: primaryColor,
+      ),
+      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,
       ),
     ),
-  ),
-  dialogTheme: const DialogTheme(
-    surfaceTintColor: Colors.transparent,
-  ),
-  inputDecorationTheme: const InputDecorationTheme(
-    focusedBorder: OutlineInputBorder(
-      borderSide: BorderSide(
-        color: immichDarkThemePrimaryColor,
+    elevatedButtonTheme: ElevatedButtonThemeData(
+      style: ElevatedButton.styleFrom(
+        backgroundColor: primaryColor,
+        foregroundColor: isDark ? Colors.black87 : Colors.white,
       ),
     ),
-    labelStyle: TextStyle(
-      color: immichDarkThemePrimaryColor,
+    chipTheme: const ChipThemeData(
+      side: BorderSide.none,
     ),
-    hintStyle: TextStyle(
-      fontSize: 14.0,
-      fontWeight: FontWeight.normal,
+    sliderTheme: const SliderThemeData(
+      thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
+      trackHeight: 2.0,
     ),
-  ),
-  textSelectionTheme: const TextSelectionThemeData(
-    cursorColor: immichDarkThemePrimaryColor,
-  ),
-);
+    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: 13,
+          fontWeight: FontWeight.w500,
+        ),
+      ),
+    ),
+    inputDecorationTheme: InputDecorationTheme(
+      focusedBorder: OutlineInputBorder(
+        borderSide: BorderSide(
+          color: primaryColor,
+        ),
+      ),
+      enabledBorder: OutlineInputBorder(
+        borderSide: BorderSide(
+          color: colorScheme.outlineVariant,
+        ),
+      ),
+      labelStyle: TextStyle(
+        color: primaryColor,
+      ),
+      hintStyle: const TextStyle(
+        fontSize: 14.0,
+        fontWeight: FontWeight.normal,
+      ),
+    ),
+    textSelectionTheme: TextSelectionThemeData(
+      cursorColor: primaryColor,
+    ),
+  );
+}
diff --git a/mobile/lib/widgets/album/album_action_outlined_button.dart b/mobile/lib/widgets/album/album_action_filled_button.dart
similarity index 70%
rename from mobile/lib/widgets/album/album_action_outlined_button.dart
rename to mobile/lib/widgets/album/album_action_filled_button.dart
index 02676ae6e2..6a466aa4f1 100644
--- a/mobile/lib/widgets/album/album_action_outlined_button.dart
+++ b/mobile/lib/widgets/album/album_action_filled_button.dart
@@ -1,12 +1,12 @@
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 
-class AlbumActionOutlinedButton extends StatelessWidget {
+class AlbumActionFilledButton extends StatelessWidget {
   final VoidCallback? onPressed;
   final String labelText;
   final IconData iconData;
 
-  const AlbumActionOutlinedButton({
+  const AlbumActionFilledButton({
     super.key,
     this.onPressed,
     required this.labelText,
@@ -17,18 +17,13 @@ class AlbumActionOutlinedButton extends StatelessWidget {
   Widget build(BuildContext context) {
     return Padding(
       padding: const EdgeInsets.only(right: 16.0),
-      child: OutlinedButton.icon(
-        style: OutlinedButton.styleFrom(
+      child: FilledButton.icon(
+        style: FilledButton.styleFrom(
           padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
           shape: RoundedRectangleBorder(
             borderRadius: BorderRadius.circular(25),
           ),
-          side: BorderSide(
-            width: 1,
-            color: context.isDarkTheme
-                ? const Color.fromARGB(255, 63, 63, 63)
-                : const Color.fromARGB(255, 206, 206, 206),
-          ),
+          backgroundColor: context.colorScheme.surfaceContainerHigh,
         ),
         icon: Icon(
           iconData,
diff --git a/mobile/lib/widgets/album/album_thumbnail_card.dart b/mobile/lib/widgets/album/album_thumbnail_card.dart
index 737e8b383f..42fa55cdd4 100644
--- a/mobile/lib/widgets/album/album_thumbnail_card.dart
+++ b/mobile/lib/widgets/album/album_thumbnail_card.dart
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 import 'package:immich_mobile/entities/album.entity.dart';
 import 'package:immich_mobile/entities/store.entity.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
 
 class AlbumThumbnailCard extends StatelessWidget {
@@ -23,8 +24,6 @@ class AlbumThumbnailCard extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    var isDarkTheme = context.isDarkTheme;
-
     return LayoutBuilder(
       builder: (context, constraints) {
         var cardSize = constraints.maxWidth;
@@ -34,12 +33,13 @@ class AlbumThumbnailCard extends StatelessWidget {
             height: cardSize,
             width: cardSize,
             decoration: BoxDecoration(
-              color: isDarkTheme ? Colors.grey[800] : Colors.grey[200],
+              color: context.colorScheme.surfaceContainerHigh,
             ),
             child: Center(
               child: Icon(
                 Icons.no_photography,
                 size: cardSize * .15,
+                color: context.colorScheme.primary,
               ),
             ),
           );
@@ -65,6 +65,9 @@ class AlbumThumbnailCard extends StatelessWidget {
           return RichText(
             overflow: TextOverflow.fade,
             text: TextSpan(
+              style: context.textTheme.bodyMedium?.copyWith(
+                color: context.colorScheme.onSurfaceSecondary,
+              ),
               children: [
                 TextSpan(
                   text: album.assetCount == 1
@@ -72,14 +75,9 @@ class AlbumThumbnailCard extends StatelessWidget {
                           .tr(args: ['${album.assetCount}'])
                       : 'album_thumbnail_card_items'
                           .tr(args: ['${album.assetCount}']),
-                  style: context.textTheme.bodyMedium,
                 ),
                 if (owner != null) const TextSpan(text: ' · '),
-                if (owner != null)
-                  TextSpan(
-                    text: owner,
-                    style: context.textTheme.bodyMedium,
-                  ),
+                if (owner != null) TextSpan(text: owner),
               ],
             ),
           );
@@ -112,7 +110,7 @@ class AlbumThumbnailCard extends StatelessWidget {
                           album.name,
                           overflow: TextOverflow.ellipsis,
                           style: context.textTheme.bodyMedium?.copyWith(
-                            color: context.primaryColor,
+                            color: context.colorScheme.onSurface,
                             fontWeight: FontWeight.w500,
                           ),
                         ),
diff --git a/mobile/lib/widgets/album/album_title_text_field.dart b/mobile/lib/widgets/album/album_title_text_field.dart
index 8715c0c038..d005a96417 100644
--- a/mobile/lib/widgets/album/album_title_text_field.dart
+++ b/mobile/lib/widgets/album/album_title_text_field.dart
@@ -20,8 +20,6 @@ class AlbumTitleTextField extends ConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    final isDarkTheme = context.isDarkTheme;
-
     return TextField(
       onChanged: (v) {
         if (v.isEmpty) {
@@ -35,7 +33,7 @@ class AlbumTitleTextField extends ConsumerWidget {
       focusNode: albumTitleTextFieldFocusNode,
       style: TextStyle(
         fontSize: 28,
-        color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
+        color: context.colorScheme.onSurface,
         fontWeight: FontWeight.bold,
       ),
       controller: albumTitleController,
@@ -61,24 +59,18 @@ class AlbumTitleTextField extends ConsumerWidget {
                 splashRadius: 10,
               )
             : null,
-        enabledBorder: OutlineInputBorder(
-          borderSide: const BorderSide(color: Colors.transparent),
-          borderRadius: BorderRadius.circular(10),
+        enabledBorder: const OutlineInputBorder(
+          borderSide: BorderSide(color: Colors.transparent),
         ),
-        focusedBorder: OutlineInputBorder(
-          borderSide: const BorderSide(color: Colors.transparent),
-          borderRadius: BorderRadius.circular(10),
+        focusedBorder: const OutlineInputBorder(
+          borderSide: BorderSide(color: Colors.transparent),
         ),
         hintText: 'share_add_title'.tr(),
-        hintStyle: TextStyle(
+        hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
           fontSize: 28,
-          color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
-          fontWeight: FontWeight.bold,
         ),
         focusColor: Colors.grey[300],
-        fillColor: isDarkTheme
-            ? const Color.fromARGB(255, 32, 33, 35)
-            : Colors.grey[200],
+        fillColor: context.scaffoldBackgroundColor,
         filled: isAlbumTitleTextFieldFocus.value,
       ),
     );
diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart
index 6fb58f8082..1067d7241e 100644
--- a/mobile/lib/widgets/album/album_viewer_appbar.dart
+++ b/mobile/lib/widgets/album/album_viewer_appbar.dart
@@ -95,7 +95,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
                   'action_common_confirm',
                   style: TextStyle(
                     fontWeight: FontWeight.bold,
-                    color: !context.isDarkTheme ? Colors.red : Colors.red[300],
+                    color: context.colorScheme.error,
                   ),
                 ).tr(),
               ),
diff --git a/mobile/lib/widgets/album/album_viewer_editable_title.dart b/mobile/lib/widgets/album/album_viewer_editable_title.dart
index 788c61d8a4..59e09aa050 100644
--- a/mobile/lib/widgets/album/album_viewer_editable_title.dart
+++ b/mobile/lib/widgets/album/album_viewer_editable_title.dart
@@ -73,24 +73,18 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
                   splashRadius: 10,
                 )
               : null,
-          enabledBorder: OutlineInputBorder(
-            borderSide: const BorderSide(color: Colors.transparent),
-            borderRadius: BorderRadius.circular(10),
+          enabledBorder: const OutlineInputBorder(
+            borderSide: BorderSide(color: Colors.transparent),
           ),
-          focusedBorder: OutlineInputBorder(
-            borderSide: const BorderSide(color: Colors.transparent),
-            borderRadius: BorderRadius.circular(10),
+          focusedBorder: const OutlineInputBorder(
+            borderSide: BorderSide(color: Colors.transparent),
           ),
           focusColor: Colors.grey[300],
-          fillColor: context.isDarkTheme
-              ? const Color.fromARGB(255, 32, 33, 35)
-              : Colors.grey[200],
+          fillColor: context.scaffoldBackgroundColor,
           filled: titleFocusNode.hasFocus,
           hintText: 'share_add_title'.tr(),
-          hintStyle: TextStyle(
+          hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
             fontSize: 28,
-            color: context.isDarkTheme ? Colors.grey[300] : Colors.grey[700],
-            fontWeight: FontWeight.bold,
           ),
         ),
       ),
diff --git a/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart b/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart
index 060e0bc04e..e6d769a3d7 100644
--- a/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart
+++ b/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart
@@ -281,7 +281,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
         ScrollController scrollController,
       ) {
         return Card(
-          color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
+          color: context.colorScheme.surfaceContainerLow,
           surfaceTintColor: Colors.transparent,
           elevation: 18.0,
           shape: const RoundedRectangleBorder(
diff --git a/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart b/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart
index 9d26745b16..50b38c2a4a 100644
--- a/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart
+++ b/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart
@@ -22,12 +22,15 @@ class DisableMultiSelectButton extends ConsumerWidget {
           padding: const EdgeInsets.symmetric(horizontal: 4.0),
           child: ElevatedButton.icon(
             onPressed: () => onPressed(),
-            icon: const Icon(Icons.close_rounded),
+            icon: Icon(
+              Icons.close_rounded,
+              color: context.colorScheme.onPrimary,
+            ),
             label: Text(
               '$selectedItemCount',
               style: context.textTheme.titleMedium?.copyWith(
                 height: 2.5,
-                color: context.isDarkTheme ? Colors.black : Colors.white,
+                color: context.colorScheme.onPrimary,
               ),
             ),
           ),
diff --git a/mobile/lib/widgets/asset_grid/group_divider_title.dart b/mobile/lib/widgets/asset_grid/group_divider_title.dart
index 4c1f468343..3a411c09db 100644
--- a/mobile/lib/widgets/asset_grid/group_divider_title.dart
+++ b/mobile/lib/widgets/asset_grid/group_divider_title.dart
@@ -2,6 +2,7 @@ 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
 import 'package:immich_mobile/providers/app_settings.provider.dart';
 import 'package:immich_mobile/services/app_settings.service.dart';
@@ -74,9 +75,9 @@ class GroupDividerTitle extends HookConsumerWidget {
                     Icons.check_circle_rounded,
                     color: context.primaryColor,
                   )
-                : const Icon(
+                : Icon(
                     Icons.check_circle_outline_rounded,
-                    color: Colors.grey,
+                    color: context.colorScheme.onSurfaceSecondary,
                   ),
           ),
         ],
diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart
index 906d0e5969..ea65031a0c 100644
--- a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart
+++ b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart
@@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 import 'package:immich_mobile/extensions/collection_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart';
 import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart';
 import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
@@ -266,7 +267,9 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
             scrollStateListener: dragScrolling,
             itemPositionsListener: _itemPositionsListener,
             controller: _itemScrollController,
-            backgroundColor: context.themeData.hintColor,
+            backgroundColor: context.isDarkTheme
+                ? context.colorScheme.primary.darken(amount: .5)
+                : context.colorScheme.primary,
             labelTextBuilder: _labelBuilder,
             padding: appBarOffset()
                 ? const EdgeInsets.only(top: 60)
diff --git a/mobile/lib/widgets/asset_grid/thumbnail_image.dart b/mobile/lib/widgets/asset_grid/thumbnail_image.dart
index d9c9aa0566..2480f44278 100644
--- a/mobile/lib/widgets/asset_grid/thumbnail_image.dart
+++ b/mobile/lib/widgets/asset_grid/thumbnail_image.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 import 'package:immich_mobile/entities/asset.entity.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
 import 'package:immich_mobile/utils/storage_indicator.dart';
 import 'package:isar/isar.dart';
@@ -42,8 +43,8 @@ class ThumbnailImage extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final assetContainerColor = context.isDarkTheme
-        ? Colors.blueGrey
-        : context.themeData.primaryColorLight;
+        ? context.primaryColor.darken(amount: 0.6)
+        : context.primaryColor.lighten(amount: 0.8);
     // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
     final isFromDto = asset.id == Isar.autoIncrement;
 
@@ -192,8 +193,8 @@ class ThumbnailImage extends ConsumerWidget {
             bottom: 5,
             child: Icon(
               storageIcon(asset),
-              color: Colors.white,
-              size: 18,
+              color: Colors.white.withOpacity(.8),
+              size: 16,
             ),
           ),
         if (asset.isFavorite)
diff --git a/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart b/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart
index d762704835..5b12426a50 100644
--- a/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart
+++ b/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart
@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 
 class ThumbnailPlaceholder extends StatelessWidget {
   final EdgeInsets margin;
@@ -13,25 +14,20 @@ class ThumbnailPlaceholder extends StatelessWidget {
     this.height = 250,
   });
 
-  static const _brightColors = [
-    Color(0xFFF1F3F4),
-    Color(0xFFB4B6B8),
-  ];
-
-  static const _darkColors = [
-    Color(0xFF3B3F42),
-    Color(0xFF2B2F32),
-  ];
-
   @override
   Widget build(BuildContext context) {
+    var gradientColors = [
+      context.colorScheme.surfaceContainer,
+      context.colorScheme.surfaceContainer.darken(amount: .1),
+    ];
+
     return Container(
       width: width,
       height: height,
       margin: margin,
       decoration: BoxDecoration(
         gradient: LinearGradient(
-          colors: context.isDarkTheme ? _darkColors : _brightColors,
+          colors: gradientColors,
           begin: Alignment.topCenter,
           end: Alignment.bottomCenter,
         ),
diff --git a/mobile/lib/widgets/asset_viewer/description_input.dart b/mobile/lib/widgets/asset_viewer/description_input.dart
index 7422e43335..1a91d1614b 100644
--- a/mobile/lib/widgets/asset_viewer/description_input.dart
+++ b/mobile/lib/widgets/asset_viewer/description_input.dart
@@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/entities/exif_info.entity.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 import 'package:immich_mobile/entities/asset.entity.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/user.provider.dart';
 import 'package:immich_mobile/services/asset_description.service.dart';
 import 'package:immich_mobile/widgets/common/immich_toast.dart';
@@ -23,7 +24,6 @@ class DescriptionInput extends HookConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    final textColor = context.isDarkTheme ? Colors.white : Colors.black;
     final controller = useTextEditingController();
     final focusNode = useFocusNode();
     final isFocus = useState(false);
@@ -71,7 +71,7 @@ class DescriptionInput extends HookConsumerWidget {
         },
         icon: Icon(
           Icons.cancel_rounded,
-          color: Colors.grey[500],
+          color: context.colorScheme.onSurfaceSecondary,
         ),
         splashRadius: 10,
       );
@@ -100,9 +100,6 @@ class DescriptionInput extends HookConsumerWidget {
       decoration: InputDecoration(
         hintText: 'description_input_hint_text'.tr(),
         border: InputBorder.none,
-        hintStyle: context.textTheme.labelLarge?.copyWith(
-          color: textColor.withOpacity(0.5),
-        ),
         suffixIcon: suffixIcon,
       ),
     );
diff --git a/mobile/lib/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart b/mobile/lib/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart
index a0505e3d48..ae32c133c3 100644
--- a/mobile/lib/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart
+++ b/mobile/lib/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart
@@ -22,7 +22,7 @@ class ExifBottomSheet extends HookConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final assetWithExif = ref.watch(assetDetailProvider(asset));
-    var textColor = context.isDarkTheme ? Colors.white : Colors.black;
+    var textColor = context.colorScheme.onSurface;
     final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo;
     // Format the date time with the timezone
     final (dt, timeZone) =
diff --git a/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart b/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart
index 70fd5e3b89..2157a1aebb 100644
--- a/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart
+++ b/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart
@@ -178,6 +178,7 @@ class TopControlAppBar extends HookConsumerWidget {
       actionsIconTheme: const IconThemeData(
         size: iconSize,
       ),
+      shape: const Border(),
       actions: [
         if (asset.isRemote && isOwner) buildFavoriteButton(a),
         if (asset.livePhotoVideoId != null) buildLivePhotoButton(),
diff --git a/mobile/lib/widgets/backup/album_info_list_tile.dart b/mobile/lib/widgets/backup/album_info_list_tile.dart
index 2e10fe0b75..7cdc595c7f 100644
--- a/mobile/lib/widgets/backup/album_info_list_tile.dart
+++ b/mobile/lib/widgets/backup/album_info_list_tile.dart
@@ -47,22 +47,22 @@ class AlbumInfoListTile extends HookConsumerWidget {
 
     buildIcon() {
       if (isSelected) {
-        return const Icon(
+        return Icon(
           Icons.check_circle_rounded,
-          color: Colors.green,
+          color: context.colorScheme.primary,
         );
       }
 
       if (isExcluded) {
-        return const Icon(
+        return Icon(
           Icons.remove_circle_rounded,
-          color: Colors.red,
+          color: context.colorScheme.error,
         );
       }
 
       return Icon(
         Icons.circle,
-        color: context.isDarkTheme ? Colors.grey[400] : Colors.black45,
+        color: context.colorScheme.surfaceContainerHighest,
       );
     }
 
diff --git a/mobile/lib/widgets/backup/backup_info_card.dart b/mobile/lib/widgets/backup/backup_info_card.dart
index e1b56a970a..58fc89cb65 100644
--- a/mobile/lib/widgets/backup/backup_info_card.dart
+++ b/mobile/lib/widgets/backup/backup_info_card.dart
@@ -1,6 +1,7 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 
 class BackupInfoCard extends StatelessWidget {
   final String title;
@@ -19,9 +20,7 @@ class BackupInfoCard extends StatelessWidget {
       shape: RoundedRectangleBorder(
         borderRadius: BorderRadius.circular(20), // if you need this
         side: BorderSide(
-          color: context.isDarkTheme
-              ? const Color.fromARGB(255, 56, 56, 56)
-              : Colors.black12,
+          color: context.colorScheme.outlineVariant,
           width: 1,
         ),
       ),
@@ -38,7 +37,9 @@ class BackupInfoCard extends StatelessWidget {
           padding: const EdgeInsets.only(top: 4.0, right: 18.0),
           child: Text(
             subtitle,
-            style: context.textTheme.bodyMedium,
+            style: context.textTheme.bodyMedium?.copyWith(
+              color: context.colorScheme.onSurfaceSecondary,
+            ),
           ),
         ),
         trailing: Column(
diff --git a/mobile/lib/widgets/backup/current_backup_asset_info_box.dart b/mobile/lib/widgets/backup/current_backup_asset_info_box.dart
index 2520acedf1..8e58905aaa 100644
--- a/mobile/lib/widgets/backup/current_backup_asset_info_box.dart
+++ b/mobile/lib/widgets/backup/current_backup_asset_info_box.dart
@@ -7,6 +7,7 @@ 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/extensions/theme_extensions.dart';
 import 'package:immich_mobile/models/backup/backup_state.model.dart';
 import 'package:immich_mobile/providers/backup/backup.provider.dart';
 import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
@@ -82,22 +83,20 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
     Widget buildAssetInfoTable() {
       return Table(
         border: TableBorder.all(
-          color: context.themeData.primaryColorLight,
+          color: context.colorScheme.outlineVariant,
           width: 1,
         ),
         children: [
           TableRow(
-            decoration: const BoxDecoration(
-                // color: Colors.grey[100],
-                ),
             children: [
               TableCell(
                 verticalAlignment: TableCellVerticalAlignment.middle,
                 child: Padding(
                   padding: const EdgeInsets.all(6.0),
-                  child: const Text(
+                  child: Text(
                     'backup_controller_page_filename',
                     style: TextStyle(
+                      color: context.colorScheme.onSurfaceSecondary,
                       fontWeight: FontWeight.bold,
                       fontSize: 10.0,
                     ),
@@ -109,17 +108,15 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
             ],
           ),
           TableRow(
-            decoration: const BoxDecoration(
-                // color: Colors.grey[200],
-                ),
             children: [
               TableCell(
                 verticalAlignment: TableCellVerticalAlignment.middle,
                 child: Padding(
                   padding: const EdgeInsets.all(6.0),
-                  child: const Text(
+                  child: Text(
                     "backup_controller_page_created",
                     style: TextStyle(
+                      color: context.colorScheme.onSurfaceSecondary,
                       fontWeight: FontWeight.bold,
                       fontSize: 10.0,
                     ),
@@ -131,16 +128,14 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
             ],
           ),
           TableRow(
-            decoration: const BoxDecoration(
-                // color: Colors.grey[100],
-                ),
             children: [
               TableCell(
                 child: Padding(
                   padding: const EdgeInsets.all(6.0),
-                  child: const Text(
+                  child: Text(
                     "backup_controller_page_id",
                     style: TextStyle(
+                      color: context.colorScheme.onSurfaceSecondary,
                       fontWeight: FontWeight.bold,
                       fontSize: 10.0,
                     ),
@@ -181,8 +176,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
                 child: LinearProgressIndicator(
                   minHeight: 10.0,
                   value: uploadProgress / 100.0,
-                  backgroundColor: Colors.grey,
-                  color: context.primaryColor,
+                  borderRadius: const BorderRadius.all(Radius.circular(10.0)),
                 ),
               ),
               Text(
@@ -214,8 +208,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
               child: LinearProgressIndicator(
                 minHeight: 10.0,
                 value: uploadProgress / 100.0,
-                backgroundColor: Colors.grey,
-                color: context.primaryColor,
+                borderRadius: const BorderRadius.all(Radius.circular(10.0)),
               ),
             ),
             Text(
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 fbcfd64713..5b6e60b1db 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
@@ -57,6 +57,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
                   ? 'assets/immich-text-dark.png'
                   : 'assets/immich-text-light.png',
               height: 16,
+              color: context.primaryColor,
             ),
           ),
         ],
@@ -88,7 +89,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
 
     buildSettingButton() {
       return buildActionButton(
-        Icons.settings_rounded,
+        Icons.settings_outlined,
         "profile_drawer_settings",
         () => context.pushRoute(const SettingsRoute()),
       );
@@ -146,9 +147,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
         child: Container(
           padding: const EdgeInsets.symmetric(vertical: 4),
           decoration: BoxDecoration(
-            color: context.isDarkTheme
-                ? context.scaffoldBackgroundColor
-                : const Color.fromARGB(255, 225, 229, 240),
+            color: context.colorScheme.surface,
           ),
           child: ListTile(
             minLeadingWidth: 50,
@@ -171,10 +170,10 @@ class ImmichAppBarDialog extends HookConsumerWidget {
                   Padding(
                     padding: const EdgeInsets.only(top: 8.0),
                     child: LinearProgressIndicator(
-                      minHeight: 5.0,
+                      minHeight: 10.0,
                       value: percentage,
-                      backgroundColor: Colors.grey,
-                      color: theme.primaryColor,
+                      borderRadius:
+                          const BorderRadius.all(Radius.circular(10.0)),
                     ),
                   ),
                   Padding(
@@ -248,7 +247,6 @@ class ImmichAppBarDialog extends HookConsumerWidget {
         right: horizontalPadding,
         bottom: isHorizontal ? 20 : 100,
       ),
-      backgroundColor: theme.cardColor,
       shape: RoundedRectangleBorder(
         borderRadius: BorderRadius.circular(20),
       ),
diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart
index 5e768f3241..a40dcf914e 100644
--- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart
+++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:image_picker/image_picker.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/upload_profile_image.provider.dart';
 import 'package:immich_mobile/entities/store.entity.dart';
 import 'package:immich_mobile/providers/user.provider.dart';
@@ -79,9 +80,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
       child: Container(
         width: double.infinity,
         decoration: BoxDecoration(
-          color: context.isDarkTheme
-              ? context.scaffoldBackgroundColor
-              : const Color.fromARGB(255, 225, 229, 240),
+          color: context.colorScheme.surface,
           borderRadius: const BorderRadius.only(
             topLeft: Radius.circular(10),
             topRight: Radius.circular(10),
@@ -99,9 +98,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
                   bottom: -5,
                   right: -8,
                   child: Material(
-                    color: context.isDarkTheme
-                        ? Colors.blueGrey[800]
-                        : Colors.white,
+                    color: context.colorScheme.surfaceContainerHighest,
                     elevation: 3,
                     shape: RoundedRectangleBorder(
                       borderRadius: BorderRadius.circular(50.0),
@@ -129,7 +126,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
           subtitle: Text(
             authState.userEmail,
             style: context.textTheme.bodySmall?.copyWith(
-              color: context.textTheme.bodySmall?.color?.withAlpha(200),
+              color: context.colorScheme.onSurfaceSecondary,
             ),
           ),
         ),
diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart
index 0beb45c49f..8cab0bd72f 100644
--- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart
+++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/models/server_info/server_info.model.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:immich_mobile/providers/server_info.provider.dart';
@@ -42,9 +43,7 @@ class AppBarServerInfo extends HookConsumerWidget {
       padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
       child: Container(
         decoration: BoxDecoration(
-          color: context.isDarkTheme
-              ? context.scaffoldBackgroundColor
-              : const Color.fromARGB(255, 225, 229, 240),
+          color: context.colorScheme.surface,
           borderRadius: const BorderRadius.only(
             bottomLeft: Radius.circular(10),
             bottomRight: Radius.circular(10),
@@ -71,10 +70,7 @@ class AppBarServerInfo extends HookConsumerWidget {
               ),
               const Padding(
                 padding: EdgeInsets.symmetric(horizontal: 10),
-                child: Divider(
-                  color: Color.fromARGB(101, 201, 201, 201),
-                  thickness: 1,
-                ),
+                child: Divider(thickness: 1),
               ),
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -100,8 +96,7 @@ class AppBarServerInfo extends HookConsumerWidget {
                         "${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}",
                         style: TextStyle(
                           fontSize: contentFontSize,
-                          color: context.textTheme.labelSmall?.color
-                              ?.withOpacity(0.5),
+                          color: context.colorScheme.onSurfaceSecondary,
                           fontWeight: FontWeight.bold,
                         ),
                       ),
@@ -111,10 +106,7 @@ class AppBarServerInfo extends HookConsumerWidget {
               ),
               const Padding(
                 padding: EdgeInsets.symmetric(horizontal: 10),
-                child: Divider(
-                  color: Color.fromARGB(101, 201, 201, 201),
-                  thickness: 1,
-                ),
+                child: Divider(thickness: 1),
               ),
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -142,8 +134,7 @@ class AppBarServerInfo extends HookConsumerWidget {
                             : "--",
                         style: TextStyle(
                           fontSize: contentFontSize,
-                          color: context.textTheme.labelSmall?.color
-                              ?.withOpacity(0.5),
+                          color: context.colorScheme.onSurfaceSecondary,
                           fontWeight: FontWeight.bold,
                         ),
                       ),
@@ -153,10 +144,7 @@ class AppBarServerInfo extends HookConsumerWidget {
               ),
               const Padding(
                 padding: EdgeInsets.symmetric(horizontal: 10),
-                child: Divider(
-                  color: Color.fromARGB(101, 201, 201, 201),
-                  thickness: 1,
-                ),
+                child: Divider(thickness: 1),
               ),
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -197,8 +185,7 @@ class AppBarServerInfo extends HookConsumerWidget {
                           getServerUrl() ?? '--',
                           style: TextStyle(
                             fontSize: contentFontSize,
-                            color: context.textTheme.labelSmall?.color
-                                ?.withOpacity(0.5),
+                            color: context.colorScheme.onSurfaceSecondary,
                             fontWeight: FontWeight.bold,
                             overflow: TextOverflow.ellipsis,
                           ),
@@ -211,10 +198,7 @@ class AppBarServerInfo extends HookConsumerWidget {
               ),
               const Padding(
                 padding: EdgeInsets.symmetric(horizontal: 10),
-                child: Divider(
-                  color: Color.fromARGB(101, 201, 201, 201),
-                  thickness: 1,
-                ),
+                child: Divider(thickness: 1),
               ),
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -255,8 +239,7 @@ class AppBarServerInfo extends HookConsumerWidget {
                             : "--",
                         style: TextStyle(
                           fontSize: contentFontSize,
-                          color: context.textTheme.labelSmall?.color
-                              ?.withOpacity(0.5),
+                          color: context.colorScheme.onSurfaceSecondary,
                           fontWeight: FontWeight.bold,
                         ),
                       ),
diff --git a/mobile/lib/widgets/common/confirm_dialog.dart b/mobile/lib/widgets/common/confirm_dialog.dart
index 5f24f75d51..5e043cf8de 100644
--- a/mobile/lib/widgets/common/confirm_dialog.dart
+++ b/mobile/lib/widgets/common/confirm_dialog.dart
@@ -47,7 +47,7 @@ class ConfirmDialog extends StatelessWidget {
           child: Text(
             ok,
             style: TextStyle(
-              color: Colors.red[400],
+              color: context.colorScheme.error,
               fontWeight: FontWeight.bold,
             ),
           ).tr(),
diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart
index a3b3a19f34..30802a435a 100644
--- a/mobile/lib/widgets/common/immich_app_bar.dart
+++ b/mobile/lib/widgets/common/immich_app_bar.dart
@@ -111,7 +111,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
 
     buildBackupIndicator() {
       final indicatorIcon = getBackupBadgeIcon();
-      final badgeBackground = isDarkTheme ? Colors.blueGrey[800] : Colors.white;
+      final badgeBackground = context.colorScheme.surfaceContainer;
 
       return InkWell(
         onTap: () => context.pushRoute(const BackupControllerRoute()),
@@ -123,7 +123,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
             decoration: BoxDecoration(
               color: badgeBackground,
               border: Border.all(
-                color: isDarkTheme ? Colors.black : Colors.grey,
+                color: context.colorScheme.outline.withOpacity(.3),
               ),
               borderRadius: BorderRadius.circular(widgetSize / 2),
             ),
diff --git a/mobile/lib/widgets/common/immich_title_text.dart b/mobile/lib/widgets/common/immich_title_text.dart
index 2a4edb4230..711d0bf396 100644
--- a/mobile/lib/widgets/common/immich_title_text.dart
+++ b/mobile/lib/widgets/common/immich_title_text.dart
@@ -21,6 +21,7 @@ class ImmichTitleText extends StatelessWidget {
       ),
       width: fontSize * 4,
       filterQuality: FilterQuality.high,
+      color: context.primaryColor,
     );
   }
 }
diff --git a/mobile/lib/widgets/common/immich_toast.dart b/mobile/lib/widgets/common/immich_toast.dart
index e15623c86c..d33f6c4caf 100644
--- a/mobile/lib/widgets/common/immich_toast.dart
+++ b/mobile/lib/widgets/common/immich_toast.dart
@@ -51,9 +51,9 @@ class ImmichToast {
         padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
         decoration: BoxDecoration(
           borderRadius: BorderRadius.circular(5.0),
-          color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[50],
+          color: context.colorScheme.surfaceContainer,
           border: Border.all(
-            color: Colors.black12,
+            color: context.colorScheme.outline.withOpacity(.5),
             width: 1,
           ),
         ),
diff --git a/mobile/lib/widgets/forms/change_password_form.dart b/mobile/lib/widgets/forms/change_password_form.dart
index 0d1ac539dc..98ce66d2d1 100644
--- a/mobile/lib/widgets/forms/change_password_form.dart
+++ b/mobile/lib/widgets/forms/change_password_form.dart
@@ -51,7 +51,7 @@ class ChangePasswordForm extends HookConsumerWidget {
                   ),
                   style: TextStyle(
                     fontSize: 14,
-                    color: Colors.grey[700],
+                    color: context.colorScheme.onSurface,
                     fontWeight: FontWeight.w600,
                   ),
                 ),
@@ -191,9 +191,6 @@ class ChangePasswordButton extends ConsumerWidget {
     return ElevatedButton(
       style: ElevatedButton.styleFrom(
         visualDensity: VisualDensity.standard,
-        backgroundColor: context.primaryColor,
-        foregroundColor: Colors.grey[50],
-        elevation: 2,
         padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
       ),
       onPressed: onPressed,
diff --git a/mobile/lib/widgets/map/map_theme_override.dart b/mobile/lib/widgets/map/map_theme_override.dart
index f56942c69c..3b66a1cc35 100644
--- a/mobile/lib/widgets/map/map_theme_override.dart
+++ b/mobile/lib/widgets/map/map_theme_override.dart
@@ -70,6 +70,7 @@ class _MapThemeOverideState extends ConsumerState<MapThemeOveride>
   Widget build(BuildContext context) {
     _theme = widget.themeMode ??
         ref.watch(mapStateNotifierProvider.select((v) => v.themeMode));
+    var appTheme = ref.watch(immichThemeProvider);
 
     useValueChanged<ThemeMode, void>(_theme, (_, __) {
       if (_theme == ThemeMode.system) {
@@ -83,7 +84,9 @@ class _MapThemeOverideState extends ConsumerState<MapThemeOveride>
     });
 
     return Theme(
-      data: _isDarkTheme ? immichDarkTheme : immichLightTheme,
+      data: _isDarkTheme
+          ? getThemeData(colorScheme: appTheme.dark)
+          : getThemeData(colorScheme: appTheme.light),
       child: widget.mapBuilder.call(
         ref.watch(
           mapStateNotifierProvider.select(
diff --git a/mobile/lib/widgets/memories/memory_epilogue.dart b/mobile/lib/widgets/memories/memory_epilogue.dart
index b817d67f05..9796bee6b1 100644
--- a/mobile/lib/widgets/memories/memory_epilogue.dart
+++ b/mobile/lib/widgets/memories/memory_epilogue.dart
@@ -1,6 +1,5 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
-import 'package:immich_mobile/constants/immich_colors.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 
 class MemoryEpilogue extends StatefulWidget {
@@ -49,24 +48,26 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
             child: Column(
               mainAxisAlignment: MainAxisAlignment.center,
               children: [
-                const Icon(
+                Icon(
                   Icons.check_circle_outline_sharp,
-                  color: immichDarkThemePrimaryColor,
+                  color: context.isDarkTheme
+                      ? context.colorScheme.primary
+                      : context.colorScheme.inversePrimary,
                   size: 64.0,
                 ),
                 const SizedBox(height: 16.0),
                 Text(
                   "memories_all_caught_up",
-                  style: Theme.of(context).textTheme.headlineMedium?.copyWith(
-                        color: Colors.white,
-                      ),
+                  style: context.textTheme.headlineMedium?.copyWith(
+                    color: Colors.white,
+                  ),
                 ).tr(),
                 const SizedBox(height: 16.0),
                 Text(
                   "memories_check_back_tomorrow",
-                  style: Theme.of(context).textTheme.bodyMedium?.copyWith(
-                        color: Colors.white,
-                      ),
+                  style: context.textTheme.bodyMedium?.copyWith(
+                    color: Colors.white,
+                  ),
                 ).tr(),
                 const SizedBox(height: 16.0),
                 TextButton(
@@ -74,7 +75,9 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
                   child: Text(
                     "memories_start_over",
                     style: context.textTheme.displayMedium?.copyWith(
-                      color: immichDarkThemePrimaryColor,
+                      color: context.isDarkTheme
+                          ? context.colorScheme.primary
+                          : context.colorScheme.inversePrimary,
                     ),
                   ).tr(),
                 ),
@@ -108,9 +111,9 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
                   ),
                   Text(
                     "memories_swipe_to_close",
-                    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
-                          color: Colors.white,
-                        ),
+                    style: context.textTheme.bodyMedium?.copyWith(
+                      color: Colors.white,
+                    ),
                   ).tr(),
                 ],
               ),
diff --git a/mobile/lib/widgets/memories/memory_progress_indicator.dart b/mobile/lib/widgets/memories/memory_progress_indicator.dart
index 0ee3893cb9..438816d99c 100644
--- a/mobile/lib/widgets/memories/memory_progress_indicator.dart
+++ b/mobile/lib/widgets/memories/memory_progress_indicator.dart
@@ -1,5 +1,5 @@
 import 'package:flutter/material.dart';
-import 'package:immich_mobile/constants/immich_colors.dart';
+import 'package:immich_mobile/extensions/build_context_extensions.dart';
 
 class MemoryProgressIndicator extends StatelessWidget {
   /// The number of ticks in the progress indicator
@@ -25,8 +25,11 @@ class MemoryProgressIndicator extends StatelessWidget {
             children: [
               LinearProgressIndicator(
                 value: value,
-                backgroundColor: Colors.grey[600],
-                color: immichDarkThemePrimaryColor,
+                borderRadius: const BorderRadius.all(Radius.circular(10.0)),
+                backgroundColor: Colors.grey[800],
+                color: context.isDarkTheme
+                    ? context.colorScheme.primary
+                    : context.colorScheme.inversePrimary,
               ),
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
diff --git a/mobile/lib/widgets/search/search_filter/search_filter_chip.dart b/mobile/lib/widgets/search/search_filter/search_filter_chip.dart
index b2e0d086ac..7db2eea70b 100644
--- a/mobile/lib/widgets/search/search_filter/search_filter_chip.dart
+++ b/mobile/lib/widgets/search/search_filter/search_filter_chip.dart
@@ -22,9 +22,9 @@ class SearchFilterChip extends StatelessWidget {
         onTap: onTap,
         child: Card(
           elevation: 0,
-          color: context.primaryColor.withAlpha(25),
+          color: context.primaryColor.withOpacity(.5),
           shape: StadiumBorder(
-            side: BorderSide(color: context.primaryColor),
+            side: BorderSide(color: context.colorScheme.secondaryContainer),
           ),
           child: Padding(
             padding:
@@ -47,8 +47,9 @@ class SearchFilterChip extends StatelessWidget {
       onTap: onTap,
       child: Card(
         elevation: 0,
-        shape:
-            StadiumBorder(side: BorderSide(color: Colors.grey.withAlpha(100))),
+        shape: StadiumBorder(
+          side: BorderSide(color: context.colorScheme.outline.withOpacity(.5)),
+        ),
         child: Padding(
           padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
           child: Row(
diff --git a/mobile/lib/widgets/search/thumbnail_with_info_container.dart b/mobile/lib/widgets/search/thumbnail_with_info_container.dart
index 6df45ec464..d2084bdcc8 100644
--- a/mobile/lib/widgets/search/thumbnail_with_info_container.dart
+++ b/mobile/lib/widgets/search/thumbnail_with_info_container.dart
@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 
 class ThumbnailWithInfoContainer extends StatelessWidget {
   const ThumbnailWithInfoContainer({
@@ -25,7 +26,14 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
           Container(
             decoration: BoxDecoration(
               borderRadius: BorderRadius.circular(borderRadius),
-              color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
+              gradient: LinearGradient(
+                colors: [
+                  context.colorScheme.surfaceContainer,
+                  context.colorScheme.surfaceContainer.darken(amount: .1),
+                ],
+                begin: Alignment.topCenter,
+                end: Alignment.bottomCenter,
+              ),
             ),
             foregroundDecoration: BoxDecoration(
               borderRadius: BorderRadius.circular(borderRadius),
@@ -34,7 +42,7 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
                 begin: FractionalOffset.topCenter,
                 end: FractionalOffset.bottomCenter,
                 colors: [
-                  Colors.grey.withOpacity(0.0),
+                  Colors.transparent,
                   label == ''
                       ? Colors.black.withOpacity(0.1)
                       : Colors.black.withOpacity(0.5),
diff --git a/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart b/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart
index 12efa52b2d..2e1f165602 100644
--- a/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart
+++ b/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/routing/router.dart';
 
 class CustomeProxyHeaderSettings extends StatelessWidget {
@@ -20,8 +21,8 @@ class CustomeProxyHeaderSettings extends StatelessWidget {
       ),
       subtitle: Text(
         "headers_settings_tile_subtitle".tr(),
-        style: const TextStyle(
-          fontSize: 14,
+        style: context.textTheme.bodyMedium?.copyWith(
+          color: context.colorScheme.onSurfaceSecondary,
         ),
       ),
       onTap: () => context.pushRoute(const HeaderSettingsRoute()),
diff --git a/mobile/lib/widgets/settings/language_settings.dart b/mobile/lib/widgets/settings/language_settings.dart
index 378d32085e..990dcfdfe8 100644
--- a/mobile/lib/widgets/settings/language_settings.dart
+++ b/mobile/lib/widgets/settings/language_settings.dart
@@ -40,9 +40,7 @@ class LanguageSettings extends HookConsumerWidget {
                   ),
                 ),
                 backgroundColor: WidgetStatePropertyAll<Color>(
-                  context.isDarkTheme
-                      ? Colors.grey[900]!
-                      : context.scaffoldBackgroundColor,
+                  context.colorScheme.surfaceContainer,
                 ),
               ),
               menuHeight: context.height * 0.5,
diff --git a/mobile/lib/widgets/settings/local_storage_settings.dart b/mobile/lib/widgets/settings/local_storage_settings.dart
index 6e7723cbff..5b21d9bd4d 100644
--- a/mobile/lib/widgets/settings/local_storage_settings.dart
+++ b/mobile/lib/widgets/settings/local_storage_settings.dart
@@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState;
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
 import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/db.provider.dart';
 
 class LocalStorageSettings extends HookConsumerWidget {
@@ -35,10 +36,10 @@ class LocalStorageSettings extends HookConsumerWidget {
           fontWeight: FontWeight.w500,
         ),
       ).tr(args: ["${cacheItemCount.value}"]),
-      subtitle: const Text(
+      subtitle: Text(
         "cache_settings_duplicated_assets_subtitle",
-        style: TextStyle(
-          fontSize: 14,
+        style: context.textTheme.bodyMedium?.copyWith(
+          color: context.colorScheme.onSurfaceSecondary,
         ),
       ).tr(),
       trailing: TextButton(
diff --git a/mobile/lib/widgets/settings/preference_settings/preference_setting.dart b/mobile/lib/widgets/settings/preference_settings/preference_setting.dart
index 62508df6e2..8a3684e093 100644
--- a/mobile/lib/widgets/settings/preference_settings/preference_setting.dart
+++ b/mobile/lib/widgets/settings/preference_settings/preference_setting.dart
@@ -15,6 +15,9 @@ class PreferenceSetting extends StatelessWidget {
       HapticSetting(),
     ];
 
-    return const SettingsSubPageScaffold(settings: preferenceSettings);
+    return const SettingsSubPageScaffold(
+      settings: preferenceSettings,
+      showDivider: true,
+    );
   }
 }
diff --git a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart
new file mode 100644
index 0000000000..1c7cd1f207
--- /dev/null
+++ b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart
@@ -0,0 +1,221 @@
+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/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.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';
+
+class PrimaryColorSetting extends HookConsumerWidget {
+  const PrimaryColorSetting({
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final themeProvider = ref.read(immichThemeProvider);
+
+    final primaryColorSetting =
+        useAppSettingsState(AppSettingsEnum.primaryColor);
+    final systemPrimaryColorSetting =
+        useAppSettingsState(AppSettingsEnum.dynamicTheme);
+
+    final currentPreset = useValueNotifier(ref.read(immichThemePresetProvider));
+    const tileSize = 55.0;
+
+    useValueChanged(
+      primaryColorSetting.value,
+      (_, __) => currentPreset.value = ImmichColorPreset.values
+          .firstWhere((e) => e.name == primaryColorSetting.value),
+    );
+
+    void popBottomSheet() {
+      Future.delayed(const Duration(milliseconds: 200), () {
+        Navigator.pop(context);
+      });
+    }
+
+    onUseSystemColorChange(bool newValue) {
+      systemPrimaryColorSetting.value = newValue;
+      ref.watch(dynamicThemeSettingProvider.notifier).state = newValue;
+      ref.invalidate(immichThemeProvider);
+      popBottomSheet();
+    }
+
+    onPrimaryColorChange(ImmichColorPreset colorPreset) {
+      primaryColorSetting.value = colorPreset.name;
+      ref.watch(immichThemePresetProvider.notifier).state = colorPreset;
+      ref.invalidate(immichThemeProvider);
+
+      //turn off system color setting
+      if (systemPrimaryColorSetting.value) {
+        onUseSystemColorChange(false);
+      } else {
+        popBottomSheet();
+      }
+    }
+
+    buildPrimaryColorTile({
+      required Color topColor,
+      required Color bottomColor,
+      required double tileSize,
+      required bool showSelector,
+    }) {
+      return Container(
+        margin: const EdgeInsets.all(4.0),
+        child: Stack(
+          children: [
+            Container(
+              height: tileSize,
+              width: tileSize,
+              decoration: BoxDecoration(
+                color: bottomColor,
+                borderRadius: const BorderRadius.all(Radius.circular(100)),
+              ),
+            ),
+            Container(
+              height: tileSize / 2,
+              width: tileSize,
+              decoration: BoxDecoration(
+                color: topColor,
+                borderRadius: const BorderRadius.only(
+                  topLeft: Radius.circular(100),
+                  topRight: Radius.circular(100),
+                ),
+              ),
+            ),
+            if (showSelector)
+              Positioned(
+                left: 0,
+                right: 0,
+                top: 0,
+                bottom: 0,
+                child: Container(
+                  decoration: BoxDecoration(
+                    borderRadius: const BorderRadius.all(Radius.circular(100)),
+                    color: Colors.grey[900]?.withOpacity(.4),
+                  ),
+                  child: const Padding(
+                    padding: EdgeInsets.all(3),
+                    child: Icon(
+                      Icons.check_rounded,
+                      color: Colors.white,
+                      size: 25,
+                    ),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      );
+    }
+
+    bottomSheetContent() {
+      return Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          Align(
+            alignment: Alignment.center,
+            child: Text(
+              "theme_setting_primary_color_title".tr(),
+              style: context.textTheme.titleLarge,
+            ),
+          ),
+          if (isDynamicThemeAvailable)
+            Container(
+              padding: const EdgeInsets.symmetric(horizontal: 20),
+              margin: const EdgeInsets.only(top: 10),
+              child: SwitchListTile.adaptive(
+                contentPadding:
+                    const EdgeInsets.symmetric(vertical: 6, horizontal: 20),
+                dense: true,
+                activeColor: context.primaryColor,
+                tileColor: context.colorScheme.surfaceContainerHigh,
+                shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(15),
+                ),
+                title: Text(
+                  'theme_setting_system_primary_color_title'.tr(),
+                  style: context.textTheme.bodyLarge?.copyWith(
+                    fontWeight: FontWeight.w500,
+                    height: 1.5,
+                  ),
+                ),
+                value: systemPrimaryColorSetting.value,
+                onChanged: onUseSystemColorChange,
+              ),
+            ),
+          const SizedBox(height: 20),
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 20),
+            child: Wrap(
+              crossAxisAlignment: WrapCrossAlignment.center,
+              children: ImmichColorPreset.values.map((themePreset) {
+                var theme = themePreset.getTheme();
+
+                return GestureDetector(
+                  onTap: () => onPrimaryColorChange(themePreset),
+                  child: buildPrimaryColorTile(
+                    topColor: theme.light.primary,
+                    bottomColor: theme.dark.primary,
+                    tileSize: tileSize,
+                    showSelector: currentPreset.value == themePreset &&
+                        !systemPrimaryColorSetting.value,
+                  ),
+                );
+              }).toList(),
+            ),
+          ),
+        ],
+      );
+    }
+
+    return ListTile(
+      onTap: () => showModalBottomSheet(
+        context: context,
+        isScrollControlled: true,
+        builder: (BuildContext ctx) {
+          return Padding(
+            padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 0),
+            child: bottomSheetContent(),
+          );
+        },
+      ),
+      contentPadding: const EdgeInsets.symmetric(horizontal: 20),
+      title: Row(
+        children: [
+          Expanded(
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                Text(
+                  "theme_setting_primary_color_title".tr(),
+                  style: context.textTheme.bodyLarge?.copyWith(
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+                Text(
+                  "theme_setting_primary_color_subtitle".tr(),
+                  style: context.textTheme.bodyMedium
+                      ?.copyWith(color: context.colorScheme.onSurfaceSecondary),
+                ),
+              ],
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 8.0),
+            child: buildPrimaryColorTile(
+              topColor: themeProvider.light.primary,
+              bottomColor: themeProvider.dark.primary,
+              tileSize: 42.0,
+              showSelector: false,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart
index 5780054428..050593a229 100644
--- a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart
+++ b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.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';
@@ -16,11 +17,16 @@ class ThemeSetting extends HookConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final currentThemeString = useAppSettingsState(AppSettingsEnum.themeMode);
-    final currentTheme = useValueNotifier(ref.read(immichThemeProvider));
+    final currentTheme = useValueNotifier(ref.read(immichThemeModeProvider));
     final isDarkTheme = useValueNotifier(currentTheme.value == ThemeMode.dark);
     final isSystemTheme =
         useValueNotifier(currentTheme.value == ThemeMode.system);
 
+    final applyThemeToBackgroundSetting =
+        useAppSettingsState(AppSettingsEnum.colorfulInterface);
+    final applyThemeToBackgroundProvider =
+        useValueNotifier(ref.read(colorfulInterfaceSettingProvider));
+
     useValueChanged(
       currentThemeString.value,
       (_, __) => currentTheme.value = switch (currentThemeString.value) {
@@ -30,12 +36,18 @@ class ThemeSetting extends HookConsumerWidget {
       },
     );
 
+    useValueChanged(
+      applyThemeToBackgroundSetting.value,
+      (_, __) => applyThemeToBackgroundProvider.value =
+          applyThemeToBackgroundSetting.value,
+    );
+
     void onThemeChange(bool isDark) {
       if (isDark) {
-        ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark;
+        ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
         currentThemeString.value = "dark";
       } else {
-        ref.watch(immichThemeProvider.notifier).state = ThemeMode.light;
+        ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
         currentThemeString.value = "light";
       }
     }
@@ -44,7 +56,7 @@ class ThemeSetting extends HookConsumerWidget {
       if (isSystem) {
         currentThemeString.value = "system";
         isSystemTheme.value = true;
-        ref.watch(immichThemeProvider.notifier).state = ThemeMode.system;
+        ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.system;
       } else {
         final currentSystemBrightness =
             MediaQuery.platformBrightnessOf(context);
@@ -52,14 +64,20 @@ class ThemeSetting extends HookConsumerWidget {
         isDarkTheme.value = currentSystemBrightness == Brightness.dark;
         if (currentSystemBrightness == Brightness.light) {
           currentThemeString.value = "light";
-          ref.watch(immichThemeProvider.notifier).state = ThemeMode.light;
+          ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
         } else if (currentSystemBrightness == Brightness.dark) {
           currentThemeString.value = "dark";
-          ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark;
+          ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
         }
       }
     }
 
+    void onSurfaceColorSettingChange(bool useColorfulInterface) {
+      applyThemeToBackgroundSetting.value = useColorfulInterface;
+      ref.watch(colorfulInterfaceSettingProvider.notifier).state =
+          useColorfulInterface;
+    }
+
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
@@ -75,6 +93,13 @@ class ThemeSetting extends HookConsumerWidget {
             title: 'theme_setting_dark_mode_switch'.tr(),
             onChanged: onThemeChange,
           ),
+        const PrimaryColorSetting(),
+        SettingsSwitchListTile(
+          valueNotifier: applyThemeToBackgroundProvider,
+          title: "theme_setting_colorful_interface_title".tr(),
+          subtitle: 'theme_setting_colorful_interface_subtitle'.tr(),
+          onChanged: onSurfaceColorSettingChange,
+        ),
       ],
     );
   }
diff --git a/mobile/lib/widgets/settings/settings_button_list_tile.dart b/mobile/lib/widgets/settings/settings_button_list_tile.dart
index fca5b878de..196e3d170f 100644
--- a/mobile/lib/widgets/settings/settings_button_list_tile.dart
+++ b/mobile/lib/widgets/settings/settings_button_list_tile.dart
@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 
 class SettingsButtonListTile extends StatelessWidget {
   final IconData icon;
@@ -39,7 +40,12 @@ class SettingsButtonListTile extends StatelessWidget {
         children: [
           if (subtileText != null) const SizedBox(height: 4),
           if (subtileText != null)
-            Text(subtileText!, style: context.textTheme.bodyMedium),
+            Text(
+              subtileText!,
+              style: context.textTheme.bodyMedium?.copyWith(
+                color: context.colorScheme.onSurfaceSecondary,
+              ),
+            ),
           if (subtitle != null) subtitle!,
           const SizedBox(height: 6),
           ElevatedButton(onPressed: onButtonTap, child: Text(buttonText)),
diff --git a/mobile/lib/widgets/settings/settings_switch_list_tile.dart b/mobile/lib/widgets/settings/settings_switch_list_tile.dart
index c7328f0b96..78f1738266 100644
--- a/mobile/lib/widgets/settings/settings_switch_list_tile.dart
+++ b/mobile/lib/widgets/settings/settings_switch_list_tile.dart
@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 
 class SettingsSwitchListTile extends StatelessWidget {
   final ValueNotifier<bool> valueNotifier;
@@ -54,7 +55,9 @@ class SettingsSwitchListTile extends StatelessWidget {
           ? Text(
               subtitle!,
               style: context.textTheme.bodyMedium?.copyWith(
-                color: enabled ? null : context.themeData.disabledColor,
+                color: enabled
+                    ? context.colorScheme.onSurfaceSecondary
+                    : context.themeData.disabledColor,
               ),
             )
           : null,
diff --git a/mobile/lib/widgets/settings/ssl_client_cert_settings.dart b/mobile/lib/widgets/settings/ssl_client_cert_settings.dart
index 0daddd6d88..21d9738b84 100644
--- a/mobile/lib/widgets/settings/ssl_client_cert_settings.dart
+++ b/mobile/lib/widgets/settings/ssl_client_cert_settings.dart
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:immich_mobile/entities/store.entity.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
+import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
 
 class SslClientCertSettings extends StatefulWidget {
@@ -40,7 +41,9 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
         children: [
           Text(
             "client_cert_subtitle".tr(),
-            style: context.textTheme.bodyMedium,
+            style: context.textTheme.bodyMedium?.copyWith(
+              color: context.colorScheme.onSurfaceSecondary,
+            ),
           ),
           const SizedBox(
             height: 6,
diff --git a/mobile/lib/widgets/shared_link/shared_link_item.dart b/mobile/lib/widgets/shared_link/shared_link_item.dart
index 86c0890cd2..9e29f5f9a0 100644
--- a/mobile/lib/widgets/shared_link/shared_link_item.dart
+++ b/mobile/lib/widgets/shared_link/shared_link_item.dart
@@ -65,8 +65,8 @@ class SharedLinkItem extends ConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    final themeData = context.themeData;
-    final isDarkMode = themeData.brightness == Brightness.dark;
+    final colorScheme = context.colorScheme;
+    final isDarkMode = colorScheme.brightness == Brightness.dark;
     final thumbnailUrl = sharedLink.thumbAssetId != null
         ? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!)
         : null;
@@ -159,7 +159,7 @@ class SharedLinkItem extends ConsumerWidget {
       return Padding(
         padding: const EdgeInsets.only(right: 10),
         child: Chip(
-          backgroundColor: themeData.primaryColor,
+          backgroundColor: colorScheme.primary,
           label: Text(
             labelText,
             style: TextStyle(
@@ -240,7 +240,7 @@ class SharedLinkItem extends ConsumerWidget {
             child: Tooltip(
               verticalOffset: 0,
               decoration: BoxDecoration(
-                color: themeData.primaryColor.withOpacity(0.9),
+                color: colorScheme.primary.withOpacity(0.9),
                 borderRadius: BorderRadius.circular(10),
               ),
               textStyle: TextStyle(
@@ -253,7 +253,7 @@ class SharedLinkItem extends ConsumerWidget {
               child: Text(
                 sharedLink.title,
                 style: TextStyle(
-                  color: themeData.primaryColor,
+                  color: colorScheme.primary,
                   fontWeight: FontWeight.bold,
                   overflow: TextOverflow.ellipsis,
                 ),
@@ -268,7 +268,7 @@ class SharedLinkItem extends ConsumerWidget {
                 child: Tooltip(
                   verticalOffset: 0,
                   decoration: BoxDecoration(
-                    color: themeData.primaryColor.withOpacity(0.9),
+                    color: colorScheme.primary.withOpacity(0.9),
                     borderRadius: BorderRadius.circular(10),
                   ),
                   textStyle: TextStyle(
diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock
index c7e397999c..8d5a912a51 100644
--- a/mobile/pubspec.lock
+++ b/mobile/pubspec.lock
@@ -377,6 +377,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "7.0.0"
+  dynamic_color:
+    dependency: "direct main"
+    description:
+      name: dynamic_color
+      sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.7.0"
   easy_image_viewer:
     dependency: "direct main"
     description:
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index c830707182..9b74bec14c 100644
--- a/mobile/pubspec.yaml
+++ b/mobile/pubspec.yaml
@@ -61,9 +61,11 @@ dependencies:
   octo_image: ^2.0.0
   thumbhash: 0.1.0+1
   async: ^2.11.0
+  dynamic_color: ^1.7.0 #package to apply system theme
 
   #image editing packages
   crop_image: ^1.0.13
+
   openapi:
     path: openapi