CW-1190: Minor Fixes (#2614)

* fix: Enhance WalletConnect URI handling and validation, better handle errors * fix: Disable swipe to send if wallet is not synchronized * fix: Handle session expiry gracefully * fix: Disable swipe to send if wallet is not synced

David Adegoke committed Nov 8, 2025 at 22:46 UTC 9fa4584d972ad3e9b6f558a920e628e17c3faaa8
9 files changed +79 -30
lib/cake_pay/src/cards/cake_pay_buy_card_page.dart
+1
@@ -627,6 +627,7 @@ class CakePayBuyCardPage extends BasePage {
627 feeFiatAmount: _sendViewModel.pendingTransactionFeeFiatAmountFormatted,
628 outputs: displayingOutputs,
629 footerType: FooterType.slideActionButton,
630 + isSlideActionEnabled: _sendViewModel.isReadyForSend,
631 slideActionButtonText:
632 cakePayBuyCardViewModel.isSimulating ? 'Swipe to simulate' : 'Swipe to send',
633 accessibleNavigationModeSlideActionButtonText:
lib/src/screens/exchange_trade/exchange_trade_page.dart
+1
@@ -284,6 +284,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
284 return ConfirmSendingBottomSheet(
285 key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
286 footerType: FooterType.slideActionButton,
287 + isSlideActionEnabled: widget.exchangeTradeViewModel.sendViewModel.isReadyForSend,
288 walletType: widget.exchangeTradeViewModel.sendViewModel.walletType,
289 titleText: S.of(bottomSheetContext).confirm_transaction,
290 titleIconPath:
lib/src/screens/send/send_page.dart
+1
@@ -567,6 +567,7 @@ class SendPage extends BasePage {
567 titleText: S.of(bottomSheetContext).confirm_transaction,
568 accessibleNavigationModeSlideActionButtonText: S.of(bottomSheetContext).send,
569 footerType: FooterType.slideActionButton,
570 + isSlideActionEnabled: sendViewModel.isReadyForSend,
571 walletType: sendViewModel.walletType,
572 titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
573 currency: sendViewModel.selectedCryptoCurrency,
lib/src/screens/transaction_details/rbf_details_page.dart
+1
@@ -190,6 +190,7 @@ class RBFDetailsPage extends BasePage {
190 return ConfirmSendingBottomSheet(
191 key: ValueKey('rbf_confirm_sending_bottom_sheet'),
192 titleText: S.of(bottomSheetContext).confirm_transaction,
193 + isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
194 walletType: transactionDetailsViewModel.sendViewModel.walletType,
195 titleIconPath:
196 transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
lib/src/screens/wallet_connect/services/walletkit_service.dart
+7
@@ -202,6 +202,13 @@ abstract class WalletKitServiceBase with Store {
202 ),
203 );
204 }
205 + } on ReownSignError catch (e) {
206 + if (e.code == 6) {
207 + try {
208 + await deletePairing(topic: session.pairingTopic);
209 + } catch (_) {}
210 + _refreshPairings();
211 + }
212 } catch (_) {}
213 }
214 }
lib/src/screens/wallet_connect/wc_connections_listing_view.dart
+33 -11
@@ -33,9 +33,13 @@ class WalletConnectConnectionsView extends StatelessWidget {
33
34 final query = actualLinkList[1];
35
36 - final uri = Uri.decodeComponent(query);
36 + final decoded = Uri.decodeComponent(query).trim();
37
38 - final uriData = Uri.parse(uri);
38 + final sanitized = decoded.startsWith('@') ? decoded.substring(1) : decoded;
39 +
40 + final uriData = Uri.tryParse(sanitized);
41 +
42 + if (uriData == null || (uriData.scheme.isEmpty)) return;
43
44 await walletKitService.pairWithUri(uriData);
45 }
@@ -88,7 +92,25 @@ class WCPairingsWidget extends BasePage {
92 if (walletConnectURI == null) return _invalidUriToast(context, S.current.nullURIError);
93
94 log('_onFoundUri: $walletConnectURI');
91 - final Uri uriData = Uri.parse(walletConnectURI);
95 + // Accept either a raw WC URI or a full URL containing `uri=` parameter
96 + String input = walletConnectURI.trim();
97 +
98 + if (input.contains('uri=')) {
99 + final parts = input.split('uri=');
100 + if (parts.length > 1) {
101 + input = Uri.decodeComponent(parts.last);
102 + }
103 + }
104 +
105 + // Some scanners may prefix with '@', strip it
106 + if (input.startsWith('@')) {
107 + input = input.substring(1);
108 + }
109 + final Uri? uriData = Uri.tryParse(input);
110 + final bool hasValidScheme = uriData != null && uriData.scheme.isNotEmpty;
111 + if (!hasValidScheme) {
112 + return _invalidUriToast(context, S.current.invalid_input);
113 + }
114 await walletKitService.pairWithUri(uriData);
115 }
116
@@ -121,10 +143,10 @@ class WCPairingsWidget extends BasePage {
143 Text(
144 S.current.connectWalletPrompt,
145 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
124 - fontSize: 16.0,
125 - fontWeight: FontWeight.normal,
126 - color: Theme.of(context).colorScheme.onSurface,
127 - ),
146 + fontSize: 16.0,
147 + fontWeight: FontWeight.normal,
148 + color: Theme.of(context).colorScheme.onSurface,
149 + ),
150 ),
151 SizedBox(height: 16),
152 PrimaryButton(
@@ -156,10 +178,10 @@ class WCPairingsWidget extends BasePage {
178 S.current.activeConnectionsPrompt,
179 textAlign: TextAlign.center,
180 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
159 - fontSize: 16.0,
160 - fontWeight: FontWeight.normal,
161 - color: Theme.of(context).colorScheme.onSurface,
162 - ),
181 + fontSize: 16.0,
182 + fontWeight: FontWeight.normal,
183 + color: Theme.of(context).colorScheme.onSurface,
184 + ),
185 ),
186 ),
187 replacement: ListView.builder(
lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
+3
@@ -12,6 +12,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
12 required this.footerType,
13 this.slideActionButtonText,
14 this.onSlideActionComplete,
15 + this.isSlideActionEnabled = true,
16 this.singleActionButtonText,
17 this.accessibleNavigationModeSlideActionButtonText,
18 this.onSingleActionButtonPressed,
@@ -30,6 +31,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
31 final FooterType footerType;
32 final String? slideActionButtonText;
33 final VoidCallback? onSlideActionComplete;
34 + final bool isSlideActionEnabled;
35 final String? singleActionButtonText;
36 final String? accessibleNavigationModeSlideActionButtonText;
37 final VoidCallback? onSingleActionButtonPressed;
@@ -112,6 +114,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
114 buttonText: slideActionButtonText ?? '',
115 onSlideComplete: onSlideActionComplete ?? () {},
116 accessibleNavigationModeButtonText: accessibleNavigationModeSlideActionButtonText ?? '',
117 + isDisabled: !isSlideActionEnabled,
118 ),
119 );
120
lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart
+2
@@ -21,6 +21,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
21 String? titleIconPath,
22 String? slideActionButtonText,
23 VoidCallback? onSlideActionComplete,
24 + bool isSlideActionEnabled = true,
25 String? accessibleNavigationModeSlideActionButtonText,
26 required this.currency,
27 this.paymentId,
@@ -48,6 +49,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
49 footerType: footerType,
50 slideActionButtonText: slideActionButtonText ?? 'Swipe to send',
51 onSlideActionComplete: onSlideActionComplete,
52 + isSlideActionEnabled: isSlideActionEnabled,
53 accessibleNavigationModeSlideActionButtonText:
54 accessibleNavigationModeSlideActionButtonText,
55 key: key);
lib/src/widgets/standard_slide_button_widget.dart
+30 -19
@@ -11,6 +11,7 @@ class StandardSlideButton extends StatefulWidget {
11 required this.accessibleNavigationModeButtonText,
12 this.tileBackgroundColor,
13 this.knobColor,
14 + this.isDisabled = false,
15 }) : super(key: key);
16
17 final VoidCallback onSlideComplete;
@@ -19,6 +20,7 @@ class StandardSlideButton extends StatefulWidget {
20 final String accessibleNavigationModeButtonText;
21 final Color? tileBackgroundColor;
22 final Color? knobColor;
23 + final bool isDisabled;
24
25 @override
26 StandardSlideButtonState createState() => StandardSlideButtonState();
@@ -36,14 +38,16 @@ class StandardSlideButtonState extends State<StandardSlideButton> {
38 Widget build(BuildContext context) {
39 final bool accessible = MediaQuery.of(context).accessibleNavigation;
40
39 - final tileBackgroundColor = context.currentTheme.customColors.backgroundGradientColor;
41 + final tileBackgroundColor = widget.isDisabled
42 + ? context.currentTheme.customColors.backgroundGradientColor.withOpacity(0.5)
43 + : context.currentTheme.customColors.backgroundGradientColor;
44
45 return accessible
46 ? PrimaryButton(
47 text: widget.accessibleNavigationModeButtonText,
48 color: Theme.of(context).colorScheme.primary,
49 textColor: Theme.of(context).colorScheme.onPrimary,
46 - onPressed: () => widget.onSlideComplete(),
50 + onPressed: widget.isDisabled ? null : () => widget.onSlideComplete(),
51 )
52 : LayoutBuilder(
53 builder: (context, constraints) {
@@ -56,7 +60,10 @@ class StandardSlideButtonState extends State<StandardSlideButton> {
60 height: widget.height,
61 decoration: BoxDecoration(
62 borderRadius: BorderRadius.circular(10),
59 - color: widget.tileBackgroundColor ?? tileBackgroundColor,
63 + color: (widget.isDisabled
64 + ? widget.tileBackgroundColor?.withOpacity(0.5)
65 + : widget.tileBackgroundColor) ??
66 + tileBackgroundColor,
67 ),
68 child: Stack(
69 alignment: Alignment.centerLeft,
@@ -74,22 +81,26 @@ class StandardSlideButtonState extends State<StandardSlideButton> {
81 left: sideMargin + _dragPosition,
82 child: GestureDetector(
83 key: ValueKey('standard_slide_button_widget_slider_key'),
77 - onHorizontalDragUpdate: (details) {
78 - setState(() {
79 - _dragPosition += details.delta.dx;
80 - if (_dragPosition < 0) _dragPosition = 0;
81 - if (_dragPosition > effectiveMaxWidth - sliderWidth) {
82 - _dragPosition = effectiveMaxWidth - sliderWidth;
83 - }
84 - });
85 - },
86 - onHorizontalDragEnd: (details) {
87 - if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) {
88 - widget.onSlideComplete();
89 - } else {
90 - setState(() => _dragPosition = 0);
91 - }
92 - },
84 + onHorizontalDragUpdate: widget.isDisabled
85 + ? null
86 + : (details) {
87 + setState(() {
88 + _dragPosition += details.delta.dx;
89 + if (_dragPosition < 0) _dragPosition = 0;
90 + if (_dragPosition > effectiveMaxWidth - sliderWidth) {
91 + _dragPosition = effectiveMaxWidth - sliderWidth;
92 + }
93 + });
94 + },
95 + onHorizontalDragEnd: widget.isDisabled
96 + ? null
97 + : (details) {
98 + if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) {
99 + widget.onSlideComplete();
100 + } else {
101 + setState(() => _dragPosition = 0);
102 + }
103 + },
104 child: Container(
105 key: ValueKey('standard_slide_button_widget_slider_container_key'),
106 width: sliderWidth,