1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-30 09:47:31 +02: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_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
"theme_setting_image_viewer_quality_title": "Image viewer quality",
"theme_setting_primary_color_title": "Primary color",
"theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
"theme_setting_colorful_interface_title": "Colorful interface",
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
"theme_setting_system_primary_color_title": "Use system color",
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
"theme_setting_theme_subtitle": "Choose the app's theme setting",
"theme_setting_theme_title": "Theme",

View File

@ -1,5 +1,108 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
const Color immichBackgroundColor = Color(0xFFf6f8fe);
const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);
enum ImmichColorPreset {
indigo,
deepPurple,
pink,
red,
orange,
yellow,
lime,
green,
cyan,
slateGray
}
const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo;
const String defaultColorPresetName = "indigo";
const Color immichBrandColorLight = Color(0xFF4150AF);
const Color immichBrandColorDark = Color(0xFFACCBFA);
final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
ImmichColorPreset.indigo: ImmichTheme(
light: ColorScheme.fromSeed(
seedColor: immichBrandColorLight,
).copyWith(primary: immichBrandColorLight),
dark: ColorScheme.fromSeed(
seedColor: immichBrandColorDark,
brightness: Brightness.dark,
).copyWith(primary: immichBrandColorDark),
),
ImmichColorPreset.deepPurple: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFF6F43C0)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFFD3BBFF),
brightness: Brightness.dark,
),
),
ImmichColorPreset.pink: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFFED79B5)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFFED79B5),
brightness: Brightness.dark,
),
),
ImmichColorPreset.red: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFFC51C16)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFFD3302F),
brightness: Brightness.dark,
),
),
ImmichColorPreset.orange: ImmichTheme(
light: ColorScheme.fromSeed(
seedColor: const Color(0xffff5b01),
dynamicSchemeVariant: DynamicSchemeVariant.fidelity,
),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFFCC6D08),
brightness: Brightness.dark,
dynamicSchemeVariant: DynamicSchemeVariant.fidelity,
),
),
ImmichColorPreset.yellow: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFFFFB400)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFFFFB400),
brightness: Brightness.dark,
),
),
ImmichColorPreset.lime: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFFCDDC39)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFFCDDC39),
brightness: Brightness.dark,
),
),
ImmichColorPreset.green: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFF18C249)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFF18C249),
brightness: Brightness.dark,
),
),
ImmichColorPreset.cyan: ImmichTheme(
light: ColorScheme.fromSeed(seedColor: const Color(0xFF00BCD4)),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xFF00BCD4),
brightness: Brightness.dark,
),
),
ImmichColorPreset.slateGray: ImmichTheme(
light: ColorScheme.fromSeed(
seedColor: const Color(0xFF696969),
dynamicSchemeVariant: DynamicSchemeVariant.neutral,
),
dark: ColorScheme.fromSeed(
seedColor: const Color(0xff696969),
brightness: Brightness.dark,
dynamicSchemeVariant: DynamicSchemeVariant.neutral,
),
),
};
extension ImmichColorModeExtension on ImmichColorPreset {
ImmichTheme getTheme() => _themePresetsMap[this]!;
}

View File

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

