Compare commits

...

7 commits

Author SHA1 Message Date
shalong-tanwen 3b8656a603 style: colors 2023-12-03 23:54:47 +05:30
shalong-tanwen 85841e3349 refactor: theme provider to generator 2023-12-03 23:08:10 +05:30
shalong-tanwen 966b38b99f Merge branch 'main' into refactor/mobile-colors 2023-12-03 23:02:28 +05:30
shalong-tanwen 748821d7a7 chore: pull main 2023-11-19 12:04:25 +05:30
shalong-tanwen ecb1669ab8 wip(mobile): unify-colors 2023-11-19 11:54:42 +05:30
shalong-tanwen 780d3b174d chore: pull main 2023-11-11 15:11:12 +05:30
shalong-tanwen d20801c65c wip(mobile): unify-colors 2023-11-11 09:48:29 +05:30
66 changed files with 712 additions and 700 deletions

View file

@ -1,5 +0,0 @@
import 'package:flutter/material.dart';
const Color immichBackgroundColor = Color(0xFFf6f8fe);
const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);

View file

@ -1,5 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
extension ContextHelper on BuildContext {
// Returns the current size from MediaQuery
@ -32,6 +34,12 @@ extension ContextHelper on BuildContext {
// Current ColorScheme used
ColorScheme get colorScheme => themeData.colorScheme;
// Red Accent harmonized with primary color
Color get redColor => redAccent.harmonizeWith(primaryColor);
// Orange Accent harmonized with primary color
Color get orangeColor => orangeAccent.harmonizeWith(primaryColor);
// Pop-out from the current context with optional result
void pop<T>([T? result]) => Navigator.of(this).pop(result);

View file

@ -0,0 +1,27 @@
import 'dart:ui';
extension LightenDarken on Color {
/// Darken a color by [percent] amount (100 = black)
Color darken([int percent = 10]) {
assert(1 <= percent && percent <= 100);
var f = 1 - percent / 100;
return Color.fromARGB(
alpha,
(red * f).round(),
(green * f).round(),
(blue * f).round(),
);
}
/// Lighten a color by [percent] amount (100 = white)
Color lighten([int percent = 10]) {
assert(1 <= percent && percent <= 100);
var p = percent / 100;
return Color.fromARGB(
alpha,
red + ((255 - red) * p).round(),
green + ((255 - green) * p).round(),
blue + ((255 - blue) * p).round(),
);
}
}

View file

@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/providers/theme.provider.dart';
import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';

View file

@ -50,9 +50,8 @@ class ActivitiesPage extends HookConsumerWidget {
);
buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final textStyle = context.textTheme.bodyMedium
?.copyWith(color: textColor.withOpacity(0.6));
final textStyle = context.textTheme.bodyMedium?.copyWith(
color: context.textTheme.bodyMedium?.color?.withOpacity(0.6),);
return Row(
mainAxisAlignment: leftAlign
@ -150,14 +149,14 @@ class ActivitiesPage extends HookConsumerWidget {
},
),
),
suffixIconColor: liked ? Colors.red[700] : null,
suffixIconColor: liked ? context.redColor : null,
hintText: isReadOnly
? 'shared_album_activities_input_disable'.tr()
: 'shared_album_activities_input_hint'.tr(),
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Colors.grey[600],
color: context.themeData.hintColor,
),
),
onEditingComplete: () async {
@ -200,27 +199,31 @@ class ActivitiesPage extends HookConsumerWidget {
onDismissed: (direction) async =>
await ref.read(provider.notifier).removeActivity(activity.id),
background: Container(
color: canDelete ? Colors.red[400] : Colors.grey[600],
color: canDelete
? context.colorScheme.error
: context.themeData.disabledColor,
alignment: AlignmentDirectional.centerStart,
child: canDelete
? const Padding(
padding: EdgeInsets.all(15),
? Padding(
padding: const EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
color: context.colorScheme.surface,
),
)
: null,
),
secondaryBackground: Container(
color: canDelete ? Colors.red[400] : Colors.grey[600],
color: canDelete
? context.colorScheme.error
: context.themeData.disabledColor,
alignment: AlignmentDirectional.centerEnd,
child: canDelete
? const Padding(
padding: EdgeInsets.all(15),
? Padding(
padding: const EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
color: context.colorScheme.surface,
),
)
: null,
@ -285,7 +288,7 @@ class ActivitiesPage extends HookConsumerWidget {
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
color: context.redColor,
),
),
title: buildTitleWithTimestamp(activity),

View file

@ -23,32 +23,38 @@ class AlbumThumbnailCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
var isDarkTheme = context.isDarkTheme;
return LayoutBuilder(
builder: (context, constraints) {
var cardSize = constraints.maxWidth;
buildEmptyThumbnail() {
return Container(
return SizedBox(
height: cardSize,
width: cardSize,
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[800] : Colors.grey[200],
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
),
),
),
);
}
buildAlbumThumbnail() => ImmichImage(
album.thumbnail.value,
width: cardSize,
height: cardSize,
buildAlbumThumbnail() => Card(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: ImmichImage(
album.thumbnail.value,
width: cardSize,
height: cardSize,
),
);
buildAlbumTextRow() {
@ -62,9 +68,9 @@ class AlbumThumbnailCard extends StatelessWidget {
}
}
return RichText(
return Text.rich(
overflow: TextOverflow.fade,
text: TextSpan(
TextSpan(
children: [
TextSpan(
text: album.assetCount == 1
@ -78,7 +84,9 @@ class AlbumThumbnailCard extends StatelessWidget {
if (owner != null)
TextSpan(
text: owner,
style: context.textTheme.bodyMedium,
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurface,
),
),
],
),
@ -87,40 +95,36 @@ class AlbumThumbnailCard extends StatelessWidget {
return GestureDetector(
onTap: onTap,
child: Flex(
direction: Axis.vertical,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: cardSize,
height: cardSize,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: album.thumbnail.value == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: SizedBox(
width: cardSize,
child: Text(
album.name,
style: context.textTheme.bodyMedium?.copyWith(
color: context.primaryColor,
fontWeight: FontWeight.w500,
),
),
),
),
buildAlbumTextRow(),
],
SizedBox(
width: cardSize,
height: cardSize,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20)),
child: album.thumbnail.value == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0, left: 8.0),
child: SizedBox(
width: cardSize,
child: Text(
album.name,
style: context.textTheme.bodyMedium?.copyWith(
color: context.primaryColor,
fontWeight: FontWeight.w500,
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 2.0, left: 8.0),
child: buildAlbumTextRow(),
),
],
),
);

View file

@ -23,36 +23,46 @@ class AlbumThumbnailListTile extends StatelessWidget {
var cardSize = 68.0;
buildEmptyThumbnail() {
return Container(
decoration: BoxDecoration(
color: context.isDarkTheme ? Colors.grey[800] : Colors.grey[200],
),
child: SizedBox(
height: cardSize,
width: cardSize,
child: const Center(
child: Icon(Icons.no_photography),
return SizedBox(
height: cardSize,
width: cardSize,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
),
),
),
);
}
buildAlbumThumbnail() {
return CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
return Card(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
),
httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
},
cacheKey:
getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
),
httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
},
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
);
}

View file

@ -20,8 +20,6 @@ class AlbumTitleTextField extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isDarkTheme = context.isDarkTheme;
return TextField(
onChanged: (v) {
if (v.isEmpty) {
@ -33,9 +31,8 @@ class AlbumTitleTextField extends ConsumerWidget {
ref.watch(albumTitleProvider.notifier).setAlbumTitle(v);
},
focusNode: albumTitleTextFieldFocusNode,
style: TextStyle(
style: const TextStyle(
fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
controller: albumTitleController,
@ -70,15 +67,10 @@ class AlbumTitleTextField extends ConsumerWidget {
borderRadius: BorderRadius.circular(10),
),
hintText: 'share_add_title'.tr(),
hintStyle: TextStyle(
hintStyle: const TextStyle(
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],
filled: isAlbumTitleTextFieldFocus.value,
),
);

View file

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

View file

@ -78,15 +78,10 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
borderSide: const BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10),
),
focusColor: Colors.grey[300],
fillColor: context.isDarkTheme
? const Color.fromARGB(255, 32, 33, 35)
: Colors.grey[200],
filled: titleFocusNode.hasFocus,
hintText: 'share_add_title'.tr(),
hintStyle: TextStyle(
hintStyle: const TextStyle(
fontSize: 28,
color: context.isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
),

View file

@ -4,6 +4,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/color_extensions.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@ -129,7 +130,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
),
subtitle: Text(
album.owner.value?.email ?? "",
style: TextStyle(color: Colors.grey[600]),
style: TextStyle(color: context.colorScheme.onSurface.darken(40)),
),
trailing: Text(
"shared_album_section_people_owner_label",
@ -157,7 +158,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
),
subtitle: Text(
user.email,
style: TextStyle(color: Colors.grey[600]),
style: TextStyle(color: context.colorScheme.onSurface.darken(40)),
),
trailing: userId == user.id || isOwner
? const Icon(Icons.more_horiz_rounded)
@ -201,9 +202,6 @@ class AlbumOptionsPage extends HookConsumerWidget {
album.activityEnabled = value;
}
},
activeColor: activityEnabled.value
? context.primaryColor
: context.themeData.disabledColor,
dense: true,
title: Text(
"shared_album_activity_setting_title",

View file

@ -148,7 +148,7 @@ class AlbumViewerPage extends HookConsumerWidget {
titleFocusNode: titleFocusNode,
)
: Padding(
padding: const EdgeInsets.only(left: 8.0),
padding: const EdgeInsets.only(left: 8.0, bottom: 20.0),
child: Text(
album.name,
style: context.textTheme.headlineMedium,

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/color_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart';
import 'package:immich_mobile/routing/router.dart';
@ -21,7 +22,6 @@ class LibraryPage extends HookConsumerWidget {
final trashEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
final albums = ref.watch(albumProvider);
var isDarkTheme = context.isDarkTheme;
var settings = ref.watch(appSettingsServiceProvider);
useEffect(
@ -102,9 +102,8 @@ class LibraryPage extends HookConsumerWidget {
),
Text(
option,
style: TextStyle(
style: context.textTheme.displaySmall?.copyWith(
color: selected ? context.primaryColor : null,
fontSize: 12.0,
),
),
],
@ -135,49 +134,50 @@ class LibraryPage extends HookConsumerWidget {
}
Widget buildCreateAlbumButton() {
return GestureDetector(
onTap: () {
context.autoPush(CreateAlbumRoute(isSharedAlbum: false));
return LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
onTap: () =>
context.autoPush(CreateAlbumRoute(isSharedAlbum: false)),
child: Padding(
padding: const EdgeInsets.only(bottom: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox.square(
dimension: constraints.maxWidth,
child: Card(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
color: context.themeData.cardColor.lighten(5),
child: Center(
child: Icon(
Icons.add_rounded,
size: 28,
color: context.primaryColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 8.0,
bottom: 16,
left: 8.0,
),
child: Text(
'library_page_new_album',
style: context.textTheme.labelLarge,
).tr(),
),
],
),
),
);
},
child: Padding(
padding: const EdgeInsets.only(bottom: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
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],
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Icon(
Icons.add_rounded,
size: 28,
color: context.primaryColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 8.0,
bottom: 16,
),
child: Text(
'library_page_new_album',
style: context.textTheme.labelLarge,
).tr(),
),
],
),
),
);
}
@ -187,30 +187,17 @@ class LibraryPage extends HookConsumerWidget {
Function() onClick,
) {
return Expanded(
child: OutlinedButton.icon(
child: ElevatedButton.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),
),
),
child: Text(label),
),
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: context.themeData.elevatedButtonTheme.style?.copyWith(
alignment: Alignment.centerLeft,
),
icon: Icon(
icon,
color: context.primaryColor,
),
),
);
@ -224,7 +211,7 @@ class LibraryPage extends HookConsumerWidget {
return trashEnabled
? InkWell(
onTap: () => context.autoPush(const TrashRoute()),
borderRadius: BorderRadius.circular(12),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: const Icon(
Icons.delete_rounded,
size: 25,
@ -250,8 +237,8 @@ class LibraryPage extends HookConsumerWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildLibraryNavButton(
"library_page_favorites".tr(), Icons.favorite_border, () {
buildLibraryNavButton("library_page_favorites".tr(),
Icons.favorite_outline_rounded, () {
context.autoNavigate(const FavoritesRoute());
}),
const SizedBox(width: 12.0),
@ -290,7 +277,6 @@ class LibraryPage extends HookConsumerWidget {
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 250,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: .7,
),

View file

@ -29,9 +29,11 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: context.primaryColor,
child: const Icon(
radius: 22,
child: Icon(
Icons.check_rounded,
size: 25,
color: context.colorScheme.surface,
size: 24,
),
);
} else {
@ -49,14 +51,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Chip(
backgroundColor: context.primaryColor.withOpacity(0.15),
backgroundColor: context.colorScheme.primaryContainer,
label: Text(
user.email,
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
style: context.textTheme.displaySmall,
),
),
),
@ -72,9 +70,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
padding: const EdgeInsets.all(16.0),
child: Text(
'select_additional_user_for_sharing_page_suggestions'.tr(),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Colors.grey,
color: context.themeData.hintColor,
fontWeight: FontWeight.bold,
),
),

View file

@ -56,9 +56,11 @@ class SelectUserForSharingPage extends HookConsumerWidget {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: context.primaryColor,
child: const Icon(
radius: 22,
child: Icon(
Icons.check_rounded,
size: 25,
color: context.colorScheme.surface,
size: 24,
),
);
} else {
@ -76,14 +78,10 @@ class SelectUserForSharingPage extends HookConsumerWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Chip(
backgroundColor: context.primaryColor.withOpacity(0.15),
backgroundColor: context.colorScheme.primaryContainer,
label: Text(
user.email,
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
style: context.textTheme.displaySmall,
),
),
),
@ -97,11 +95,11 @@ class SelectUserForSharingPage extends HookConsumerWidget {
),
Padding(
padding: const EdgeInsets.all(16.0),
child: const Text(
child: Text(
'select_user_for_sharing_page_share_suggestions',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
color: context.themeData.hintColor,
fontWeight: FontWeight.bold,
),
).tr(),

View file

@ -128,6 +128,9 @@ class SharingPage extends HookConsumerWidget {
Icons.photo_album_outlined,
size: 20,
),
style: context.themeData.elevatedButtonTheme.style?.copyWith(
alignment: Alignment.centerLeft,
),
label: const Text(
"sharing_silver_appbar_create_shared_album",
maxLines: 1,
@ -146,6 +149,9 @@ class SharingPage extends HookConsumerWidget {
Icons.link,
size: 20,
),
style: context.themeData.elevatedButtonTheme.style?.copyWith(
alignment: Alignment.centerLeft,
),
label: const Text(
"sharing_silver_appbar_shared_links",
style: TextStyle(
@ -169,8 +175,8 @@ class SharingPage extends HookConsumerWidget {
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(
color: Colors.grey,
side: BorderSide(
color: context.themeData.hintColor,
width: 0.5,
),
),
@ -191,7 +197,8 @@ class SharingPage extends HookConsumerWidget {
padding: const EdgeInsets.all(8.0),
child: Text(
'sharing_page_empty_list',
style: context.textTheme.displaySmall,
style: context.textTheme.displaySmall
?.copyWith(color: context.primaryColor),
).tr(),
),
Padding(

View file

@ -3,6 +3,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/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
class AdvancedBottomSheet extends HookConsumerWidget {
final Asset assetDetail;
@ -24,33 +25,47 @@ class AdvancedBottomSheet extends HookConsumerWidget {
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: LayoutBuilder(
builder: (context, constraints) {
builder: (ctx, constraints) {
// One column
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 32.0),
const Align(
child: Text(
"ADVANCED INFO",
style: TextStyle(fontSize: 12.0),
const Padding(
padding: EdgeInsets.only(top: 15, bottom: 10),
child: CustomDraggingHandle(),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextButton.icon(
label: Text(
"ADVANCED INFO",
style: context.textTheme.displaySmall,
),
icon: Icon(
Icons.copy,
size: 16.0,
color: context.primaryColor,
),
onPressed: () {
Clipboard.setData(
ClipboardData(text: assetDetail.toString()),
).then((_) {
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(
content: Text("Copied to clipboard"),
behavior: SnackBarBehavior.floating,
),
);
});
},
),
),
const SizedBox(height: 32.0),
Container(
decoration: BoxDecoration(
color: context.isDarkTheme
? Colors.grey[900]
: Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 16,
top: 8,
bottom: 16,
),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ListView(
shrinkWrap: true,
children: [

View file

@ -20,7 +20,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);
@ -68,7 +67,7 @@ class DescriptionInput extends HookConsumerWidget {
},
icon: Icon(
Icons.cancel_rounded,
color: Colors.grey[500],
color: context.themeData.hintColor,
),
splashRadius: 10,
);
@ -98,7 +97,7 @@ class DescriptionInput extends HookConsumerWidget {
hintText: 'description_input_hint_text'.tr(),
border: InputBorder.none,
hintStyle: context.textTheme.labelLarge?.copyWith(
color: textColor.withOpacity(0.5),
color: context.colorScheme.onSurface.withOpacity(0.5),
),
suffixIcon: suffixIcon,
),

View file

@ -112,7 +112,7 @@ class ExifBottomSheet extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final assetWithExif = ref.watch(assetDetailProvider(asset));
final exifInfo = (assetWithExif.value ?? asset).exifInfo;
var textColor = context.isDarkTheme ? Colors.white : Colors.black;
var textColor = context.colorScheme.onSurface;
buildMap() {
return Padding(

View file

@ -61,7 +61,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
Widget buildAssetInfoTable() {
return Table(
border: TableBorder.all(
color: context.themeData.primaryColorLight,
color: context.colorScheme.onSurfaceVariant,
width: 1,
),
children: [

View file

@ -2,7 +2,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/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
@ -135,13 +134,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
album.name,
style: TextStyle(
fontSize: 12,
color: isDarkTheme ? Colors.black : immichBackgroundColor,
color: context.colorScheme.surface,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.red[300],
deleteIconColor:
isDarkTheme ? Colors.black : immichBackgroundColor,
deleteIconColor: context.colorScheme.surface,
deleteIcon: const Icon(
Icons.cancel_rounded,
size: 15,

View file

@ -674,7 +674,6 @@ class BackupControllerPage extends HookConsumerWidget {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: const Text(
"backup_controller_page_backup",
).tr(),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class DisableMultiSelectButton extends ConsumerWidget {
class DisableMultiSelectButton extends StatelessWidget {
const DisableMultiSelectButton({
Key? key,
required this.onPressed,
@ -12,25 +12,29 @@ class DisableMultiSelectButton extends ConsumerWidget {
final int selectedItemCount;
@override
Widget build(BuildContext context, WidgetRef ref) {
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton.icon(
onPressed: () {
onPressed();
},
icon: const Icon(Icons.close_rounded),
label: Text(
'$selectedItemCount',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
),
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
foregroundColor: context.colorScheme.surface,
backgroundColor: context.primaryColor,
),
onPressed: () {
onPressed();
},
icon: const Icon(Icons.close_rounded),
label: Text(
'$selectedItemCount',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
),
),
);
}
}

View file

@ -6,6 +6,7 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
/// Build the Scroll Thumb and label using the current configuration
typedef ScrollThumbBuilder = Widget Function(
Color backgroundColor,
Color foregroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
@ -33,6 +34,9 @@ class DraggableScrollbar extends StatefulWidget {
/// The background color of the label and thumb
final Color backgroundColor;
/// The foreground color of the arrows in the thumb
final Color foregroundColor;
/// The amount of padding that should surround the thumb
final EdgeInsetsGeometry? padding;
@ -66,6 +70,7 @@ class DraggableScrollbar extends StatefulWidget {
required this.scrollStateListener,
this.heightScrollThumb = 48.0,
this.backgroundColor = Colors.white,
this.foregroundColor = Colors.white,
this.padding,
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
@ -85,6 +90,7 @@ class DraggableScrollbar extends StatefulWidget {
static buildScrollThumbAndLabel({
required Widget scrollThumb,
required Color backgroundColor,
required Color foregroundColor,
required Animation<double>? thumbAnimation,
required Animation<double>? labelAnimation,
required Text? labelText,
@ -123,6 +129,7 @@ class DraggableScrollbar extends StatefulWidget {
) {
return (
Color backgroundColor,
Color foregroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
@ -131,7 +138,7 @@ class DraggableScrollbar extends StatefulWidget {
}) {
final scrollThumb = CustomPaint(
key: scrollThumbKey,
foregroundPainter: ArrowCustomPainter(Colors.white),
foregroundPainter: ArrowCustomPainter(foregroundColor),
child: Material(
elevation: 4.0,
color: backgroundColor,
@ -150,6 +157,7 @@ class DraggableScrollbar extends StatefulWidget {
return buildScrollThumbAndLabel(
scrollThumb: scrollThumb,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
thumbAnimation: thumbAnimation,
labelAnimation: labelAnimation,
labelText: labelText,
@ -286,6 +294,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
padding: widget.padding,
child: widget.scrollThumbBuilder(
widget.backgroundColor,
widget.foregroundColor,
_thumbAnimation,
_labelAnimation,
widget.heightScrollThumb,

View file

@ -74,9 +74,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.onBackground.withAlpha(100),
),
),
],

View file

@ -222,9 +222,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
child: Text(
title,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w500,
style: context.textTheme.displayLarge?.copyWith(
color: context.colorScheme.onSurface,
),
),
);
@ -243,7 +242,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
bottom: widget.margin,
right: i + 1 == num ? 0.0 : widget.margin,
),
color: Colors.grey,
color: context.colorScheme.surfaceVariant,
),
],
);
@ -328,8 +327,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
return Text(
DateFormat.yMMMM().format(date),
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
);
@ -372,7 +371,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
scrollStateListener: dragScrolling,
itemPositionsListener: _itemPositionsListener,
controller: _itemScrollController,
backgroundColor: context.themeData.hintColor,
backgroundColor: context.primaryColor,
foregroundColor: context.colorScheme.onPrimary,
labelTextBuilder: _labelBuilder,
labelConstraints: const BoxConstraints(maxHeight: 28),
scrollbarAnimationDuration: const Duration(milliseconds: 300),

View file

@ -43,9 +43,6 @@ class ThumbnailImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final assetContainerColor = context.isDarkTheme
? Colors.blueGrey
: context.themeData.primaryColorLight;
// 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;
@ -54,11 +51,13 @@ class ThumbnailImage extends StatelessWidget {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: assetContainerColor,
color: context.colorScheme.surfaceVariant,
),
child: Icon(
Icons.check_circle_rounded,
color: context.primaryColor,
color: onDeselect == null
? context.primaryColor.withAlpha(120)
: context.primaryColor,
),
);
} else {
@ -132,9 +131,10 @@ class ThumbnailImage extends StatelessWidget {
}
Widget buildImage() {
final image = SizedBox(
final image = Container(
width: 300,
height: 300,
color: context.colorScheme.surfaceVariant,
child: Hero(
tag: isFromDto
? '${asset.remoteId}-$heroOffset'
@ -153,9 +153,9 @@ class ThumbnailImage extends StatelessWidget {
decoration: BoxDecoration(
border: Border.all(
width: 0,
color: onDeselect == null ? Colors.grey : assetContainerColor,
color: context.colorScheme.surfaceVariant,
),
color: onDeselect == null ? Colors.grey : assetContainerColor,
color: context.colorScheme.surfaceVariant,
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
@ -203,10 +203,8 @@ class ThumbnailImage extends StatelessWidget {
decoration: BoxDecoration(
border: multiselectEnabled && isSelected
? Border.all(
color: onDeselect == null
? Colors.grey
: assetContainerColor,
width: 8,
color: context.colorScheme.surfaceVariant,
width: 12,
)
: const Border(),
),

View file

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
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/extensions/color_extensions.dart';
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
import 'package:immich_mobile/modules/home/models/selection_state.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
@ -128,9 +129,8 @@ class ControlBottomAppBar extends ConsumerWidget {
ScrollController scrollController,
) {
return Card(
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
surfaceTintColor: Colors.transparent,
elevation: 18.0,
elevation: 2,
color: context.colorScheme.surface.lighten(15),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),

View file

@ -306,7 +306,8 @@ class LoginForm extends HookConsumerWidget {
children: [
Text(
sanitizeUrl(serverEndpointController.text),
style: context.textTheme.displaySmall,
style: context.textTheme.displaySmall
?.copyWith(color: context.primaryColor),
textAlign: TextAlign.center,
),
if (isPasswordLoginEnable.value) ...[
@ -616,7 +617,7 @@ class LoadingIcon extends StatelessWidget {
height: 24,
child: FittedBox(
child: CircularProgressIndicator(
strokeWidth: 2,
strokeWidth: 5,
),
),
),

View file

@ -125,8 +125,11 @@ class MapPageState extends ConsumerState<MapPage> {
final refetchMarkers = useState(true);
final isLoading =
ref.watch(mapStateNotifier.select((state) => state.isLoading));
final mapStyle =
ref.watch(mapStateNotifier.select((state) => state.mapStyle));
final maxZoom = ref.read(mapStateNotifier.notifier).maxZoom;
final zoomLevel = math.min(maxZoom, 14.0);
final themeData = isDarkTheme ? immichDarkTheme : immichLightTheme;
if (refetchMarkers.value) {
mapMarkerData.value = ref.watch(mapMarkersProvider).when(
@ -195,7 +198,7 @@ class MapPageState extends ConsumerState<MapPage> {
showDialog(
context: context,
builder: (context) => Theme(
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
data: themeData,
child: LocationServiceDisabledDialog(),
),
);
@ -209,7 +212,7 @@ class MapPageState extends ConsumerState<MapPage> {
shouldRequestPermission = await showDialog(
context: context,
builder: (context) => Theme(
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
data: themeData,
child: LocationPermissionDisabledDialog(),
),
);
@ -426,7 +429,7 @@ class MapPageState extends ConsumerState<MapPage> {
),
child: Theme(
// Override app theme based on map theme
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
data: themeData,
child: Scaffold(
appBar: MapAppBar(
isDarkTheme: isDarkTheme,
@ -439,7 +442,7 @@ class MapPageState extends ConsumerState<MapPage> {
extendBodyBehindAppBar: true,
body: Stack(
children: [
if (!isLoading)
if (!isLoading || mapStyle != null)
FlutterMap(
mapController: mapController,
options: MapOptions(
@ -464,7 +467,7 @@ class MapPageState extends ConsumerState<MapPage> {
markerLayer,
],
),
if (!isLoading)
if (!isLoading || mapStyle != null)
MapPageBottomSheet(
mapPageEventStream: mapPageEventSC.stream,
bottomSheetEventSC: bottomSheetEventSC,
@ -473,10 +476,13 @@ class MapPageState extends ConsumerState<MapPage> {
isDarkTheme: isDarkTheme,
),
if (showLoadingIndicator.value || isLoading)
Positioned(
top: context.height * 0.35,
left: context.width * 0.425,
child: const ImmichLoadingIndicator(),
IgnorePointer(
child: Container(
width: double.infinity,
height: double.infinity,
color: context.colorScheme.surface.withAlpha(70),
child: const Center(child: ImmichLoadingIndicator()),
),
),
],
),

View file

@ -6,7 +6,7 @@ part of 'person.service.dart';
// RiverpodGenerator
// **************************************************************************
String _$personServiceHash() => r'3fc3dcf4603c7b55c0deae65f39f6c212eea492b';
String _$personServiceHash() => r'cde0a9c029d16ddde2adcd58ae8c863bf8cc1fed';
/// See also [personService].
@ProviderFor(personService)

View file

@ -31,8 +31,8 @@ class CuratedPeopleRow extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: imageSize,
height: imageSize,
width: 120,
height: 120,
child: ThumbnailWithInfo(
textInfo: '',
onTap: () {},
@ -69,6 +69,7 @@ class CuratedPeopleRow extends StatelessWidget {
elevation: 3,
child: CircleAvatar(
maxRadius: imageSize / 2,
backgroundColor: context.colorScheme.surfaceVariant,
backgroundImage: NetworkImage(
getFaceThumbnailUrl(person.id),
headers: headers,

View file

@ -52,13 +52,13 @@ class CuratedPlacesRow extends CuratedRow {
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black,
color: context.colorScheme.shadow,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.blueGrey.withOpacity(0.0),
Colors.black.withOpacity(0.4),
context.colorScheme.shadow.withOpacity(0.1),
context.colorScheme.shadow.withOpacity(0.2),
],
stops: const [0.0, 0.4],
),

View file

@ -23,8 +23,6 @@ class ThumbnailWithInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
var textAndIconColor =
context.isDarkTheme ? Colors.grey[100] : Colors.grey[700];
return GestureDetector(
onTap: () {
onTap();
@ -35,7 +33,7 @@ class ThumbnailWithInfo extends StatelessWidget {
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
color: context.colorScheme.surfaceVariant,
),
child: imageUrl != null
? ClipRRect(
@ -44,6 +42,16 @@ class ThumbnailWithInfo extends StatelessWidget {
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
placeholder: (context, url) {
return SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
),
);
},
imageUrl: imageUrl!,
httpHeaders: {
"Authorization":
@ -56,22 +64,22 @@ class ThumbnailWithInfo extends StatelessWidget {
: Center(
child: Icon(
noImageIcon ?? Icons.not_listed_location,
color: textAndIconColor,
color: context.primaryColor,
),
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: Colors.white,
color: context.colorScheme.inverseSurface,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.grey.withOpacity(0.0),
context.colorScheme.shadow.withOpacity(0),
textInfo == ''
? Colors.black.withOpacity(0.1)
: Colors.black.withOpacity(0.5),
? context.colorScheme.shadow.withOpacity(0.1)
: context.colorScheme.shadow.withOpacity(0.2),
],
stops: const [0.0, 1.0],
),

View file

@ -7,7 +7,7 @@ part of 'app_settings.provider.dart';
// **************************************************************************
String _$appSettingsServiceHash() =>
r'957a65af6967701112f3076b507f9738fec4b7be';
r'45ea609a91d250290431a7a08a14d16b37c7515d';
/// See also [appSettingsService].
@ProviderFor(appSettingsService)

View file

@ -81,7 +81,6 @@ class AdvancedSettings extends HookConsumerWidget {
min: 1.0,
divisions: 7,
label: logLevel,
activeColor: context.primaryColor,
),
),
SettingsSwitchListTile(

View file

@ -51,7 +51,6 @@ class LayoutSettings extends HookConsumerWidget {
return Column(
children: [
SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_dynamic_layout_title",
style: context.textTheme.labelLarge,
@ -73,7 +72,6 @@ class LayoutSettings extends HookConsumerWidget {
).tr(),
),
RadioListTile(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_group_by_month_day",
style: context.textTheme.labelLarge,
@ -84,7 +82,6 @@ class LayoutSettings extends HookConsumerWidget {
controlAffinity: ListTileControlAffinity.trailing,
),
RadioListTile(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_group_by_month",
style: context.textTheme.labelLarge,
@ -95,7 +92,6 @@ class LayoutSettings extends HookConsumerWidget {
controlAffinity: ListTileControlAffinity.trailing,
),
RadioListTile(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_group_automatically",
style: context.textTheme.labelLarge,

View file

@ -34,7 +34,6 @@ class StorageIndicator extends HookConsumerWidget {
);
return SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
"theme_setting_asset_list_storage_indicator_title",
style: context.textTheme.labelLarge,

View file

@ -49,7 +49,6 @@ class TilesPerRow extends HookConsumerWidget {
max: 6,
divisions: 4,
label: "${itemsValue.value.toInt()}",
activeColor: context.primaryColor,
),
],
);

View file

@ -143,7 +143,6 @@ class NotificationSetting extends HookConsumerWidget {
max: 5.0,
divisions: 5,
label: formattedValue,
activeColor: context.primaryColor,
),
),
],

View file

@ -35,17 +35,21 @@ class SettingsSwitchListTile extends StatelessWidget {
onChanged!(value);
}
},
activeColor:
enabled ? context.primaryColor : context.themeData.disabledColor,
activeColor: enabled ? null : context.themeData.disabledColor,
dense: true,
title: Text(
title,
style: context.textTheme.titleSmall,
style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
color: enabled ? null : context.themeData.disabledColor,
),
),
subtitle: subtitle != null
? Text(
subtitle!,
style: context.textTheme.bodyMedium,
style: TextStyle(
color: enabled ? null : context.themeData.disabledColor,
),
)
: null,
);

View file

@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
import 'package:immich_mobile/shared/providers/theme.provider.dart';
class ThemeSetting extends HookConsumerWidget {
const ThemeSetting({
@ -35,7 +35,6 @@ class ThemeSetting extends HookConsumerWidget {
).tr(),
children: [
SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
'theme_setting_system_theme_switch',
style: context.textTheme.labelLarge
@ -48,20 +47,26 @@ class ThemeSetting extends HookConsumerWidget {
if (isSystem) {
currentTheme.value = ThemeMode.system;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.system;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.system);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "system");
} else {
if (currentSystemBrightness == Brightness.light) {
currentTheme.value = ThemeMode.light;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.light;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.light);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "light");
} else if (currentSystemBrightness == Brightness.dark) {
currentTheme.value = ThemeMode.dark;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.dark);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "dark");
@ -71,7 +76,6 @@ class ThemeSetting extends HookConsumerWidget {
),
if (currentTheme.value != ThemeMode.system)
SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
'theme_setting_dark_mode_switch',
style: context.textTheme.labelLarge
@ -80,12 +84,16 @@ class ThemeSetting extends HookConsumerWidget {
value: ref.watch(immichThemeProvider) == ThemeMode.dark,
onChanged: (bool isDark) {
if (isDark) {
ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.dark);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "dark");
} else {
ref.watch(immichThemeProvider.notifier).state = ThemeMode.light;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.light);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "light");

View file

@ -21,14 +21,6 @@ class TabNavigationObserver extends AutoRouterObserver {
required this.ref,
});
@override
void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) {
// Perform tasks on first navigation to SearchRoute
if (route.name == 'SearchRoute') {
// ref.refresh(getCuratedLocationProvider);
}
}
@override
Future<void> didChangeTabRoute(
TabPageRoute route,

View file

@ -6,7 +6,7 @@ part of 'api.provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$apiServiceHash() => r'03cbd33147a7058d56175e532ac47e1aa4858c6d';
String _$apiServiceHash() => r'5b8beddb448316bdae5e3963ff77601653715729';
/// See also [apiService].
@ProviderFor(apiService)

View file

@ -0,0 +1,14 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ImmichLoadingOverlayNotifier extends StateNotifier<bool> {
ImmichLoadingOverlayNotifier() : super(false);
void show() => state = true;
void hide() => state = false;
}
final immichLoadingOverlayController =
StateNotifierProvider.autoDispose<ImmichLoadingOverlayNotifier, bool>(
(_) => ImmichLoadingOverlayNotifier(),
);

View file

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'theme.provider.g.dart';
@Riverpod(keepAlive: true)
class ImmichTheme extends _$ImmichTheme {
@override
ThemeMode build() {
final themeMode = ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.themeMode);
switch (themeMode) {
case "light":
return ThemeMode.light;
case "dark":
return ThemeMode.dark;
default:
return ThemeMode.system;
}
}
void updateTheme(ThemeMode newMode) => state = newMode;
}

View file

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'theme.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$immichThemeHash() => r'22952207d5d5e6289a8244589e703c816501d6e3';
/// See also [ImmichTheme].
@ProviderFor(ImmichTheme)
final immichThemeProvider = NotifierProvider<ImmichTheme, ThemeMode>.internal(
ImmichTheme.new,
name: r'immichThemeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$immichThemeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ImmichTheme = Notifier<ThemeMode>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View file

@ -135,11 +135,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 3),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
color: context.isDarkTheme
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
),
decoration: BoxDecoration(color: context.colorScheme.surface),
child: ListTile(
minLeadingWidth: 50,
leading: Icon(
@ -163,7 +159,8 @@ class ImmichAppBarDialog extends HookConsumerWidget {
child: LinearProgressIndicator(
minHeight: 5.0,
value: backupState.serverInfo.diskUsagePercentage / 100.0,
backgroundColor: Colors.grey,
backgroundColor:
context.colorScheme.onSurface.withAlpha(50),
color: theme.primaryColor,
),
),

View file

@ -77,9 +77,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),
@ -97,9 +95,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
bottom: -5,
right: -8,
child: Material(
color: context.isDarkTheme
? Colors.blueGrey[800]
: Colors.white,
color: context.colorScheme.primaryContainer,
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),

View file

@ -42,9 +42,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),
@ -186,8 +184,7 @@ class AppBarServerInfo extends HookConsumerWidget {
borderRadius: BorderRadius.circular(10),
),
textStyle: TextStyle(
color:
context.isDarkTheme ? Colors.black : Colors.white,
color: context.colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
message: getServerUrl() ?? '--',

View file

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

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class CustomDraggingHandle extends StatelessWidget {
const CustomDraggingHandle({super.key});
@ -9,7 +10,7 @@ class CustomDraggingHandle extends StatelessWidget {
height: 5,
width: 30,
decoration: BoxDecoration(
color: Colors.grey[500],
color: context.themeData.dividerColor,
borderRadius: BorderRadius.circular(16),
),
);

View file

@ -25,7 +25,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
backupState.backgroundBackup || backupState.autoBackup;
final ServerInfo serverInfoState = ref.watch(serverInfoProvider);
final user = Store.tryGet(StoreKey.currentUser);
final isDarkTheme = context.isDarkTheme;
const widgetSize = 30.0;
buildProfileIndicator() {
@ -42,9 +41,9 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
color: Colors.black,
borderRadius: BorderRadius.circular(widgetSize / 2),
),
child: const Icon(
child: Icon(
Icons.info,
color: Color.fromARGB(255, 243, 188, 106),
color: context.orangeColor,
size: widgetSize / 2,
),
),
@ -68,9 +67,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
);
}
getBackupBadgeIcon() {
final iconColor = isDarkTheme ? Colors.white : Colors.black;
Widget? getBackupBadgeIcon() {
if (isEnableAutoBackup) {
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
return Container(
@ -78,7 +75,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
child: CircularProgressIndicator(
strokeWidth: 2,
strokeCap: StrokeCap.round,
valueColor: AlwaysStoppedAnimation<Color>(iconColor),
color: context.colorScheme.onSurfaceVariant,
),
);
} else if (backupState.backupProgress !=
@ -87,7 +84,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
return Icon(
Icons.check_outlined,
size: 9,
color: iconColor,
color: context.colorScheme.onSurfaceVariant,
);
}
}
@ -96,14 +93,14 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
return Icon(
Icons.cloud_off_rounded,
size: 9,
color: iconColor,
color: context.colorScheme.onSurfaceVariant,
);
}
return null;
}
buildBackupIndicator() {
final indicatorIcon = getBackupBadgeIcon();
final badgeBackground = isDarkTheme ? Colors.blueGrey[800] : Colors.white;
return InkWell(
onTap: () => context.autoPush(const BackupControllerRoute()),
@ -113,11 +110,11 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
width: widgetSize / 2,
height: widgetSize / 2,
decoration: BoxDecoration(
color: badgeBackground,
border: Border.all(
color: isDarkTheme ? Colors.black : Colors.grey,
color: context.colorScheme.surface,
),
borderRadius: BorderRadius.circular(widgetSize / 2),
color: context.colorScheme.surfaceVariant,
),
child: indicatorIcon,
),
@ -125,16 +122,16 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
alignment: Alignment.bottomRight,
isLabelVisible: indicatorIcon != null,
offset: const Offset(2, 2),
child: Icon(
child: const Icon(
Icons.backup_rounded,
size: widgetSize,
color: context.primaryColor,
),
),
);
}
return AppBar(
elevation: 0.0,
backgroundColor: context.themeData.appBarTheme.backgroundColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
@ -157,12 +154,13 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
),
Container(
margin: const EdgeInsets.only(left: 10),
child: const Text(
child: Text(
'IMMICH',
style: TextStyle(
fontFamily: 'SnowburstOne',
fontWeight: FontWeight.bold,
fontSize: 24,
color: context.primaryColor,
),
),
),

View file

@ -33,8 +33,8 @@ class ImmichImage extends StatelessWidget {
Widget build(BuildContext context) {
if (this.asset == null) {
return Container(
decoration: const BoxDecoration(
color: Colors.grey,
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
child: SizedBox(
width: width,
@ -61,10 +61,12 @@ class ImmichImage extends StatelessWidget {
return Stack(
children: [
if (useGrayBoxPlaceholder)
const SizedBox.square(
SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
),
),
if (useProgressIndicator)
@ -110,10 +112,12 @@ class ImmichImage extends StatelessWidget {
return Stack(
children: [
if (useGrayBoxPlaceholder)
const SizedBox.square(
SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
),
),
if (useProgressIndicator)

View file

@ -15,12 +15,12 @@ class ImmichLoadingIndicator extends StatelessWidget {
height: 60,
width: 60,
decoration: BoxDecoration(
color: context.primaryColor.withAlpha(200),
color: context.primaryColor,
borderRadius: BorderRadius.circular(borderRadius ?? 10),
),
padding: const EdgeInsets.all(15),
child: const CircularProgressIndicator(
color: Colors.white,
child: CircularProgressIndicator(
color: context.colorScheme.onPrimary,
strokeWidth: 3,
),
);

View file

@ -9,7 +9,7 @@ class ImmichToast {
required BuildContext context,
required String msg,
ToastType toastType = ToastType.info,
ToastGravity gravity = ToastGravity.TOP,
ToastGravity gravity = ToastGravity.BOTTOM,
int durationInSecond = 3,
}) {
final fToast = FToast();
@ -18,7 +18,7 @@ class ImmichToast {
Color getColor(ToastType type, BuildContext context) {
switch (type) {
case ToastType.info:
return context.primaryColor;
return const Color.fromARGB(255, 48, 111, 220);
case ToastType.success:
return const Color.fromARGB(255, 78, 140, 124);
case ToastType.error:
@ -26,41 +26,39 @@ class ImmichToast {
}
}
Icon getIcon(ToastType type) {
IconData getIcon(ToastType type) {
switch (type) {
case ToastType.info:
return Icon(
Icons.info_outline_rounded,
color: context.primaryColor,
);
return Icons.info_outline_rounded;
case ToastType.success:
return const Icon(
Icons.check_circle_rounded,
color: Color.fromARGB(255, 78, 140, 124),
);
return Icons.check_circle_outline_rounded;
case ToastType.error:
return const Icon(
Icons.error_outline_rounded,
color: Color.fromARGB(255, 240, 162, 156),
);
return Icons.report_problem_outlined;
}
}
fToast.showToast(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
padding: const EdgeInsets.symmetric(vertical: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[50],
color: context.colorScheme.inverseSurface,
border: Border.all(
color: Colors.black12,
color: context.colorScheme.inverseSurface,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
getIcon(toastType),
Padding(
padding: const EdgeInsets.only(left: 15, right: 5),
child: Icon(
getIcon(toastType),
color: getColor(toastType, context),
),
),
const SizedBox(
width: 12.0,
),
@ -68,8 +66,7 @@ class ImmichToast {
child: Text(
msg,
style: TextStyle(
color: getColor(toastType, context),
fontWeight: FontWeight.bold,
color: context.colorScheme.onInverseSurface,
fontSize: 15,
),
),

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class PhotoViewDefaultError extends StatelessWidget {
const PhotoViewDefaultError({Key? key, required this.decoration})
@ -13,7 +14,7 @@ class PhotoViewDefaultError extends StatelessWidget {
child: Center(
child: Icon(
Icons.broken_image,
color: Colors.grey[400],
color: context.primaryColor,
size: 40.0,
),
),

View file

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
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/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
@ -22,7 +23,6 @@ class UserCircleAvatar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
bool isDarkTheme = Theme.of(context).brightness == Brightness.dark;
final profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}';
@ -30,10 +30,8 @@ class UserCircleAvatar extends ConsumerWidget {
user.name[0].toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary
? Colors.black
: Colors.white,
fontSize: 16,
color: context.colorScheme.surface,
),
);
return CircleAvatar(

View file

@ -11,8 +11,6 @@ class AppLogDetailPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
var isDarkTheme = context.isDarkTheme;
buildStackMessage(String stackTrace) {
return Padding(
padding: const EdgeInsets.all(8.0),
@ -60,7 +58,7 @@ class AppLogDetailPage extends HookConsumerWidget {
),
Container(
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
color: context.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
@ -126,7 +124,7 @@ class AppLogDetailPage extends HookConsumerWidget {
),
Container(
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
color: context.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
@ -165,7 +163,7 @@ class AppLogDetailPage extends HookConsumerWidget {
),
Container(
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
color: context.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(

View file

@ -16,7 +16,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(
@ -39,12 +38,13 @@ class AppLogPage extends HookConsumerWidget {
case LogLevel.INFO:
return colorStatusIndicator(context.primaryColor);
case LogLevel.SEVERE:
return colorStatusIndicator(Colors.redAccent);
return colorStatusIndicator(context.redColor);
case LogLevel.WARNING:
return colorStatusIndicator(Colors.orangeAccent);
return colorStatusIndicator(context.orangeColor);
default:
return colorStatusIndicator(Colors.grey);
return colorStatusIndicator(
context.colorScheme.onSurfaceVariant.withAlpha(150),
);
}
}
@ -53,15 +53,11 @@ 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 context.redColor.withAlpha(50);
case LogLevel.WARNING:
return isDarkTheme
? Colors.orangeAccent.withOpacity(0.25)
: Colors.orangeAccent.withOpacity(0.075);
return context.orangeColor.withAlpha(50);
default:
return context.primaryColor.withOpacity(0.1);
return context.primaryColor.withAlpha(50);
}
}
@ -116,7 +112,7 @@ class AppLogPage extends HookConsumerWidget {
separatorBuilder: (context, index) {
return Divider(
height: 0,
color: isDarkTheme ? Colors.white70 : Colors.grey[600],
color: context.themeData.dividerColor,
);
},
itemCount: logMessages.value.length,
@ -139,7 +135,7 @@ class AppLogPage extends HookConsumerWidget {
TextSpan(
text: "#$index ",
style: TextStyle(
color: isDarkTheme ? Colors.white70 : Colors.grey[600],
color: context.themeData.dividerColor,
fontSize: 14.0,
fontWeight: FontWeight.bold,
),
@ -158,7 +154,7 @@ class AppLogPage extends HookConsumerWidget {
"[${logMessage.context1}] Logged on ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)}",
style: TextStyle(
fontSize: 12.0,
color: Colors.grey[600],
color: context.themeData.hintColor,
),
),
leading: buildLeadingIcon(logMessage.level),

View file

@ -7,8 +7,9 @@ final _loadingEntry = OverlayEntry(
builder: (context) => SizedBox.square(
dimension: double.infinity,
child: DecoratedBox(
decoration:
BoxDecoration(color: context.colorScheme.surface.withAlpha(200)),
decoration: BoxDecoration(
color: context.colorScheme.surface.withAlpha(70),
),
child: const Center(child: ImmichLoadingIndicator()),
),
),

View file

@ -92,8 +92,11 @@ class SplashScreenPage extends HookConsumerWidget {
[],
);
return const Scaffold(
body: Center(
return Scaffold(
appBar: AppBar(
elevation: 0.0,
),
body: const Center(
child: Image(
image: AssetImage('assets/immich-logo-no-outline.png'),
width: 80,

View file

@ -25,7 +25,7 @@ class TabControllerPage extends HookConsumerWidget {
children: [
icon,
Positioned(
right: -14,
right: -16,
child: SizedBox(
height: 12,
width: 12,
@ -115,9 +115,8 @@ class TabControllerPage extends HookConsumerWidget {
Icons.photo_library_outlined,
),
selectedIcon: buildIcon(
Icon(
const Icon(
Icons.photo_library,
color: context.primaryColor,
),
),
),
@ -126,9 +125,8 @@ class TabControllerPage extends HookConsumerWidget {
icon: const Icon(
Icons.search_rounded,
),
selectedIcon: Icon(
selectedIcon: const Icon(
Icons.search,
color: context.primaryColor,
),
),
NavigationDestination(
@ -136,9 +134,8 @@ class TabControllerPage extends HookConsumerWidget {
icon: const Icon(
Icons.group_outlined,
),
selectedIcon: Icon(
selectedIcon: const Icon(
Icons.group,
color: context.primaryColor,
),
),
NavigationDestination(
@ -147,9 +144,8 @@ class TabControllerPage extends HookConsumerWidget {
Icons.photo_album_outlined,
),
selectedIcon: buildIcon(
Icon(
const Icon(
Icons.photo_album_rounded,
color: context.primaryColor,
),
),
),

View file

@ -1,278 +1,177 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
final immichThemeProvider = StateProvider<ThemeMode>((ref) {
var themeMode = ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.themeMode);
debugPrint("Current themeMode $themeMode");
if (themeMode == "light") {
return ThemeMode.light;
} else if (themeMode == "dark") {
return ThemeMode.dark;
} else {
return ThemeMode.system;
}
});
ThemeData base = ThemeData(
chipTheme: const ChipThemeData(
side: BorderSide.none,
),
sliderTheme: const SliderThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
),
);
ThemeData immichLightTheme = ThemeData(
useMaterial3: true,
ColorScheme _lightColorScheme = const ColorScheme(
brightness: Brightness.light,
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: MaterialStatePropertyAll(
IconThemeData(color: Colors.grey[700]),
),
backgroundColor: immichBackgroundColor,
surfaceTintColor: Colors.transparent,
labelTextStyle: MaterialStatePropertyAll(
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,
),
primary: Color(0xff4755b5),
onPrimary: Color(0xffffffff),
primaryContainer: Color(0xffdfe0ff),
onPrimaryContainer: Color(0xff000d60),
secondary: Color(0xff5b5d72),
onSecondary: Color(0xffffffff),
secondaryContainer: Color(0xFFD6D8FF),
onSecondaryContainer: Color(0xff181a2c),
tertiary: Color(0xff77536c),
onTertiary: Color(0xffffffff),
tertiaryContainer: Color(0xffffd7f0),
onTertiaryContainer: Color(0xff2d1127),
error: Color(0xffba1a1a),
onError: Color(0xffffffff),
errorContainer: Color(0xffffdad6),
onErrorContainer: Color(0xff410002),
background: Color(0xfff9f6fc),
onBackground: Color(0xff1b1b1f),
surface: Color(0xfff9f6fc),
onSurface: Color(0xff1b1b1f),
surfaceVariant: Color(0xffdeddea),
onSurfaceVariant: Color(0xff46464f),
outline: Color(0xff777680),
outlineVariant: Color(0xffc7c5d0),
shadow: Color(0xff000000),
scrim: Color(0xff000000),
inverseSurface: Color(0xff303137),
onInverseSurface: Color(0xfff3f0f4),
inversePrimary: Color(0xffbcc3ff),
surfaceTint: Color(0xff4755b5),
);
ThemeData immichDarkTheme = ThemeData(
useMaterial3: true,
ColorScheme _darkColorScheme = const ColorScheme(
brightness: Brightness.dark,
primarySwatch: Colors.indigo,
primaryColor: immichDarkThemePrimaryColor,
scaffoldBackgroundColor: immichDarkBackgroundColor,
hintColor: Colors.grey[600],
fontFamily: 'Overpass',
snackBarTheme: SnackBarThemeData(
contentTextStyle: const TextStyle(
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor,
fontWeight: FontWeight.bold,
),
backgroundColor: Colors.grey[900],
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: immichDarkThemePrimaryColor,
),
),
appBarTheme: const AppBarTheme(
titleTextStyle: TextStyle(
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor,
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor: Color.fromARGB(255, 32, 33, 35),
foregroundColor: immichDarkThemePrimaryColor,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: Color.fromARGB(255, 35, 36, 37),
selectedItemColor: immichDarkThemePrimaryColor,
),
drawerTheme: DrawerThemeData(
backgroundColor: immichDarkBackgroundColor,
scrimColor: Colors.white.withOpacity(0.1),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
displayMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: immichDarkThemePrimaryColor,
),
titleSmall: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
cardColor: Colors.grey[900],
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.black87,
backgroundColor: immichDarkThemePrimaryColor,
),
),
chipTheme: base.chipTheme,
sliderTheme: base.sliderTheme,
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
surfaceTintColor: Colors.transparent,
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
iconTheme: MaterialStatePropertyAll(
IconThemeData(color: Colors.grey[500]),
),
backgroundColor: Colors.grey[900],
surfaceTintColor: Colors.transparent,
labelTextStyle: MaterialStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey[300],
),
),
),
dialogTheme: const DialogTheme(
surfaceTintColor: Colors.transparent,
),
inputDecorationTheme: const InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: immichDarkThemePrimaryColor,
),
),
labelStyle: TextStyle(
color: immichDarkThemePrimaryColor,
),
hintStyle: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: immichDarkThemePrimaryColor,
),
primary: Color(0xffa4c8ff),
onPrimary: Color(0xff00315e),
primaryContainer: Color(0xFF182C40),
onPrimaryContainer: Color(0xffd4e3ff),
secondary: Color(0xffbcc7dc),
onSecondary: Color(0xff37474f),
secondaryContainer: Color(0xff3d4758),
onSecondaryContainer: Color(0xffd8e3f8),
tertiary: Color(0xffdabde2),
onTertiary: Color(0xff3d2946),
tertiaryContainer: Color(0xff543f5e),
onTertiaryContainer: Color(0xfff6d9ff),
error: Color(0xffffb4ab),
onError: Color(0xff690005),
errorContainer: Color(0xff93000a),
onErrorContainer: Color(0xffffb4ab),
background: Color(0xff101214),
onBackground: Color(0xffe2e2e5),
surface: Color(0xff101214),
onSurface: Color(0xffe2e2e5),
surfaceVariant: Color(0xff363c42),
onSurfaceVariant: Color(0xffc1c7ce),
outline: Color(0xff8b9198),
outlineVariant: Color(0xff41474d),
shadow: Color(0xff000000),
scrim: Color(0xff000000),
inverseSurface: Color(0xffeeeef1),
onInverseSurface: Color(0xff2e3133),
inversePrimary: Color(0xff1c5fa5),
surfaceTint: Color(0xff90caf9),
);
ThemeData getThemeForScheme(ColorScheme scheme) {
return ThemeData(
useMaterial3: true,
brightness: scheme.brightness,
colorScheme: scheme,
primaryColor: scheme.primary,
scaffoldBackgroundColor: scheme.background,
fontFamily: 'Overpass',
appBarTheme: AppBarTheme(
iconTheme: IconThemeData(color: scheme.primary),
titleTextStyle: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
centerTitle: true,
scrolledUnderElevation: 4.0,
elevation: 4.0,
),
snackBarTheme: const SnackBarThemeData(
contentTextStyle: TextStyle(
fontFamily: 'Overpass',
fontWeight: FontWeight.bold,
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
),
cardTheme: const CardTheme(elevation: 2.0),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard,
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 11,
),
shadowColor: scheme.shadow,
foregroundColor: scheme.onPrimary,
backgroundColor: scheme.primary,
),
),
navigationBarTheme: NavigationBarThemeData(
iconTheme: MaterialStateProperty.resolveWith<IconThemeData?>((states) {
if (states.contains(MaterialState.selected)) {
return IconThemeData(color: scheme.primary);
}
return null;
}),
labelTextStyle: const MaterialStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
textTheme: TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
displayMedium: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
displaySmall: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
titleSmall: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
),
chipTheme: const ChipThemeData(
side: BorderSide.none,
),
sliderTheme: const SliderThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: scheme.primary,
),
hintStyle: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
);
}
final ThemeData immichLightTheme = getThemeForScheme(_lightColorScheme);
final ThemeData immichDarkTheme = getThemeForScheme(_darkColorScheme);
const redAccent = Colors.redAccent;
const orangeAccent = Colors.orangeAccent;

View file

@ -369,6 +369,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
dynamic_color:
dependency: "direct main"
description:
name: dynamic_color
sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f"
url: "https://pub.dev"
source: hosted
version: "1.6.8"
easy_image_viewer:
dependency: "direct main"
description:

View file

@ -58,6 +58,7 @@ dependencies:
wakelock_plus: ^1.1.1
flutter_local_notifications: ^15.1.0+1
timezone: ^0.9.2
dynamic_color: ^1.6.8
openapi:
path: openapi