Files
hash-wallet/lib/new-ui/widgets/apps_widget.dart
T
Seth For PrivacyandGitHub a6bcf7c97a a11y: dashboard and navigation semantics (navbar, cards, sync, switchers) (#3468)
* Label new-UI dashboard navigation and sync status for screen readers

Each bottom-nav tab becomes one button node carrying its localized name, its
selected state and mutual exclusion; the pill text no longer duplicates the
selected tab. The assets/history tabs gain selected state, the Bitcoin/Lightning
switcher becomes a labeled toggle, and the sync bar becomes a single button whose
label is the localized status (plus Tor/MWEB/Silent Payments badges) with a hint
pointing at node management. Compact mode's pulsing dot and the chain icon's
progress ring now have text equivalents, and the invisible pointer-absorbing
strip behind the nav bar is excluded from traversal.

* Make new-UI dashboard cards, lists and menus operable by screen readers

Collapse the wallet-name row into one labeled button (hardware-wallet glyph
folded into the label, inner accounts button no longer a second stop) and give
balance cards a labeled, selectable node with a long-press action for show/hide
balance. Balances hidden behind AnimatedOpacity, card artwork and decorative
glyphs leave the semantics tree; the 3-dots customize target and the card action
buttons are now named buttons. Assets/history rows, the history header, the MWEB
promo, long-press menu items, the account cards and the add-account and
generate-name controls become single button nodes, the phantom action chip in the
assets header is built conditionally instead of drawn at opacity 0, and app cards
get a close tooltip plus an 'opens externally' hint.

* Use expression bodies for the new a11y helpers

* Restore untouched expressions in cards_view

* Address review: state-aware balance hint, tooltip-free close label

* Address review: keep header height without the chip, name the selected index
2026-08-03 19:58:51 +02:00

170 lines
6.3 KiB
Dart

import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class AppsWidget extends StatelessWidget {
AppsWidget({
this.onTap,
required this.title,
required this.subTitle,
this.hint,
this.svgPicture,
this.image,
this.icon,
this.onClose,
this.customBorder,
this.shadowSpread,
this.shadowBlur,
super.key,
this.marginV,
this.marginH,
this.isWide,
this.isLink,
this.isCake,
});
final VoidCallback? onTap;
final VoidCallback? onClose;
final String title;
final String subTitle;
final Widget? hint;
final SvgPicture? svgPicture;
final Widget? icon;
final String? image;
final double? customBorder;
final double? marginV;
final double? marginH;
final double? shadowSpread;
final double? shadowBlur;
final bool? isWide;
final bool? isLink;
final bool? isCake;
@override
Widget build(BuildContext context) {
if (isWide == true) {
return Stack(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: marginH ?? 20, vertical: marginV ?? 5),
width: double.infinity,
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18)),
gradient: LinearGradient(
colors: [
context.customColors.cardGradientColorPrimary,
context.customColors.cardGradientColorSecondary,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
// The card is one node; the arrow glyph's "leaves the app" meaning
// becomes a hint instead of an unlabeled icon.
child: MergeSemantics(
child: Semantics(
hint: isLink == true ? S.of(context).opens_externally : null,
child: TextButton(
onPressed: onTap,
style: TextButton.styleFrom(
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(
width: 1.25, color: Theme.of(context).colorScheme.surfaceContainerHigh),
),
padding: EdgeInsets.all(24),
),
child: Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 20),
child: ExcludeSemantics(
child: CakeImageWidget(imageUrl: image, height: 54, width: 54),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 6.0,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w800,
fontSize: 20,
),
softWrap: true,
),
isCake == true
? ExcludeSemantics(
child: CakeImageWidget(
imageUrl: "assets/new-ui/cakelabs-icon.svg",
color: Theme.of(context)
.colorScheme
.onSurfaceVariant),
)
: SizedBox(),
],
),
SizedBox(height: 5),
Text(
subTitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
softWrap: true,
),
],
),
),
ExcludeSemantics(
child: Icon(
isLink == true ? Icons.arrow_outward : Icons.arrow_forward_ios,
color: Theme.of(context).colorScheme.onSurfaceVariant,
size: 20,
),
)
],
),
if (hint != null) ...[
SizedBox(height: 10),
hint!,
]
],
),
),
),
),
),
if (onClose != null)
Positioned(
top: 10,
right: 10,
// Label the icon-only button without adding a visible tooltip.
child: MergeSemantics(
child: Semantics(
label: S.of(context).close,
child: IconButton(
icon: Icon(Icons.close),
onPressed: onClose,
//color: Theme.of(context).colorScheme.onSurface,
),
),
),
),
],
);
} else {
return Stack();
}
}
}