feat(shopinbit): better ShopInBit settings page on mobile
What changed, and why it matters
This commit is a routine user-interface improvement for the ShopInBit settings page in the Stack Wallet app. It adds a settings gear icon on mobile, a settings button on desktop, and restyles the warning dialogs that remind users to save their customer key before changing it. There is no security-relevant change in behavior.
No security action required; treat as normal UI/UX code review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff refactors four Dart/Flutter UI files. It replaces generic AlertDialog/StackDialog widgets with project-specific DesktopDialog/StackDialogBase widgets, adds a settings navigation entry point in services_view.dart and desktop_services_view.dart, and improves visual styling (warning colors, selectable text, rounded containers). The customer-key verification logic and navigation flow remain unchanged; no cryptographic, network, or permission code is modified.
Changed components
lib/pages/more_view/services_view.dartlib/pages/shopinbit/shopinbit_settings_view.dartlib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dartlib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dartInspect captured patch +274 / −121
diff --git a/lib/pages/more_view/services_view.dart b/lib/pages/more_view/services_view.dart
index d304567..c85cdcb 100644
--- a/lib/pages/more_view/services_view.dart
+++ b/lib/pages/more_view/services_view.dart
@@ -14,6 +14,7 @@ import '../../widgets/desktop/primary_button.dart';
import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_dialog.dart';
+import '../shopinbit/shopinbit_settings_view.dart';
import '../shopinbit/shopinbit_step_1.dart';
import '../shopinbit/shopinbit_tickets_view.dart';
@@ -184,9 +185,26 @@ class _ServicesViewState extends State<ServicesView> {
height: 32,
),
const SizedBox(width: 12),
- Text(
- "ShopInBit",
- style: STextStyles.titleBold12(context),
+ Expanded(
+ child: Text(
+ "ShopInBit",
+ style: STextStyles.titleBold12(context),
+ ),
+ ),
+ GestureDetector(
+ onTap: () {
+ Navigator.of(
+ context,
+ ).pushNamed(ShopInBitSettingsView.routeName);
+ },
+ child: SvgPicture.asset(
+ Assets.svg.gear,
+ width: 20,
+ height: 20,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ ),
),
],
),
diff --git a/lib/pages/shopinbit/shopinbit_settings_view.dart b/lib/pages/shopinbit/shopinbit_settings_view.dart
index c6605d9..42cc4b8 100644
--- a/lib/pages/shopinbit/shopinbit_settings_view.dart
+++ b/lib/pages/shopinbit/shopinbit_settings_view.dart
@@ -138,33 +138,76 @@ class _ShopInBitSettingsViewState extends ConsumerState<ShopInBitSettingsView> {
final result = await showDialog<bool>(
context: context,
barrierDismissible: true,
- builder: (context) => StackDialog(
- title: "Save your current key",
- message:
- "Your current customer key is:\n\n$_currentKey\n\n"
- "Changing your key will disconnect you from existing "
- "ShopInBit conversations. Make sure you have saved your "
- "current key before proceeding.",
- leftButton: TextButton(
- style: Theme.of(
- context,
- ).extension<StackColors>()!.getSecondaryEnabledButtonStyle(context),
- onPressed: () => Navigator.of(context).pop(false),
- child: Text(
- "Cancel",
- style: STextStyles.button(context).copyWith(
+ builder: (context) => StackDialogBase(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Save your current key",
+ style: STextStyles.pageTitleH2(context),
+ ),
+ const SizedBox(height: 8),
+ SelectableText(
+ "Your current customer key is:",
+ style: STextStyles.smallMed14(context),
+ ),
+ const SizedBox(height: 8),
+ RoundedContainer(
color: Theme.of(
context,
- ).extension<StackColors>()!.accentColorDark,
+ ).extension<StackColors>()!.warningBackground,
+ child: SelectableText(
+ _currentKey!,
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.warningForeground,
+ ),
+ ),
),
- ),
- ),
- rightButton: TextButton(
- style: Theme.of(
- context,
- ).extension<StackColors>()!.getPrimaryEnabledButtonStyle(context),
- onPressed: () => Navigator.of(context).pop(null),
- child: Text("I saved my key", style: STextStyles.button(context)),
+ const SizedBox(height: 8),
+ SelectableText(
+ "Changing your key will disconnect you from "
+ "existing ShopInBit conversations. Make sure "
+ "you have saved your current key before "
+ "proceeding.",
+ style: STextStyles.smallMed14(context),
+ ),
+ const SizedBox(height: 20),
+ Row(
+ children: [
+ Expanded(
+ child: TextButton(
+ style: Theme.of(context)
+ .extension<StackColors>()!
+ .getSecondaryEnabledButtonStyle(context),
+ onPressed: () => Navigator.of(context).pop(false),
+ child: Text(
+ "Cancel",
+ style: STextStyles.button(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorDark,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: TextButton(
+ style: Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryEnabledButtonStyle(context),
+ onPressed: () => Navigator.of(context).pop(null),
+ child: Text(
+ "I saved my key",
+ style: STextStyles.button(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
),
),
);
@@ -183,14 +226,18 @@ class _ShopInBitSettingsViewState extends ConsumerState<ShopInBitSettingsView> {
return StatefulBuilder(
builder: (ctx, setDialogState) {
final matches = _verifyKeyController.text.trim() == _currentKey;
- return StackDialog(
- title: "Verify your key",
- message:
- "Enter your current customer key to confirm "
- "you have saved it.",
- leftButton: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
+ return StackDialogBase(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ Text("Verify your key", style: STextStyles.pageTitleH2(ctx)),
+ const SizedBox(height: 8),
+ Text(
+ "Enter your current customer key to "
+ "confirm you have saved it.",
+ style: STextStyles.smallMed14(ctx),
+ ),
+ const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -207,7 +254,7 @@ class _ShopInBitSettingsViewState extends ConsumerState<ShopInBitSettingsView> {
onChanged: (_) => setDialogState(() {}),
),
),
- const SizedBox(height: 12),
+ const SizedBox(height: 20),
Row(
children: [
Expanded(
diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart
index d1f1e36..983804c 100644
--- a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart
+++ b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart
@@ -1,30 +1,35 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:url_launcher/url_launcher.dart';
+import '../../../app_config.dart';
import '../../../db/isar/main_db.dart';
import '../../../models/shopinbit/shopinbit_order_model.dart';
import '../../../pages/shopinbit/shopinbit_step_1.dart';
import '../../../pages/shopinbit/shopinbit_tickets_view.dart';
-import '../../../themes/stack_colors.dart';
+import '../../../providers/desktop/current_desktop_menu_item.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/text_styles.dart';
import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/rounded_white_container.dart';
+import '../../desktop_menu.dart';
+import '../../settings/settings_menu.dart';
-class DesktopServicesView extends StatefulWidget {
+class DesktopServicesView extends ConsumerStatefulWidget {
const DesktopServicesView({super.key});
static const String routeName = "/desktopServicesView";
@override
- State<DesktopServicesView> createState() => _DesktopServicesViewState();
+ ConsumerState<DesktopServicesView> createState() =>
+ _DesktopServicesViewState();
}
-class _DesktopServicesViewState extends State<DesktopServicesView> {
+class _DesktopServicesViewState extends ConsumerState<DesktopServicesView> {
Future<bool> _showOpenBrowserWarning(BuildContext context, String url) async {
final uri = Uri.parse(url);
final shouldContinue = await showDialog<bool>(
@@ -275,6 +280,27 @@ class _DesktopServicesViewState extends State<DesktopServicesView> {
);
},
),
+ const SizedBox(width: 16),
+ SecondaryButton(
+ width: 140,
+ buttonHeight: ButtonHeight.m,
+ label: "Settings",
+ onPressed: () {
+ // ShopInBit is the last settings menu item.
+ var idx = 8;
+ if (AppConfig.hasFeature(AppFeature.themeSelection)) {
+ idx++;
+ }
+ ref
+ .read(
+ selectedSettingsMenuItemStateProvider.state,
+ )
+ .state =
+ idx;
+ ref.read(currentDesktopMenuItemProvider.state).state =
+ DesktopMenuItemId.settings;
+ },
+ ),
],
),
),
diff --git a/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart
index a3f45c7..1d39a5d 100644
--- a/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart
+++ b/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart
@@ -11,7 +11,10 @@ import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/constants.dart';
import '../../../utilities/text_styles.dart';
+import '../../../widgets/desktop/desktop_dialog.dart';
+import '../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../widgets/desktop/primary_button.dart';
+import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_text_field.dart';
@@ -135,50 +138,78 @@ class _ShopInBitDesktopSettingsState
final result = await showDialog<bool>(
context: context,
barrierDismissible: true,
- builder: (context) => AlertDialog(
- title: Text(
- "Save your current key",
- style: STextStyles.desktopH3(context),
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
+ builder: (ctx) => DesktopDialog(
+ maxWidth: 550,
+ maxHeight: double.infinity,
+ child: Column(
children: [
- Text(
- "Your current customer key is:",
- style: STextStyles.desktopTextExtraExtraSmall(context),
- ),
- const SizedBox(height: 8),
- SelectableText(
- _currentKey!,
- style: STextStyles.desktopTextSmall(context),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(32),
+ child: Text(
+ "Save your current key",
+ style: STextStyles.desktopH3(ctx),
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
),
- const SizedBox(height: 16),
- Text(
- "Changing your key will disconnect you from existing "
- "ShopInBit conversations. Make sure you have saved "
- "your current key before proceeding.",
- style: STextStyles.desktopTextExtraExtraSmall(context),
+ Padding(
+ padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Your current customer key is:",
+ style: STextStyles.desktopTextExtraExtraSmall(ctx),
+ ),
+ const SizedBox(height: 8),
+ RoundedWhiteContainer(
+ borderColor: Theme.of(
+ ctx,
+ ).extension<StackColors>()!.textSubtitle6,
+ child: SelectableText(
+ _currentKey!,
+ style: STextStyles.desktopTextSmall(ctx),
+ ),
+ ),
+ const SizedBox(height: 16),
+ Text(
+ "Changing your key will disconnect you from "
+ "existing ShopInBit conversations. Make sure "
+ "you have saved your current key before "
+ "proceeding.",
+ style: STextStyles.desktopTextExtraExtraSmall(ctx),
+ ),
+ const SizedBox(height: 32),
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ label: "Cancel",
+ buttonHeight: ButtonHeight.l,
+ onPressed: () =>
+ Navigator.of(ctx, rootNavigator: true).pop(false),
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: PrimaryButton(
+ label: "I saved my key",
+ buttonHeight: ButtonHeight.l,
+ onPressed: () =>
+ Navigator.of(ctx, rootNavigator: true).pop(null),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(false),
- child: Text(
- "Cancel",
- style: STextStyles.button(context).copyWith(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.accentColorDark,
- ),
- ),
- ),
- TextButton(
- onPressed: () => Navigator.of(context).pop(null),
- child: Text("I saved my key", style: STextStyles.button(context)),
- ),
- ],
),
);
@@ -196,56 +227,87 @@ class _ShopInBitDesktopSettingsState
return StatefulBuilder(
builder: (ctx, setDialogState) {
final matches = _verifyKeyController.text.trim() == _currentKey;
- return AlertDialog(
- title: Text("Verify your key", style: STextStyles.desktopH3(ctx)),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
+ return DesktopDialog(
+ maxWidth: 550,
+ maxHeight: double.infinity,
+ child: Column(
children: [
- Text(
- "Enter your current customer key to confirm "
- "you have saved it.",
- style: STextStyles.desktopTextExtraExtraSmall(ctx),
- ),
- const SizedBox(height: 16),
- SizedBox(
- width: 400,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- controller: _verifyKeyController,
- focusNode: _verifyKeyFocusNode,
- style: STextStyles.field(ctx),
- decoration: standardInputDecoration(
- "Enter current key",
- _verifyKeyFocusNode,
- ctx,
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(32),
+ child: Text(
+ "Verify your key",
+ style: STextStyles.desktopH3(ctx),
),
- onChanged: (_) => setDialogState(() {}),
),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ right: 32,
+ bottom: 32,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Enter your current customer key to "
+ "confirm you have saved it.",
+ style: STextStyles.desktopTextExtraExtraSmall(ctx),
+ ),
+ const SizedBox(height: 16),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ controller: _verifyKeyController,
+ focusNode: _verifyKeyFocusNode,
+ style: STextStyles.field(ctx),
+ decoration: standardInputDecoration(
+ "Enter current key",
+ _verifyKeyFocusNode,
+ ctx,
+ ),
+ onChanged: (_) => setDialogState(() {}),
+ ),
+ ),
+ const SizedBox(height: 32),
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ label: "Cancel",
+ buttonHeight: ButtonHeight.l,
+ onPressed: () => Navigator.of(
+ ctx,
+ rootNavigator: true,
+ ).pop(false),
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: PrimaryButton(
+ label: "Confirm",
+ buttonHeight: ButtonHeight.l,
+ enabled: matches,
+ onPressed: () => Navigator.of(
+ ctx,
+ rootNavigator: true,
+ ).pop(true),
+ ),
+ ),
+ ],
+ ),
+ ],
),
),
],
),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(ctx).pop(false),
- child: Text(
- "Cancel",
- style: STextStyles.button(ctx).copyWith(
- color: Theme.of(
- ctx,
- ).extension<StackColors>()!.accentColorDark,
- ),
- ),
- ),
- TextButton(
- onPressed: matches ? () => Navigator.of(ctx).pop(true) : null,
- child: Text("Confirm", style: STextStyles.button(ctx)),
- ),
- ],
);
},
);
Why this scored 15/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.