87
offset: _committed ? const Offset(-1, 0) : Offset.zero,
88
duration: const Duration(milliseconds: 300),
89
curve: Curves.easeOutCubic,
90
- child: SendTransactionDetails(
91
- sendViewModel: widget.sendViewModel,
92
- isPage: widget.isPage,
93
- title: widget.title,
94
- iconPath: widget.iconPath,
90
+ // Both screens stay mounted; only the visible one may be reachable.
91
+ child: ExcludeSemantics(
92
+ excluding: _committed,
93
+ child: SendTransactionDetails(
94
+ sendViewModel: widget.sendViewModel,
95
+ isPage: widget.isPage,
96
+ title: widget.title,
97
+ iconPath: widget.iconPath,
98
+ ),
99
),
100
),
101
),
106
offset: _committed ? Offset.zero : const Offset(1, 0),
107
duration: const Duration(milliseconds: 300),
108
curve: Curves.easeOutCubic,
105
- child: TransactionCommitedScreen(
106
- sendViewModel: widget.sendViewModel,
109
+ child: ExcludeSemantics(
110
+ excluding: !_committed,
111
+ child: TransactionCommitedScreen(
112
+ sendViewModel: widget.sendViewModel,
113
+ ),
114
),
115
),
116
),
161
width: 28,
162
height: 28,
163
),
157
- Text(
158
- title ?? S.of(context).send,
159
- style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
164
+ Semantics(
165
+ header: true,
166
+ // Android reads the heading from headingLevel since the
167
+ // Flutter 3.41 engine; header: alone only covers iOS.
168
+ headingLevel: 1,
169
+ child: Text(
170
+ title ?? S.of(context).send,
171
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
172
+ ),
173
)
174
],
175
),
263
mainAxisAlignment: MainAxisAlignment.start,
264
spacing: 24,
265
children: [
253
- Column(
254
- children: [
255
- Row(
256
- mainAxisSize: MainAxisSize.max,
257
- mainAxisAlignment: MainAxisAlignment.center,
258
- spacing: 4,
259
- children: [
260
- Flexible(
261
- child: Text(
262
- amount,
263
- style: TextStyle(
264
- fontSize: 36,
265
- fontWeight: FontWeight.w400,
266
- color: Theme.of(context).colorScheme.onSurface),
266
+ // The amount being sent is the value under review: announce it as one group.
267
+ MergeSemantics(
268
+ child: Column(
269
+ children: [
270
+ Row(
271
+ mainAxisSize: MainAxisSize.max,
272
+ mainAxisAlignment: MainAxisAlignment.center,
273
+ spacing: 4,
274
+ children: [
275
+ Flexible(
276
+ child: Text(
277
+ amount,
278
+ style: TextStyle(
279
+ fontSize: 36,
280
+ fontWeight: FontWeight.w400,
281
+ color: Theme.of(context).colorScheme.onSurface),
282
+ ),
283
),
268
- ),
269
- Text(currencySymbol,
270
- style: TextStyle(
271
- fontSize: 36,
272
- fontWeight: FontWeight.w400,
273
- color: Theme.of(context).colorScheme.onSurfaceVariant))
274
- ],
275
- ),
276
- Text(
277
- fiatAmount,
278
- style: TextStyle(
279
- fontSize: 20,
280
- fontWeight: FontWeight.w500,
281
- color: Theme.of(context).colorScheme.onSurfaceVariant),
282
- ),
283
- ],
284
+ Text(currencySymbol,
285
+ style: TextStyle(
286
+ fontSize: 36,
287
+ fontWeight: FontWeight.w400,
288
+ color: Theme.of(context).colorScheme.onSurfaceVariant))
289
+ ],
290
+ ),
291
+ Text(
292
+ fiatAmount,
293
+ style: TextStyle(
294
+ fontSize: 20,
295
+ fontWeight: FontWeight.w500,
296
+ color: Theme.of(context).colorScheme.onSurfaceVariant),
297
+ ),
298
+ ],
299
+ ),
300
),
301
if (outputs.length >= 1 &&
302
(outputs.first.extractedAddress.isNotEmpty || outputs.first.address.isNotEmpty) &&
368
children: [
369
Padding(
370
padding: const EdgeInsets.all(12.0),
355
- child: Row(
356
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
357
- children: [
358
- Text(S.of(context).fee,
359
- style: TextStyle(
360
- fontSize: 14,
361
- fontWeight: FontWeight.w400,
362
- color: Theme.of(context).colorScheme.onSurface)),
363
- Column(
364
- crossAxisAlignment: CrossAxisAlignment.end,
365
- children: [
366
- Text(
367
- "${fee.withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.currencySymbol}",
371
+ child: MergeSemantics(
372
+ child: Row(
373
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
374
+ children: [
375
+ Text(S.of(context).fee,
376
style: TextStyle(
377
fontSize: 14,
378
fontWeight: FontWeight.w400,
371
- color: Theme.of(context).colorScheme.onSurfaceVariant),
372
- ),
373
- Text(fiatFee.withLocalSeperator(sendViewModel.languageCode),
379
+ color: Theme.of(context).colorScheme.onSurface)),
380
+ Column(
381
+ crossAxisAlignment: CrossAxisAlignment.end,
382
+ children: [
383
+ Text(
384
+ "${fee.withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.currencySymbol}",
385
style: TextStyle(
386
fontSize: 14,
387
fontWeight: FontWeight.w400,
377
- color: Theme.of(context).colorScheme.onSurfaceVariant))
378
- ],
379
- )
380
- ],
388
+ color: Theme.of(context).colorScheme.onSurfaceVariant),
389
+ ),
390
+ Text(fiatFee.withLocalSeperator(sendViewModel.languageCode),
391
+ style: TextStyle(
392
+ fontSize: 14,
393
+ fontWeight: FontWeight.w400,
394
+ color: Theme.of(context).colorScheme.onSurfaceVariant))
395
+ ],
396
+ )
397
+ ],
398
+ ),
399
),
400
),
401
if (sendViewModel.isElectrumWallet) ...[
408
),
409
Padding(
410
padding: const EdgeInsets.all(12.0),
393
- child: Row(
394
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
395
- children: [
396
- Text(S.of(context).network,
397
- style: TextStyle(
398
- fontSize: 14,
399
- fontWeight: FontWeight.w400,
400
- color: Theme.of(context).colorScheme.onSurface)),
401
- Column(
402
- children: [
403
- Text(
404
- sendViewModel.selectedCryptoCurrency == CryptoCurrency.btcln
405
- ? "Lightning"
406
- : bitcoin!.getNetworkName(sendViewModel.wallet),
407
- style: TextStyle(
408
- fontSize: 14,
409
- fontWeight: FontWeight.w400,
410
- color: Theme.of(context).colorScheme.onSurfaceVariant))
411
- ],
412
- )
413
- ],
411
+ child: MergeSemantics(
412
+ child: Row(
413
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
414
+ children: [
415
+ Text(S.of(context).network,
416
+ style: TextStyle(
417
+ fontSize: 14,
418
+ fontWeight: FontWeight.w400,
419
+ color: Theme.of(context).colorScheme.onSurface)),
420
+ Column(
421
+ children: [
422
+ Text(
423
+ sendViewModel.selectedCryptoCurrency == CryptoCurrency.btcln
424
+ ? "Lightning"
425
+ : bitcoin!.getNetworkName(sendViewModel.wallet),
426
+ style: TextStyle(
427
+ fontSize: 14,
428
+ fontWeight: FontWeight.w400,
429
+ color: Theme.of(context).colorScheme.onSurfaceVariant))
430
+ ],
431
+ )
432
+ ],
433
+ ),
434
),
435
)
436
],
479
SizedBox(
480
height: 12,
481
),
462
- Text(
463
- S.of(context).transaction_sent_new,
464
- style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
482
+ // The sheet swaps its content in place, so this title becoming visible is what
483
+ // tells a screen reader that the transaction went through.
484
+ Semantics(
485
+ liveRegion: true,
486
+ child: Text(
487
+ S.of(context).transaction_sent_new,
488
+ style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
489
+ ),
490
),
491
SizedBox(),
492
CakeImageWidget(width: 200, height: 200, imageUrl: "assets/new-ui/birthday_cake.svg"),
577
@override
578
Widget build(BuildContext context) {
579
return Flexible(
555
- child: GestureDetector(
556
- onTap: onTap,
557
- child: Container(
558
- decoration: BoxDecoration(
559
- borderRadius: BorderRadius.circular(16),
560
- color: Theme.of(context).colorScheme.surfaceContainer),
561
- child: Padding(
562
- padding: EdgeInsets.all(16),
563
- child: Row(
564
- mainAxisAlignment: MainAxisAlignment.center,
565
- spacing: 10,
566
- children: [
567
- isLoading
568
- ? CupertinoActivityIndicator()
569
- : CakeImageWidget(
570
- imageUrl: iconPath,
571
- width: 24,
572
- height: 24,
573
- colorFilter: ColorFilter.mode(
574
- Theme.of(context).colorScheme.primary, BlendMode.srcIn),
575
- ),
576
- Text(
577
- text,
578
- style: TextStyle(
579
- color: Theme.of(context).colorScheme.primary,
580
- fontWeight: FontWeight.w500),
581
- )
582
- ],
583
- ),
584
- ),
585
- )));
580
+ child: Semantics(
581
+ button: true,
582
+ enabled: !isLoading,
583
+ label: text,
584
+ value: isLoading ? S.of(context).loading : null,
585
+ onTap: isLoading ? null : onTap,
586
+ excludeSemantics: true,
587
+ child: GestureDetector(
588
+ onTap: isLoading ? null : onTap,
589
+ child: Container(
590
+ decoration: BoxDecoration(
591
+ borderRadius: BorderRadius.circular(16),
592
+ color: Theme.of(context).colorScheme.surfaceContainer),
593
+ child: Padding(
594
+ padding: EdgeInsets.all(16),
595
+ child: Row(
596
+ mainAxisAlignment: MainAxisAlignment.center,
597
+ spacing: 10,
598
+ children: [
599
+ isLoading
600
+ ? CupertinoActivityIndicator()
601
+ : CakeImageWidget(
602
+ imageUrl: iconPath,
603
+ width: 24,
604
+ height: 24,
605
+ colorFilter: ColorFilter.mode(
606
+ Theme.of(context).colorScheme.primary, BlendMode.srcIn),
607
+ ),
608
+ Text(
609
+ text,
610
+ style: TextStyle(
611
+ color: Theme.of(context).colorScheme.primary,
612
+ fontWeight: FontWeight.w500),
613
+ )
614
+ ],
615
+ ),
616
+ ),
617
+ ))));
618
}
619
}
620
655
style: TextStyle(fontFamily: "IBM Plex Mono"),
656
),
657
if (!_expanded)
626
- GestureDetector(
627
- onTap: () {
628
- setState(() {
629
- _expanded = true;
630
- });
631
- },
632
- child: Text(middleTruncate(widget.address, 8, 8),
633
- style: TextStyle(
634
- fontFamily: "IBM Plex Mono",
635
- color: Theme.of(context).colorScheme.primary)))
658
+ Semantics(
659
+ button: true,
660
+ // Read the whole address rather than the truncated form.
661
+ label: widget.address,
662
+ hint: S.of(context).show_full_address,
663
+ onTap: () {
664
+ setState(() {
665
+ _expanded = true;
666
+ });
667
+ },
668
+ excludeSemantics: true,
669
+ child: GestureDetector(
670
+ onTap: () {
671
+ setState(() {
672
+ _expanded = true;
673
+ });
674
+ },
675
+ child: Text(middleTruncate(widget.address, 8, 8),
676
+ style: TextStyle(
677
+ fontFamily: "IBM Plex Mono",
678
+ color: Theme.of(context).colorScheme.primary))),
679
+ )
680
else
681
AddressFormatter.buildSegmentedAddress(
682
address: widget.address,