1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-17 01:06:46 +01:00

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>
This commit is contained in:
Pruthvi Bugidi 2024-08-06 19:50:27 +05:30 committed by GitHub
parent 20262209ce
commit 0eacdf93eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 944 additions and 563 deletions

View file

@ -531,6 +531,11 @@
"theme_setting_dark_mode_switch": "Dark mode", "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_subtitle": "Adjust the quality of the detail image viewer",
"theme_setting_image_viewer_quality_title": "Image viewer quality", "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_system_theme_switch": "Automatic (Follow system setting)",
"theme_setting_theme_subtitle": "Choose the app's theme setting", "theme_setting_theme_subtitle": "Choose the app's theme setting",
"theme_setting_theme_title": "Theme", "theme_setting_theme_title": "Theme",

View file

@ -1,5 +1,108 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
const Color immichBackgroundColor = Color(0xFFf6f8fe); enum ImmichColorPreset {
const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0); indigo,
const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250); 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]!;
}

View file

@ -229,6 +229,11 @@ enum StoreKey<T> {
mapwithPartners<bool>(125, type: bool), mapwithPartners<bool>(125, type: bool),
enableHapticFeedback<bool>(126, type: bool), enableHapticFeedback<bool>(126, type: bool),
customHeaders<String>(127, type: String), customHeaders<String>(127, type: String),
// theme settings
primaryColor<String>(128, type: String),
dynamicTheme<bool>(129, type: bool),
colorfulInterface<bool>(130, type: bool),
; ;
const StoreKey( const StoreKey(

View file

@ -20,10 +20,10 @@ extension ContextHelper on BuildContext {
bool get isDarkTheme => themeData.brightness == Brightness.dark; bool get isDarkTheme => themeData.brightness == Brightness.dark;
// Returns the current Primary color of the Theme // 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 // Returns the Scaffold background color of the Theme
Color get scaffoldBackgroundColor => themeData.scaffoldBackgroundColor; Color get scaffoldBackgroundColor => colorScheme.surface;
// Returns the current TextTheme // Returns the current TextTheme
TextTheme get textTheme => themeData.textTheme; TextTheme get textTheme => themeData.textTheme;

View file

@ -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,
);
}
}

View file

@ -65,6 +65,8 @@ Future<void> initApp() async {
} }
} }
await fetchSystemPalette();
// Initialize Immich Logger Service // Initialize Immich Logger Service
ImmichLogger(); ImmichLogger();
@ -187,6 +189,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var router = ref.watch(appRouterProvider); var router = ref.watch(appRouterProvider);
var immichTheme = ref.watch(immichThemeProvider);
return MaterialApp( return MaterialApp(
localizationsDelegates: context.localizationDelegates, localizationsDelegates: context.localizationDelegates,
@ -196,9 +199,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
home: MaterialApp.router( home: MaterialApp.router(
title: 'Immich', title: 'Immich',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
themeMode: ref.watch(immichThemeProvider), themeMode: ref.watch(immichThemeModeProvider),
darkTheme: immichDarkTheme, darkTheme: getThemeData(colorScheme: immichTheme.dark),
theme: immichLightTheme, theme: getThemeData(colorScheme: immichTheme.light),
routeInformationParser: router.defaultRouteParser(), routeInformationParser: router.defaultRouteParser(),
routerDelegate: router.delegate( routerDelegate: router.delegate(
navigatorObservers: () => [TabNavigationObserver(ref: ref)], navigatorObservers: () => [TabNavigationObserver(ref: ref)],

View file

@ -4,6 +4,8 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/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:immich_mobile/widgets/common/immich_loading_indicator.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
@ -46,7 +48,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
"ID ${album.id}", "ID ${album.id}",
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: Colors.grey[600], color: context.colorScheme.onSurfaceSecondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),

View file

@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/widgets/backup/album_info_card.dart'; import 'package:immich_mobile/widgets/backup/album_info_card.dart';
@ -128,13 +127,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
album.name, album.name,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: isDarkTheme ? Colors.black : immichBackgroundColor, color: context.scaffoldBackgroundColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
backgroundColor: Colors.red[300], backgroundColor: Colors.red[300],
deleteIconColor: deleteIconColor: context.scaffoldBackgroundColor,
isDarkTheme ? Colors.black : immichBackgroundColor,
deleteIcon: const Icon( deleteIcon: const Icon(
Icons.cancel_rounded, Icons.cancel_rounded,
size: 15, size: 15,

View file

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/models/backup/backup_state.model.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/album/album.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
@ -130,9 +131,7 @@ class BackupControllerPage extends HookConsumerWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
side: BorderSide( side: BorderSide(
color: context.isDarkTheme color: context.colorScheme.outlineVariant,
? const Color.fromARGB(255, 56, 56, 56)
: Colors.black12,
width: 1, width: 1,
), ),
), ),
@ -151,7 +150,9 @@ class BackupControllerPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
"backup_controller_page_to_backup", "backup_controller_page_to_backup",
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
).tr(), ).tr(),
buildSelectedAlbumName(), buildSelectedAlbumName(),
buildExcludedAlbumName(), buildExcludedAlbumName(),

View file

@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/album/shared_album.provider.dart'; import 'package:immich_mobile/providers/album/shared_album.provider.dart';
import 'package:immich_mobile/providers/authentication.provider.dart'; import 'package:immich_mobile/providers/authentication.provider.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart'; import 'package:immich_mobile/utils/immich_loading_overlay.dart';
@ -102,7 +103,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
} }
showModalBottomSheet( showModalBottomSheet(
backgroundColor: context.scaffoldBackgroundColor, backgroundColor: context.colorScheme.surfaceContainer,
isScrollControlled: false, isScrollControlled: false,
context: context, context: context,
builder: (context) { builder: (context) {
@ -131,7 +132,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
), ),
subtitle: Text( subtitle: Text(
album.owner.value?.email ?? "", album.owner.value?.email ?? "",
style: TextStyle(color: Colors.grey[600]), style: TextStyle(color: context.colorScheme.onSurfaceSecondary),
), ),
trailing: Text( trailing: Text(
"shared_album_section_people_owner_label", "shared_album_section_people_owner_label",
@ -160,7 +161,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
), ),
subtitle: Text( subtitle: Text(
user.email, user.email,
style: TextStyle(color: Colors.grey[600]), style: TextStyle(
color: context.colorScheme.onSurfaceSecondary,
),
), ),
trailing: userId == user.id || isOwner trailing: userId == user.id || isOwner
? const Icon(Icons.more_horiz_rounded) ? const Icon(Icons.more_horiz_rounded)
@ -214,7 +217,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
subtitle: Text( subtitle: Text(
"shared_album_activity_setting_subtitle", "shared_album_activity_setting_subtitle",
style: context.textTheme.labelLarge?.copyWith( style: context.textTheme.labelLarge?.copyWith(
color: context.textTheme.labelLarge?.color?.withAlpha(175), color: context.colorScheme.onSurfaceSecondary,
), ),
).tr(), ).tr(),
), ),

View file

@ -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/providers/album/shared_album.provider.dart';
import 'package:immich_mobile/utils/immich_loading_overlay.dart'; import 'package:immich_mobile/utils/immich_loading_overlay.dart';
import 'package:immich_mobile/services/album.service.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/widgets/album/album_viewer_editable_title.dart';
import 'package:immich_mobile/providers/multiselect.provider.dart'; import 'package:immich_mobile/providers/multiselect.provider.dart';
import 'package:immich_mobile/providers/authentication.provider.dart'; import 'package:immich_mobile/providers/authentication.provider.dart';
@ -114,13 +114,13 @@ class AlbumViewerPage extends HookConsumerWidget {
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: [ children: [
AlbumActionOutlinedButton( AlbumActionFilledButton(
iconData: Icons.add_photo_alternate_outlined, iconData: Icons.add_photo_alternate_outlined,
onPressed: () => onAddPhotosPressed(album), onPressed: () => onAddPhotosPressed(album),
labelText: "share_add_photos".tr(), labelText: "share_add_photos".tr(),
), ),
if (userId == album.ownerId) if (userId == album.ownerId)
AlbumActionOutlinedButton( AlbumActionFilledButton(
iconData: Icons.person_add_alt_rounded, iconData: Icons.person_add_alt_rounded,
onPressed: () => onAddUsersPressed(album), onPressed: () => onAddUsersPressed(album),
labelText: "album_viewer_page_share_add_users".tr(), labelText: "album_viewer_page_share_add_users".tr(),

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/logger_message.entity.dart'; import 'package:immich_mobile/entities/logger_message.entity.dart';
import 'package:immich_mobile/services/immich_logger.service.dart'; import 'package:immich_mobile/services/immich_logger.service.dart';
@ -18,7 +19,6 @@ class AppLogPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final immichLogger = ImmichLogger(); final immichLogger = ImmichLogger();
final logMessages = useState(immichLogger.messages); final logMessages = useState(immichLogger.messages);
final isDarkTheme = context.isDarkTheme;
Widget colorStatusIndicator(Color color) { Widget colorStatusIndicator(Color color) {
return Column( return Column(
@ -55,13 +55,9 @@ class AppLogPage extends HookConsumerWidget {
case LogLevel.INFO: case LogLevel.INFO:
return Colors.transparent; return Colors.transparent;
case LogLevel.SEVERE: case LogLevel.SEVERE:
return isDarkTheme return Colors.redAccent.withOpacity(0.25);
? Colors.redAccent.withOpacity(0.25)
: Colors.redAccent.withOpacity(0.075);
case LogLevel.WARNING: case LogLevel.WARNING:
return isDarkTheme return Colors.orangeAccent.withOpacity(0.25);
? Colors.orangeAccent.withOpacity(0.25)
: Colors.orangeAccent.withOpacity(0.075);
default: default:
return context.primaryColor.withOpacity(0.1); return context.primaryColor.withOpacity(0.1);
} }
@ -120,10 +116,7 @@ class AppLogPage extends HookConsumerWidget {
), ),
body: ListView.separated( body: ListView.separated(
separatorBuilder: (context, index) { separatorBuilder: (context, index) {
return Divider( return const Divider(height: 0);
height: 0,
color: isDarkTheme ? Colors.white70 : Colors.grey[600],
);
}, },
itemCount: logMessages.value.length, itemCount: logMessages.value.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -141,8 +134,9 @@ class AppLogPage extends HookConsumerWidget {
minLeadingWidth: 10, minLeadingWidth: 10,
title: Text( title: Text(
truncateLogMessage(logMessage.message, 4), truncateLogMessage(logMessage.message, 4),
style: const TextStyle( style: TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: context.colorScheme.onSurface,
fontFamily: "Inconsolata", fontFamily: "Inconsolata",
), ),
), ),
@ -150,7 +144,7 @@ class AppLogPage extends HookConsumerWidget {
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.context1}", "at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.context1}",
style: TextStyle( style: TextStyle(
fontSize: 12.0, fontSize: 12.0,
color: Colors.grey[600], color: context.colorScheme.onSurfaceSecondary,
), ),
), ),
leading: buildLeadingIcon(logMessage.level), leading: buildLeadingIcon(logMessage.level),

View file

@ -13,8 +13,6 @@ class AppLogDetailPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
var isDarkTheme = context.isDarkTheme;
buildTextWithCopyButton(String header, String text) { buildTextWithCopyButton(String header, String text) {
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
@ -61,7 +59,7 @@ class AppLogDetailPage extends HookConsumerWidget {
), ),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200], color: context.colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
), ),
child: Padding( child: Padding(
@ -100,7 +98,7 @@ class AppLogDetailPage extends HookConsumerWidget {
), ),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200], color: context.colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
), ),
child: Padding( child: Padding(

View file

@ -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/album/album_title.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/routing/router.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/album_title_text_field.dart';
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart'; import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
@ -109,20 +109,16 @@ class CreateAlbumPage extends HookConsumerWidget {
if (selectedAssets.value.isEmpty) { if (selectedAssets.value.isEmpty) {
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 16, left: 18, right: 18), padding: const EdgeInsets.only(top: 16, left: 16, right: 16),
child: OutlinedButton.icon( child: FilledButton.icon(
style: OutlinedButton.styleFrom( style: FilledButton.styleFrom(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: padding:
const EdgeInsets.symmetric(vertical: 22, horizontal: 16), const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
side: BorderSide(
color: context.isDarkTheme
? const Color.fromARGB(255, 63, 63, 63)
: const Color.fromARGB(255, 129, 129, 129),
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(10),
), ),
backgroundColor: context.colorScheme.surfaceContainerHighest,
), ),
onPressed: onSelectPhotosButtonPressed, onPressed: onSelectPhotosButtonPressed,
icon: Icon( icon: Icon(
@ -134,7 +130,7 @@ class CreateAlbumPage extends HookConsumerWidget {
child: Text( child: Text(
'create_shared_album_page_share_select_photos', 'create_shared_album_page_share_select_photos',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: context.primaryColor, fontWeight: FontWeight.normal,
), ),
).tr(), ).tr(),
), ),
@ -154,7 +150,7 @@ class CreateAlbumPage extends HookConsumerWidget {
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: [ children: [
AlbumActionOutlinedButton( AlbumActionFilledButton(
iconData: Icons.add_photo_alternate_outlined, iconData: Icons.add_photo_alternate_outlined,
onPressed: onSelectPhotosButtonPressed, onPressed: onSelectPhotosButtonPressed,
labelText: "share_add_photos".tr(), labelText: "share_add_photos".tr(),

View file

@ -49,10 +49,6 @@ class SettingsPage extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
centerTitle: false, centerTitle: false,
bottom: const PreferredSize(
preferredSize: Size.fromHeight(1),
child: Divider(height: 1),
),
title: const Text('setting_pages_app_bar_settings').tr(), title: const Text('setting_pages_app_bar_settings').tr(),
), ),
body: context.isMobile ? _MobileLayout() : _TabletLayout(), body: context.isMobile ? _MobileLayout() : _TabletLayout(),
@ -67,13 +63,18 @@ class _MobileLayout extends StatelessWidget {
children: SettingSection.values children: SettingSection.values
.map( .map(
(s) => ListTile( (s) => ListTile(
title: Text( contentPadding:
s.title, const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
style: const TextStyle(
fontWeight: FontWeight.bold,
),
).tr(),
leading: Icon(s.icon), 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)), onTap: () => context.pushRoute(SettingsSubRoute(section: s)),
), ),
) )
@ -102,7 +103,7 @@ class _TabletLayout extends HookWidget {
leading: Icon(s.icon), leading: Icon(s.icon),
selected: s.index == selectedSection.value.index, selected: s.index == selectedSection.value.index,
selectedColor: context.primaryColor, selectedColor: context.primaryColor,
selectedTileColor: context.primaryColor.withAlpha(50), selectedTileColor: context.themeData.highlightColor,
onTap: () => selectedSection.value = s, onTap: () => selectedSection.value = s,
), ),
), ),

View file

@ -20,7 +20,6 @@ class LibraryPage extends HookConsumerWidget {
final trashEnabled = final trashEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
final albums = ref.watch(albumProvider); final albums = ref.watch(albumProvider);
final isDarkTheme = context.isDarkTheme;
final albumSortOption = ref.watch(albumSortByOptionsProvider); final albumSortOption = ref.watch(albumSortByOptionsProvider);
final albumSortIsReverse = ref.watch(albumSortOrderProvider); final albumSortIsReverse = ref.watch(albumSortOrderProvider);
@ -116,12 +115,7 @@ class LibraryPage extends HookConsumerWidget {
width: cardSize, width: cardSize,
height: cardSize, height: cardSize,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( color: context.colorScheme.surfaceContainer,
color: isDarkTheme
? const Color.fromARGB(255, 53, 53, 53)
: const Color.fromARGB(255, 203, 203, 203),
),
color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
), ),
child: Center( child: Center(
@ -139,7 +133,9 @@ class LibraryPage extends HookConsumerWidget {
), ),
child: Text( child: Text(
'library_page_new_album', 'library_page_new_album',
style: context.textTheme.labelLarge, style: context.textTheme.labelLarge?.copyWith(
color: context.colorScheme.onSurface,
),
).tr(), ).tr(),
), ),
], ],
@ -156,26 +152,25 @@ class LibraryPage extends HookConsumerWidget {
Function() onClick, Function() onClick,
) { ) {
return Expanded( return Expanded(
child: OutlinedButton.icon( child: FilledButton.icon(
onPressed: onClick, onPressed: onClick,
label: Padding( label: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
label, label,
style: TextStyle( style: TextStyle(
color: context.isDarkTheme color: context.colorScheme.onSurface,
? Colors.white
: Colors.black.withAlpha(200),
), ),
), ),
), ),
style: OutlinedButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), elevation: 0,
backgroundColor: isDarkTheme ? Colors.grey[900] : Colors.grey[50], padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
side: BorderSide( backgroundColor: context.colorScheme.surfaceContainer,
color: isDarkTheme ? Colors.grey[800]! : Colors.grey[300]!,
),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
), ),
icon: Icon( icon: Icon(
icon, icon,
@ -247,6 +242,7 @@ class LibraryPage extends HookConsumerWidget {
Text( Text(
'library_page_albums', 'library_page_albums',
style: context.textTheme.bodyLarge?.copyWith( style: context.textTheme.bodyLarge?.copyWith(
color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
).tr(), ).tr(),

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/widgets/forms/login/login_form.dart'; import 'package:immich_mobile/widgets/forms/login/login_form.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
@ -39,8 +40,8 @@ class LoginPage extends HookConsumerWidget {
children: [ children: [
Text( Text(
'v${appVersion.value}', 'v${appVersion.value}',
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: context.colorScheme.onSurfaceSecondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontFamily: "Inconsolata", fontFamily: "Inconsolata",
), ),

View file

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/models/search/search_curated_content.model.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/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart';
@ -38,7 +39,7 @@ class SearchPage extends HookConsumerWidget {
fontSize: 15.0, fontSize: 15.0,
); );
Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black; Color categoryIconColor = context.colorScheme.onSurface;
showNameEditModel( showNameEditModel(
String personId, String personId,
@ -128,13 +129,9 @@ class SearchPage extends HookConsumerWidget {
}, },
child: Card( child: Card(
elevation: 0, elevation: 0,
color: context.colorScheme.surfaceContainerHigh,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(50),
side: BorderSide(
color: context.isDarkTheme
? Colors.grey[800]!
: const Color.fromARGB(255, 225, 225, 225),
),
), ),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding( child: Padding(
@ -144,13 +141,15 @@ class SearchPage extends HookConsumerWidget {
), ),
child: Row( child: Row(
children: [ children: [
Icon(Icons.search, color: context.primaryColor), Icon(
Icons.search,
color: context.colorScheme.onSurfaceSecondary,
),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
Text( Text(
"search_bar_hint", "search_bar_hint",
style: context.textTheme.bodyLarge?.copyWith( style: context.textTheme.bodyLarge?.copyWith(
color: color: context.colorScheme.onSurfaceSecondary,
context.isDarkTheme ? Colors.white70 : Colors.black54,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
).tr(), ).tr(),

View file

@ -7,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.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/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
@ -509,7 +510,7 @@ class SearchInputPage extends HookConsumerWidget {
? 'contextual_search'.tr() ? 'contextual_search'.tr()
: 'filename_search'.tr(), : 'filename_search'.tr(),
hintStyle: context.textTheme.bodyLarge?.copyWith( hintStyle: context.textTheme.bodyLarge?.copyWith(
color: context.themeData.colorScheme.onSurface.withOpacity(0.75), color: context.themeData.colorScheme.onSurfaceSecondary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
enabledBorder: const UnderlineInputBorder( enabledBorder: const UnderlineInputBorder(

View file

@ -30,6 +30,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
const padding = 20.0; const padding = 20.0;
final themeData = context.themeData; final themeData = context.themeData;
final colorScheme = context.colorScheme;
final descriptionController = final descriptionController =
useTextEditingController(text: existingLink?.description ?? ""); useTextEditingController(text: existingLink?.description ?? "");
final descriptionFocusNode = useFocusNode(); final descriptionFocusNode = useFocusNode();
@ -58,7 +59,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
Text( Text(
existingLink!.title, existingLink!.title,
style: TextStyle( style: TextStyle(
color: themeData.primaryColor, color: colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@ -81,7 +82,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
child: Text( child: Text(
existingLink!.description ?? "--", existingLink!.description ?? "--",
style: TextStyle( style: TextStyle(
color: themeData.primaryColor, color: colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -109,7 +110,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
labelText: 'shared_link_edit_description'.tr(), labelText: 'shared_link_edit_description'.tr(),
labelStyle: TextStyle( labelStyle: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: themeData.primaryColor, color: colorScheme.primary,
), ),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
@ -135,7 +136,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
labelText: 'shared_link_edit_password'.tr(), labelText: 'shared_link_edit_password'.tr(),
labelStyle: TextStyle( labelStyle: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: themeData.primaryColor, color: colorScheme.primary,
), ),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
@ -157,7 +158,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
onChanged: newShareLink.value.isEmpty onChanged: newShareLink.value.isEmpty
? (value) => showMetadata.value = value ? (value) => showMetadata.value = value
: null, : null,
activeColor: themeData.primaryColor, activeColor: colorScheme.primary,
dense: true, dense: true,
title: Text( title: Text(
"shared_link_edit_show_meta", "shared_link_edit_show_meta",
@ -173,7 +174,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
onChanged: newShareLink.value.isEmpty onChanged: newShareLink.value.isEmpty
? (value) => allowDownload.value = value ? (value) => allowDownload.value = value
: null, : null,
activeColor: themeData.primaryColor, activeColor: colorScheme.primary,
dense: true, dense: true,
title: Text( title: Text(
"shared_link_edit_allow_download", "shared_link_edit_allow_download",
@ -189,7 +190,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
onChanged: newShareLink.value.isEmpty onChanged: newShareLink.value.isEmpty
? (value) => allowUpload.value = value ? (value) => allowUpload.value = value
: null, : null,
activeColor: themeData.primaryColor, activeColor: colorScheme.primary,
dense: true, dense: true,
title: Text( title: Text(
"shared_link_edit_allow_upload", "shared_link_edit_allow_upload",
@ -205,7 +206,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
onChanged: newShareLink.value.isEmpty onChanged: newShareLink.value.isEmpty
? (value) => editExpiry.value = value ? (value) => editExpiry.value = value
: null, : null,
activeColor: themeData.primaryColor, activeColor: colorScheme.primary,
dense: true, dense: true,
title: Text( title: Text(
"shared_link_edit_change_expiry", "shared_link_edit_change_expiry",
@ -221,7 +222,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
"shared_link_edit_expire_after", "shared_link_edit_expire_after",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: themeData.primaryColor, color: colorScheme.primary,
), ),
).tr(), ).tr(),
enableSearch: false, enableSearch: false,

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.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/providers/album/shared_album.provider.dart';
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart'; import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
@ -83,20 +84,24 @@ class SharingPage extends HookConsumerWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
color: context.primaryColor, color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
subtitle: isOwner subtitle: isOwner
? Text( ? Text(
'album_thumbnail_owned'.tr(), 'album_thumbnail_owned'.tr(),
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
) )
: album.ownerName != null : album.ownerName != null
? Text( ? Text(
'album_thumbnail_shared_by' 'album_thumbnail_shared_by'
.tr(args: [album.ownerName!]), .tr(args: [album.ownerName!]),
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
) )
: null, : null,
onTap: () => context onTap: () => context
@ -166,11 +171,13 @@ class SharingPage extends HookConsumerWidget {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Card( child: Card(
elevation: 0, elevation: 0,
shape: const RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
side: BorderSide( side: BorderSide(
color: Colors.grey, color: context.isDarkTheme
width: 0.5, ? const Color(0xFF383838)
: Colors.black12,
width: 1,
), ),
), ),
child: Padding( child: Padding(

View file

@ -1,3 +1,4 @@
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
enum AppSettingsEnum<T> { enum AppSettingsEnum<T> {
@ -8,6 +9,21 @@ enum AppSettingsEnum<T> {
"themeMode", "themeMode",
"system", "system",
), // "light","dark","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), tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false), dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false),
groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0), groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),

View file

@ -1,10 +1,22 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.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/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.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 var themeMode = ref
.watch(appSettingsServiceProvider) .watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.themeMode); .getSetting(AppSettingsEnum.themeMode);
@ -20,266 +32,241 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
} }
}); });
final ThemeData base = ThemeData( final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) {
chipTheme: const ChipThemeData( var appSettingsProvider = ref.watch(appSettingsServiceProvider);
side: BorderSide.none, var primaryColorName =
), appSettingsProvider.getSetting(AppSettingsEnum.primaryColor);
sliderTheme: const SliderThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
),
);
final ThemeData immichLightTheme = ThemeData( debugPrint("Current theme preset $primaryColorName");
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,
),
);
final ThemeData immichDarkTheme = ThemeData( try {
useMaterial3: true, return ImmichColorPreset.values
brightness: Brightness.dark, .firstWhere((e) => e.name == primaryColorName);
primarySwatch: Colors.indigo, } catch (e) {
primaryColor: immichDarkThemePrimaryColor, debugPrint(
colorScheme: ColorScheme.fromSeed( "Theme preset $primaryColorName not found. Applying default preset.",
seedColor: immichDarkThemePrimaryColor, );
brightness: Brightness.dark, appSettingsProvider.setSetting(
), AppSettingsEnum.primaryColor,
scaffoldBackgroundColor: immichDarkBackgroundColor, defaultColorPresetName,
hintColor: Colors.grey[600], );
fontFamily: 'Overpass', return defaultColorPreset;
snackBarTheme: SnackBarThemeData( }
contentTextStyle: const TextStyle( });
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor, final dynamicThemeSettingProvider = StateProvider<bool>((ref) {
fontWeight: FontWeight.bold, 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], dark: theme.dark.copyWith(
), surface: const Color(0xFF131313),
textButtonTheme: TextButtonThemeData( onSurface: const Color(0xFFE2E2E2),
style: TextButton.styleFrom( surfaceContainerLowest: const Color(0xFF0E0E0E),
foregroundColor: immichDarkThemePrimaryColor, 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', ThemeData getThemeData({required ColorScheme colorScheme}) {
color: immichDarkThemePrimaryColor, var isDark = colorScheme.brightness == Brightness.dark;
fontWeight: FontWeight.bold, var primaryColor = colorScheme.primary;
fontSize: 18,
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), fontFamily: 'Overpass',
foregroundColor: immichDarkThemePrimaryColor, snackBarTheme: SnackBarThemeData(
elevation: 0, contentTextStyle: TextStyle(
scrolledUnderElevation: 0, fontFamily: 'Overpass',
centerTitle: true, color: primaryColor,
), fontWeight: FontWeight.bold,
bottomNavigationBarTheme: const BottomNavigationBarThemeData( ),
type: BottomNavigationBarType.fixed, backgroundColor: colorScheme.surfaceContainerHighest,
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),
), ),
displayMedium: TextStyle( appBarTheme: AppBarTheme(
fontSize: 14, titleTextStyle: TextStyle(
fontWeight: FontWeight.bold, color: primaryColor,
color: Color.fromARGB(255, 255, 255, 255), fontFamily: 'Overpass',
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor:
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
foregroundColor: primaryColor,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
), ),
displaySmall: TextStyle( textTheme: TextTheme(
fontSize: 12, displayLarge: TextStyle(
fontWeight: FontWeight.bold, fontSize: 26,
color: immichDarkThemePrimaryColor, fontWeight: FontWeight.bold,
), color: isDark ? Colors.white : primaryColor,
titleSmall: TextStyle( ),
fontSize: 16.0, displayMedium: TextStyle(
fontWeight: FontWeight.bold, fontSize: 14,
), fontWeight: FontWeight.bold,
titleMedium: TextStyle( color: isDark ? Colors.white : Colors.black87,
fontSize: 18.0, ),
fontWeight: FontWeight.bold, displaySmall: TextStyle(
), fontSize: 12,
titleLarge: TextStyle( fontWeight: FontWeight.bold,
fontSize: 26.0, color: primaryColor,
fontWeight: FontWeight.bold, ),
), titleSmall: const TextStyle(
), fontSize: 16.0,
cardColor: Colors.grey[900], fontWeight: FontWeight.bold,
elevatedButtonTheme: ElevatedButtonThemeData( ),
style: ElevatedButton.styleFrom( titleMedium: const TextStyle(
foregroundColor: Colors.black87, fontSize: 18.0,
backgroundColor: immichDarkThemePrimaryColor, fontWeight: FontWeight.bold,
), ),
), titleLarge: const TextStyle(
chipTheme: base.chipTheme, fontSize: 26.0,
sliderTheme: base.sliderTheme, fontWeight: FontWeight.bold,
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],
), ),
), ),
), elevatedButtonTheme: ElevatedButtonThemeData(
dialogTheme: const DialogTheme( style: ElevatedButton.styleFrom(
surfaceTintColor: Colors.transparent, backgroundColor: primaryColor,
), foregroundColor: isDark ? Colors.black87 : Colors.white,
inputDecorationTheme: const InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: immichDarkThemePrimaryColor,
), ),
), ),
labelStyle: TextStyle( chipTheme: const ChipThemeData(
color: immichDarkThemePrimaryColor, side: BorderSide.none,
), ),
hintStyle: TextStyle( sliderTheme: const SliderThemeData(
fontSize: 14.0, thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
fontWeight: FontWeight.normal, trackHeight: 2.0,
), ),
), bottomNavigationBarTheme: const BottomNavigationBarThemeData(
textSelectionTheme: const TextSelectionThemeData( type: BottomNavigationBarType.fixed,
cursorColor: immichDarkThemePrimaryColor, ),
), 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,
),
);
}

View file

@ -1,12 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
class AlbumActionOutlinedButton extends StatelessWidget { class AlbumActionFilledButton extends StatelessWidget {
final VoidCallback? onPressed; final VoidCallback? onPressed;
final String labelText; final String labelText;
final IconData iconData; final IconData iconData;
const AlbumActionOutlinedButton({ const AlbumActionFilledButton({
super.key, super.key,
this.onPressed, this.onPressed,
required this.labelText, required this.labelText,
@ -17,18 +17,13 @@ class AlbumActionOutlinedButton extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 16.0), padding: const EdgeInsets.only(right: 16.0),
child: OutlinedButton.icon( child: FilledButton.icon(
style: OutlinedButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10), padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
), ),
side: BorderSide( backgroundColor: context.colorScheme.surfaceContainerHigh,
width: 1,
color: context.isDarkTheme
? const Color.fromARGB(255, 63, 63, 63)
: const Color.fromARGB(255, 206, 206, 206),
),
), ),
icon: Icon( icon: Icon(
iconData, iconData,

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/store.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'; import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
class AlbumThumbnailCard extends StatelessWidget { class AlbumThumbnailCard extends StatelessWidget {
@ -23,8 +24,6 @@ class AlbumThumbnailCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isDarkTheme = context.isDarkTheme;
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
var cardSize = constraints.maxWidth; var cardSize = constraints.maxWidth;
@ -34,12 +33,13 @@ class AlbumThumbnailCard extends StatelessWidget {
height: cardSize, height: cardSize,
width: cardSize, width: cardSize,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[800] : Colors.grey[200], color: context.colorScheme.surfaceContainerHigh,
), ),
child: Center( child: Center(
child: Icon( child: Icon(
Icons.no_photography, Icons.no_photography,
size: cardSize * .15, size: cardSize * .15,
color: context.colorScheme.primary,
), ),
), ),
); );
@ -65,6 +65,9 @@ class AlbumThumbnailCard extends StatelessWidget {
return RichText( return RichText(
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
text: TextSpan( text: TextSpan(
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
children: [ children: [
TextSpan( TextSpan(
text: album.assetCount == 1 text: album.assetCount == 1
@ -72,14 +75,9 @@ class AlbumThumbnailCard extends StatelessWidget {
.tr(args: ['${album.assetCount}']) .tr(args: ['${album.assetCount}'])
: 'album_thumbnail_card_items' : 'album_thumbnail_card_items'
.tr(args: ['${album.assetCount}']), .tr(args: ['${album.assetCount}']),
style: context.textTheme.bodyMedium,
), ),
if (owner != null) const TextSpan(text: ' · '), if (owner != null) const TextSpan(text: ' · '),
if (owner != null) if (owner != null) TextSpan(text: owner),
TextSpan(
text: owner,
style: context.textTheme.bodyMedium,
),
], ],
), ),
); );
@ -112,7 +110,7 @@ class AlbumThumbnailCard extends StatelessWidget {
album.name, album.name,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
color: context.primaryColor, color: context.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),

View file

@ -20,8 +20,6 @@ class AlbumTitleTextField extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isDarkTheme = context.isDarkTheme;
return TextField( return TextField(
onChanged: (v) { onChanged: (v) {
if (v.isEmpty) { if (v.isEmpty) {
@ -35,7 +33,7 @@ class AlbumTitleTextField extends ConsumerWidget {
focusNode: albumTitleTextFieldFocusNode, focusNode: albumTitleTextFieldFocusNode,
style: TextStyle( style: TextStyle(
fontSize: 28, fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700], color: context.colorScheme.onSurface,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
controller: albumTitleController, controller: albumTitleController,
@ -61,24 +59,18 @@ class AlbumTitleTextField extends ConsumerWidget {
splashRadius: 10, splashRadius: 10,
) )
: null, : null,
enabledBorder: OutlineInputBorder( enabledBorder: const OutlineInputBorder(
borderSide: const BorderSide(color: Colors.transparent), borderSide: BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: const OutlineInputBorder(
borderSide: const BorderSide(color: Colors.transparent), borderSide: BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10),
), ),
hintText: 'share_add_title'.tr(), hintText: 'share_add_title'.tr(),
hintStyle: TextStyle( hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
fontSize: 28, fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
), ),
focusColor: Colors.grey[300], focusColor: Colors.grey[300],
fillColor: isDarkTheme fillColor: context.scaffoldBackgroundColor,
? const Color.fromARGB(255, 32, 33, 35)
: Colors.grey[200],
filled: isAlbumTitleTextFieldFocus.value, filled: isAlbumTitleTextFieldFocus.value,
), ),
); );

View file

@ -95,7 +95,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
'action_common_confirm', 'action_common_confirm',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: !context.isDarkTheme ? Colors.red : Colors.red[300], color: context.colorScheme.error,
), ),
).tr(), ).tr(),
), ),

View file

@ -73,24 +73,18 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
splashRadius: 10, splashRadius: 10,
) )
: null, : null,
enabledBorder: OutlineInputBorder( enabledBorder: const OutlineInputBorder(
borderSide: const BorderSide(color: Colors.transparent), borderSide: BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: const OutlineInputBorder(
borderSide: const BorderSide(color: Colors.transparent), borderSide: BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10),
), ),
focusColor: Colors.grey[300], focusColor: Colors.grey[300],
fillColor: context.isDarkTheme fillColor: context.scaffoldBackgroundColor,
? const Color.fromARGB(255, 32, 33, 35)
: Colors.grey[200],
filled: titleFocusNode.hasFocus, filled: titleFocusNode.hasFocus,
hintText: 'share_add_title'.tr(), hintText: 'share_add_title'.tr(),
hintStyle: TextStyle( hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
fontSize: 28, fontSize: 28,
color: context.isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
), ),
), ),
), ),

View file

@ -281,7 +281,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
ScrollController scrollController, ScrollController scrollController,
) { ) {
return Card( return Card(
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100], color: context.colorScheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
elevation: 18.0, elevation: 18.0,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(

View file

@ -22,12 +22,15 @@ class DisableMultiSelectButton extends ConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => onPressed(), onPressed: () => onPressed(),
icon: const Icon(Icons.close_rounded), icon: Icon(
Icons.close_rounded,
color: context.colorScheme.onPrimary,
),
label: Text( label: Text(
'$selectedItemCount', '$selectedItemCount',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
height: 2.5, height: 2.5,
color: context.isDarkTheme ? Colors.black : Colors.white, color: context.colorScheme.onPrimary,
), ),
), ),
), ),

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.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/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
@ -74,9 +75,9 @@ class GroupDividerTitle extends HookConsumerWidget {
Icons.check_circle_rounded, Icons.check_circle_rounded,
color: context.primaryColor, color: context.primaryColor,
) )
: const Icon( : Icon(
Icons.check_circle_outline_rounded, Icons.check_circle_outline_rounded,
color: Colors.grey, color: context.colorScheme.onSurfaceSecondary,
), ),
), ),
], ],

View file

@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/collection_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/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/asset_drag_region.dart';
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
@ -266,7 +267,9 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
scrollStateListener: dragScrolling, scrollStateListener: dragScrolling,
itemPositionsListener: _itemPositionsListener, itemPositionsListener: _itemPositionsListener,
controller: _itemScrollController, controller: _itemScrollController,
backgroundColor: context.themeData.hintColor, backgroundColor: context.isDarkTheme
? context.colorScheme.primary.darken(amount: .5)
: context.colorScheme.primary,
labelTextBuilder: _labelBuilder, labelTextBuilder: _labelBuilder,
padding: appBarOffset() padding: appBarOffset()
? const EdgeInsets.only(top: 60) ? const EdgeInsets.only(top: 60)

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/asset.entity.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/widgets/common/immich_thumbnail.dart';
import 'package:immich_mobile/utils/storage_indicator.dart'; import 'package:immich_mobile/utils/storage_indicator.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
@ -42,8 +43,8 @@ class ThumbnailImage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final assetContainerColor = context.isDarkTheme final assetContainerColor = context.isDarkTheme
? Colors.blueGrey ? context.primaryColor.darken(amount: 0.6)
: context.themeData.primaryColorLight; : 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 // 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; final isFromDto = asset.id == Isar.autoIncrement;
@ -192,8 +193,8 @@ class ThumbnailImage extends ConsumerWidget {
bottom: 5, bottom: 5,
child: Icon( child: Icon(
storageIcon(asset), storageIcon(asset),
color: Colors.white, color: Colors.white.withOpacity(.8),
size: 18, size: 16,
), ),
), ),
if (asset.isFavorite) if (asset.isFavorite)

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class ThumbnailPlaceholder extends StatelessWidget { class ThumbnailPlaceholder extends StatelessWidget {
final EdgeInsets margin; final EdgeInsets margin;
@ -13,25 +14,20 @@ class ThumbnailPlaceholder extends StatelessWidget {
this.height = 250, this.height = 250,
}); });
static const _brightColors = [
Color(0xFFF1F3F4),
Color(0xFFB4B6B8),
];
static const _darkColors = [
Color(0xFF3B3F42),
Color(0xFF2B2F32),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var gradientColors = [
context.colorScheme.surfaceContainer,
context.colorScheme.surfaceContainer.darken(amount: .1),
];
return Container( return Container(
width: width, width: width,
height: height, height: height,
margin: margin, margin: margin,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: context.isDarkTheme ? _darkColors : _brightColors, colors: gradientColors,
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
), ),

View file

@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/exif_info.entity.dart'; import 'package:immich_mobile/entities/exif_info.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/asset.entity.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/providers/user.provider.dart';
import 'package:immich_mobile/services/asset_description.service.dart'; import 'package:immich_mobile/services/asset_description.service.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
@ -23,7 +24,6 @@ class DescriptionInput extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final controller = useTextEditingController(); final controller = useTextEditingController();
final focusNode = useFocusNode(); final focusNode = useFocusNode();
final isFocus = useState(false); final isFocus = useState(false);
@ -71,7 +71,7 @@ class DescriptionInput extends HookConsumerWidget {
}, },
icon: Icon( icon: Icon(
Icons.cancel_rounded, Icons.cancel_rounded,
color: Colors.grey[500], color: context.colorScheme.onSurfaceSecondary,
), ),
splashRadius: 10, splashRadius: 10,
); );
@ -100,9 +100,6 @@ class DescriptionInput extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'description_input_hint_text'.tr(), hintText: 'description_input_hint_text'.tr(),
border: InputBorder.none, border: InputBorder.none,
hintStyle: context.textTheme.labelLarge?.copyWith(
color: textColor.withOpacity(0.5),
),
suffixIcon: suffixIcon, suffixIcon: suffixIcon,
), ),
); );

