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
}