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

This commit is contained in:
Seth For Privacy
2026-08-11 03:15:32 +02:00
committed by GitHub
parent 90194f7136
commit 8ed08fb10e
5 changed files with 38 additions and 3 deletions
@@ -218,6 +218,7 @@ class _BridgeConfirmSheetState extends State<BridgeConfirmSheet> {
bridgeViewModel.executeBridge(); bridgeViewModel.executeBridge();
}, },
swiperText: S.of(context).swipe_to_bridge, swiperText: S.of(context).swipe_to_bridge,
accessibleNavigationModeButtonText: S.of(context).confirm,
); );
}, },
), ),
+33 -2
View File
@@ -1,12 +1,23 @@
import 'dart:math'; import 'dart:math';
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ConfirmSwiper extends StatefulWidget { class ConfirmSwiper extends StatefulWidget {
final VoidCallback onConfirmed; final VoidCallback onConfirmed;
final String swiperText; final String swiperText;
const ConfirmSwiper({super.key, required this.onConfirmed, required this.swiperText}); /// Label of the plain button rendered instead of the swiper when the platform
/// reports accessible navigation (VoiceOver / TalkBack), which intercepts the
/// horizontal drag the swiper depends on. Falls back to [swiperText].
final String? accessibleNavigationModeButtonText;
const ConfirmSwiper({
super.key,
required this.onConfirmed,
required this.swiperText,
this.accessibleNavigationModeButtonText,
});
@override @override
State<ConfirmSwiper> createState() => _ConfirmSwiperState(); State<ConfirmSwiper> createState() => _ConfirmSwiperState();
@@ -30,6 +41,15 @@ class _ConfirmSwiperState extends State<ConfirmSwiper> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (MediaQuery.of(context).accessibleNavigation) {
return NewPrimaryButton(
onPressed: widget.onConfirmed,
text: widget.accessibleNavigationModeButtonText ?? widget.swiperText,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
);
}
final radius = (pillSize + pillHorizontalPadding * 2) / 2; final radius = (pillSize + pillHorizontalPadding * 2) / 2;
return LayoutBuilder( return LayoutBuilder(
@@ -41,7 +61,7 @@ class _ConfirmSwiperState extends State<ConfirmSwiper> {
final maxDrag = areaWidth - pillSize - pillHorizontalPadding; final maxDrag = areaWidth - pillSize - pillHorizontalPadding;
final triggerAt = maxDrag - _triggerThreshold; final triggerAt = maxDrag - _triggerThreshold;
return GestureDetector( final swiper = GestureDetector(
onHorizontalDragUpdate: (d) { onHorizontalDragUpdate: (d) {
setState(() { setState(() {
drag = max(pillHorizontalPadding, min(maxDrag, drag + d.delta.dx)); drag = max(pillHorizontalPadding, min(maxDrag, drag + d.delta.dx));
@@ -98,6 +118,17 @@ class _ConfirmSwiperState extends State<ConfirmSwiper> {
), ),
), ),
); );
// A single accessibility node for the whole control: the flowing label and
// the arrow knob are decorative and must not become separate stops. No
// semantics action is exposed here; a screen reader gets the button
// variant above instead.
return Semantics(
container: true,
excludeSemantics: true,
label: widget.swiperText,
child: swiper,
);
}, },
); );
} }
@@ -47,7 +47,8 @@ class SendConfirmBottomWidget extends StatelessWidget {
onConfirmed: () { onConfirmed: () {
sendViewModel.commitTransaction(context); sendViewModel.commitTransaction(context);
}, },
swiperText: "${S.of(context).swipe_to_send}"); swiperText: "${S.of(context).swipe_to_send}",
accessibleNavigationModeButtonText: S.of(context).send);
case IsExecutingState: case IsExecutingState:
return LoadingBottomWidget( return LoadingBottomWidget(
text: "${S.of(context).generating_transaction}...", text: "${S.of(context).generating_transaction}...",
@@ -80,6 +80,7 @@ class WCConnectionRequestSheet extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
child: ConfirmSwiper( child: ConfirmSwiper(
swiperText: S.of(context).wc_swipe_to_approve, swiperText: S.of(context).wc_swipe_to_approve,
accessibleNavigationModeButtonText: S.of(context).wc_action_approve,
onConfirmed: () { onConfirmed: () {
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
Navigator.of(context).pop(WCBottomSheetResult.one); Navigator.of(context).pop(WCBottomSheetResult.one);
@@ -82,6 +82,7 @@ class WCSigningRequestSheet extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
child: ConfirmSwiper( child: ConfirmSwiper(
swiperText: swipeLabel, swiperText: swipeLabel,
accessibleNavigationModeButtonText: S.of(context).confirm,
onConfirmed: () { onConfirmed: () {
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
Navigator.of(context).pop(WCBottomSheetResult.one); Navigator.of(context).pop(WCBottomSheetResult.one);