Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve a11n by forcing a label on Nutri-Score / Eco-score / NOVA buttons #4356

Merged
merged 8 commits into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/smooth_app/lib/cards/category_cards/svg_cache.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:smooth_app/cards/category_cards/abstract_cache.dart';
import 'package:smooth_app/cards/category_cards/asset_cache_helper.dart';
Expand Down Expand Up @@ -62,6 +63,7 @@ class SvgCache extends AbstractCache {
width: width,
height: height,
fit: BoxFit.contain,
semanticsLabel: getSemanticsLabel(context, iconUrl!),
placeholderBuilder: (BuildContext context) => displayAssetWhileWaiting
? SvgAsyncAsset(
AssetCacheHelper(
Expand All @@ -75,4 +77,31 @@ class SvgCache extends AbstractCache {
: getCircularProgressIndicator(),
);
}

static String? getSemanticsLabel(BuildContext context, String iconUrl) {
final AppLocalizations localizations = AppLocalizations.of(context);

return switch (Uri.parse(iconUrl).pathSegments.last) {
'ecoscore-a.svg' => localizations.ecoscore_a,
'ecoscore-b.svg' => localizations.ecoscore_b,
'ecoscore-c.svg' => localizations.ecoscore_c,
'ecoscore-d.svg' => localizations.ecoscore_d,
'ecoscore-e.svg' => localizations.ecoscore_e,
'ecoscore-unknown.svg' => localizations.ecoscore_unknown,
'ecoscore-not-applicable.svg' => localizations.ecoscore_not_applicable,
'nova-group-1.svg' => localizations.nova_group_1,
'nova-group-2.svg' => localizations.nova_group_2,
'nova-group-3.svg' => localizations.nova_group_3,
'nova-group-4.svg' => localizations.nova_group_4,
'nova-group-unknown.svg' => localizations.nova_group_unknown,
'nutriscore-a.svg' => localizations.nutriscore_a,
'nutriscore-b.svg' => localizations.nutriscore_b,
'nutriscore-c.svg' => localizations.nutriscore_c,
'nutriscore-d.svg' => localizations.nutriscore_d,
'nutriscore-e.svg' => localizations.nutriscore_e,
'nutriscore-unknown.svg' => localizations.nutriscore_unknown,
'nutriscore-not-applicable.svg' => localizations.ecoscore_not_applicable,
_ => null,
};
}
}
72 changes: 44 additions & 28 deletions packages/smooth_app/lib/cards/data_cards/score_card.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/cards/category_cards/svg_cache.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/svg_icon_chip.dart';
import 'package:smooth_app/helpers/score_card_helper.dart';
Expand Down Expand Up @@ -89,39 +90,54 @@ class ScoreCard extends StatelessWidget {
final SvgIconChip? iconChip =
iconUrl == null ? null : SvgIconChip(iconUrl!, height: iconHeight);

return Padding(
padding: margin ?? const EdgeInsets.symmetric(vertical: SMALL_SPACE),
child: Ink(
padding: const EdgeInsets.all(SMALL_SPACE),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: ANGULAR_BORDER_RADIUS,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
if (iconChip != null)
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsetsDirectional.only(end: SMALL_SPACE),
child: iconChip,
return Semantics(
value: _generateSemanticsValue(context),
excludeSemantics: true,
button: true,
child: Padding(
padding: margin ?? const EdgeInsets.symmetric(vertical: SMALL_SPACE),
child: Ink(
padding: const EdgeInsets.all(SMALL_SPACE),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: ANGULAR_BORDER_RADIUS,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
if (iconChip != null)
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsetsDirectional.only(end: SMALL_SPACE),
child: iconChip,
),
),
),
Expanded(
flex: 3,
child: Center(
child: Text(
description,
style: themeData.textTheme.headlineMedium!
.apply(color: textColor),
Expanded(
flex: 3,
child: Center(
child: Text(
description,
style: themeData.textTheme.headlineMedium!
.apply(color: textColor),
),
),
),
),
if (isClickable) Icon(ConstantIcons.instance.getForwardIcon()),
],
if (isClickable) Icon(ConstantIcons.instance.getForwardIcon()),
],
),
),
),
);
}

