From 18bffd8646c1c10d2b3cb3ca6e17ad6c875ea7e6 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Tue, 12 Sep 2023 00:05:44 +0330 Subject: [PATCH] Add accessability semantics --- assets/translations/strings.i18n.json | 7 +- assets/translations/strings_fa.i18n.json | 7 +- lib/core/app/app_view.dart | 7 + lib/features/common/profile_tile.dart | 146 +++++++++--------- lib/features/common/stats/stats_overview.dart | 10 +- lib/features/home/view/home_page.dart | 31 ++-- .../profiles/view/profiles_modal.dart | 5 +- .../widgets/theme_mode_switch_button.dart | 25 ++- pubspec.lock | 8 + pubspec.yaml | 15 +- 10 files changed, 156 insertions(+), 105 deletions(-) diff --git a/assets/translations/strings.i18n.json b/assets/translations/strings.i18n.json index c9ac05bf..e479fbff 100644 --- a/assets/translations/strings.i18n.json +++ b/assets/translations/strings.i18n.json @@ -21,7 +21,9 @@ }, "stats": { "traffic": "Live Traffic", - "trafficTotal": "Total Traffic" + "trafficTotal": "Total Traffic", + "uplink": "Uplink", + "downlink": "Downlink" } }, "profile": { @@ -54,7 +56,8 @@ "successMsg": "Profile updated successfully" }, "edit": { - "buttonTxt": "Edit" + "buttonTxt": "Edit", + "selectActiveTxt": "Select active profile" }, "delete": { "buttonTxt": "Delete", diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 74c8b6a9..f47cce18 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -21,7 +21,9 @@ }, "stats": { "traffic": "مصرف لحظه‌ای", - "trafficTotal": "مصرف کل" + "trafficTotal": "مصرف کل", + "uplink": "ارسال", + "downlink": "دریافت" } }, "profile": { @@ -54,7 +56,8 @@ "successMsg": "پروفایل با موفقیت بروزرسانی شد" }, "edit": { - "buttonTxt": "ویرایش" + "buttonTxt": "ویرایش", + "selectActiveTxt": "انتخاب پروفایل فعال" }, "delete": { "buttonTxt": "حذف", diff --git a/lib/core/app/app_view.dart b/lib/core/app/app_view.dart index 5fe37a83..f3f6dd21 100644 --- a/lib/core/app/app_view.dart +++ b/lib/core/app/app_view.dart @@ -1,3 +1,4 @@ +import 'package:accessibility_tools/accessibility_tools.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:hiddify/core/core_providers.dart'; @@ -19,6 +20,12 @@ class AppView extends HookConsumerWidget with PresLogger { ref.watch(commonControllersProvider); return MaterialApp.router( + builder: (context, child) { + return AccessibilityTools( + checkFontOverflows: true, + child: child, + ); + }, routerConfig: router, locale: locale, supportedLocales: AppLocaleUtils.supportedLocales, diff --git a/lib/features/common/profile_tile.dart b/lib/features/common/profile_tile.dart index 18621e0a..5a02154c 100644 --- a/lib/features/common/profile_tile.dart +++ b/lib/features/common/profile_tile.dart @@ -48,6 +48,7 @@ class ProfileTile extends HookConsumerWidget { profile.active ? theme.colorScheme.outlineVariant : Colors.transparent; return Card( + semanticContainer: false, margin: effectiveMargin, elevation: effectiveElevation, shape: RoundedRectangleBorder( @@ -55,86 +56,87 @@ class ProfileTile extends HookConsumerWidget { borderRadius: BorderRadius.circular(16), ), shadowColor: Colors.transparent, - child: InkWell( - onTap: isMain - ? null - : () { - if (selectActiveMutation.state.isInProgress) return; - if (profile.active) return; - selectActiveMutation.setFuture( - ref - .read(profilesNotifierProvider.notifier) - .selectActiveProfile(profile.id), - ); - }, - child: IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SizedBox( - width: 48, - child: ProfileActionButton(profile, !isMain), - ), - VerticalDivider( - width: 1, - color: effectiveOutlineColor, - ), - Flexible( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (isMain) - Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Material( - borderRadius: BorderRadius.circular(8), - color: Colors.transparent, - clipBehavior: Clip.antiAlias, - child: Semantics( - button: true, - label: t.profile.overviewPageTitle, - child: InkWell( - onTap: () => const ProfilesRoute().go(context), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - profile.name, - style: theme.textTheme.titleMedium, - ), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + width: 48, + child: ProfileActionButton(profile, !isMain), + ), + VerticalDivider( + width: 1, + color: effectiveOutlineColor, + ), + Flexible( + child: Semantics( + button: true, + label: isMain + ? t.profile.overviewPageTitle + : t.profile.edit.selectActiveTxt, + child: InkWell( + onTap: () { + if (isMain) { + const ProfilesRoute().go(context); + } else { + if (selectActiveMutation.state.isInProgress) return; + if (profile.active) return; + selectActiveMutation.setFuture( + ref + .read(profilesNotifierProvider.notifier) + .selectActiveProfile(profile.id), + ); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isMain) + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Material( + borderRadius: BorderRadius.circular(8), + color: Colors.transparent, + clipBehavior: Clip.antiAlias, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + profile.name, + style: theme.textTheme.titleMedium, ), - const Icon(Icons.arrow_drop_down), - ], - ), + ), + const Icon(Icons.arrow_drop_down), + ], ), ), + ) + else + Text( + profile.name, + style: theme.textTheme.titleMedium, ), - ) - else - Text( - profile.name, - style: theme.textTheme.titleMedium, - ), - if (subInfo != null) ...[ - const Gap(4), - RemainingTrafficIndicator(subInfo.ratio), - const Gap(4), - ProfileSubscriptionInfo(subInfo), - const Gap(4), + if (subInfo != null) ...[ + const Gap(4), + RemainingTrafficIndicator(subInfo.ratio), + const Gap(4), + ProfileSubscriptionInfo(subInfo), + const Gap(4), + ], ], - ], + ), ), ), ), - ], - ), + ), + ], ), ), ); diff --git a/lib/features/common/stats/stats_overview.dart b/lib/features/common/stats/stats_overview.dart index 92599321..e54ae216 100644 --- a/lib/features/common/stats/stats_overview.dart +++ b/lib/features/common/stats/stats_overview.dart @@ -26,10 +26,12 @@ class StatsOverview extends HookConsumerWidget { firstStat: ( label: "↑", data: stats.uplink.speed(), + semanticLabel: t.home.stats.uplink, ), secondStat: ( label: "↓", data: stats.downlink.speed(), + semanticLabel: t.home.stats.downlink, ), ), const Gap(8), @@ -38,10 +40,12 @@ class StatsOverview extends HookConsumerWidget { firstStat: ( label: "↑", data: stats.uplinkTotal.size(), + semanticLabel: t.home.stats.uplink, ), secondStat: ( label: "↓", data: stats.downlinkTotal.size(), + semanticLabel: t.home.stats.downlink, ), ), ], @@ -58,8 +62,8 @@ class _StatCard extends HookConsumerWidget { }); final String title; - final ({String label, String data}) firstStat; - final ({String label, String data}) secondStat; + final ({String label, String data, String semanticLabel}) firstStat; + final ({String label, String data, String semanticLabel}) secondStat; @override Widget build(BuildContext context, WidgetRef ref) { @@ -80,6 +84,7 @@ class _StatCard extends HookConsumerWidget { children: [ Text( firstStat.label, + semanticsLabel: firstStat.semanticLabel, style: const TextStyle(color: Colors.green), ), Text( @@ -93,6 +98,7 @@ class _StatCard extends HookConsumerWidget { children: [ Text( secondStat.label, + semanticsLabel: secondStat.semanticLabel, style: TextStyle(color: theme.colorScheme.error), ), Text( diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index 2f7516c6..e6a14383 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -83,6 +83,7 @@ class AppVersionLabel extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); final theme = Theme.of(context); final version = ref.watch( @@ -96,19 +97,23 @@ class AppVersionLabel extends HookConsumerWidget { if (version.isEmpty) return const SizedBox(); - return Container( - decoration: BoxDecoration( - color: theme.colorScheme.secondaryContainer, - borderRadius: BorderRadius.circular(4), - ), - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 1, - ), - child: Text( - version, - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.onSecondaryContainer, + return Semantics( + label: t.about.version, + button: false, + child: Container( + decoration: BoxDecoration( + color: theme.colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(4), + ), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 1, + ), + child: Text( + version, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSecondaryContainer, + ), ), ), ); diff --git a/lib/features/profiles/view/profiles_modal.dart b/lib/features/profiles/view/profiles_modal.dart index 6c8d7cac..081f68ff 100644 --- a/lib/features/profiles/view/profiles_modal.dart +++ b/lib/features/profiles/view/profiles_modal.dart @@ -125,7 +125,10 @@ class ProfilesSortModal extends HookConsumerWidget { icon: AnimatedRotation( turns: arrowTurn, duration: const Duration(milliseconds: 100), - child: const Icon(Icons.arrow_upward), + child: Icon( + Icons.arrow_upward, + semanticLabel: sort.mode.name, + ), ), ) : null, diff --git a/lib/features/settings/widgets/theme_mode_switch_button.dart b/lib/features/settings/widgets/theme_mode_switch_button.dart index 164d85da..580a633b 100644 --- a/lib/features/settings/widgets/theme_mode_switch_button.dart +++ b/lib/features/settings/widgets/theme_mode_switch_button.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:hiddify/core/core_providers.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; -class ThemeModeSwitch extends StatelessWidget { +class ThemeModeSwitch extends HookConsumerWidget { const ThemeModeSwitch({ super.key, required this.themeMode, @@ -10,7 +12,9 @@ class ThemeModeSwitch extends StatelessWidget { final ValueChanged onChanged; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + final List isSelected = [ themeMode == ThemeMode.light, themeMode == ThemeMode.system, @@ -28,10 +32,19 @@ class ThemeModeSwitch extends StatelessWidget { onChanged(ThemeMode.dark); } }, - children: const [ - Icon(Icons.wb_sunny), - Icon(Icons.phone_iphone), - Icon(Icons.bedtime), + children: [ + Icon( + Icons.wb_sunny, + semanticLabel: t.settings.general.themeModes.light, + ), + Icon( + Icons.phone_iphone, + semanticLabel: t.settings.general.themeModes.system, + ), + Icon( + Icons.bedtime, + semanticLabel: t.settings.general.themeModes.dark, + ), ], ); } diff --git a/pubspec.lock b/pubspec.lock index a2ee14c0..b3239d3b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "60.0.0" + accessibility_tools: + dependency: "direct main" + description: + name: accessibility_tools + sha256: "0a16adc8dfa3a7ebd38775135d86443011a65d4ecbb438913e4992b5d29135fe" + url: "https://pub.dev" + source: hosted + version: "1.0.0" analyzer: dependency: "direct overridden" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5429d5f9..1f944049 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: hiddify description: A Proxy Frontend. -publish_to: 'none' +publish_to: "none" version: 0.1.0 environment: - sdk: '>=3.0.5 <4.0.0' + sdk: ">=3.0.5 <4.0.0" dependencies: flutter: @@ -65,6 +65,7 @@ dependencies: dartx: ^1.2.0 uuid: ^3.0.7 tint: ^2.0.1 + accessibility_tools: ^1.0.0 # widgets go_router: ^10.1.2 @@ -96,7 +97,7 @@ dev_dependencies: icons_launcher: ^2.1.3 dependency_overrides: - analyzer: '5.12.0' + analyzer: "5.12.0" flutter: uses-material-design: true @@ -152,9 +153,9 @@ flutter_native_splash: image: assets/images/source/ic_launcher_foreground.png ffigen: - name: 'SingboxNativeLibrary' - description: 'Bindings to Singbox' - output: 'lib/gen/singbox_generated_bindings.dart' + name: "SingboxNativeLibrary" + description: "Bindings to Singbox" + output: "lib/gen/singbox_generated_bindings.dart" headers: entry-points: - - 'libcore/bin/libcore.h' \ No newline at end of file + - "libcore/bin/libcore.h"