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,