View file

@ -22,7 +22,7 @@ class ExifBottomSheet extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final assetWithExif = ref.watch(assetDetailProvider(asset)); 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; final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo;
// Format the date time with the timezone // Format the date time with the timezone
final (dt, timeZone) = final (dt, timeZone) =

View file

@ -178,6 +178,7 @@ class TopControlAppBar extends HookConsumerWidget {
actionsIconTheme: const IconThemeData( actionsIconTheme: const IconThemeData(
size: iconSize, size: iconSize,
), ),
shape: const Border(),
actions: [ actions: [
if (asset.isRemote && isOwner) buildFavoriteButton(a), if (asset.isRemote && isOwner) buildFavoriteButton(a),
if (asset.livePhotoVideoId != null) buildLivePhotoButton(), if (asset.livePhotoVideoId != null) buildLivePhotoButton(),

View file

@ -47,22 +47,22 @@ class AlbumInfoListTile extends HookConsumerWidget {
buildIcon() { buildIcon() {
if (isSelected) { if (isSelected) {
return const Icon( return Icon(
Icons.check_circle_rounded, Icons.check_circle_rounded,
color: Colors.green, color: context.colorScheme.primary,
); );
} }
if (isExcluded) { if (isExcluded) {
return const Icon( return Icon(
Icons.remove_circle_rounded, Icons.remove_circle_rounded,
color: Colors.red, color: context.colorScheme.error,
); );
} }
return Icon( return Icon(
Icons.circle, Icons.circle,
color: context.isDarkTheme ? Colors.grey[400] : Colors.black45, color: context.colorScheme.surfaceContainerHighest,
); );
} }

View file

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class BackupInfoCard extends StatelessWidget { class BackupInfoCard extends StatelessWidget {
final String title; final String title;
@ -19,9 +20,7 @@ class BackupInfoCard extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), // if you need this borderRadius: BorderRadius.circular(20), // if you need this
side: BorderSide( side: BorderSide(
color: context.isDarkTheme color: context.colorScheme.outlineVariant,
? const Color.fromARGB(255, 56, 56, 56)
: Colors.black12,
width: 1, width: 1,
), ),
), ),
@ -38,7 +37,9 @@ class BackupInfoCard extends StatelessWidget {
padding: const EdgeInsets.only(top: 4.0, right: 18.0), padding: const EdgeInsets.only(top: 4.0, right: 18.0),
child: Text( child: Text(
subtitle, subtitle,
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
), ),
), ),
trailing: Column( trailing: Column(

View file

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/models/backup/backup_state.model.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/backup.provider.dart';
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
@ -82,22 +83,20 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
Widget buildAssetInfoTable() { Widget buildAssetInfoTable() {
return Table( return Table(
border: TableBorder.all( border: TableBorder.all(
color: context.themeData.primaryColorLight, color: context.colorScheme.outlineVariant,
width: 1, width: 1,
), ),
children: [ children: [
TableRow( TableRow(
decoration: const BoxDecoration(
// color: Colors.grey[100],
),
children: [ children: [
TableCell( TableCell(
verticalAlignment: TableCellVerticalAlignment.middle, verticalAlignment: TableCellVerticalAlignment.middle,
child: Padding( child: Padding(
padding: const EdgeInsets.all(6.0), padding: const EdgeInsets.all(6.0),
child: const Text( child: Text(
'backup_controller_page_filename', 'backup_controller_page_filename',
style: TextStyle( style: TextStyle(
color: context.colorScheme.onSurfaceSecondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 10.0, fontSize: 10.0,
), ),
@ -109,17 +108,15 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
], ],
), ),
TableRow( TableRow(
decoration: const BoxDecoration(
// color: Colors.grey[200],
),
children: [ children: [
TableCell( TableCell(
verticalAlignment: TableCellVerticalAlignment.middle, verticalAlignment: TableCellVerticalAlignment.middle,
child: Padding( child: Padding(
padding: const EdgeInsets.all(6.0), padding: const EdgeInsets.all(6.0),
child: const Text( child: Text(
"backup_controller_page_created", "backup_controller_page_created",
style: TextStyle( style: TextStyle(
color: context.colorScheme.onSurfaceSecondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 10.0, fontSize: 10.0,
), ),
@ -131,16 +128,14 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
], ],
), ),
TableRow( TableRow(
decoration: const BoxDecoration(
// color: Colors.grey[100],
),
children: [ children: [
TableCell( TableCell(
child: Padding( child: Padding(
padding: const EdgeInsets.all(6.0), padding: const EdgeInsets.all(6.0),
child: const Text( child: Text(
"backup_controller_page_id", "backup_controller_page_id",
style: TextStyle( style: TextStyle(
color: context.colorScheme.onSurfaceSecondary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 10.0, fontSize: 10.0,
), ),
@ -181,8 +176,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
child: LinearProgressIndicator( child: LinearProgressIndicator(
minHeight: 10.0, minHeight: 10.0,
value: uploadProgress / 100.0, value: uploadProgress / 100.0,
backgroundColor: Colors.grey, borderRadius: const BorderRadius.all(Radius.circular(10.0)),
color: context.primaryColor,
), ),
), ),
Text( Text(
@ -214,8 +208,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
child: LinearProgressIndicator( child: LinearProgressIndicator(
minHeight: 10.0, minHeight: 10.0,
value: uploadProgress / 100.0, value: uploadProgress / 100.0,
backgroundColor: Colors.grey, borderRadius: const BorderRadius.all(Radius.circular(10.0)),
color: context.primaryColor,
), ),
), ),
Text( Text(

View file

@ -57,6 +57,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
? 'assets/immich-text-dark.png' ? 'assets/immich-text-dark.png'
: 'assets/immich-text-light.png', : 'assets/immich-text-light.png',
height: 16, height: 16,
color: context.primaryColor,
), ),
), ),
], ],
@ -88,7 +89,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
buildSettingButton() { buildSettingButton() {
return buildActionButton( return buildActionButton(
Icons.settings_rounded, Icons.settings_outlined,
"profile_drawer_settings", "profile_drawer_settings",
() => context.pushRoute(const SettingsRoute()), () => context.pushRoute(const SettingsRoute()),
); );
@ -146,9 +147,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.isDarkTheme color: context.colorScheme.surface,
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
), ),
child: ListTile( child: ListTile(
minLeadingWidth: 50, minLeadingWidth: 50,
@ -171,10 +170,10 @@ class ImmichAppBarDialog extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: LinearProgressIndicator( child: LinearProgressIndicator(
minHeight: 5.0, minHeight: 10.0,
value: percentage, value: percentage,
backgroundColor: Colors.grey, borderRadius:
color: theme.primaryColor, const BorderRadius.all(Radius.circular(10.0)),
), ),
), ),
Padding( Padding(
@ -248,7 +247,6 @@ class ImmichAppBarDialog extends HookConsumerWidget {
right: horizontalPadding, right: horizontalPadding,
bottom: isHorizontal ? 20 : 100, bottom: isHorizontal ? 20 : 100,
), ),
backgroundColor: theme.cardColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/upload_profile_image.provider.dart'; import 'package:immich_mobile/providers/upload_profile_image.provider.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
@ -79,9 +80,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.isDarkTheme color: context.colorScheme.surface,
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10), topLeft: Radius.circular(10),
topRight: Radius.circular(10), topRight: Radius.circular(10),
@ -99,9 +98,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
bottom: -5, bottom: -5,
right: -8, right: -8,
child: Material( child: Material(
color: context.isDarkTheme color: context.colorScheme.surfaceContainerHighest,
? Colors.blueGrey[800]
: Colors.white,
elevation: 3, elevation: 3,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
@ -129,7 +126,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
subtitle: Text( subtitle: Text(
authState.userEmail, authState.userEmail,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: context.textTheme.bodySmall?.color?.withAlpha(200), color: context.colorScheme.onSurfaceSecondary,
), ),
), ),
), ),

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:immich_mobile/providers/server_info.provider.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), padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.isDarkTheme color: context.colorScheme.surface,
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10), bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10), bottomRight: Radius.circular(10),
@ -71,10 +70,7 @@ class AppBarServerInfo extends HookConsumerWidget {
), ),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider( child: Divider(thickness: 1),
color: Color.fromARGB(101, 201, 201, 201),
thickness: 1,
),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -100,8 +96,7 @@ class AppBarServerInfo extends HookConsumerWidget {
"${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}", "${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}",
style: TextStyle( style: TextStyle(
fontSize: contentFontSize, fontSize: contentFontSize,
color: context.textTheme.labelSmall?.color color: context.colorScheme.onSurfaceSecondary,
?.withOpacity(0.5),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@ -111,10 +106,7 @@ class AppBarServerInfo extends HookConsumerWidget {
), ),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider( child: Divider(thickness: 1),
color: Color.fromARGB(101, 201, 201, 201),
thickness: 1,
),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -142,8 +134,7 @@ class AppBarServerInfo extends HookConsumerWidget {
: "--", : "--",
style: TextStyle( style: TextStyle(
fontSize: contentFontSize, fontSize: contentFontSize,
color: context.textTheme.labelSmall?.color color: context.colorScheme.onSurfaceSecondary,
?.withOpacity(0.5),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@ -153,10 +144,7 @@ class AppBarServerInfo extends HookConsumerWidget {
), ),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider( child: Divider(thickness: 1),
color: Color.fromARGB(101, 201, 201, 201),
thickness: 1,
),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -197,8 +185,7 @@ class AppBarServerInfo extends HookConsumerWidget {
getServerUrl() ?? '--', getServerUrl() ?? '--',
style: TextStyle( style: TextStyle(
fontSize: contentFontSize, fontSize: contentFontSize,
color: context.textTheme.labelSmall?.color color: context.colorScheme.onSurfaceSecondary,
?.withOpacity(0.5),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -211,10 +198,7 @@ class AppBarServerInfo extends HookConsumerWidget {
), ),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider( child: Divider(thickness: 1),
color: Color.fromARGB(101, 201, 201, 201),
thickness: 1,
),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -255,8 +239,7 @@ class AppBarServerInfo extends HookConsumerWidget {
: "--", : "--",
style: TextStyle( style: TextStyle(
fontSize: contentFontSize, fontSize: contentFontSize,
color: context.textTheme.labelSmall?.color color: context.colorScheme.onSurfaceSecondary,
?.withOpacity(0.5),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),

View file

@ -47,7 +47,7 @@ class ConfirmDialog extends StatelessWidget {
child: Text( child: Text(
ok, ok,
style: TextStyle( style: TextStyle(
color: Colors.red[400], color: context.colorScheme.error,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
).tr(), ).tr(),

View file

@ -111,7 +111,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
buildBackupIndicator() { buildBackupIndicator() {
final indicatorIcon = getBackupBadgeIcon(); final indicatorIcon = getBackupBadgeIcon();
final badgeBackground = isDarkTheme ? Colors.blueGrey[800] : Colors.white; final badgeBackground = context.colorScheme.surfaceContainer;
return InkWell( return InkWell(
onTap: () => context.pushRoute(const BackupControllerRoute()), onTap: () => context.pushRoute(const BackupControllerRoute()),
@ -123,7 +123,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: badgeBackground, color: badgeBackground,
border: Border.all( border: Border.all(
color: isDarkTheme ? Colors.black : Colors.grey, color: context.colorScheme.outline.withOpacity(.3),
), ),
borderRadius: BorderRadius.circular(widgetSize / 2), borderRadius: BorderRadius.circular(widgetSize / 2),
), ),

View file

@ -21,6 +21,7 @@ class ImmichTitleText extends StatelessWidget {
), ),
width: fontSize * 4, width: fontSize * 4,
filterQuality: FilterQuality.high, filterQuality: FilterQuality.high,
color: context.primaryColor,
); );
} }
} }

View file

@ -51,9 +51,9 @@ class ImmichToast {
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(5.0),
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[50], color: context.colorScheme.surfaceContainer,
border: Border.all( border: Border.all(
color: Colors.black12, color: context.colorScheme.outline.withOpacity(.5),
width: 1, width: 1,
), ),
), ),

View file

@ -51,7 +51,7 @@ class ChangePasswordForm extends HookConsumerWidget {
), ),
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey[700], color: context.colorScheme.onSurface,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
@ -191,9 +191,6 @@ class ChangePasswordButton extends ConsumerWidget {
return ElevatedButton( return ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard, visualDensity: VisualDensity.standard,
backgroundColor: context.primaryColor,
foregroundColor: Colors.grey[50],
elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
), ),
onPressed: onPressed, onPressed: onPressed,

View file

@ -70,6 +70,7 @@ class _MapThemeOverideState extends ConsumerState<MapThemeOveride>
Widget build(BuildContext context) { Widget build(BuildContext context) {
_theme = widget.themeMode ?? _theme = widget.themeMode ??
ref.watch(mapStateNotifierProvider.select((v) => v.themeMode)); ref.watch(mapStateNotifierProvider.select((v) => v.themeMode));
var appTheme = ref.watch(immichThemeProvider);
useValueChanged<ThemeMode, void>(_theme, (_, __) { useValueChanged<ThemeMode, void>(_theme, (_, __) {
if (_theme == ThemeMode.system) { if (_theme == ThemeMode.system) {
@ -83,7 +84,9 @@ class _MapThemeOverideState extends ConsumerState<MapThemeOveride>
}); });
return Theme( return Theme(
data: _isDarkTheme ? immichDarkTheme : immichLightTheme, data: _isDarkTheme
? getThemeData(colorScheme: appTheme.dark)
: getThemeData(colorScheme: appTheme.light),
child: widget.mapBuilder.call( child: widget.mapBuilder.call(
ref.watch( ref.watch(
mapStateNotifierProvider.select( mapStateNotifierProvider.select(

View file

@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
class MemoryEpilogue extends StatefulWidget { class MemoryEpilogue extends StatefulWidget {
@ -49,24 +48,26 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( Icon(
Icons.check_circle_outline_sharp, Icons.check_circle_outline_sharp,
color: immichDarkThemePrimaryColor, color: context.isDarkTheme
? context.colorScheme.primary
: context.colorScheme.inversePrimary,
size: 64.0, size: 64.0,
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Text( Text(
"memories_all_caught_up", "memories_all_caught_up",
style: Theme.of(context).textTheme.headlineMedium?.copyWith( style: context.textTheme.headlineMedium?.copyWith(
color: Colors.white, color: Colors.white,
), ),
).tr(), ).tr(),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Text( Text(
"memories_check_back_tomorrow", "memories_check_back_tomorrow",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
color: Colors.white, color: Colors.white,
), ),
).tr(), ).tr(),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
TextButton( TextButton(
@ -74,7 +75,9 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
child: Text( child: Text(
"memories_start_over", "memories_start_over",
style: context.textTheme.displayMedium?.copyWith( style: context.textTheme.displayMedium?.copyWith(
color: immichDarkThemePrimaryColor, color: context.isDarkTheme
? context.colorScheme.primary
: context.colorScheme.inversePrimary,
), ),
).tr(), ).tr(),
), ),
@ -108,9 +111,9 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
), ),
Text( Text(
"memories_swipe_to_close", "memories_swipe_to_close",
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
color: Colors.white, color: Colors.white,
), ),
).tr(), ).tr(),
], ],
), ),

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; 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 { class MemoryProgressIndicator extends StatelessWidget {
/// The number of ticks in the progress indicator /// The number of ticks in the progress indicator
@ -25,8 +25,11 @@ class MemoryProgressIndicator extends StatelessWidget {
children: [ children: [
LinearProgressIndicator( LinearProgressIndicator(
value: value, value: value,
backgroundColor: Colors.grey[600], borderRadius: const BorderRadius.all(Radius.circular(10.0)),
color: immichDarkThemePrimaryColor, backgroundColor: Colors.grey[800],
color: context.isDarkTheme
? context.colorScheme.primary
: context.colorScheme.inversePrimary,
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,

View file

@ -22,9 +22,9 @@ class SearchFilterChip extends StatelessWidget {
onTap: onTap, onTap: onTap,
child: Card( child: Card(
elevation: 0, elevation: 0,
color: context.primaryColor.withAlpha(25), color: context.primaryColor.withOpacity(.5),
shape: StadiumBorder( shape: StadiumBorder(
side: BorderSide(color: context.primaryColor), side: BorderSide(color: context.colorScheme.secondaryContainer),
), ),
child: Padding( child: Padding(
padding: padding:
@ -47,8 +47,9 @@ class SearchFilterChip extends StatelessWidget {
onTap: onTap, onTap: onTap,
child: Card( child: Card(
elevation: 0, elevation: 0,
shape: shape: StadiumBorder(
StadiumBorder(side: BorderSide(color: Colors.grey.withAlpha(100))), side: BorderSide(color: context.colorScheme.outline.withOpacity(.5)),
),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0), padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
child: Row( child: Row(

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class ThumbnailWithInfoContainer extends StatelessWidget { class ThumbnailWithInfoContainer extends StatelessWidget {
const ThumbnailWithInfoContainer({ const ThumbnailWithInfoContainer({
@ -25,7 +26,14 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius), 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( foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius), borderRadius: BorderRadius.circular(borderRadius),
@ -34,7 +42,7 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
begin: FractionalOffset.topCenter, begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter, end: FractionalOffset.bottomCenter,
colors: [ colors: [
Colors.grey.withOpacity(0.0), Colors.transparent,
label == '' label == ''
? Colors.black.withOpacity(0.1) ? Colors.black.withOpacity(0.1)
: Colors.black.withOpacity(0.5), : Colors.black.withOpacity(0.5),

View file

@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
class CustomeProxyHeaderSettings extends StatelessWidget { class CustomeProxyHeaderSettings extends StatelessWidget {
@ -20,8 +21,8 @@ class CustomeProxyHeaderSettings extends StatelessWidget {
), ),
subtitle: Text( subtitle: Text(
"headers_settings_tile_subtitle".tr(), "headers_settings_tile_subtitle".tr(),
style: const TextStyle( style: context.textTheme.bodyMedium?.copyWith(
fontSize: 14, color: context.colorScheme.onSurfaceSecondary,
), ),
), ),
onTap: () => context.pushRoute(const HeaderSettingsRoute()), onTap: () => context.pushRoute(const HeaderSettingsRoute()),

View file

@ -40,9 +40,7 @@ class LanguageSettings extends HookConsumerWidget {
), ),
), ),
backgroundColor: WidgetStatePropertyAll<Color>( backgroundColor: WidgetStatePropertyAll<Color>(
context.isDarkTheme context.colorScheme.surfaceContainer,
? Colors.grey[900]!
: context.scaffoldBackgroundColor,
), ),
), ),
menuHeight: context.height * 0.5, menuHeight: context.height * 0.5,

View file

@ -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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
class LocalStorageSettings extends HookConsumerWidget { class LocalStorageSettings extends HookConsumerWidget {
@ -35,10 +36,10 @@ class LocalStorageSettings extends HookConsumerWidget {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
).tr(args: ["${cacheItemCount.value}"]), ).tr(args: ["${cacheItemCount.value}"]),
subtitle: const Text( subtitle: Text(
"cache_settings_duplicated_assets_subtitle", "cache_settings_duplicated_assets_subtitle",
style: TextStyle( style: context.textTheme.bodyMedium?.copyWith(
fontSize: 14, color: context.colorScheme.onSurfaceSecondary,
), ),
).tr(), ).tr(),
trailing: TextButton( trailing: TextButton(

View file

@ -15,6 +15,9 @@ class PreferenceSetting extends StatelessWidget {
HapticSetting(), HapticSetting(),
]; ];
return const SettingsSubPageScaffold(settings: preferenceSettings); return const SettingsSubPageScaffold(
settings: preferenceSettings,
showDivider: true,
);
} }
} }

View file

@ -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,
),
),
],
),
);
}
}

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
@ -16,11 +17,16 @@ class ThemeSetting extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final currentThemeString = useAppSettingsState(AppSettingsEnum.themeMode); 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 isDarkTheme = useValueNotifier(currentTheme.value == ThemeMode.dark);
final isSystemTheme = final isSystemTheme =
useValueNotifier(currentTheme.value == ThemeMode.system); useValueNotifier(currentTheme.value == ThemeMode.system);
final applyThemeToBackgroundSetting =
useAppSettingsState(AppSettingsEnum.colorfulInterface);
final applyThemeToBackgroundProvider =
useValueNotifier(ref.read(colorfulInterfaceSettingProvider));
useValueChanged( useValueChanged(
currentThemeString.value, currentThemeString.value,
(_, __) => currentTheme.value = switch (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) { void onThemeChange(bool isDark) {
if (isDark) { if (isDark) {
ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark; ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
currentThemeString.value = "dark"; currentThemeString.value = "dark";
} else { } else {
ref.watch(immichThemeProvider.notifier).state = ThemeMode.light; ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
currentThemeString.value = "light"; currentThemeString.value = "light";
} }
} }
@ -44,7 +56,7 @@ class ThemeSetting extends HookConsumerWidget {
if (isSystem) { if (isSystem) {
currentThemeString.value = "system"; currentThemeString.value = "system";
isSystemTheme.value = true; isSystemTheme.value = true;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.system; ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.system;
} else { } else {
final currentSystemBrightness = final currentSystemBrightness =
MediaQuery.platformBrightnessOf(context); MediaQuery.platformBrightnessOf(context);
@ -52,14 +64,20 @@ class ThemeSetting extends HookConsumerWidget {
isDarkTheme.value = currentSystemBrightness == Brightness.dark; isDarkTheme.value = currentSystemBrightness == Brightness.dark;
if (currentSystemBrightness == Brightness.light) { if (currentSystemBrightness == Brightness.light) {
currentThemeString.value = "light"; currentThemeString.value = "light";
ref.watch(immichThemeProvider.notifier).state = ThemeMode.light; ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
} else if (currentSystemBrightness == Brightness.dark) { } else if (currentSystemBrightness == Brightness.dark) {
currentThemeString.value = "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( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -75,6 +93,13 @@ class ThemeSetting extends HookConsumerWidget {
title: 'theme_setting_dark_mode_switch'.tr(), title: 'theme_setting_dark_mode_switch'.tr(),
onChanged: onThemeChange, onChanged: onThemeChange,
), ),
const PrimaryColorSetting(),
SettingsSwitchListTile(
valueNotifier: applyThemeToBackgroundProvider,
title: "theme_setting_colorful_interface_title".tr(),
subtitle: 'theme_setting_colorful_interface_subtitle'.tr(),
onChanged: onSurfaceColorSettingChange,
),
], ],
); );
} }

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class SettingsButtonListTile extends StatelessWidget { class SettingsButtonListTile extends StatelessWidget {
final IconData icon; final IconData icon;
@ -39,7 +40,12 @@ class SettingsButtonListTile extends StatelessWidget {
children: [ children: [
if (subtileText != null) const SizedBox(height: 4), if (subtileText != null) const SizedBox(height: 4),
if (subtileText != null) if (subtileText != null)
Text(subtileText!, style: context.textTheme.bodyMedium), Text(
subtileText!,
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
),
if (subtitle != null) subtitle!, if (subtitle != null) subtitle!,
const SizedBox(height: 6), const SizedBox(height: 6),
ElevatedButton(onPressed: onButtonTap, child: Text(buttonText)), ElevatedButton(onPressed: onButtonTap, child: Text(buttonText)),

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class SettingsSwitchListTile extends StatelessWidget { class SettingsSwitchListTile extends StatelessWidget {
final ValueNotifier<bool> valueNotifier; final ValueNotifier<bool> valueNotifier;
@ -54,7 +55,9 @@ class SettingsSwitchListTile extends StatelessWidget {
? Text( ? Text(
subtitle!, subtitle!,
style: context.textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
color: enabled ? null : context.themeData.disabledColor, color: enabled
? context.colorScheme.onSurfaceSecondary
: context.themeData.disabledColor,
), ),
) )
: null, : null,

View file

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
class SslClientCertSettings extends StatefulWidget { class SslClientCertSettings extends StatefulWidget {
@ -40,7 +41,9 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
children: [ children: [
Text( Text(
"client_cert_subtitle".tr(), "client_cert_subtitle".tr(),
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
), ),
const SizedBox( const SizedBox(
height: 6, height: 6,

View file

@ -65,8 +65,8 @@ class SharedLinkItem extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final themeData = context.themeData; final colorScheme = context.colorScheme;
final isDarkMode = themeData.brightness == Brightness.dark; final isDarkMode = colorScheme.brightness == Brightness.dark;
final thumbnailUrl = sharedLink.thumbAssetId != null final thumbnailUrl = sharedLink.thumbAssetId != null
? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!) ? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!)
: null; : null;
@ -159,7 +159,7 @@ class SharedLinkItem extends ConsumerWidget {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
child: Chip( child: Chip(
backgroundColor: themeData.primaryColor, backgroundColor: colorScheme.primary,
label: Text( label: Text(
labelText, labelText,
style: TextStyle( style: TextStyle(
@ -240,7 +240,7 @@ class SharedLinkItem extends ConsumerWidget {
child: Tooltip( child: Tooltip(
verticalOffset: 0, verticalOffset: 0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: themeData.primaryColor.withOpacity(0.9), color: colorScheme.primary.withOpacity(0.9),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
textStyle: TextStyle( textStyle: TextStyle(
@ -253,7 +253,7 @@ class SharedLinkItem extends ConsumerWidget {
child: Text( child: Text(
sharedLink.title, sharedLink.title,
style: TextStyle( style: TextStyle(
color: themeData.primaryColor, color: colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -268,7 +268,7 @@ class SharedLinkItem extends ConsumerWidget {
child: Tooltip( child: Tooltip(
verticalOffset: 0, verticalOffset: 0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: themeData.primaryColor.withOpacity(0.9), color: colorScheme.primary.withOpacity(0.9),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
textStyle: TextStyle( textStyle: TextStyle(

View file

@ -377,6 +377,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" 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: easy_image_viewer:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -61,9 +61,11 @@ dependencies:
octo_image: ^2.0.0 octo_image: ^2.0.0
thumbhash: 0.1.0+1 thumbhash: 0.1.0+1
async: ^2.11.0 async: ^2.11.0
dynamic_color: ^1.7.0 #package to apply system theme
#image editing packages #image editing packages
crop_image: ^1.0.13 crop_image: ^1.0.13
openapi: openapi:
path: openapi path: openapi