Skip to content

Commit d870106

Browse files
committed
Theme, custom-dialog and toast-button options for confirm and toast actions
ConfirmAction can show a canvas as its dialog (dialogId, params) that answers through a Navigate back action whose params hold confirmed, and carries optional colour, radius and barrier overrides for the built-in dialog. ShowToastAction gains a button label with actions (e.g. Undo), a theme style and a top or bottom position; its colours are now optional so saved toasts, which omitted the old defaults, take the app's theme. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VtH72Dg3rGsqwaEe62Sha4
1 parent 2bfd49c commit d870106

6 files changed

Lines changed: 372 additions & 23 deletions

File tree

‎lib/src/api/models/action/confirm_action.dart‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ part 'confirm_action.g.dart';
88

99
/// Asks the user to confirm, e.g. "Leave album?", then runs [actions] when
1010
/// they confirm and [cancelActions] when they cancel or dismiss the dialog.
11+
///
12+
/// By default the dialog is built by the runtime and takes the app's look:
13+
/// its font, primary color and surface come from the layout that shows it.
14+
/// The optional color and radius fields override that look.
15+
///
16+
/// With a [dialogId] the dialog is that canvas instead, like
17+
/// [ShowDialogAction]. A button in it answers the dialog with a "Navigate
18+
/// back" action whose params hold [resultKey]: `true` confirms, anything else
19+
/// cancels. Closing the dialog any other way cancels. The params popped with a
20+
/// confirm are the `${result}` the [actions] read.
1121
@JsonSerializable()
1222
class ConfirmAction extends ActionModel with Equatable, SerializableMixin {
1323
/// The dialog's title. Supports variables and formulas.
@@ -32,6 +42,54 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin {
3242
/// Actions to run, in order, when the user cancels.
3343
final List<ActionModel> cancelActions;
3444

45+
/// Id of a canvas to show as the dialog instead of the built-in one, or
46+
/// null for the built-in dialog.
47+
final String? dialogId;
48+
49+
/// Parameters passed to the [dialogId] canvas, like
50+
/// [ShowDialogAction.params].
51+
final Map<String, dynamic> params;
52+
53+
/// Whether tapping outside the dialog dismisses it, which cancels.
54+
final bool barrierDismissible;
55+
56+
/// Color of the barrier behind the dialog. Null uses the platform's
57+
/// default scrim.
58+
final ColorRGBA? barrierColor;
59+
60+
/// Background of the built-in dialog. Null uses the app's surface color.
61+
final ColorRGBA? backgroundColor;
62+
63+
/// Title and message color of the built-in dialog. Null uses the app's
64+
/// text color.
65+
final ColorRGBA? textColor;
66+
67+
/// Fill of the built-in dialog's confirm button. Null uses the app's
68+
/// primary color, or its error color when [destructive].
69+
final ColorRGBA? confirmColor;
70+
71+
/// Corner radius of the built-in dialog. Null follows the app's buttons.
72+
final double? cornerRadius;
73+
74+
/// The key of the "Navigate back" params that answers a [dialogId] dialog.
75+
static const String resultKey = 'confirmed';
76+
77+
/// Whether a dialog that closed with [result] was confirmed: a map (the
78+
/// params of a "Navigate back" action) whose [resultKey] is true, or true.
79+
static bool isConfirmResult(Object? result) {
80+
final Object? value = result is Map ? result[resultKey] : result;
81+
if (value is bool) return value;
82+
if (value is num) return value != 0;
83+
if (value is String) {
84+
final String text = value.trim().toLowerCase();
85+
return text == 'true' ||
86+
text == 'yes' ||
87+
text == '1' ||
88+
text == 'confirm';
89+
}
90+
return false;
91+
}
92+
3593
/// Creates a [ConfirmAction].
3694
ConfirmAction({
3795
required this.title,
@@ -41,10 +99,19 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin {
4199
this.destructive = false,
42100
List<ActionModel>? actions,
43101
List<ActionModel>? cancelActions,
102+
this.dialogId,
103+
Map<String, dynamic>? params,
104+
this.barrierDismissible = true,
105+
this.barrierColor,
106+
this.backgroundColor,
107+
this.textColor,
108+
this.confirmColor,
109+
this.cornerRadius,
44110
super.nonBlocking,
45111
super.enabled,
46112
}) : actions = actions ?? [],
47113
cancelActions = cancelActions ?? [],
114+
params = params ?? {},
48115
super(type: ActionType.confirm);
49116

50117
/// Duplicates this action with the given values replaced.
@@ -56,6 +123,20 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin {
56123
bool? destructive,
57124
List<ActionModel>? actions,
58125
List<ActionModel>? cancelActions,
126+
String? dialogId,
127+
bool clearDialogId = false,
128+
Map<String, dynamic>? params,
129+
bool? barrierDismissible,
130+
ColorRGBA? barrierColor,
131+
bool clearBarrierColor = false,
132+
ColorRGBA? backgroundColor,
133+
bool clearBackgroundColor = false,
134+
ColorRGBA? textColor,
135+
bool clearTextColor = false,
136+
ColorRGBA? confirmColor,
137+
bool clearConfirmColor = false,
138+
double? cornerRadius,
139+
bool clearCornerRadius = false,
59140
}) => ConfirmAction(
60141
title: title ?? this.title,
61142
message: message ?? this.message,
@@ -64,6 +145,16 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin {
64145
destructive: destructive ?? this.destructive,
65146
actions: actions ?? this.actions,
66147
cancelActions: cancelActions ?? this.cancelActions,
148+
dialogId: clearDialogId ? null : dialogId ?? this.dialogId,
149+
params: params ?? this.params,
150+
barrierDismissible: barrierDismissible ?? this.barrierDismissible,
151+
barrierColor: clearBarrierColor ? null : barrierColor ?? this.barrierColor,
152+
backgroundColor: clearBackgroundColor
153+
? null
154+
: backgroundColor ?? this.backgroundColor,
155+
textColor: clearTextColor ? null : textColor ?? this.textColor,
156+
confirmColor: clearConfirmColor ? null : confirmColor ?? this.confirmColor,
157+
cornerRadius: clearCornerRadius ? null : cornerRadius ?? this.cornerRadius,
67158
nonBlocking: nonBlocking,
68159
enabled: enabled,
69160
);
@@ -77,6 +168,14 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin {
77168
destructive,
78169
actions,
79170
cancelActions,
171+
dialogId,
172+
params,
173+
barrierDismissible,
174+
barrierColor,
175+
backgroundColor,
176+
textColor,
177+
confirmColor,
178+
cornerRadius,
80179
];
81180

82181
/// Creates an instance from a JSON object.

‎lib/src/api/models/action/confirm_action.g.dart‎

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/src/api/models/action/show_toast_action.dart‎

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,54 @@ import '../models.dart';
66

77
part 'show_toast_action.g.dart';
88

9-
/// An action that shows a short message in a pill near the bottom of the
10-
/// screen, then hides it.
9+
/// The look of a [ShowToastAction], colored from the app's theme.
10+
enum ToastStyle {
11+
/// Dark on a light app and light on a dark one, like a snackbar.
12+
neutral,
13+
14+
/// The app's primary color.
15+
info,
16+
17+
/// Green.
18+
success,
19+
20+
/// Amber.
21+
warning,
22+
23+
/// The app's error color.
24+
error;
25+
26+
/// Displayable name.
27+
String get prettify => switch (this) {
28+
ToastStyle.neutral => 'Neutral',
29+
ToastStyle.info => 'Info',
30+
ToastStyle.success => 'Success',
31+
ToastStyle.warning => 'Warning',
32+
ToastStyle.error => 'Error',
33+
};
34+
}
35+
36+
/// Where a [ShowToastAction] shows on screen.
37+
enum ToastPosition {
38+
/// Above the bottom edge.
39+
bottom,
40+
41+
/// Below the top edge and its safe area.
42+
top;
43+
44+
/// Displayable name.
45+
String get prettify => switch (this) {
46+
ToastPosition.bottom => 'Bottom',
47+
ToastPosition.top => 'Top',
48+
};
49+
}
50+
51+
/// An action that shows a short message near the bottom or top of the screen,
52+
/// then hides it. The toast can carry a button, e.g. Undo, that runs
53+
/// [actions].
54+
///
55+
/// Its colors come from [style] and the app's theme unless [backgroundColor]
56+
/// or [textColor] are set. Its font is the app's.
1157
@JsonSerializable()
1258
class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
1359
/// The message. Supports variables and formulas, e.g.
@@ -17,36 +63,64 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
1763
/// How long the message stays on screen, in milliseconds.
1864
final int duration;
1965

20-
/// Pill color.
21-
final ColorRGBA backgroundColor;
66+
/// Background color. Null takes the [style]'s color from the app's theme.
67+
final ColorRGBA? backgroundColor;
68+
69+
/// Message color. Null takes the color that reads on the background.
70+
final ColorRGBA? textColor;
2271

23-
/// Message color.
24-
final ColorRGBA textColor;
72+
/// Which theme color the toast takes.
73+
@JsonKey(unknownEnumValue: ToastStyle.neutral)
74+
final ToastStyle style;
75+
76+
/// Where the toast shows.
77+
@JsonKey(unknownEnumValue: ToastPosition.bottom)
78+
final ToastPosition position;
79+
80+
/// Label of the toast's button, e.g. Undo. Supports variables. The toast
81+
/// has no button when it is empty.
82+
final String actionLabel;
83+
84+
/// Actions to run, in order, when the toast's button is tapped.
85+
final List<ActionModel> actions;
2586

2687
/// Optional icon before the message.
2788
final MultiSourceIconModel? icon;
2889

29-
/// Distance from the bottom of the screen.
90+
/// Distance from the bottom of the screen when [position] is
91+
/// [ToastPosition.bottom].
3092
final double bottomOffset;
3193

3294
/// Creates a new [ShowToastAction].
3395
ShowToastAction({
3496
required this.message,
3597
this.duration = 2600,
36-
this.backgroundColor = const ColorRGBA(r: 0.169, g: 0.141, b: 0.125, a: 1),
37-
this.textColor = ColorRGBA.white,
98+
this.backgroundColor,
99+
this.textColor,
100+
this.style = ToastStyle.neutral,
101+
this.position = ToastPosition.bottom,
102+
this.actionLabel = '',
103+
List<ActionModel>? actions,
38104
this.icon,
39105
this.bottomOffset = 104,
40106
super.nonBlocking,
41107
super.enabled,
42-
}) : super(type: ActionType.showToast);
108+
}) : actions = actions ?? [],
109+
super(type: ActionType.showToast);
110+
111+
/// Whether the toast shows a button.
112+
bool get hasAction => actionLabel.trim().isNotEmpty;
43113

44114
@override
45115
List<Object?> get props => [
46116
message,
47117
duration,
48118
backgroundColor,
49119
textColor,
120+
style,
121+
position,
122+
actionLabel,
123+
actions,
50124
icon,
51125
bottomOffset,
52126
];
@@ -56,15 +130,27 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin {
56130
String? message,
57131
int? duration,
58132
ColorRGBA? backgroundColor,
133+
bool clearBackgroundColor = false,
59134
ColorRGBA? textColor,
135+
bool clearTextColor = false,
136+
ToastStyle? style,
137+
ToastPosition? position,
138+
String? actionLabel,
139+
List<ActionModel>? actions,
60140
MultiSourceIconModel? icon,
61141
bool clearIcon = false,
62142
double? bottomOffset,
63143
}) => ShowToastAction(
64144
message: message ?? this.message,
65145
duration: duration ?? this.duration,
66-
backgroundColor: backgroundColor ?? this.backgroundColor,
67-
textColor: textColor ?? this.textColor,
146+
backgroundColor: clearBackgroundColor
147+
? null
148+
: backgroundColor ?? this.backgroundColor,
149+
textColor: clearTextColor ? null : textColor ?? this.textColor,
150+
style: style ?? this.style,
151+
position: position ?? this.position,
152+
actionLabel: actionLabel ?? this.actionLabel,
153+
actions: actions ?? this.actions,
68154
icon: clearIcon ? null : icon ?? this.icon,
69155
bottomOffset: bottomOffset ?? this.bottomOffset,
70156
nonBlocking: nonBlocking,

0 commit comments

Comments
 (0)