fix(ui): only show close warning dialog when appropriate
What changed, and why it matters
This commit fixes a user-interface annoyance in Stack Wallet. Previously, closing certain pop-up dialogs would show an unnecessary 'are you sure you want to close?' warning even when no important progress was at risk. The change makes the warning appear only when appropriate, such as when the user is in the middle of a multi-step flow, and skips it on the very first step or when the navigation stack is empty. There is no direct evidence this is a security fix.
Treat as a routine UI/UX fix. No security response is indicated based on the commit content alone. If the project has a related bug report about accidental data loss from dialog dismissal, review whether the warning logic now covers all stateful steps appropriately.
Security signals we found
No security-relevant keywords in commit title or message
No changes to authentication, authorization, cryptography, networking, or data storage
Diff is purely UI/UX flow control
No vendor or researcher attribution for a security issue
No CVE or advisory references present
Evidence from the diff
The patch modifies the nested navigator dialog close behavior. It removes explicit NestedNavigatorDialog.of(context).close() overrides from CakePay card/order views, letting the default close logic apply. It adds an isActuallyFirstStep flag to ShopInBitStep2, passes it through route arguments, and uses it to choose between CloseArgs.noWarning() and CloseArgs.genericWarning(). The _CloseOnEmptyObserver now also closes without a warning when the navigator stack becomes empty. This is a UX consistency fix; no security boundary, cryptographic operation, or sensitive data handling is changed.
Changed components
lib/pages/cakepay/cakepay_card_detail_view.dartlib/pages/cakepay/cakepay_order_view.dartlib/pages/shopinbit/shopinbit_step_2.dartlib/pages_desktop_specific/services/shopin_bit/desktop_shopinbit_view.dartlib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dartlib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog_route_generator.dartInspect captured patch +30 / −15
diff --git a/lib/pages/cakepay/cakepay_card_detail_view.dart b/lib/pages/cakepay/cakepay_card_detail_view.dart
index 8eb6597..09a205c 100644
--- a/lib/pages/cakepay/cakepay_card_detail_view.dart
+++ b/lib/pages/cakepay/cakepay_card_detail_view.dart
@@ -12,7 +12,6 @@ import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../widgets/desktop/primary_button.dart';
-import '../../widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart';
import '../../widgets/dialogs/request_external_link_navigation_dialog.dart';
import '../../widgets/dialogs/s_dialog.dart';
import '../../widgets/icon_widgets/credit_card_icon.dart';
@@ -170,10 +169,7 @@ class _CakePayCardDetailViewState extends State<CakePayCardDetailView> {
style: STextStyles.desktopH3(context),
),
),
- DesktopDialogCloseButton(
- onPressedOverride: () =>
- NestedNavigatorDialog.of(context).close(),
- ),
+ const DesktopDialogCloseButton(),
],
),
Flexible(
diff --git a/lib/pages/cakepay/cakepay_order_view.dart b/lib/pages/cakepay/cakepay_order_view.dart
index fb4b307..aa693c7 100644
--- a/lib/pages/cakepay/cakepay_order_view.dart
+++ b/lib/pages/cakepay/cakepay_order_view.dart
@@ -23,7 +23,6 @@ import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../widgets/desktop/primary_button.dart';
-import '../../widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart';
import '../../widgets/dialogs/s_dialog.dart';
import '../../widgets/qr.dart';
import '../../widgets/refresh_control.dart';
@@ -874,10 +873,7 @@ class _CakePayOrderViewState extends ConsumerState<CakePayOrderView> {
onPressed: () => onRefresh(),
),
const SizedBox(width: 8),
- DesktopDialogCloseButton(
- onPressedOverride: () =>
- NestedNavigatorDialog.of(context).close(),
- ),
+ const DesktopDialogCloseButton(),
],
),
],
diff --git a/lib/pages/shopinbit/shopinbit_step_2.dart b/lib/pages/shopinbit/shopinbit_step_2.dart
index 86e3baf..2a4d9f0 100644
--- a/lib/pages/shopinbit/shopinbit_step_2.dart
+++ b/lib/pages/shopinbit/shopinbit_step_2.dart
@@ -21,11 +21,16 @@ import 'shopinbit_step_3.dart';
import 'shopinbit_step_4.dart';
class ShopInBitStep2 extends ConsumerStatefulWidget {
- const ShopInBitStep2({super.key, required this.model});
+ const ShopInBitStep2({
+ super.key,
+ required this.model,
+ this.isActuallyFirstStep = false,
+ });
static const String routeName = "/shopInBitStep2";
final ShopInBitOrderModel model;
+ final bool isActuallyFirstStep;
@override
ConsumerState<ShopInBitStep2> createState() => _ShopInBitStep2State();
@@ -78,13 +83,22 @@ class _ShopInBitStep2State extends ConsumerState<ShopInBitStep2> {
children: [
Row(
children: [
- const AppBarBackButton(isCompact: true, iconSize: 23),
+ widget.isActuallyFirstStep
+ ? const SizedBox(width: 32)
+ : const AppBarBackButton(
+ isCompact: true,
+ iconSize: 23,
+ ),
Text("ShopinBit", style: STextStyles.desktopH3(context)),
],
),
DesktopDialogCloseButton(
onPressedOverride: () =>
- NestedNavigatorDialog.of(context).close(),
+ NestedNavigatorDialog.of(context).close(
+ args: widget.isActuallyFirstStep
+ ? const .noWarning()
+ : const .genericWarning(),
+ ),
),
],
),
diff --git a/lib/pages_desktop_specific/services/shopin_bit/desktop_shopinbit_view.dart b/lib/pages_desktop_specific/services/shopin_bit/desktop_shopinbit_view.dart
index aac7b12..d5a8109 100644
--- a/lib/pages_desktop_specific/services/shopin_bit/desktop_shopinbit_view.dart
+++ b/lib/pages_desktop_specific/services/shopin_bit/desktop_shopinbit_view.dart
@@ -87,7 +87,7 @@ class _DesktopServicesViewState extends ConsumerState<DesktopShopInBitView> {
barrierDismissible: false,
builder: (_) => NestedNavigatorDialog(
initialRoute: ShopInBitStep2.routeName,
- initialRouteArgs: model,
+ initialRouteArgs: (model: model, isActuallyFirstStep: true),
),
);
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 277ae6f..14f788b 100644
--- a/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart
+++ b/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog.dart
@@ -118,7 +118,7 @@ class NestedNavigatorDialogState extends State<NestedNavigatorDialog> {
@override
void initState() {
super.initState();
- _observer = _CloseOnEmptyObserver(close);
+ _observer = _CloseOnEmptyObserver(() => close(args: const .noWarning()));
_navigatorKey = widget.navigatorKey ?? GlobalKey<NavigatorState>();
}
diff --git a/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog_route_generator.dart b/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog_route_generator.dart
index 3aa7d81..1b7567c 100644
--- a/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog_route_generator.dart
+++ b/lib/widgets/dialogs/nested_navigator_dialog/nested_navigator_dialog_route_generator.dart
@@ -60,6 +60,15 @@ abstract final class NestedNavigatorDialogRouteGenerator {
settings: RouteSettings(name: settings.name),
);
}
+ if (args is ({ShopInBitOrderModel model, bool isActuallyFirstStep})) {
+ return getRoute(
+ builder: (_) => ShopInBitStep2(
+ model: args.model,
+ isActuallyFirstStep: args.isActuallyFirstStep,
+ ),
+ settings: RouteSettings(name: settings.name),
+ );
+ }
return _routeError(
"${settings.name} invalid args\n"
"Got ${args.runtimeType}\n"
Why this scored 18/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.