View File

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

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
ImmichLogger();
@ -187,6 +189,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
@override
Widget build(BuildContext context) {
var router = ref.watch(appRouterProvider);
var immichTheme = ref.watch(immichThemeProvider);
return MaterialApp(
localizationsDelegates: context.localizationDelegates,
@ -196,9 +199,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
home: MaterialApp.router(
title: 'Immich',
debugShowCheckedModeBanner: false,
themeMode: ref.watch(immichThemeProvider),
darkTheme: immichDarkTheme,
theme: immichLightTheme,
themeMode: ref.watch(immichThemeModeProvider),
darkTheme: getThemeData(colorScheme: immichTheme.dark),
theme: getThemeData(colorScheme: immichTheme.light),
routeInformationParser: router.defaultRouteParser(),
routerDelegate: router.delegate(
navigatorObservers: () => [TabNavigationObserver(ref: ref)],

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,22 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
final immichThemeProvider = StateProvider<ThemeMode>((ref) {
class ImmichTheme {
ColorScheme light;
ColorScheme dark;
ImmichTheme({required this.light, required this.dark});
}
ImmichTheme? _immichDynamicTheme;
bool get isDynamicThemeAvailable => _immichDynamicTheme != null;
final immichThemeModeProvider = StateProvider<ThemeMode>((ref) {
var themeMode = ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.themeMode);
@ -20,7 +32,195 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
}
});
final ThemeData base = ThemeData(
final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) {
var appSettingsProvider = ref.watch(appSettingsServiceProvider);
var primaryColorName =
appSettingsProvider.getSetting(AppSettingsEnum.primaryColor);
debugPrint("Current theme preset $primaryColorName");
try {
return ImmichColorPreset.values
.firstWhere((e) => e.name == primaryColorName);
} catch (e) {
debugPrint(
"Theme preset $primaryColorName not found. Applying default preset.",
);
appSettingsProvider.setSetting(
AppSettingsEnum.primaryColor,
defaultColorPresetName,
);
return defaultColorPreset;
}
});
final dynamicThemeSettingProvider = StateProvider<bool>((ref) {
return ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.dynamicTheme);
});
final colorfulInterfaceSettingProvider = StateProvider<bool>((ref) {
return ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.colorfulInterface);
});
// Provider for current selected theme
final immichThemeProvider = StateProvider<ImmichTheme>((ref) {
var primaryColor = ref.read(immichThemePresetProvider);
var useSystemColor = ref.watch(dynamicThemeSettingProvider);
var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider);
var currentTheme = (useSystemColor && _immichDynamicTheme != null)
? _immichDynamicTheme!
: primaryColor.getTheme();
return useColorfulInterface
? currentTheme
: _decolorizeSurfaces(theme: currentTheme);
});
// Method to fetch dynamic system colors
Future<void> fetchSystemPalette() async {
try {
final corePalette = await DynamicColorPlugin.getCorePalette();
if (corePalette != null) {
final primaryColor = corePalette.toColorScheme().primary;
debugPrint('dynamic_color: Core palette detected.');
// Some palettes do not generate surface container colors accurately,
// so we regenerate all colors using the primary color
_immichDynamicTheme = ImmichTheme(
light: ColorScheme.fromSeed(
seedColor: primaryColor,
brightness: Brightness.light,
),
dark: ColorScheme.fromSeed(
seedColor: primaryColor,
brightness: Brightness.dark,
),
);
}
} catch (e) {
debugPrint('dynamic_color: Failed to obtain core palette.');
}
}
// This method replaces all surface shades in ImmichTheme to a static ones
// as we are creating the colorscheme through seedColor the default surfaces are
// tinted with primary color
ImmichTheme _decolorizeSurfaces({
required ImmichTheme theme,
}) {
return ImmichTheme(
light: theme.light.copyWith(
surface: const Color(0xFFf9f9f9),
onSurface: const Color(0xFF1b1b1b),
surfaceContainerLowest: const Color(0xFFffffff),
surfaceContainerLow: const Color(0xFFf3f3f3),
surfaceContainer: const Color(0xFFeeeeee),
surfaceContainerHigh: const Color(0xFFe8e8e8),
surfaceContainerHighest: const Color(0xFFe2e2e2),
surfaceDim: const Color(0xFFdadada),
surfaceBright: const Color(0xFFf9f9f9),
onSurfaceVariant: const Color(0xFF4c4546),
inverseSurface: const Color(0xFF303030),
onInverseSurface: const Color(0xFFf1f1f1),
),
dark: theme.dark.copyWith(
surface: const Color(0xFF131313),
onSurface: const Color(0xFFE2E2E2),
surfaceContainerLowest: const Color(0xFF0E0E0E),
surfaceContainerLow: const Color(0xFF1B1B1B),
surfaceContainer: const Color(0xFF1F1F1F),
surfaceContainerHigh: const Color(0xFF242424),
surfaceContainerHighest: const Color(0xFF2E2E2E),
surfaceDim: const Color(0xFF131313),
surfaceBright: const Color(0xFF353535),
onSurfaceVariant: const Color(0xFFCfC4C5),
inverseSurface: const Color(0xFFE2E2E2),
onInverseSurface: const Color(0xFF303030),
),
);
}
ThemeData getThemeData({required ColorScheme colorScheme}) {
var isDark = colorScheme.brightness == Brightness.dark;
var primaryColor = colorScheme.primary;
return ThemeData(
useMaterial3: true,
brightness: isDark ? Brightness.dark : Brightness.light,
colorScheme: colorScheme,
primaryColor: primaryColor,
hintColor: colorScheme.onSurfaceSecondary,
focusColor: primaryColor,
scaffoldBackgroundColor: colorScheme.surface,
splashColor: primaryColor.withOpacity(0.1),
highlightColor: primaryColor.withOpacity(0.1),
dialogBackgroundColor: colorScheme.surfaceContainer,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: colorScheme.surfaceContainer,
),
fontFamily: 'Overpass',
snackBarTheme: SnackBarThemeData(
contentTextStyle: TextStyle(
fontFamily: 'Overpass',
color: primaryColor,
fontWeight: FontWeight.bold,
),
backgroundColor: colorScheme.surfaceContainerHighest,
),
appBarTheme: AppBarTheme(
titleTextStyle: TextStyle(
color: primaryColor,
fontFamily: 'Overpass',
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor:
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
foregroundColor: primaryColor,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
textTheme: TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : primaryColor,
),
displayMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: primaryColor,
),
titleSmall: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: const TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: isDark ? Colors.black87 : Colors.white,
),
),
chipTheme: const ChipThemeData(
side: BorderSide.none,
),
@ -28,258 +228,45 @@ final ThemeData base = ThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
),
);
final ThemeData immichLightTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
),
primarySwatch: Colors.indigo,
primaryColor: Colors.indigo,
hintColor: Colors.indigo,
focusColor: Colors.indigo,
splashColor: Colors.indigo.withOpacity(0.15),
fontFamily: 'Overpass',
scaffoldBackgroundColor: immichBackgroundColor,
snackBarTheme: const SnackBarThemeData(
contentTextStyle: TextStyle(
fontFamily: 'Overpass',
color: Colors.indigo,
fontWeight: FontWeight.bold,
),
backgroundColor: Colors.white,
),
appBarTheme: const AppBarTheme(
titleTextStyle: TextStyle(
fontFamily: 'Overpass',
color: Colors.indigo,
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor: immichBackgroundColor,
foregroundColor: Colors.indigo,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: immichBackgroundColor,
selectedItemColor: Colors.indigo,
),
cardTheme: const CardTheme(
surfaceTintColor: Colors.transparent,
),
drawerTheme: const DrawerThemeData(
backgroundColor: immichBackgroundColor,
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
displayMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
titleSmall: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
),
chipTheme: base.chipTheme,
sliderTheme: base.sliderTheme,
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
surfaceTintColor: Colors.transparent,
color: Colors.white,
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: Colors.indigo.withOpacity(0.15),
iconTheme: WidgetStatePropertyAll(
IconThemeData(color: Colors.grey[700]),
),
backgroundColor: immichBackgroundColor,
surfaceTintColor: Colors.transparent,
labelTextStyle: WidgetStatePropertyAll(
backgroundColor:
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
labelTextStyle: const WidgetStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey[800],
),
),
),
dialogTheme: const DialogTheme(
surfaceTintColor: Colors.transparent,
),
inputDecorationTheme: const InputDecorationTheme(
inputDecorationTheme: InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.indigo,
color: primaryColor,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: colorScheme.outlineVariant,
),
),
labelStyle: TextStyle(
color: Colors.indigo,
color: primaryColor,
),
hintStyle: TextStyle(
hintStyle: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: Colors.indigo,
textSelectionTheme: TextSelectionThemeData(
cursorColor: primaryColor,
),
);
final ThemeData immichDarkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
primarySwatch: Colors.indigo,
primaryColor: immichDarkThemePrimaryColor,
colorScheme: ColorScheme.fromSeed(
seedColor: immichDarkThemePrimaryColor,
brightness: Brightness.dark,
),
scaffoldBackgroundColor: immichDarkBackgroundColor,
hintColor: Colors.grey[600],
fontFamily: 'Overpass',
snackBarTheme: SnackBarThemeData(
contentTextStyle: const TextStyle(
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor,
fontWeight: FontWeight.bold,
),
backgroundColor: Colors.grey[900],
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: immichDarkThemePrimaryColor,
),
),
appBarTheme: const AppBarTheme(
titleTextStyle: TextStyle(
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor,
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor: Color.fromARGB(255, 32, 33, 35),
foregroundColor: immichDarkThemePrimaryColor,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: Color.fromARGB(255, 35, 36, 37),
selectedItemColor: immichDarkThemePrimaryColor,
),
drawerTheme: DrawerThemeData(
backgroundColor: immichDarkBackgroundColor,
scrimColor: Colors.white.withOpacity(0.1),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
displayMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: immichDarkThemePrimaryColor,
),
titleSmall: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
cardColor: Colors.grey[900],
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.black87,
backgroundColor: immichDarkThemePrimaryColor,
),
),
chipTheme: base.chipTheme,
sliderTheme: base.sliderTheme,
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
surfaceTintColor: Colors.transparent,
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
iconTheme: WidgetStatePropertyAll(
IconThemeData(color: Colors.grey[500]),
),
backgroundColor: Colors.grey[900],
surfaceTintColor: Colors.transparent,
labelTextStyle: WidgetStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey[300],
),
),
),
dialogTheme: const DialogTheme(
surfaceTintColor: Colors.transparent,
),
inputDecorationTheme: const InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: immichDarkThemePrimaryColor,
),
),
labelStyle: TextStyle(
color: immichDarkThemePrimaryColor,
),
hintStyle: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: immichDarkThemePrimaryColor,
),
);
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
import 'package:immich_mobile/utils/storage_indicator.dart';
import 'package:isar/isar.dart';
@ -42,8 +43,8 @@ class ThumbnailImage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final assetContainerColor = context.isDarkTheme
? Colors.blueGrey
: context.themeData.primaryColorLight;
? context.primaryColor.darken(amount: 0.6)
: context.primaryColor.lighten(amount: 0.8);
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == Isar.autoIncrement;
@ -192,8 +193,8 @@ class ThumbnailImage extends ConsumerWidget {
bottom: 5,
child: Icon(
storageIcon(asset),
color: Colors.white,
size: 18,
color: Colors.white.withOpacity(.8),
size: 16,
),
),
if (asset.isFavorite)

View File

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

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

View File

@ -22,7 +22,7 @@ class ExifBottomSheet extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final assetWithExif = ref.watch(assetDetailProvider(asset));
var textColor = context.isDarkTheme ? Colors.white : Colors.black;
var textColor = context.colorScheme.onSurface;
final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo;
// Format the date time with the timezone
final (dt, timeZone) =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class ThumbnailWithInfoContainer extends StatelessWidget {
const ThumbnailWithInfoContainer({
@ -25,7 +26,14 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
gradient: LinearGradient(
colors: [
context.colorScheme.surfaceContainer,
context.colorScheme.surfaceContainer.darken(amount: .1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
@ -34,7 +42,7 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.grey.withOpacity(0.0),
Colors.transparent,
label == ''
? Colors.black.withOpacity(0.1)
: Colors.black.withOpacity(0.5),

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -377,6 +377,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
dynamic_color:
dependency: "direct main"
description:
name: dynamic_color
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev"
source: hosted
version: "1.7.0"
easy_image_viewer:
dependency: "direct main"
description:

View File

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