From 437a7ea594d7d9231cfc93d02c0907575fee9584 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Tue, 13 Feb 2024 18:49:58 +0330 Subject: [PATCH] Improve accessibility --- assets/translations/strings_en.i18n.json | 16 +++- .../home/widget/connection_button.dart | 8 +- .../active/active_proxy_delay_indicator.dart | 9 +- .../proxy/active/active_proxy_footer.dart | 32 ++++--- lib/features/proxy/active/ip_widget.dart | 95 +++++++++++-------- 5 files changed, 103 insertions(+), 57 deletions(-) diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index 291896e0..cf340c63 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -15,7 +15,8 @@ "notSet": "Not Set", "agree": "Agree", "decline": "Decline", - "unknown": "Unknown" + "unknown": "Unknown", + "hidden": "Hidden" }, "intro": { "termsAndPolicyCaution(rich)": "by continuing you agree with ${tap(@:about.termsAndConditions)}", @@ -127,6 +128,19 @@ "unsorted": "Default", "name": "Alphabetically", "delay": "By Delay" + }, + "activeProxySemanticLabel": "Active proxy", + "delaySemantics": { + "result": "delay: ${delay}ms", + "testing": "delay: testing..." + }, + "ipInfoSemantics": { + "address": "IP address", + "country": "Country" + }, + "statsSemantics": { + "speed": "Speed", + "totalTransferred": "Total transferred" } }, "logs": { diff --git a/lib/features/home/widget/connection_button.dart b/lib/features/home/widget/connection_button.dart index 485bccae..eb1c3efe 100644 --- a/lib/features/home/widget/connection_button.dart +++ b/lib/features/home/widget/connection_button.dart @@ -149,9 +149,11 @@ class _ConnectionButton extends StatelessWidget { .scaleXY(end: .88, curve: Curves.easeIn), ), const Gap(16), - Text( - label, - style: Theme.of(context).textTheme.titleMedium, + ExcludeSemantics( + child: Text( + label, + style: Theme.of(context).textTheme.titleMedium, + ), ), ], ); diff --git a/lib/features/proxy/active/active_proxy_delay_indicator.dart b/lib/features/proxy/active/active_proxy_delay_indicator.dart index b89bca9a..43828eeb 100644 --- a/lib/features/proxy/active/active_proxy_delay_indicator.dart +++ b/lib/features/proxy/active/active_proxy_delay_indicator.dart @@ -1,6 +1,7 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/widget/animated_visibility.dart'; import 'package:hiddify/core/widget/shimmer_skeleton.dart'; import 'package:hiddify/features/proxy/active/active_proxy_notifier.dart'; @@ -11,6 +12,7 @@ class ActiveProxyDelayIndicator extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); final theme = Theme.of(context); final activeProxy = ref.watch(activeProxyNotifierProvider); @@ -39,6 +41,8 @@ class ActiveProxyDelayIndicator extends HookConsumerWidget { const Gap(8), if (delay > 0) Text.rich( + semanticsLabel: + t.proxies.delaySemantics.result(delay: delay), TextSpan( children: [ TextSpan( @@ -51,7 +55,10 @@ class ActiveProxyDelayIndicator extends HookConsumerWidget { ), ) else - const ShimmerSkeleton(width: 48, height: 18), + Semantics( + label: t.proxies.delaySemantics.testing, + child: const ShimmerSkeleton(width: 48, height: 18), + ), ], ), ), diff --git a/lib/features/proxy/active/active_proxy_footer.dart b/lib/features/proxy/active/active_proxy_footer.dart index 241302fe..a33250f9 100644 --- a/lib/features/proxy/active/active_proxy_footer.dart +++ b/lib/features/proxy/active/active_proxy_footer.dart @@ -1,6 +1,7 @@ import 'package:dartx/dartx.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/widget/animated_visibility.dart'; @@ -38,6 +39,7 @@ class ActiveProxyFooter extends HookConsumerWidget { text: proxy.selectedName.isNotNullOrBlank ? proxy.selectedName! : proxy.name, + semanticLabel: t.proxies.activeProxySemanticLabel, ), const Gap(8), switch (ipInfo) { @@ -90,6 +92,7 @@ class _StatsColumn extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); final stats = ref.watch(statsNotifierProvider).value; return Directionality( @@ -101,11 +104,13 @@ class _StatsColumn extends HookConsumerWidget { _InfoProp( icon: FluentIcons.arrow_bidirectional_up_down_20_regular, text: (stats?.downlinkTotal ?? 0).size(), + semanticLabel: t.proxies.statsSemantics.totalTransferred, ), const Gap(8), _InfoProp( icon: FluentIcons.arrow_download_20_regular, text: (stats?.downlink ?? 0).speed(), + semanticLabel: t.proxies.statsSemantics.speed, ), ], ), @@ -118,25 +123,30 @@ class _InfoProp extends StatelessWidget { const _InfoProp({ required this.icon, required this.text, + this.semanticLabel, }); final IconData icon; final String text; + final String? semanticLabel; @override Widget build(BuildContext context) { - return Row( - children: [ - Icon(icon), - const Gap(8), - Flexible( - child: Text( - text, - style: Theme.of(context).textTheme.labelMedium, - overflow: TextOverflow.ellipsis, + return Semantics( + label: semanticLabel, + child: Row( + children: [ + Icon(icon), + const Gap(8), + Flexible( + child: Text( + text, + style: Theme.of(context).textTheme.labelMedium, + overflow: TextOverflow.ellipsis, + ), ), - ), - ], + ], + ), ); } } diff --git a/lib/features/proxy/active/ip_widget.dart b/lib/features/proxy/active/ip_widget.dart index 16b4d2d6..a1e88ee1 100644 --- a/lib/features/proxy/active/ip_widget.dart +++ b/lib/features/proxy/active/ip_widget.dart @@ -1,6 +1,7 @@ import 'package:circle_flags/circle_flags.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; +import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/utils/riverpod_utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -23,36 +24,43 @@ class IPText extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); final isVisible = ref.watch(_showIp); final textTheme = Theme.of(context).textTheme; - return InkWell( - onTap: () { - ref.read(_showIp.notifier).state = !isVisible; - }, - onLongPress: onLongPress, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 2), - child: AnimatedCrossFade( - firstChild: Text( - ip, - style: textTheme.labelMedium, - overflow: TextOverflow.ellipsis, - ), - secondChild: Padding( - padding: constrained - ? EdgeInsets.zero - : const EdgeInsetsDirectional.only(end: 48), - child: Text( - "*.*.*.*", - style: constrained ? textTheme.labelMedium : textTheme.labelLarge, + return Semantics( + label: t.proxies.ipInfoSemantics.address, + child: InkWell( + onTap: () { + ref.read(_showIp.notifier).state = !isVisible; + }, + onLongPress: onLongPress, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: AnimatedCrossFade( + firstChild: Text( + ip, + style: textTheme.labelMedium, overflow: TextOverflow.ellipsis, ), + secondChild: Padding( + padding: constrained + ? EdgeInsets.zero + : const EdgeInsetsDirectional.only(end: 48), + child: Text( + "*.*.*.*", + semanticsLabel: t.general.hidden, + style: + constrained ? textTheme.labelMedium : textTheme.labelLarge, + overflow: TextOverflow.ellipsis, + ), + ), + crossFadeState: isVisible + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + duration: const Duration(milliseconds: 200), ), - crossFadeState: - isVisible ? CrossFadeState.showFirst : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 200), ), ), ); @@ -67,25 +75,30 @@ class IPCountryFlag extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); final isVisible = ref.watch(_showIp); - return InkWell( - onTap: () { - ref.read(_showIp.notifier).state = !isVisible; - }, - borderRadius: BorderRadius.circular(12), - child: Container( - width: size, - height: size, - padding: const EdgeInsets.all(2), - child: Center( - child: AnimatedCrossFade( - firstChild: CircleFlag(countryCode), - secondChild: Icon(FluentIcons.eye_off_24_regular, size: size * .8), - crossFadeState: isVisible - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 200), + return Semantics( + label: t.proxies.ipInfoSemantics.country, + child: InkWell( + onTap: () { + ref.read(_showIp.notifier).state = !isVisible; + }, + borderRadius: BorderRadius.circular(12), + child: Container( + width: size, + height: size, + padding: const EdgeInsets.all(2), + child: Center( + child: AnimatedCrossFade( + firstChild: CircleFlag(countryCode), + secondChild: + Icon(FluentIcons.eye_off_24_regular, size: size * .8), + crossFadeState: isVisible + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + duration: const Duration(milliseconds: 200), + ), ), ), ),