mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00: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:
parent
20262209ce
commit
0eacdf93eb
65 changed files with 944 additions and 563 deletions
|
@ -531,6 +531,11 @@
|
|||
"theme_setting_dark_mode_switch": "Dark mode",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||
"theme_setting_primary_color_title": "Primary color",
|
||||
"theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.",
|
||||
"theme_setting_colorful_interface_title": "Colorful interface",
|
||||
"theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.",
|
||||
"theme_setting_system_primary_color_title": "Use system color",
|
||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||
"theme_setting_theme_title": "Theme",
|
||||
|
@ -562,4 +567,4 @@
|
|||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]!;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
24
mobile/lib/extensions/theme_extensions.dart
Normal file
24
mobile/lib/extensions/theme_extensions.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)],
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -49,10 +49,6 @@ class SettingsPage extends StatelessWidget {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
bottom: const PreferredSize(
|
||||
preferredSize: Size.fromHeight(1),
|
||||
child: Divider(height: 1),
|
||||
),
|
||||
title: const Text('setting_pages_app_bar_settings').tr(),
|
||||
),
|
||||
body: context.isMobile ? _MobileLayout() : _TabletLayout(),
|
||||
|
@ -67,13 +63,18 @@ class _MobileLayout extends StatelessWidget {
|
|||
children: SettingSection.values
|
||||
.map(
|
||||
(s) => ListTile(
|
||||
title: Text(
|
||||
s.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 2.0, horizontal: 16.0),
|
||||
leading: Icon(s.icon),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
s.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
onTap: () => context.pushRoute(SettingsSubRoute(section: s)),
|
||||
),
|
||||
)
|
||||
|
@ -102,7 +103,7 @@ class _TabletLayout extends HookWidget {
|
|||
leading: Icon(s.icon),
|
||||
selected: s.index == selectedSection.value.index,
|
||||
selectedColor: context.primaryColor,
|
||||
selectedTileColor: context.primaryColor.withAlpha(50),
|
||||
selectedTileColor: context.themeData.highlightColor,
|
||||
onTap: () => selectedSection.value = s,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
|
||||
final immichThemeProvider = StateProvider<ThemeMode>((ref) {
|
||||
class ImmichTheme {
|
||||
ColorScheme light;
|
||||
ColorScheme dark;
|
||||
|
||||
ImmichTheme({required this.light, required this.dark});
|
||||
}
|
||||
|
||||
ImmichTheme? _immichDynamicTheme;
|
||||
bool get isDynamicThemeAvailable => _immichDynamicTheme != null;
|
||||
|
||||
final immichThemeModeProvider = StateProvider<ThemeMode>((ref) {
|
||||
var themeMode = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.themeMode);
|
||||
|
@ -20,266 +32,241 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
|
|||
}
|
||||
});
|
||||
|
||||
final ThemeData base = ThemeData(
|
||||
chipTheme: const ChipThemeData(
|
||||
side: BorderSide.none,
|
||||
),
|
||||
sliderTheme: const SliderThemeData(
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
|
||||
trackHeight: 2.0,
|
||||
),
|
||||
);
|
||||
final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) {
|
||||
var appSettingsProvider = ref.watch(appSettingsServiceProvider);
|
||||
var primaryColorName =
|
||||
appSettingsProvider.getSetting(AppSettingsEnum.primaryColor);
|
||||
|
||||
final ThemeData immichLightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.indigo,
|
||||
),
|
||||
primarySwatch: Colors.indigo,
|
||||
primaryColor: Colors.indigo,
|
||||
hintColor: Colors.indigo,
|
||||
focusColor: Colors.indigo,
|
||||
splashColor: Colors.indigo.withOpacity(0.15),
|
||||
fontFamily: 'Overpass',
|
||||
scaffoldBackgroundColor: immichBackgroundColor,
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
contentTextStyle: TextStyle(
|
||||
fontFamily: 'Overpass',
|
||||
color: Colors.indigo,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
fontFamily: 'Overpass',
|
||||
color: Colors.indigo,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
backgroundColor: immichBackgroundColor,
|
||||
foregroundColor: Colors.indigo,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
backgroundColor: immichBackgroundColor,
|
||||
selectedItemColor: Colors.indigo,
|
||||
),
|
||||
cardTheme: const CardTheme(
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
drawerTheme: const DrawerThemeData(
|
||||
backgroundColor: immichBackgroundColor,
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.indigo,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
chipTheme: base.chipTheme,
|
||||
sliderTheme: base.sliderTheme,
|
||||
popupMenuTheme: const PopupMenuThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
color: Colors.white,
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
indicatorColor: Colors.indigo.withOpacity(0.15),
|
||||
iconTheme: WidgetStatePropertyAll(
|
||||
IconThemeData(color: Colors.grey[700]),
|
||||
),
|
||||
backgroundColor: immichBackgroundColor,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
labelTextStyle: WidgetStatePropertyAll(
|
||||
TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
),
|
||||
dialogTheme: const DialogTheme(
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Colors.indigo,
|
||||
),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.indigo,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
textSelectionTheme: const TextSelectionThemeData(
|
||||
cursorColor: Colors.indigo,
|
||||
),
|
||||
);
|
||||
debugPrint("Current theme preset $primaryColorName");
|
||||
|
||||
final ThemeData immichDarkTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Colors.indigo,
|
||||
primaryColor: immichDarkThemePrimaryColor,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: immichDarkThemePrimaryColor,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
scaffoldBackgroundColor: immichDarkBackgroundColor,
|
||||
hintColor: Colors.grey[600],
|
||||
fontFamily: 'Overpass',
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
contentTextStyle: const TextStyle(
|
||||
fontFamily: 'Overpass',
|
||||
color: immichDarkThemePrimaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
try {
|
||||
return ImmichColorPreset.values
|
||||
.firstWhere((e) => e.name == primaryColorName);
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
"Theme preset $primaryColorName not found. Applying default preset.",
|
||||
);
|
||||
appSettingsProvider.setSetting(
|
||||
AppSettingsEnum.primaryColor,
|
||||
defaultColorPresetName,
|
||||
);
|
||||
return defaultColorPreset;
|
||||
}
|
||||
});
|
||||
|
||||
final dynamicThemeSettingProvider = StateProvider<bool>((ref) {
|
||||
return ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.dynamicTheme);
|
||||
});
|
||||
|
||||
final colorfulInterfaceSettingProvider = StateProvider<bool>((ref) {
|
||||
return ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.colorfulInterface);
|
||||
});
|
||||
|
||||
// Provider for current selected theme
|
||||
final immichThemeProvider = StateProvider<ImmichTheme>((ref) {
|
||||
var primaryColor = ref.read(immichThemePresetProvider);
|
||||
var useSystemColor = ref.watch(dynamicThemeSettingProvider);
|
||||
var useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider);
|
||||
|
||||
var currentTheme = (useSystemColor && _immichDynamicTheme != null)
|
||||
? _immichDynamicTheme!
|
||||
: primaryColor.getTheme();
|
||||
|
||||
return useColorfulInterface
|
||||
? currentTheme
|
||||
: _decolorizeSurfaces(theme: currentTheme);
|
||||
});
|
||||
|
||||
// Method to fetch dynamic system colors
|
||||
Future<void> fetchSystemPalette() async {
|
||||
try {
|
||||
final corePalette = await DynamicColorPlugin.getCorePalette();
|
||||
if (corePalette != null) {
|
||||
final primaryColor = corePalette.toColorScheme().primary;
|
||||
debugPrint('dynamic_color: Core palette detected.');
|
||||
|
||||
// Some palettes do not generate surface container colors accurately,
|
||||
// so we regenerate all colors using the primary color
|
||||
_immichDynamicTheme = ImmichTheme(
|
||||
light: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
dark: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('dynamic_color: Failed to obtain core palette.');
|
||||
}
|
||||
}
|
||||
|
||||
// This method replaces all surface shades in ImmichTheme to a static ones
|
||||
// as we are creating the colorscheme through seedColor the default surfaces are
|
||||
// tinted with primary color
|
||||
ImmichTheme _decolorizeSurfaces({
|
||||
required ImmichTheme theme,
|
||||
}) {
|
||||
return ImmichTheme(
|
||||
light: theme.light.copyWith(
|
||||
surface: const Color(0xFFf9f9f9),
|
||||
onSurface: const Color(0xFF1b1b1b),
|
||||
surfaceContainerLowest: const Color(0xFFffffff),
|
||||
surfaceContainerLow: const Color(0xFFf3f3f3),
|
||||
surfaceContainer: const Color(0xFFeeeeee),
|
||||
surfaceContainerHigh: const Color(0xFFe8e8e8),
|
||||
surfaceContainerHighest: const Color(0xFFe2e2e2),
|
||||
surfaceDim: const Color(0xFFdadada),
|
||||
surfaceBright: const Color(0xFFf9f9f9),
|
||||
onSurfaceVariant: const Color(0xFF4c4546),
|
||||
inverseSurface: const Color(0xFF303030),
|
||||
onInverseSurface: const Color(0xFFf1f1f1),
|
||||
),
|
||||
backgroundColor: Colors.grey[900],
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: immichDarkThemePrimaryColor,
|
||||
dark: theme.dark.copyWith(
|
||||
surface: const Color(0xFF131313),
|
||||
onSurface: const Color(0xFFE2E2E2),
|
||||
surfaceContainerLowest: const Color(0xFF0E0E0E),
|
||||
surfaceContainerLow: const Color(0xFF1B1B1B),
|
||||
surfaceContainer: const Color(0xFF1F1F1F),
|
||||
surfaceContainerHigh: const Color(0xFF242424),
|
||||
surfaceContainerHighest: const Color(0xFF2E2E2E),
|
||||
surfaceDim: const Color(0xFF131313),
|
||||
surfaceBright: const Color(0xFF353535),
|
||||
onSurfaceVariant: const Color(0xFFCfC4C5),
|
||||
inverseSurface: const Color(0xFFE2E2E2),
|
||||
onInverseSurface: const Color(0xFF303030),
|
||||
),
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
fontFamily: 'Overpass',
|
||||
color: immichDarkThemePrimaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData getThemeData({required ColorScheme colorScheme}) {
|
||||
var isDark = colorScheme.brightness == Brightness.dark;
|
||||
var primaryColor = colorScheme.primary;
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: isDark ? Brightness.dark : Brightness.light,
|
||||
colorScheme: colorScheme,
|
||||
primaryColor: primaryColor,
|
||||
hintColor: colorScheme.onSurfaceSecondary,
|
||||
focusColor: primaryColor,
|
||||
scaffoldBackgroundColor: colorScheme.surface,
|
||||
splashColor: primaryColor.withOpacity(0.1),
|
||||
highlightColor: primaryColor.withOpacity(0.1),
|
||||
dialogBackgroundColor: colorScheme.surfaceContainer,
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: colorScheme.surfaceContainer,
|
||||
),
|
||||
backgroundColor: Color.fromARGB(255, 32, 33, 35),
|
||||
foregroundColor: immichDarkThemePrimaryColor,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
backgroundColor: Color.fromARGB(255, 35, 36, 37),
|
||||
selectedItemColor: immichDarkThemePrimaryColor,
|
||||
),
|
||||
drawerTheme: DrawerThemeData(
|
||||
backgroundColor: immichDarkBackgroundColor,
|
||||
scrimColor: Colors.white.withOpacity(0.1),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 255, 255, 255),
|
||||
fontFamily: 'Overpass',
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
contentTextStyle: TextStyle(
|
||||
fontFamily: 'Overpass',
|
||||
color: primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 255, 255, 255),
|
||||
appBarTheme: AppBarTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
color: primaryColor,
|
||||
fontFamily: 'Overpass',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
backgroundColor:
|
||||
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
|
||||
foregroundColor: primaryColor,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: immichDarkThemePrimaryColor,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
cardColor: Colors.grey[900],
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: Colors.black87,
|
||||
backgroundColor: immichDarkThemePrimaryColor,
|
||||
),
|
||||
),
|
||||
chipTheme: base.chipTheme,
|
||||
sliderTheme: base.sliderTheme,
|
||||
popupMenuTheme: const PopupMenuThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
|
||||
iconTheme: WidgetStatePropertyAll(
|
||||
IconThemeData(color: Colors.grey[500]),
|
||||
),
|
||||
backgroundColor: Colors.grey[900],
|
||||
surfaceTintColor: Colors.transparent,
|
||||
labelTextStyle: WidgetStatePropertyAll(
|
||||
TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey[300],
|
||||
textTheme: TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : primaryColor,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: primaryColor,
|
||||
),
|
||||
titleSmall: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
titleMedium: const TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
titleLarge: const TextStyle(
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
dialogTheme: const DialogTheme(
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: immichDarkThemePrimaryColor,
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: isDark ? Colors.black87 : Colors.white,
|
||||
),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
color: immichDarkThemePrimaryColor,
|
||||
chipTheme: const ChipThemeData(
|
||||
side: BorderSide.none,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
sliderTheme: const SliderThemeData(
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
|
||||
trackHeight: 2.0,
|
||||
),
|
||||
),
|
||||
textSelectionTheme: const TextSelectionThemeData(
|
||||
cursorColor: immichDarkThemePrimaryColor,
|
||||
),
|
||||
);
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
),
|
||||
popupMenuTheme: const PopupMenuThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor:
|
||||
isDark ? colorScheme.surfaceContainer : colorScheme.surface,
|
||||
labelTextStyle: const WidgetStatePropertyAll(
|
||||
TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: primaryColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
|
@ -21,6 +21,7 @@ class ImmichTitleText extends StatelessWidget {
|
|||
),
|
||||
width: fontSize * 4,
|
||||
filterQuality: FilterQuality.high,
|
||||
color: context.primaryColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
||||
class MemoryEpilogue extends StatefulWidget {
|
||||
|
@ -49,24 +48,26 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.check_circle_outline_sharp,
|
||||
color: immichDarkThemePrimaryColor,
|
||||
color: context.isDarkTheme
|
||||
? context.colorScheme.primary
|
||||
: context.colorScheme.inversePrimary,
|
||||
size: 64.0,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Text(
|
||||
"memories_all_caught_up",
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
style: context.textTheme.headlineMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
).tr(),
|
||||
const SizedBox(height: 16.0),
|
||||
Text(
|
||||
"memories_check_back_tomorrow",
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
).tr(),
|
||||
const SizedBox(height: 16.0),
|
||||
TextButton(
|
||||
|
@ -74,7 +75,9 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
|
|||
child: Text(
|
||||
"memories_start_over",
|
||||
style: context.textTheme.displayMedium?.copyWith(
|
||||
color: immichDarkThemePrimaryColor,
|
||||
color: context.isDarkTheme
|
||||
? context.colorScheme.primary
|
||||
: context.colorScheme.inversePrimary,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
|
@ -108,9 +111,9 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
|
|||
),
|
||||
Text(
|
||||
"memories_swipe_to_close",
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -15,6 +15,9 @@ class PreferenceSetting extends StatelessWidget {
|
|||
HapticSetting(),
|
||||
];
|
||||
|
||||
return const SettingsSubPageScaffold(settings: preferenceSettings);
|
||||
return const SettingsSubPageScaffold(
|
||||
settings: preferenceSettings,
|
||||
showDivider: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue