wip(mobile): unify-colors

This commit is contained in:
shalong-tanwen 2023-11-19 11:54:42 +05:30
parent 780d3b174d
commit ecb1669ab8
37 changed files with 327 additions and 246 deletions

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);
@ -45,7 +53,7 @@ extension ContextHelper on BuildContext {
) =>
AutoRouter.of(this).navigate(route);
// Auto-Push replace route from the current context
// Auto-Push replace route from the current context
Future<T?> autoReplace<T extends Object?>(PageRouteInfo<dynamic> route) =>
AutoRouter.of(this).replace(route);

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

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/immich_loading_overlay.provider.dart';
extension LoadingOverlay on WidgetRef {
ValueNotifier<bool> useProcessingOverlay() {
final result = useState(false);
final immichOverlayController =
read(immichLoadingOverlayController.notifier);
useValueChanged(
result.value,
(_, __) => result.value
? WidgetsBinding.instance
.addPostFrameCallback((_) => immichOverlayController.show())
: WidgetsBinding.instance
.addPostFrameCallback((_) => immichOverlayController.hide()),
);
return result;
}
}

View file

@ -216,7 +216,10 @@ class ImmichAppState extends ConsumerState<ImmichApp>
navigatorObservers: () => [TabNavigationObserver(ref: ref)],
),
),
const ImmichLoadingOverlay(),
ImmichLoadingOverlay(
darkTheme: immichDarkTheme,
theme: immichLightTheme,
),
const VersionAnnouncementOverlay(),
],
),

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,
@ -288,7 +291,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,8 +23,6 @@ class AlbumThumbnailCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
var isDarkTheme = context.isDarkTheme;
return LayoutBuilder(
builder: (context, constraints) {
var cardSize = constraints.maxWidth;
@ -83,7 +81,7 @@ class AlbumThumbnailCard extends StatelessWidget {
style: TextStyle(
fontFamily: 'WorkSans',
fontSize: 12,
color: isDarkTheme ? Colors.white : Colors.black,
color: context.colorScheme.onSurface,
),
),
if (owner != null) const TextSpan(text: ' · '),
@ -124,9 +122,7 @@ class AlbumThumbnailCard extends StatelessWidget {
album.name,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme
? context.primaryColor
: Colors.black,
color: context.primaryColor,
),
),
),

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

