feat: actions undo handling (#30481)

* feat: actions undo handling

# Conflicts:
#	mobile/lib/main.dart

* address pr reviews

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2026-08-19 00:06:57 +05:30
committed by GitHub
co-authored by shenlong-tanwen
parent 1539ae8b07
commit ea3fa92776
12 changed files with 157 additions and 28 deletions
+41 -8
View File
@@ -1,8 +1,19 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:immich_ui/immich_ui.dart';
import 'package:immich_ui/src/internal.dart';
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
class SnackbarAction {
// Optional label for the action button. Falls back to the localized "Undo" label when null
final String? label;
final FutureOr<void> Function() onPressed;
const SnackbarAction({this.label, required this.onPressed});
}
class SnackbarManager {
const SnackbarManager();
@@ -10,6 +21,7 @@ class SnackbarManager {
String message,
SnackbarType type, {
Duration? duration,
SnackbarAction? action,
}) {
final messenger = scaffoldMessengerKey.currentState;
final context = scaffoldMessengerKey.currentContext;
@@ -19,10 +31,10 @@ class SnackbarManager {
duration ??= const .new(seconds: 4);
messenger.hideCurrentSnackBar();
return messenger.showSnackBar(_build(context, message, type, duration));
return messenger.showSnackBar(_build(context, message, type, duration, action));
}
SnackBar _build(BuildContext context, String message, SnackbarType type, Duration duration) {
SnackBar _build(BuildContext context, String message, SnackbarType type, Duration duration, SnackbarAction? action) {
final theme = Theme.of(context);
final colors = theme.extension<ImmichColors>() ?? ImmichColors.harmonized(theme.colorScheme);
final (IconData icon, Color background, Color foreground) = switch (type) {
@@ -31,11 +43,22 @@ class SnackbarManager {
.error => (Icons.warning_rounded, colors.error, colors.onError),
};
SnackBarAction? snackAction;
if (action != null) {
snackAction = .new(
label: action.label ?? context.translations.undo,
onPressed: action.onPressed,
textColor: foreground,
);
}
return SnackBar(
behavior: .floating,
backgroundColor: background,
duration: duration,
shape: const RoundedRectangleBorder(borderRadius: .all(.circular(ImmichRadius.sm))),
// A snackbar carrying an action stays up until it is tapped by default; our actions are optional
persist: false,
content: Row(
children: [
Icon(icon, color: foreground, size: ImmichIconSize.sm),
@@ -50,17 +73,27 @@ class SnackbarManager {
),
],
),
action: snackAction,
);
}
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? info(String message, {Duration? duration}) =>
show(message, .info, duration: duration);
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? info(
String message, {
Duration? duration,
SnackbarAction? action,
}) => show(message, .info, duration: duration, action: action);
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? success(String message, {Duration? duration}) =>
show(message, .success, duration: duration);
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? success(
String message, {
Duration? duration,
SnackbarAction? action,
}) => show(message, .success, duration: duration, action: action);
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? error(String message, {Duration? duration}) =>
show(message, .error, duration: duration);
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? error(
String message, {
Duration? duration,
SnackbarAction? action,
}) => show(message, .error, duration: duration, action: action);
}
const snackbar = SnackbarManager();
+9 -12
View File
@@ -1,27 +1,24 @@
import 'package:flutter/material.dart';
class ImmichTranslations {
late String submit;
late String password;
final String submit;
final String password;
final String undo;
ImmichTranslations({String? submit, String? password}) {
this.submit = submit ?? 'Submit';
this.password = password ?? 'Password';
}
const ImmichTranslations({String? submit, String? password, String? undo})
: submit = submit ?? 'Submit',
password = password ?? 'Password',
undo = undo ?? 'Undo';
}
class ImmichTranslationProvider extends InheritedWidget {
final ImmichTranslations? translations;
const ImmichTranslationProvider({
super.key,
this.translations,
required super.child,
});
const ImmichTranslationProvider({super.key, this.translations, required super.child});
static ImmichTranslations of(BuildContext context) {
final provider = context.dependOnInheritedWidgetOfExactType<ImmichTranslationProvider>();
return provider?.translations ?? ImmichTranslations();
return provider?.translations ?? const .new();
}
@override