Give ConfirmSwiper an accessible-navigation button fallback (#3464)

Seth For Privacy committed Aug 10, 2026 at 21:15 UTC 8ed08fb10e88463c745fd00a8349a4d4d0a4fbec
5 files changed +38 -3
lib/new-ui/pages/bridge/bridge_confirm_sheet.dart
+1
@@ -218,6 +218,7 @@ class _BridgeConfirmSheetState extends State<BridgeConfirmSheet> {
218 bridgeViewModel.executeBridge();
219 },
220 swiperText: S.of(context).swipe_to_bridge,
221 + accessibleNavigationModeButtonText: S.of(context).confirm,
222 );
223 },
224 ),
lib/new-ui/widgets/confirm_swiper.dart
+33 -2
@@ -1,12 +1,23 @@
1 import 'dart:math';
2
3 +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
4 import 'package:flutter/material.dart';
5
6 class ConfirmSwiper extends StatefulWidget {
7 final VoidCallback onConfirmed;
8 final String swiperText;
9
9 - const ConfirmSwiper({super.key, required this.onConfirmed, required this.swiperText});
10 + /// Label of the plain button rendered instead of the swiper when the platform
11 + /// reports accessible navigation (VoiceOver / TalkBack), which intercepts the
12 + /// horizontal drag the swiper depends on. Falls back to [swiperText].
13 + final String? accessibleNavigationModeButtonText;
14 +
15 + const ConfirmSwiper({
16 + super.key,
17 + required this.onConfirmed,
18 + required this.swiperText,
19 + this.accessibleNavigationModeButtonText,
20 + });
21
22 @override
23 State<ConfirmSwiper> createState() => _ConfirmSwiperState();
@@ -30,6 +41,15 @@ class _ConfirmSwiperState extends State<ConfirmSwiper> {
41
42 @override
43 Widget build(BuildContext context) {
44 + if (MediaQuery.of(context).accessibleNavigation) {
45 + return NewPrimaryButton(
46 + onPressed: widget.onConfirmed,
47 + text: widget.accessibleNavigationModeButtonText ?? widget.swiperText,
48 + color: Theme.of(context).colorScheme.primary,
49 + textColor: Theme.of(context).colorScheme.onPrimary,
50 + );
51 + }
52 +
53 final radius = (pillSize + pillHorizontalPadding * 2) / 2;
54
55 return LayoutBuilder(
@@ -41,7 +61,7 @@ class _ConfirmSwiperState extends State<ConfirmSwiper> {
61 final maxDrag = areaWidth - pillSize - pillHorizontalPadding;
62 final triggerAt = maxDrag - _triggerThreshold;
63
44 - return GestureDetector(
64 + final swiper = GestureDetector(
65 onHorizontalDragUpdate: (d) {
66 setState(() {
67 drag = max(pillHorizontalPadding, min(maxDrag, drag + d.delta.dx));
@@ -98,6 +118,17 @@ class _ConfirmSwiperState extends State<ConfirmSwiper> {
118 ),
119 ),
120 );
121 +
122 + // A single accessibility node for the whole control: the flowing label and
123 + // the arrow knob are decorative and must not become separate stops. No
124 + // semantics action is exposed here; a screen reader gets the button
125 + // variant above instead.
126 + return Semantics(
127 + container: true,
128 + excludeSemantics: true,
129 + label: widget.swiperText,
130 + child: swiper,
131 + );
132 },
133 );
134 }
lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
+2 -1
@@ -47,7 +47,8 @@ class SendConfirmBottomWidget extends StatelessWidget {
47 onConfirmed: () {
48 sendViewModel.commitTransaction(context);
49 },
50 - swiperText: "${S.of(context).swipe_to_send}");
50 + swiperText: "${S.of(context).swipe_to_send}",
51 + accessibleNavigationModeButtonText: S.of(context).send);
52 case IsExecutingState:
53 return LoadingBottomWidget(
54 text: "${S.of(context).generating_transaction}...",
lib/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart
+1
@@ -80,6 +80,7 @@ class WCConnectionRequestSheet extends StatelessWidget {
80 padding: const EdgeInsets.symmetric(horizontal: 24),
81 child: ConfirmSwiper(
82 swiperText: S.of(context).wc_swipe_to_approve,
83 + accessibleNavigationModeButtonText: S.of(context).wc_action_approve,
84 onConfirmed: () {
85 if (Navigator.canPop(context)) {
86 Navigator.of(context).pop(WCBottomSheetResult.one);
lib/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart
+1
@@ -82,6 +82,7 @@ class WCSigningRequestSheet extends StatelessWidget {
82 padding: const EdgeInsets.symmetric(horizontal: 24),
83 child: ConfirmSwiper(
84 swiperText: swipeLabel,
85 + accessibleNavigationModeButtonText: S.of(context).confirm,
86 onConfirmed: () {
87 if (Navigator.canPop(context)) {
88 Navigator.of(context).pop(WCBottomSheetResult.one);