@ -3,6 +3,7 @@ import 'package:flutter/material.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/widgetref_extensions.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
@ -14,7 +15,6 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
class AlbumViewerAppbar extends HookConsumerWidget
implements PreferredSizeWidget {
@ -43,6 +43,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
Widget build(BuildContext context, WidgetRef ref) {
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
final immichOverlayController = ref.useProcessingOverlay();
final comments = album.shared
? ref.watch(
activityStatisticsStateProvider(
@ -52,7 +53,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
: 0;
deleteAlbum() async {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
final bool success;
if (album.shared) {
@ -74,7 +75,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
);
}
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
Future<void> showConfirmationDialog() async {
@ -107,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,
),
),
),
@ -122,7 +123,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
}
void onLeaveAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album);
@ -140,11 +141,11 @@ class AlbumViewerAppbar extends HookConsumerWidget
);
}
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
void onRemoveFromAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
@ -167,7 +168,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
);
}
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
void handleShareAssets(
@ -198,9 +199,9 @@ class AlbumViewerAppbar extends HookConsumerWidget
}
void onShareAssetsTo() async {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
handleShareAssets(ref, context, selected);
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
buildBottomSheetActions() {

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,8 @@ 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/extensions/widgetref_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';
@ -11,7 +13,6 @@ import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
class AlbumOptionsPage extends HookConsumerWidget {
final Album album;
@ -25,6 +26,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
final userId = ref.watch(authenticationProvider).userId;
final activityEnabled = useState(album.activityEnabled);
final isOwner = owner?.id == userId;
final immichOverlayController = ref.useProcessingOverlay();
void showErrorMessage() {
Navigator.pop(context);
@ -37,7 +39,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
}
void leaveAlbum() async {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
try {
final isSuccess =
@ -54,11 +56,11 @@ class AlbumOptionsPage extends HookConsumerWidget {
showErrorMessage();
}
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
void removeUserFromAlbum(User user) async {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
try {
await ref
@ -71,7 +73,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
}
Navigator.pop(context);
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
void handleUserClick(User user) {
@ -131,7 +133,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
),
subtitle: Text(
album.owner.value?.email ?? "",
style: TextStyle(color: Colors.grey[500]),
style: TextStyle(color: context.colorScheme.onSurface.darken(40)),
),
trailing: const Text(
"Owner",
@ -162,7 +164,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
),
subtitle: Text(
user.email,
style: TextStyle(color: Colors.grey[500]),
style: TextStyle(color: context.colorScheme.onSurface.darken(40)),
),
trailing: userId == user.id || isOwner
? const Icon(Icons.more_horiz_rounded)
@ -208,9 +210,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

@ -5,6 +5,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/widgetref_extensions.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
@ -19,7 +20,6 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
class AlbumViewerPage extends HookConsumerWidget {
final int albumId;
@ -33,6 +33,7 @@ class AlbumViewerPage extends HookConsumerWidget {
final userId = ref.watch(authenticationProvider).userId;
final selection = useState<Set<Asset>>({});
final multiSelectEnabled = useState(false);
final immichOverlayController = ref.useProcessingOverlay();
useEffect(
() {
@ -78,7 +79,7 @@ class AlbumViewerPage extends HookConsumerWidget {
if (returnPayload != null) {
// Check if there is new assets add
if (returnPayload.selectedAssets.isNotEmpty) {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
var addAssetsResult =
await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum(
@ -91,7 +92,7 @@ class AlbumViewerPage extends HookConsumerWidget {
ref.invalidate(albumDetailProvider(albumId));
}
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
}
}
@ -102,7 +103,7 @@ class AlbumViewerPage extends HookConsumerWidget {
);
if (sharedUserIds != null) {
ImmichLoadingOverlayController.appLoader.show();
immichOverlayController.value = true;
var isSuccess = await ref
.watch(albumServiceProvider)
@ -112,7 +113,7 @@ class AlbumViewerPage extends HookConsumerWidget {
ref.invalidate(albumDetailProvider(album.id));
}
ImmichLoadingOverlayController.appLoader.hide();
immichOverlayController.value = false;
}
}

View file

@ -101,9 +101,8 @@ class LibraryPage extends HookConsumerWidget {
),
Text(
option,
style: TextStyle(
style: context.textTheme.displaySmall?.copyWith(
color: selected ? context.primaryColor : null,
fontSize: 12.0,
),
),
],
@ -244,8 +243,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),

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

@ -51,9 +51,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 {
@ -71,14 +73,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,
),
),
),
@ -92,11 +90,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

@ -81,8 +81,7 @@ class SharingPage extends HookConsumerWidget {
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
color:
context.isDarkTheme ? context.primaryColor : Colors.black,
color: context.primaryColor,
),
),
subtitle: isOwner
@ -172,8 +171,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,
),
),

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,57 +25,50 @@ 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: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () {
Clipboard.setData(
ClipboardData(text: assetDetail.toString()),
).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Copied to clipboard"),
),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: context.primaryColor,
),
),
),
SelectableText(
assetDetail.toString(),
style: const TextStyle(

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,
);
@ -102,7 +101,7 @@ class DescriptionInput extends HookConsumerWidget {
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 12,
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,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,7 +129,8 @@ class ControlBottomAppBar extends ConsumerWidget {
ScrollController scrollController,
) {
return Card(
elevation: 18.0,
elevation: 2,
color: context.colorScheme.surface.lighten(15),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),

View file

@ -7,6 +7,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/widgetref_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
@ -49,7 +50,7 @@ class HomePage extends HookConsumerWidget {
final tipOneOpacity = useState(0.0);
final refreshCount = useState(0);
final processing = useState(false);
final processing = ref.useProcessingOverlay();
useEffect(
() {
@ -363,7 +364,6 @@ class HomePage extends HookConsumerWidget {
selectionAssetState: selectionAssetState.value,
onStack: onStack,
),
if (processing.value) const Center(child: ImmichLoadingIndicator()),
],
),
);

View file

@ -125,6 +125,8 @@ 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;
@ -440,7 +442,7 @@ class MapPageState extends ConsumerState<MapPage> {
extendBodyBehindAppBar: true,
body: Stack(
children: [
if (!isLoading)
if (!isLoading || mapStyle != null)
FlutterMap(
mapController: mapController,
options: MapOptions(
@ -465,7 +467,7 @@ class MapPageState extends ConsumerState<MapPage> {
markerLayer,
],
),
if (!isLoading)
if (!isLoading || mapStyle != null)
MapPageBottomSheet(
mapPageEventStream: mapPageEventSC.stream,
bottomSheetEventSC: bottomSheetEventSC,
@ -474,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

@ -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.labelLarge?.copyWith(fontWeight: FontWeight.bold),
style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
color: enabled ? null : context.themeData.disabledColor,
),
),
subtitle: subtitle != null
? Text(
subtitle!,
style: TextStyle(
color: enabled ? null : context.themeData.disabledColor,
),
)
: null,
);

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/widgetref_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
import 'package:immich_mobile/modules/trash/providers/trashed_asset.provider.dart';
@ -11,7 +12,6 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
class TrashPage extends HookConsumerWidget {
@ -24,7 +24,7 @@ class TrashPage extends HookConsumerWidget {
ref.watch(serverInfoProvider.select((v) => v.serverConfig.trashDays));
final selectionEnabledHook = useState(false);
final selection = useState(<Asset>{});
final processing = useState(false);
final processing = ref.useProcessingOverlay();
void selectionListener(
bool multiselect,
@ -267,8 +267,6 @@ class TrashPage extends HookConsumerWidget {
),
),
if (selectionEnabledHook.value) buildBottomBar(),
if (processing.value)
const Center(child: ImmichLoadingIndicator()),
],
),
),

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

@ -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

@ -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

@ -4,8 +4,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_dialog.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
@ -26,7 +24,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
final bool isEnableAutoBackup =
backupState.backgroundBackup || backupState.autoBackup;
final ServerInfo serverInfoState = ref.watch(serverInfoProvider);
AuthenticationState authState = ref.watch(authenticationProvider);
final user = Store.tryGet(StoreKey.currentUser);
const widgetSize = 30.0;
@ -44,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,
),
),
@ -54,7 +51,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
alignment: Alignment.bottomRight,
isLabelVisible: serverInfoState.isVersionMismatch,
offset: const Offset(2, 2),
child: authState.profileImagePath.isEmpty || user == null
child: user == null
? const Icon(
Icons.face_outlined,
size: widgetSize,

View file

@ -15,13 +15,13 @@ 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,
strokeWidth: 2,
child: CircularProgressIndicator(
color: context.colorScheme.onPrimary,
strokeWidth: 3,
),
);
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/color_extensions.dart';
enum ToastType { info, success, error }
@ -9,7 +10,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 +19,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 +27,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,
);
case ToastType.success:
return const Icon(
Icons.check_circle_rounded,
color: Color.fromARGB(255, 78, 140, 124),
);
return Icons.info_outline_rounded;
case ToastType.error:
return const Icon(
Icons.error_outline_rounded,
color: Color.fromARGB(255, 240, 162, 156),
);
return Icons.check_circle_outline_rounded;
case ToastType.success:
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 +67,7 @@ class ImmichToast {
child: Text(
msg,
style: TextStyle(
color: getColor(toastType, context),
fontWeight: FontWeight.bold,
color: context.colorScheme.onInverseSurface,
fontSize: 15,
),
),

View file

@ -46,7 +46,7 @@ class UserCircleAvatar extends ConsumerWidget {
user.firstName[0].toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: context.isDarkTheme ? Colors.black : Colors.white,
color: context.colorScheme.onInverseSurface,
),
);
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),
@ -53,7 +51,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(
@ -112,7 +110,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(
@ -151,7 +149,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

@ -1,41 +1,73 @@
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/providers/immich_loading_overlay.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
class ImmichLoadingOverlay extends StatelessWidget {
const ImmichLoadingOverlay({
Key? key,
}) : super(key: key);
class ImmichLoadingOverlay extends StatefulHookConsumerWidget {
final ThemeData theme;
final ThemeData darkTheme;
const ImmichLoadingOverlay(
{super.key, required this.theme, required this.darkTheme});
@override
ImmichLoadingOverlayState createState() => ImmichLoadingOverlayState();
}
class ImmichLoadingOverlayState extends ConsumerState<ImmichLoadingOverlay>
with WidgetsBindingObserver {
Brightness currentPlatformBrightness =
WidgetsBinding.instance.platformDispatcher.platformBrightness;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangePlatformBrightness() {
super.didChangePlatformBrightness();
setState(() {
currentPlatformBrightness =
WidgetsBinding.instance.platformDispatcher.platformBrightness;
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable:
ImmichLoadingOverlayController.appLoader.loaderShowingNotifier,
builder: (context, shouldShow, child) {
return shouldShow
? const Scaffold(
backgroundColor: Colors.black54,
body: Center(
child: ImmichLoadingIndicator(),
),
)
: const SizedBox();
},
final shouldShow = ref.watch(immichLoadingOverlayController);
final themeMode = ref.watch(immichThemeProvider);
final currentBrightness = themeMode == ThemeMode.system
? currentPlatformBrightness
: themeMode == ThemeMode.dark
? Brightness.dark
: Brightness.light;
return IgnorePointer(
child: shouldShow
? Theme(
data: currentBrightness == Brightness.dark
? widget.darkTheme
: widget.theme,
child: Container(
width: double.infinity,
height: double.infinity,
color: context.colorScheme.surface.withAlpha(70),
child: const Center(child: ImmichLoadingIndicator()),
),
)
: const SizedBox(),
);
}
}
class ImmichLoadingOverlayController {
static final ImmichLoadingOverlayController appLoader =
ImmichLoadingOverlayController();
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void show() {
loaderShowingNotifier.value = true;
}
void hide() {
loaderShowingNotifier.value = false;
}
}

View file

@ -173,3 +173,6 @@ ThemeData getThemeForScheme(ColorScheme scheme) {
ThemeData immichLightTheme = getThemeForScheme(_lightColorScheme);
ThemeData immichDarkTheme = getThemeForScheme(_darkColorScheme);
const redAccent = Colors.redAccent;
const orangeAccent = Colors.orangeAccent;

View file

@ -321,6 +321,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

@ -60,6 +60,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