feat(ui): allow custom nested nav flow close args
What changed, and why it matters
This commit is a user-interface cleanup. It moves the 'Are you sure you want to close?' confirmation logic into a shared dialog widget and lets different screens choose whether to show a warning, a generic message, or no warning at all. There is no indication this fixes a security vulnerability; it appears to be a normal feature/refactoring change.
No security action required. Review as ordinary UI refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors nested navigator dialog closing in a Flutter/Dart app. It replaces the standalone confirmCloseNestedNavigatorDialog() helper with a close({NestedNavigatorDialogCloseArgs args}) method on NestedNavigatorDialogState, supporting no warning, generic warning, or custom async predicate. Five page files are updated to call NestedNavigatorDialog.of(context).close() instead of the old helper. The change centralizes close behavior and adds flexibility, but does not alter trust boundaries, input handling, cryptography, networking, or storage.
Changed components
lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dartlib/pages/cakepay/cakepay_card_detail_view.dartlib/pages/cakepay/cakepay_order_view.dartlib/pages/shopinbit/shopinbit_step_2.dartlib/pages/shopinbit/shopinbit_step_3.dartlib/pages/shopinbit/shopinbit_step_4.dartInspect captured patch +99 / −55
diff --git a/lib/pages/cakepay/cakepay_card_detail_view.dart b/lib/pages/cakepay/cakepay_card_detail_view.dart
index 8e1bb84..8eb6597 100644
--- a/lib/pages/cakepay/cakepay_card_detail_view.dart
+++ b/lib/pages/cakepay/cakepay_card_detail_view.dart
@@ -172,7 +172,7 @@ class _CakePayCardDetailViewState extends State<CakePayCardDetailView> {
),
DesktopDialogCloseButton(
onPressedOverride: () =>
- confirmCloseNestedNavigatorDialog(context),
+ NestedNavigatorDialog.of(context).close(),
),
],
),
diff --git a/lib/pages/cakepay/cakepay_order_view.dart b/lib/pages/cakepay/cakepay_order_view.dart
index f10581e..fb4b307 100644
--- a/lib/pages/cakepay/cakepay_order_view.dart
+++ b/lib/pages/cakepay/cakepay_order_view.dart
@@ -876,7 +876,7 @@ class _CakePayOrderViewState extends ConsumerState<CakePayOrderView> {
const SizedBox(width: 8),
DesktopDialogCloseButton(
onPressedOverride: () =>
- confirmCloseNestedNavigatorDialog(context),
+ NestedNavigatorDialog.of(context).close(),
),
],
),
diff --git a/lib/pages/shopinbit/shopinbit_step_2.dart b/lib/pages/shopinbit/shopinbit_step_2.dart
index 5138a12..86e3baf 100644
--- a/lib/pages/shopinbit/shopinbit_step_2.dart
+++ b/lib/pages/shopinbit/shopinbit_step_2.dart
@@ -84,7 +84,7 @@ class _ShopInBitStep2State extends ConsumerState<ShopInBitStep2> {
),
DesktopDialogCloseButton(
onPressedOverride: () =>
- confirmCloseNestedNavigatorDialog(context),
+ NestedNavigatorDialog.of(context).close(),
),
],
),
diff --git a/lib/pages/shopinbit/shopinbit_step_3.dart b/lib/pages/shopinbit/shopinbit_step_3.dart
index 0b9b9db..d5cae9d 100644
--- a/lib/pages/shopinbit/shopinbit_step_3.dart
+++ b/lib/pages/shopinbit/shopinbit_step_3.dart
@@ -170,7 +170,7 @@ class _ShopInBitStep3State extends ConsumerState<ShopInBitStep3> {
),
DesktopDialogCloseButton(
onPressedOverride: () =>
- confirmCloseNestedNavigatorDialog(context),
+ NestedNavigatorDialog.of(context).close(),
),
],
),
diff --git a/lib/pages/shopinbit/shopinbit_step_4.dart b/lib/pages/shopinbit/shopinbit_step_4.dart
index 3927795..605d6e2 100644
--- a/lib/pages/shopinbit/shopinbit_step_4.dart
+++ b/lib/pages/shopinbit/shopinbit_step_4.dart
@@ -65,7 +65,7 @@ class _ShopInBitStep4DesktopShell extends StatelessWidget {
),
DesktopDialogCloseButton(
onPressedOverride: () =>
- confirmCloseNestedNavigatorDialog(context),
+ NestedNavigatorDialog.of(context).close(),
),
],
),
diff --git a/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart b/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart
index 992507e..277ae6f 100644
--- a/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart
+++ b/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart
@@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import '../../../utilities/text_styles.dart';
-import '../../desktop/desktop_dialog.dart';
+import '../../../utilities/util.dart';
import '../../desktop/primary_button.dart';
import '../../desktop/secondary_button.dart';
+import '../s_dialog.dart';
import 'nested_navigator_dialog_route_generator.dart';
class NestedNavigatorDialog extends StatefulWidget {
@@ -42,9 +43,76 @@ class NestedNavigatorDialogState extends State<NestedNavigatorDialog> {
NavigatorState? _parentNavigator;
- /// Closes the whole dialog (not just the current step).
- void close() {
- if (mounted) _parentNavigator?.pop();
+ Future<void> close({
+ NestedNavigatorDialogCloseArgs args = const .genericWarning(),
+ }) async {
+ if (!mounted) return;
+
+ final bool proceed = switch (args) {
+ _NoWarning() => true,
+ _GenericWarning() => await _showGenericWarning(),
+ _CustomWarning(:final shouldClose) => await shouldClose(),
+ };
+
+ if (proceed && mounted) _parentNavigator?.pop();
+ }
+
+ Future<bool> _showGenericWarning() async {
+ final confirmed = await showDialog<bool>(
+ context: context,
+ barrierDismissible: false,
+ useRootNavigator: true,
+ builder: (context) {
+ assert(Util.isDesktop, "");
+
+ return SDialog(
+ padding: const .all(32),
+ child: SizedBox(
+ width: 500,
+ child: Column(
+ crossAxisAlignment: .start,
+ mainAxisSize: .min,
+ children: [
+ Text("Discard changes?", style: STextStyles.desktopH3(context)),
+ const SizedBox(height: 16),
+ Text(
+ "Are you sure you want to close?",
+ style: STextStyles.desktopTextSmall(context),
+ ),
+ const SizedBox(height: 40),
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ label: "Cancel",
+ buttonHeight: ButtonHeight.l,
+ onPressed: () => Navigator.of(
+ context,
+ rootNavigator: true,
+ ).pop(false),
+ ),
+ ),
+ const SizedBox(width: 24),
+ Expanded(
+ child: PrimaryButton(
+ label: "Discard",
+ buttonHeight: ButtonHeight.l,
+ onPressed: () => Navigator.of(
+ context,
+ rootNavigator: true,
+ ).pop(true),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+
+ return confirmed ?? false;
}
@override
@@ -111,50 +179,26 @@ class _CloseOnEmptyObserver extends NavigatorObserver {
}
}
-/// Warns before closing the whole dialog. Wire this to the X on subsequent
-/// (non-root) steps so close does not silently act like back.
-Future<void> confirmCloseNestedNavigatorDialog(BuildContext context) async {
- final confirmed = await showDialog<bool>(
- context: context,
- builder: (ctx) => DesktopDialog(
- maxWidth: 450,
- maxHeight: 210,
- child: Padding(
- padding: const EdgeInsets.all(24),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text("Close?", style: STextStyles.desktopH3(ctx)),
- const SizedBox(height: 12),
- Text(
- "Are you sure you want to close?",
- style: STextStyles.desktopTextMedium(ctx),
- ),
- const Spacer(),
- Row(
- children: [
- Expanded(
- child: SecondaryButton(
- label: "Cancel",
- onPressed: () => Navigator.of(ctx).pop(false),
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: PrimaryButton(
- label: "Close",
- onPressed: () => Navigator.of(ctx).pop(true),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- );
- if (!context.mounted) return;
- if (confirmed == true) {
- NestedNavigatorDialog.of(context).close();
- }
+sealed class NestedNavigatorDialogCloseArgs {
+ const NestedNavigatorDialogCloseArgs();
+
+ const factory NestedNavigatorDialogCloseArgs.noWarning() = _NoWarning;
+ const factory NestedNavigatorDialogCloseArgs.genericWarning() =
+ _GenericWarning;
+ const factory NestedNavigatorDialogCloseArgs.customWarning(
+ Future<bool> Function() shouldClose,
+ ) = _CustomWarning;
+}
+
+class _NoWarning extends NestedNavigatorDialogCloseArgs {
+ const _NoWarning();
+}
+
+class _GenericWarning extends NestedNavigatorDialogCloseArgs {
+ const _GenericWarning();
+}
+
+class _CustomWarning extends NestedNavigatorDialogCloseArgs {
+ const _CustomWarning(this.shouldClose);
+ final Future<bool> Function() shouldClose;
}
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.