Cw 304 enhance talkback (#887)

* fix(#536): add talkback support to missing main and common elements * fix(#564): add talkback support for slidable node items & addresses page * fix: add missing delete button from add pin widget

Rafael Saes committed Apr 18, 2023 at 17:36 UTC 5ad67b62a5ebc97be70e91727ab9801be61d8a3f
8 files changed +231 -227
lib/src/screens/base_page.dart
+17 -12
@@ -58,19 +58,24 @@ abstract class BasePage extends StatelessWidget {
58
59 bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context);
60
61 - return SizedBox(
62 - height: isMobileView ? 37 : 45,
63 - width: isMobileView ? 37 : 45,
64 - child: ButtonTheme(
65 - minWidth: double.minPositive,
66 - child: TextButton(
67 - style: ButtonStyle(
68 - overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
61 + return MergeSemantics(
62 + child: SizedBox(
63 + height: isMobileView ? 37 : 45,
64 + width: isMobileView ? 37 : 45,
65 + child: ButtonTheme(
66 + minWidth: double.minPositive,
67 + child: Semantics(
68 + label: canUseCloseIcon && !isMobileView ? 'Close' : 'Back',
69 + child: TextButton(
70 + style: ButtonStyle(
71 + overlayColor: MaterialStateColor.resolveWith(
72 + (states) => Colors.transparent),
73 + ),
74 + onPressed: () => onClose(context),
75 + child:
76 + canUseCloseIcon && !isMobileView ? _closeButton : _backButton,
77 ),
70 - onPressed: () => onClose(context),
71 - child: canUseCloseIcon && !isMobileView
72 - ? _closeButton
73 - : _backButton,
78 + ),
79 ),
80 ),
81 );
lib/src/screens/dashboard/dashboard_page.dart
+55 -36
@@ -104,7 +104,7 @@ class _DashboardPageView extends BasePage {
104 //splashColor: Colors.transparent,
105 //padding: EdgeInsets.all(0),
106 onPressed: () => onOpenEndDrawer(),
107 - child: menuButton));
107 + child: Semantics(label: 'Menu', child: menuButton)));
108 }
109
110 final DashboardViewModel dashboardViewModel;
@@ -149,17 +149,21 @@ class _DashboardPageView extends BasePage {
149 Padding(
150 padding: EdgeInsets.only(bottom: 24, top: 10),
151 child: Observer(builder: (context) {
152 - return SmoothPageIndicator(
153 - controller: controller,
154 - count: pages.length,
155 - effect: ColorTransitionEffect(
156 - spacing: 6.0,
157 - radius: 6.0,
158 - dotWidth: 6.0,
159 - dotHeight: 6.0,
160 - dotColor: Theme.of(context).indicatorColor,
161 - activeDotColor:
162 - Theme.of(context).accentTextTheme!.headline4!.backgroundColor!),
152 + return ExcludeSemantics(
153 + child: SmoothPageIndicator(
154 + controller: controller,
155 + count: pages.length,
156 + effect: ColorTransitionEffect(
157 + spacing: 6.0,
158 + radius: 6.0,
159 + dotWidth: 6.0,
160 + dotHeight: 6.0,
161 + dotColor: Theme.of(context).indicatorColor,
162 + activeDotColor: Theme.of(context)
163 + .accentTextTheme!
164 + .headline4!
165 + .backgroundColor!),
166 + ),
167 );
168 }
169 )),
@@ -184,27 +188,38 @@ class _DashboardPageView extends BasePage {
188 mainAxisAlignment: MainAxisAlignment.spaceBetween,
189 children: MainActions.all
190 .where((element) => element.canShow?.call(dashboardViewModel) ?? true)
187 - .map((action) => ActionButton(
188 - image: Image.asset(action.image,
189 - height: 24,
190 - width: 24,
191 - color: action.isEnabled?.call(dashboardViewModel) ?? true
192 - ? Theme.of(context)
193 - .accentTextTheme
194 - .headline2!
195 - .backgroundColor!
196 - : Theme.of(context)
197 - .accentTextTheme
198 - .headline3!
199 - .backgroundColor!),
200 - title: action.name(context),
201 - onClick: () async => await action.onTap(context, dashboardViewModel),
202 - textColor: action.isEnabled?.call(dashboardViewModel) ?? true
203 - ? null
204 - : Theme.of(context)
205 - .accentTextTheme
206 - .headline3!
207 - .backgroundColor!,
191 + .map((action) => Semantics(
192 + button: true,
193 + enabled: (action.isEnabled
194 + ?.call(dashboardViewModel) ??
195 + true),
196 + child: ActionButton(
197 + image: Image.asset(action.image,
198 + height: 24,
199 + width: 24,
200 + color: action.isEnabled?.call(
201 + dashboardViewModel) ??
202 + true
203 + ? Theme.of(context)
204 + .accentTextTheme
205 + .headline2!
206 + .backgroundColor!
207 + : Theme.of(context)
208 + .accentTextTheme
209 + .headline3!
210 + .backgroundColor!),
211 + title: action.name(context),
212 + onClick: () async => await action.onTap(
213 + context, dashboardViewModel),
214 + textColor: action.isEnabled
215 + ?.call(dashboardViewModel) ??
216 + true
217 + ? null
218 + : Theme.of(context)
219 + .accentTextTheme
220 + .headline3!
221 + .backgroundColor!,
222 + ),
223 ))
224 .toList(),
225 ),
@@ -222,10 +237,14 @@ class _DashboardPageView extends BasePage {
237 return;
238 }
239 if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
225 - pages.add(MarketPlacePage(dashboardViewModel: dashboardViewModel));
240 + pages.add(Semantics(
241 + label: 'Marketplace Page',
242 + child: MarketPlacePage(dashboardViewModel: dashboardViewModel)));
243 }
227 - pages.add(balancePage);
228 - pages.add(TransactionsPage(dashboardViewModel: dashboardViewModel));
244 + pages.add(Semantics(label: 'Balance Page', child: balancePage));
245 + pages.add(Semantics(
246 + label: 'Transactions Page',
247 + child: TransactionsPage(dashboardViewModel: dashboardViewModel)));
248 _isEffectsInstalled = true;
249
250 autorun((_) async {
lib/src/screens/new_wallet/new_wallet_page.dart
+29 -26
@@ -139,32 +139,35 @@ class _WalletNameFormState extends State<WalletNameForm> {
139 .decorationColor!,
140 width: 1.0),
141 ),
142 - suffixIcon: IconButton(
143 - onPressed: () async {
144 - final rName = await generateName();
145 - FocusManager.instance.primaryFocus?.unfocus();
146 -
147 - setState(() {
148 - _controller.text = rName;
149 - _walletNewVM.name = rName;
150 - _controller.selection = TextSelection.fromPosition(
151 - TextPosition(offset: _controller.text.length));
152 - });
153 - },
154 - icon: Container(
155 - padding: const EdgeInsets.all(8),
156 - decoration: BoxDecoration(
157 - borderRadius: BorderRadius.circular(6.0),
158 - color: Theme.of(context).hintColor,
159 - ),
160 - width: 34,
161 - height: 34,
162 - child: Image.asset(
163 - 'assets/images/refresh_icon.png',
164 - color: Theme.of(context)
165 - .primaryTextTheme!
166 - .headline4!
167 - .decorationColor!,
142 + suffixIcon: Semantics(
143 + label: 'Generate Name',
144 + child: IconButton(
145 + onPressed: () async {
146 + final rName = await generateName();
147 + FocusManager.instance.primaryFocus?.unfocus();
148 +
149 + setState(() {
150 + _controller.text = rName;
151 + _walletNewVM.name = rName;
152 + _controller.selection = TextSelection.fromPosition(
153 + TextPosition(offset: _controller.text.length));
154 + });
155 + },
156 + icon: Container(
157 + padding: const EdgeInsets.all(8),
158 + decoration: BoxDecoration(
159 + borderRadius: BorderRadius.circular(6.0),
160 + color: Theme.of(context).hintColor,
161 + ),
162 + width: 34,
163 + height: 34,
164 + child: Image.asset(
165 + 'assets/images/refresh_icon.png',
166 + color: Theme.of(context)
167 + .primaryTextTheme!
168 + .headline4!
169 + .decorationColor!,
170 + ),
171 ),
172 ),
173 ),
lib/src/screens/pin_code/pin_code_widget.dart
+16 -45
@@ -208,58 +208,29 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
208 const double marginLeft = 15;
209
210 if (index == 9) {
211 + // Empty container
212 return Container(
213 margin: EdgeInsets.only(left: marginLeft, right: marginRight),
213 - child: TextButton(
214 - onPressed: () => null,
215 - // (widget.hasLengthSwitcher ||
216 - // !settingsStore
217 - // .allowBiometricalAuthentication)
218 - // ? null
219 - // : () {
220 - // FIXME
221 - // if (authStore != null) {
222 - // WidgetsBinding.instance.addPostFrameCallback((_) {
223 - // final biometricAuth = BiometricAuth();
224 - // biometricAuth.isAuthenticated().then(
225 - // (isAuth) {
226 - // if (isAuth) {
227 - // authStore.biometricAuth();
228 - // _key.currentState.showSnackBar(
229 - // SnackBar(
230 - // content: Text(S.of(context).authenticated),
231 - // backgroundColor: Colors.green,
232 - // ),
233 - // );
234 - // }
235 - // }
236 - // );
237 - // });
238 - // }
239 - // },
240 - // FIX-ME: Style
241 - //color: Theme.of(context).backgroundColor,
242 - //shape: CircleBorder(),
243 - child: Container()
244 - // (widget.hasLengthSwitcher ||
245 - // !settingsStore
246 - // .allowBiometricalAuthentication)
247 - // ? Offstage()
248 - // : faceImage,
249 - ),
214 );
215 } else if (index == 10) {
216 index = 0;
217 } else if (index == 11) {
254 - return Container(
255 - margin: EdgeInsets.only(left: marginLeft, right: marginRight),
256 - child: TextButton(
257 - onPressed: () => _pop(),
258 - style: TextButton.styleFrom(
259 - backgroundColor: Theme.of(context).backgroundColor,
260 - shape: CircleBorder(),
218 + return MergeSemantics(
219 + child: Container(
220 + margin: EdgeInsets.only(left: marginLeft, right: marginRight),
221 + child: Semantics(
222 + label: 'Delete',
223 + button: true,
224 + onTap: () => _pop(),
225 + child: TextButton(
226 + onPressed: () => _pop(),
227 + style: TextButton.styleFrom(
228 + backgroundColor: Theme.of(context).backgroundColor,
229 + shape: CircleBorder(),
230 + ),
231 + child: deleteIconImage,
232 + ),
233 ),
262 - child: deleteIconImage,
234 ),
235 );
236 } else {
lib/src/screens/receive/receive_page.dart
+17 -33
@@ -41,26 +41,7 @@ class ReceivePage extends BasePage {
41 final FocusNode _cryptoAmountFocus;
42
43 @override
44 - Widget leading(BuildContext context) {
45 - final _backButton = Icon(Icons.arrow_back_ios,
46 - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
47 - size: 16,);
48 -
49 - return SizedBox(
50 - height: 37,
51 - width: 37,
52 - child: ButtonTheme(
53 - minWidth: double.minPositive,
54 - child: TextButton(
55 - // FIX-ME: Style
56 - //highlightColor: Colors.transparent,
57 - //splashColor: Colors.transparent,
58 - //padding: EdgeInsets.all(0),
59 - onPressed: () => onClose(context),
60 - child: _backButton),
61 - ),
62 - );
63 - }
44 + Color get titleColor => Colors.white;
45
46 @override
47 Widget middle(BuildContext context) {
@@ -93,19 +74,22 @@ class ReceivePage extends BasePage {
74
75 return Material(
76 color: Colors.transparent,
96 - child: IconButton(
97 - padding: EdgeInsets.zero,
98 - constraints: BoxConstraints(),
99 - highlightColor: Colors.transparent,
100 - splashColor: Colors.transparent,
101 - iconSize: 25,
102 - onPressed: () {
103 - ShareUtil.share(
104 - text: addressListViewModel.address.address,
105 - context: context,
106 - );
107 - },
108 - icon: shareImage
77 + child: Semantics(
78 + label: 'Share',
79 + child: IconButton(
80 + padding: EdgeInsets.zero,
81 + constraints: BoxConstraints(),
82 + highlightColor: Colors.transparent,
83 + splashColor: Colors.transparent,
84 + iconSize: 25,
85 + onPressed: () {
86 + ShareUtil.share(
87 + text: addressListViewModel.address.address,
88 + context: context,
89 + );
90 + },
91 + icon: shareImage
92 + ),
93 )
94 );
95 }
lib/src/screens/receive/widgets/address_cell.dart
+10 -5
@@ -70,11 +70,16 @@ class AddressCell extends StatelessWidget {
70 ),
71 ),
72 ));
73 - return Slidable(
74 - key: Key(address),
75 - startActionPane: _actionPane(context),
76 - endActionPane: _actionPane(context),
77 - child: cell,
73 + return Semantics(
74 + label: 'Slidable',
75 + selected: isCurrent,
76 + enabled: !isCurrent,
77 + child: Slidable(
78 + key: Key(address),
79 + startActionPane: _actionPane(context),
80 + endActionPane: _actionPane(context),
81 + child: cell,
82 + ),
83 );
84 }
85
lib/src/screens/settings/connection_sync_page.dart
+39 -27
@@ -41,9 +41,13 @@ class ConnectionSyncPage extends BasePage {
41 handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
42 ),
43 StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
44 - NodeHeaderListRow(
45 - title: S.of(context).add_new_node,
46 - onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
44 + Semantics(
45 + button: true,
46 + child: NodeHeaderListRow(
47 + title: S.of(context).add_new_node,
48 + onTap: (_) async =>
49 + await Navigator.of(context).pushNamed(Routes.newNode),
50 + ),
51 ),
52 StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
53 SizedBox(height: 100),
@@ -60,31 +64,39 @@ class ConnectionSyncPage extends BasePage {
64 itemBuilder: (_, sectionIndex, index) {
65 final node = nodeListViewModel.nodes[index];
66 final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
63 - final nodeListRow = NodeListRow(
64 - title: node.uriRaw,
65 - isSelected: isSelected,
66 - isAlive: node.requestNode(),
67 - onTap: (_) async {
68 - if (isSelected) {
69 - return;
70 - }
67 + final nodeListRow = Semantics(
68 + label: 'Slidable',
69 + selected: isSelected,
70 + enabled: !isSelected,
71 + child: NodeListRow(
72 + title: node.uriRaw,
73 + isSelected: isSelected,
74 + isAlive: node.requestNode(),
75 + onTap: (_) async {
76 + if (isSelected) {
77 + return;
78 + }
79
72 - await showPopUp<void>(
73 - context: context,
74 - builder: (BuildContext context) {
75 - return AlertWithTwoActions(
76 - alertTitle: S.of(context).change_current_node_title,
77 - alertContent: nodeListViewModel.getAlertContent(node.uriRaw),
78 - leftButtonText: S.of(context).cancel,
79 - rightButtonText: S.of(context).change,
80 - actionLeftButton: () => Navigator.of(context).pop(),
81 - actionRightButton: () async {
82 - await nodeListViewModel.setAsCurrent(node);
83 - Navigator.of(context).pop();
84 - },
85 - );
86 - });
87 - },
80 + await showPopUp<void>(
81 + context: context,
82 + builder: (BuildContext context) {
83 + return AlertWithTwoActions(
84 + alertTitle:
85 + S.of(context).change_current_node_title,
86 + alertContent: nodeListViewModel
87 + .getAlertContent(node.uriRaw),
88 + leftButtonText: S.of(context).cancel,
89 + rightButtonText: S.of(context).change,
90 + actionLeftButton: () =>
91 + Navigator.of(context).pop(),
92 + actionRightButton: () async {
93 + await nodeListViewModel.setAsCurrent(node);
94 + Navigator.of(context).pop();
95 + },
96 + );
97 + });
98 + },
99 + ),
100 );
101
102 final dismissibleRow = Slidable(
lib/src/widgets/introducing_card.dart
+48 -43
@@ -33,54 +33,59 @@ class IntroducingCard extends StatelessWidget {
33 children: [
34 Expanded(
35 flex: 1,
36 - child: Padding(
37 - padding: const EdgeInsets.all(24),
38 - child: Column(
39 - crossAxisAlignment: CrossAxisAlignment.start,
40 - children: [
41 - AutoSizeText(title ?? '',
42 - style: TextStyle(
43 - fontSize: 24,
44 - fontFamily: 'Lato',
45 - fontWeight: FontWeight.bold,
46 - color: Theme.of(context)
47 - .accentTextTheme!
48 - .headline2!
49 - .backgroundColor!,
50 - height: 1),
51 - maxLines: 1,
52 - textAlign: TextAlign.center),
53 - SizedBox(height: 14),
54 - Text(subTitle ?? '',
55 - textAlign: TextAlign.left,
56 - style: TextStyle(
57 - fontSize: 12,
58 - fontFamily: 'Lato',
59 - color: Theme.of(context)
60 - .accentTextTheme!
61 - .headline2!
62 - .backgroundColor!,
63 - height: 1)),
64 - ],
36 + child: MergeSemantics(
37 + child: Padding(
38 + padding: const EdgeInsets.all(24),
39 + child: Column(
40 + crossAxisAlignment: CrossAxisAlignment.start,
41 + children: [
42 + AutoSizeText(title ?? '',
43 + style: TextStyle(
44 + fontSize: 24,
45 + fontFamily: 'Lato',
46 + fontWeight: FontWeight.bold,
47 + color: Theme.of(context)
48 + .accentTextTheme!
49 + .headline2!
50 + .backgroundColor!,
51 + height: 1),
52 + maxLines: 1,
53 + textAlign: TextAlign.center),
54 + SizedBox(height: 14),
55 + Text(subTitle ?? '',
56 + textAlign: TextAlign.left,
57 + style: TextStyle(
58 + fontSize: 12,
59 + fontFamily: 'Lato',
60 + color: Theme.of(context)
61 + .accentTextTheme!
62 + .headline2!
63 + .backgroundColor!,
64 + height: 1)),
65 + ],
66 + ),
67 ),
68 ),
69 ),
70 Padding(
71 padding: const EdgeInsets.fromLTRB(0,16,16,0),
70 - child: GestureDetector(
71 - onTap: closeCard,
72 - child: Container(
73 - height: 23,
74 - width: 23,
75 - decoration: BoxDecoration(
76 - color: Colors.white, shape: BoxShape.circle),
77 - child: Center(
78 - child: Image.asset(
79 - 'assets/images/x.png',
80 - color: Palette.darkBlueCraiola,
81 - height: 15,
82 - width: 15,
83 - )),
72 + child: Semantics(
73 + label: 'Close',
74 + child: GestureDetector(
75 + onTap: closeCard,
76 + child: Container(
77 + height: 23,
78 + width: 23,
79 + decoration: BoxDecoration(
80 + color: Colors.white, shape: BoxShape.circle),
81 + child: Center(
82 + child: Image.asset(
83 + 'assets/images/x.png',
84 + color: Palette.darkBlueCraiola,
85 + height: 15,
86 + width: 15,
87 + )),
88 + ),
89 ),
90 ),
91 )