fix-talkback-slide-button-accessibility (#2200)

* fix:talkback send slide button accessibility * fix accessible button color

Serhii committed Apr 14, 2025 at 15:30 UTC 990feb48ecdd46af4fedaf086f392af2279ecdfb
3 files changed +144 -123
lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
+1 -1
@@ -33,7 +33,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
33 mainAxisAlignment: MainAxisAlignment.center,
34 children: [
35 if (titleIconPath != null)
36 - Image.asset(titleIconPath!, height: 24, width: 24)
36 + Image.asset(titleIconPath!, height: 24, width: 24, excludeFromSemantics: true)
37 else
38 Container(),
39 const SizedBox(width: 6),
lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart
+71 -62
@@ -223,6 +223,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
223 onSlideComplete: onSlideComplete,
224 buttonText: 'Swipe to send',
225 currentTheme: currentTheme,
226 + accessibleNavigationModeButtonText: S.of(context).send,
227 ),
228 );
229 }
@@ -248,24 +249,28 @@ class StandardTile extends StatelessWidget {
249
250 @override
251 Widget build(BuildContext context) {
251 - return Container(
252 - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
253 - decoration:
254 - BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
255 - child: Row(
256 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
257 - children: [
258 - Text(itemTitle, style: itemTitleTextStyle),
259 - Column(
260 - crossAxisAlignment: CrossAxisAlignment.end,
261 - children: [
262 - Text(itemValue, style: itemTitleTextStyle),
263 - itemSubTitle == null
264 - ? Container()
265 - : Text(itemSubTitle!, style: itemSubTitleTextStyle),
266 - ],
267 - ),
268 - ],
252 + return Semantics(
253 + container: true,
254 + label: itemTitle,
255 + child: Container(
256 + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
257 + decoration:
258 + BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
259 + child: Row(
260 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
261 + children: [
262 + Text(itemTitle, style: itemTitleTextStyle),
263 + Column(
264 + crossAxisAlignment: CrossAxisAlignment.end,
265 + children: [
266 + Text(itemValue, style: itemTitleTextStyle),
267 + itemSubTitle == null
268 + ? Container()
269 + : Text(itemSubTitle!, style: itemSubTitleTextStyle),
270 + ],
271 + ),
272 + ],
273 + ),
274 ),
275 );
276 }
@@ -357,54 +362,58 @@ class AddressExpansionTile extends StatelessWidget {
362
363 @override
364 Widget build(BuildContext context) {
360 - return Container(
361 - decoration: BoxDecoration(
362 - borderRadius: BorderRadius.all(Radius.circular(10)),
363 - color: tileBackgroundColor,
364 - ),
365 - child: Theme(
366 - data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
367 - child: Padding(
368 - padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8),
369 - child: ExpansionTile(
370 - childrenPadding: isBatchSending ? const EdgeInsets.only(bottom: 8) : EdgeInsets.zero,
371 - tilePadding: EdgeInsets.zero,
372 - dense: true,
373 - visualDensity: VisualDensity.compact,
374 - title: Row(
375 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
376 - children: [
377 - Expanded(
378 - child: Text(isBatchSending ? name : contactType,
379 - style: itemTitleTextStyle, softWrap: true)),
380 - Text(isBatchSending ? amount : name,
381 - style: TextStyle(
382 - fontSize: 14,
383 - fontFamily: 'Lato',
384 - fontWeight: FontWeight.w600,
385 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
386 - decoration: TextDecoration.none,
387 - )),
388 - ],
389 - ),
390 - children: [
391 - Row(
365 + return Semantics(
366 + container: true,
367 + label: name,
368 + child: Container(
369 + decoration: BoxDecoration(
370 + borderRadius: BorderRadius.all(Radius.circular(10)),
371 + color: tileBackgroundColor,
372 + ),
373 + child: Theme(
374 + data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
375 + child: Padding(
376 + padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8),
377 + child: ExpansionTile(
378 + childrenPadding: isBatchSending ? const EdgeInsets.only(bottom: 8) : EdgeInsets.zero,
379 + tilePadding: EdgeInsets.zero,
380 + dense: true,
381 + visualDensity: VisualDensity.compact,
382 + title: Row(
383 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
384 children: [
385 Expanded(
394 - child: AddressFormatter.buildSegmentedAddress(
395 - address: address,
396 - walletType: walletType,
397 - evenTextStyle: TextStyle(
398 - fontSize: 12,
399 - fontFamily: 'Lato',
400 - fontWeight: FontWeight.w600,
401 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
402 - decoration: TextDecoration.none)
403 - ),
404 - ),
386 + child: Text(isBatchSending ? name : contactType,
387 + style: itemTitleTextStyle, softWrap: true)),
388 + Text(isBatchSending ? amount : name,
389 + style: TextStyle(
390 + fontSize: 14,
391 + fontFamily: 'Lato',
392 + fontWeight: FontWeight.w600,
393 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
394 + decoration: TextDecoration.none,
395 + )),
396 ],
397 ),
407 - ],
398 + children: [
399 + Row(
400 + children: [
401 + Expanded(
402 + child: AddressFormatter.buildSegmentedAddress(
403 + address: address,
404 + walletType: walletType,
405 + evenTextStyle: TextStyle(
406 + fontSize: 12,
407 + fontFamily: 'Lato',
408 + fontWeight: FontWeight.w600,
409 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
410 + decoration: TextDecoration.none)
411 + ),
412 + ),
413 + ],
414 + ),
415 + ],
416 + ),
417 ),
418 ),
419 ),
lib/src/widgets/standard_slide_button_widget.dart
+72 -60
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/src/widgets/primary_button.dart';
2 import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
3 import 'package:cake_wallet/themes/extensions/filter_theme.dart';
4 import 'package:cake_wallet/themes/extensions/menu_theme.dart';
@@ -12,12 +13,14 @@ class StandardSlideButton extends StatefulWidget {
13 this.buttonText = '',
14 this.height = 48.0,
15 required this.currentTheme,
16 + required this.accessibleNavigationModeButtonText,
17 }) : super(key: key);
18
19 final VoidCallback onSlideComplete;
20 final String buttonText;
21 final double height;
22 final ThemeBase currentTheme;
23 + final String accessibleNavigationModeButtonText;
24
25 @override
26 _StandardSlideButtonState createState() => _StandardSlideButtonState();
@@ -28,68 +31,77 @@ class _StandardSlideButtonState extends State<StandardSlideButton> {
31
32 @override
33 Widget build(BuildContext context) {
31 - return LayoutBuilder(builder: (context, constraints) {
32 - final double maxWidth = constraints.maxWidth;
33 - const double sideMargin = 4.0;
34 - final double effectiveMaxWidth = maxWidth - 2 * sideMargin;
35 - const double sliderWidth = 42.0;
34 + final bool accessible = MediaQuery.of(context).accessibleNavigation;
35
37 - final tileBackgroundColor = widget.currentTheme.type == ThemeType.light
38 - ? Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor
39 - : widget.currentTheme.type == ThemeType.oled
40 - ? Colors.black.withOpacity(0.5)
41 - : Theme.of(context).extension<FilterTheme>()!.buttonColor;
36 + final tileBackgroundColor = widget.currentTheme.type == ThemeType.light
37 + ? Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor
38 + : widget.currentTheme.type == ThemeType.oled
39 + ? Colors.black.withOpacity(0.5)
40 + : Theme.of(context).extension<FilterTheme>()!.buttonColor;
41
43 - return Container(
44 - height: widget.height,
45 - decoration: BoxDecoration(
46 - borderRadius: BorderRadius.circular(10),
47 - color: tileBackgroundColor),
48 - child: Stack(
49 - alignment: Alignment.centerLeft,
50 - children: [
51 - Center(
52 - child: Text(widget.buttonText,
53 - style: TextStyle(
54 - fontSize: 16,
55 - fontFamily: 'Lato',
56 - fontWeight: FontWeight.w600,
57 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor))),
58 - Positioned(
59 - left: sideMargin + _dragPosition,
60 - child: GestureDetector(
61 - onHorizontalDragUpdate: (details) {
62 - setState(() {
63 - _dragPosition += details.delta.dx;
64 - if (_dragPosition < 0) _dragPosition = 0;
65 - if (_dragPosition > effectiveMaxWidth - sliderWidth) {
66 - _dragPosition = effectiveMaxWidth - sliderWidth;
67 - }
68 - });
69 - },
70 - onHorizontalDragEnd: (details) {
71 - if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) {
72 - widget.onSlideComplete();
73 - } else {
74 - setState(() => _dragPosition = 0);
75 - }
76 - },
77 - child: Container(
78 - width: sliderWidth,
79 - height: widget.height - 8,
80 - decoration: BoxDecoration(
81 - borderRadius: BorderRadius.circular(10),
82 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
83 - ),
84 - alignment: Alignment.center,
85 - child: Icon(Icons.arrow_forward,
86 - color: widget.currentTheme.type == ThemeType.bright ? Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor : Theme.of(context).extension<FilterTheme>()!.buttonColor),
87 - ),
42 + return accessible
43 + ? PrimaryButton(
44 + text: widget.accessibleNavigationModeButtonText,
45 + color: Theme.of(context).primaryColor,
46 + textColor: Colors.white,
47 + onPressed: () => widget.onSlideComplete())
48 + : LayoutBuilder(builder: (context, constraints) {
49 + final double maxWidth = constraints.maxWidth;
50 + const double sideMargin = 4.0;
51 + final double effectiveMaxWidth = maxWidth - 2 * sideMargin;
52 + const double sliderWidth = 42.0;
53 +
54 + return Container(
55 + height: widget.height,
56 + decoration: BoxDecoration(
57 + borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
58 + child: Stack(
59 + alignment: Alignment.centerLeft,
60 + children: [
61 + Center(
62 + child: Text(widget.buttonText,
63 + style: TextStyle(
64 + fontSize: 16,
65 + fontFamily: 'Lato',
66 + fontWeight: FontWeight.w600,
67 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor))),
68 + Positioned(
69 + left: sideMargin + _dragPosition,
70 + child: GestureDetector(
71 + onHorizontalDragUpdate: (details) {
72 + setState(() {
73 + _dragPosition += details.delta.dx;
74 + if (_dragPosition < 0) _dragPosition = 0;
75 + if (_dragPosition > effectiveMaxWidth - sliderWidth) {
76 + _dragPosition = effectiveMaxWidth - sliderWidth;
77 + }
78 + });
79 + },
80 + onHorizontalDragEnd: (details) {
81 + if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) {
82 + widget.onSlideComplete();
83 + } else {
84 + setState(() => _dragPosition = 0);
85 + }
86 + },
87 + child: Container(
88 + width: sliderWidth,
89 + height: widget.height - 8,
90 + decoration: BoxDecoration(
91 + borderRadius: BorderRadius.circular(10),
92 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
93 + ),
94 + alignment: Alignment.center,
95 + child: Icon(Icons.arrow_forward,
96 + color: widget.currentTheme.type == ThemeType.bright
97 + ? Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor
98 + : Theme.of(context).extension<FilterTheme>()!.buttonColor),
99 + ),
100 + ),
101 + )
102 + ],
103 ),
89 - )
90 - ],
91 - ),
92 - );
93 - });
104 + );
105 + });
106 }
107 }