refactor(shopinbit): resume car research with inline row spinner
What changed, and why it matters
This commit is a user-interface refactor for the Stack Wallet app's 'ShopInBit' car-research ticket screen. It replaces a full-screen loading dialog with a small inline spinner while the app checks the server for an in-progress car-research invoice. There is no security-relevant change: no cryptography, authentication, network trust, permissions, or data handling logic is altered.
No security action required. Treat as a normal UI refactor during routine review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff changes only lib/pages/shopinbit/shopinbit_tickets_view.dart. It removes the showLoading() helper and instead uses a local _resuming boolean plus a LoadingIndicator widget inside the existing row. The underlying network call (getCurrentCarResearchInvoices()) and the resume/navigation logic remain identical. A try/finally guard is added to ensure _resuming is reset, and the button is disabled while loading. These are UX/state robustness improvements, not security fixes.
Changed components
lib/pages/shopinbit/shopinbit_tickets_view.dartInspect captured patch +39 / −20
diff --git a/lib/pages/shopinbit/shopinbit_tickets_view.dart b/lib/pages/shopinbit/shopinbit_tickets_view.dart
index b267e38..0f02a30 100644
--- a/lib/pages/shopinbit/shopinbit_tickets_view.dart
+++ b/lib/pages/shopinbit/shopinbit_tickets_view.dart
@@ -13,7 +13,6 @@ import "../../providers/global/shopin_bit_service_provider.dart";
import "../../services/shopinbit/src/models/car_research.dart";
import "../../themes/stack_colors.dart";
import "../../utilities/assets.dart";
-import "../../utilities/show_loading.dart";
import "../../utilities/text_styles.dart";
import "../../utilities/util.dart";
import "../../widgets/background.dart";
@@ -21,6 +20,7 @@ import "../../widgets/conditional_parent.dart";
import "../../widgets/custom_buttons/app_bar_icon_button.dart";
import "../../widgets/desktop/desktop_dialog_close_button.dart";
import "../../widgets/dialogs/s_dialog.dart";
+import "../../widgets/loading_indicator.dart";
import "../../widgets/refresh_control.dart";
import "../../widgets/rounded_container.dart";
import "shopinbit_car_fee_view.dart";
@@ -42,6 +42,7 @@ class _ShopInBitTicketsViewState extends ConsumerState<ShopInBitTicketsView> {
ShopInBitTicket? _pendingTicket;
StreamSubscription<List<ShopInBitTicket>>? _ticketsSub;
bool _refreshing = false;
+ bool _resuming = false;
@override
void initState() {
@@ -77,23 +78,27 @@ class _ShopInBitTicketsViewState extends ConsumerState<ShopInBitTicketsView> {
}
Future<void> _resumeFlow(ShopInBitTicket pending) async {
+ if (_resuming) return;
final model = ShopInBitOrderModel.fromDriftRow(pending);
// Recover the live invoice from the server first so resume works even if
// local invoice state was lost.
- final response = await showLoading(
- context: context,
- rootNavigator: true,
- message: "Checking your car research payment",
- whileFuture: ref
- .read(pShopinBitService)
- .client
- .getCurrentCarResearchInvoices(),
- delay: const Duration(seconds: 1),
- );
+ setState(() => _resuming = true);
+ List<CarResearchCurrentInvoice>? current;
+ try {
+ current = (await ref
+ .read(pShopinBitService)
+ .client
+ .getCurrentCarResearchInvoices())
+ .value;
+ } catch (_) {
+ // Fall back to locally stored invoice state below.
+ } finally {
+ if (mounted) setState(() => _resuming = false);
+ }
if (!mounted) return;
- final invoice = _liveInvoiceFrom(response?.value, pending);
+ final invoice = _liveInvoiceFrom(current, pending);
if (invoice != null) {
await Navigator.of(context).pushNamed(
@@ -186,14 +191,17 @@ class _ShopInBitTicketsViewState extends ConsumerState<ShopInBitTicketsView> {
children.add(
RoundedContainer(
color: Theme.of(context).extension<StackColors>()!.popupBG,
- onPressed: () => unawaited(_resumeFlow(pending)),
+ onPressed: _resuming ? null : () => unawaited(_resumeFlow(pending)),
child: _RequestRow(
title: "Car Research (In Progress)",
- subtitle: "Tap to continue your car research payment",
+ subtitle: _resuming
+ ? "Checking your car research payment..."
+ : "Tap to continue your car research payment",
badgeText: "Resume",
badgeColor: Theme.of(
context,
).extension<StackColors>()!.accentColorYellow,
+ loading: _resuming,
),
),
);
@@ -328,12 +336,14 @@ class _RequestRow extends StatelessWidget {
required this.subtitle,
required this.badgeText,
required this.badgeColor,
+ this.loading = false,
});
final String title;
final String subtitle;
final String badgeText;
final Color badgeColor;
+ final bool loading;
@override
Widget build(BuildContext context) {
@@ -374,12 +384,21 @@ class _RequestRow extends StatelessWidget {
),
),
SizedBox(width: isDesktop ? 16 : 8),
- SvgPicture.asset(
- Assets.svg.chevronRight,
- width: 20,
- height: 20,
- colorFilter: ColorFilter.mode(stackColors.textSubtitle1, .srcIn),
- ),
+ loading
+ ? const SizedBox(
+ width: 20,
+ height: 20,
+ child: LoadingIndicator(),
+ )
+ : SvgPicture.asset(
+ Assets.svg.chevronRight,
+ width: 20,
+ height: 20,
+ colorFilter: ColorFilter.mode(
+ stackColors.textSubtitle1,
+ .srcIn,
+ ),
+ ),
],
);
}
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.