String _generateSemanticsValue(BuildContext context) {
final String? iconLabel = SvgCache.getSemanticsLabel(context, iconUrl!);

if (iconLabel == null) {
return description;
} else {
return '$iconLabel: $description';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/cards/category_cards/abstract_cache.dart';
import 'package:smooth_app/cards/category_cards/svg_cache.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/helpers/extension_on_text_helper.dart';
Expand Down Expand Up @@ -74,39 +75,44 @@ class KnowledgePanelTitleCard extends StatelessWidget {
top: VERY_SMALL_SPACE,
bottom: VERY_SMALL_SPACE,
),
child: Row(
children: <Widget>[
...iconWidget,
Expanded(
flex: IconWidgetSizer.getRemainingWidgetFlex(),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Wrap(
direction: Axis.vertical,
children: <Widget>[
SizedBox(
width: constraints.maxWidth,
child: Text(
knowledgePanelTitleElement.title,
style: TextStyle(color: colorFromEvaluation),
),
),
if (knowledgePanelTitleElement.subtitle != null)
child: Semantics(
value: _generateSemanticsValue(context),
button: isClickable,
excludeSemantics: true,
child: Row(
children: <Widget>[
...iconWidget,
Expanded(
flex: IconWidgetSizer.getRemainingWidgetFlex(),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Wrap(
direction: Axis.vertical,
children: <Widget>[
SizedBox(
width: constraints.maxWidth,
child: Text(
knowledgePanelTitleElement.subtitle!,
style:
WellSpacedTextHelper.TEXT_STYLE_WITH_WELL_SPACED,
).selectable(isSelectable: !isClickable),
knowledgePanelTitleElement.title,
style: TextStyle(color: colorFromEvaluation),
),
),
],
);
},
if (knowledgePanelTitleElement.subtitle != null)
SizedBox(
width: constraints.maxWidth,
child: Text(
knowledgePanelTitleElement.subtitle!,
style: WellSpacedTextHelper
.TEXT_STYLE_WITH_WELL_SPACED,
).selectable(isSelectable: !isClickable),
),
],
);
},
),
),
),
if (isClickable) Icon(ConstantIcons.instance.getForwardIcon()),
],
if (isClickable) Icon(ConstantIcons.instance.getForwardIcon()),
],
),
),
);
}
Expand Down Expand Up @@ -155,4 +161,25 @@ class KnowledgePanelTitleCard extends StatelessWidget {
return null;
}
}

String _generateSemanticsValue(BuildContext context) {
final StringBuffer buffer = StringBuffer();

if (knowledgePanelTitleElement.iconUrl != null) {
final String? label = SvgCache.getSemanticsLabel(
context,
knowledgePanelTitleElement.iconUrl!,
);
if (label != null) {
buffer.write('$label: ');
}
}

buffer.write(knowledgePanelTitleElement.title);
if (knowledgePanelTitleElement.subtitle != null) {
buffer.write('\n${knowledgePanelTitleElement.subtitle}');
}

return buffer.toString();
}
}
21 changes: 20 additions & 1 deletion packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2368,5 +2368,24 @@
"product_list_create_tooltip": "Create a new list",
"@product_list_create_tooltip": {
"description": "Button description to create a new list (long sentence)"
}
},
"nutriscore_a": "Nutri-Score A",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going to get "Nutri-Skor A" and the likes, which is a pain to maintain translation wise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Nutri-Score, yes maybe…
But for Eco-score, in French we should say Éco-score :/
What would be the best approach?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branding for Eco-Score is not completely stabilized. Eco-Score won't hurt, I guess.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok and what about NOVA, where "group" can also be translated?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For nova, you mean the Ultra-processed foods attributes ? Do you want to concatenate NOVA to it ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is to provide a description that will be read by screen readers.
That's why I've provided a description for Nutriscore/Nova/Ecoscore, because now screen readers won't say anything

"nutriscore_b": "Nutri-Score B",
"nutriscore_c": "Nutri-Score C",
"nutriscore_d": "Nutri-Score D",
"nutriscore_e": "Nutri-Score E",
"nutriscore_unknown": "Unknown Nutri-Score",
"nutriscore_not_applicable": "Nutri-Score is not applicable",
"ecoscore_a": "Eco-Score A",
"ecoscore_b": "Eco-Score B",
"ecoscore_c": "Eco-Score C",
"ecoscore_d": "Eco-Score D",
"ecoscore_e": "Eco-Score E",
"ecoscore_unknown": "Unknown Eco-Score",
"ecoscore_not_applicable": "Eco-Score is not applicable",
"nova_group_1": "NOVA Group 1",
"nova_group_2": "NOVA Group 2",
"nova_group_3": "NOVA Group 3",
"nova_group_4": "NOVA Group 4",
"nova_group_unknown": "Unknown NOVA Group"
}