Add accessability semantics

This commit is contained in:
problematicconsumer
2023-09-12 00:05:44 +03:30
parent d54917868b
commit 18bffd8646
10 changed files with 156 additions and 105 deletions

View File

@@ -21,7 +21,9 @@
}, },
"stats": { "stats": {
"traffic": "Live Traffic", "traffic": "Live Traffic",
"trafficTotal": "Total Traffic" "trafficTotal": "Total Traffic",
"uplink": "Uplink",
"downlink": "Downlink"
} }
}, },
"profile": { "profile": {
@@ -54,7 +56,8 @@
"successMsg": "Profile updated successfully" "successMsg": "Profile updated successfully"
}, },
"edit": { "edit": {
"buttonTxt": "Edit" "buttonTxt": "Edit",
"selectActiveTxt": "Select active profile"
}, },
"delete": { "delete": {
"buttonTxt": "Delete", "buttonTxt": "Delete",

View File

@@ -21,7 +21,9 @@
}, },
"stats": { "stats": {
"traffic": "مصرف لحظه‌ای", "traffic": "مصرف لحظه‌ای",
"trafficTotal": "مصرف کل" "trafficTotal": "مصرف کل",
"uplink": "ارسال",
"downlink": "دریافت"
} }
}, },
"profile": { "profile": {
@@ -54,7 +56,8 @@
"successMsg": "پروفایل با موفقیت بروزرسانی شد" "successMsg": "پروفایل با موفقیت بروزرسانی شد"
}, },
"edit": { "edit": {
"buttonTxt": "ویرایش" "buttonTxt": "ویرایش",
"selectActiveTxt": "انتخاب پروفایل فعال"
}, },
"delete": { "delete": {
"buttonTxt": "حذف", "buttonTxt": "حذف",

View File

@@ -1,3 +1,4 @@
import 'package:accessibility_tools/accessibility_tools.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/core/core_providers.dart';
@@ -19,6 +20,12 @@ class AppView extends HookConsumerWidget with PresLogger {
ref.watch(commonControllersProvider); ref.watch(commonControllersProvider);
return MaterialApp.router( return MaterialApp.router(
builder: (context, child) {
return AccessibilityTools(
checkFontOverflows: true,
child: child,
);
},
routerConfig: router, routerConfig: router,
locale: locale, locale: locale,
supportedLocales: AppLocaleUtils.supportedLocales, supportedLocales: AppLocaleUtils.supportedLocales,

View File

@@ -48,6 +48,7 @@ class ProfileTile extends HookConsumerWidget {
profile.active ? theme.colorScheme.outlineVariant : Colors.transparent; profile.active ? theme.colorScheme.outlineVariant : Colors.transparent;
return Card( return Card(
semanticContainer: false,
margin: effectiveMargin, margin: effectiveMargin,
elevation: effectiveElevation, elevation: effectiveElevation,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -55,86 +56,87 @@ class ProfileTile extends HookConsumerWidget {
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
child: InkWell( child: IntrinsicHeight(
onTap: isMain child: Row(
? null crossAxisAlignment: CrossAxisAlignment.stretch,
: () { children: [
if (selectActiveMutation.state.isInProgress) return; SizedBox(
if (profile.active) return; width: 48,
selectActiveMutation.setFuture( child: ProfileActionButton(profile, !isMain),
ref ),
.read(profilesNotifierProvider.notifier) VerticalDivider(
.selectActiveProfile(profile.id), width: 1,
); color: effectiveOutlineColor,
}, ),
child: IntrinsicHeight( Flexible(
child: Row( child: Semantics(
crossAxisAlignment: CrossAxisAlignment.stretch, button: true,
children: [ label: isMain
SizedBox( ? t.profile.overviewPageTitle
width: 48, : t.profile.edit.selectActiveTxt,
child: ProfileActionButton(profile, !isMain), child: InkWell(
), onTap: () {
VerticalDivider( if (isMain) {
width: 1, const ProfilesRoute().go(context);
color: effectiveOutlineColor, } else {
), if (selectActiveMutation.state.isInProgress) return;
Flexible( if (profile.active) return;
child: Padding( selectActiveMutation.setFuture(
padding: const EdgeInsets.symmetric( ref
horizontal: 12, .read(profilesNotifierProvider.notifier)
vertical: 4, .selectActiveProfile(profile.id),
), );
child: Column( }
crossAxisAlignment: CrossAxisAlignment.start, },
children: [ child: Padding(
if (isMain) padding: const EdgeInsets.symmetric(
Padding( horizontal: 12,
padding: const EdgeInsets.symmetric(vertical: 4), vertical: 4,
child: Material( ),
borderRadius: BorderRadius.circular(8), child: Column(
color: Colors.transparent, crossAxisAlignment: CrossAxisAlignment.start,
clipBehavior: Clip.antiAlias, children: [
child: Semantics( if (isMain)
button: true, Padding(
label: t.profile.overviewPageTitle, padding: const EdgeInsets.symmetric(vertical: 4),
child: InkWell( child: Material(
onTap: () => const ProfilesRoute().go(context), borderRadius: BorderRadius.circular(8),
child: Row( color: Colors.transparent,
mainAxisAlignment: clipBehavior: Clip.antiAlias,
MainAxisAlignment.spaceBetween, child: Row(
children: [ mainAxisAlignment:
Flexible( MainAxisAlignment.spaceBetween,
child: Text( children: [
profile.name, Flexible(
style: theme.textTheme.titleMedium, 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,
), ),
) if (subInfo != null) ...[
else const Gap(4),
Text( RemainingTrafficIndicator(subInfo.ratio),
profile.name, const Gap(4),
style: theme.textTheme.titleMedium, ProfileSubscriptionInfo(subInfo),
), const Gap(4),
if (subInfo != null) ...[ ],
const Gap(4),
RemainingTrafficIndicator(subInfo.ratio),
const Gap(4),
ProfileSubscriptionInfo(subInfo),
const Gap(4),
], ],
], ),
), ),
), ),
), ),
], ),
), ],
), ),
), ),
); );

View File

@@ -26,10 +26,12 @@ class StatsOverview extends HookConsumerWidget {
firstStat: ( firstStat: (
label: "", label: "",
data: stats.uplink.speed(), data: stats.uplink.speed(),
semanticLabel: t.home.stats.uplink,
), ),
secondStat: ( secondStat: (
label: "", label: "",
data: stats.downlink.speed(), data: stats.downlink.speed(),
semanticLabel: t.home.stats.downlink,
), ),
), ),
const Gap(8), const Gap(8),
@@ -38,10 +40,12 @@ class StatsOverview extends HookConsumerWidget {
firstStat: ( firstStat: (
label: "", label: "",
data: stats.uplinkTotal.size(), data: stats.uplinkTotal.size(),
semanticLabel: t.home.stats.uplink,
), ),
secondStat: ( secondStat: (
label: "", label: "",
data: stats.downlinkTotal.size(), data: stats.downlinkTotal.size(),
semanticLabel: t.home.stats.downlink,
), ),
), ),
], ],
@@ -58,8 +62,8 @@ class _StatCard extends HookConsumerWidget {
}); });
final String title; final String title;
final ({String label, String data}) firstStat; final ({String label, String data, String semanticLabel}) firstStat;
final ({String label, String data}) secondStat; final ({String label, String data, String semanticLabel}) secondStat;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -80,6 +84,7 @@ class _StatCard extends HookConsumerWidget {
children: [ children: [
Text( Text(
firstStat.label, firstStat.label,
semanticsLabel: firstStat.semanticLabel,
style: const TextStyle(color: Colors.green), style: const TextStyle(color: Colors.green),
), ),
Text( Text(
@@ -93,6 +98,7 @@ class _StatCard extends HookConsumerWidget {
children: [ children: [
Text( Text(
secondStat.label, secondStat.label,
semanticsLabel: secondStat.semanticLabel,
style: TextStyle(color: theme.colorScheme.error), style: TextStyle(color: theme.colorScheme.error),
), ),
Text( Text(

View File

@@ -83,6 +83,7 @@ class AppVersionLabel extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final t = ref.watch(translationsProvider);
final theme = Theme.of(context); final theme = Theme.of(context);
final version = ref.watch( final version = ref.watch(
@@ -96,19 +97,23 @@ class AppVersionLabel extends HookConsumerWidget {
if (version.isEmpty) return const SizedBox(); if (version.isEmpty) return const SizedBox();
return Container( return Semantics(
decoration: BoxDecoration( label: t.about.version,
color: theme.colorScheme.secondaryContainer, button: false,
borderRadius: BorderRadius.circular(4), child: Container(
), decoration: BoxDecoration(
padding: const EdgeInsets.symmetric( color: theme.colorScheme.secondaryContainer,
horizontal: 4, borderRadius: BorderRadius.circular(4),
vertical: 1, ),
), padding: const EdgeInsets.symmetric(
child: Text( horizontal: 4,
version, vertical: 1,
style: theme.textTheme.bodySmall?.copyWith( ),
color: theme.colorScheme.onSecondaryContainer, child: Text(
version,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSecondaryContainer,
),
), ),
), ),
); );

View File

@@ -125,7 +125,10 @@ class ProfilesSortModal extends HookConsumerWidget {
icon: AnimatedRotation( icon: AnimatedRotation(
turns: arrowTurn, turns: arrowTurn,
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
child: const Icon(Icons.arrow_upward), child: Icon(
Icons.arrow_upward,
semanticLabel: sort.mode.name,
),
), ),
) )
: null, : null,

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; 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({ const ThemeModeSwitch({
super.key, super.key,
required this.themeMode, required this.themeMode,
@@ -10,7 +12,9 @@ class ThemeModeSwitch extends StatelessWidget {
final ValueChanged<ThemeMode> onChanged; final ValueChanged<ThemeMode> onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final t = ref.watch(translationsProvider);
final List<bool> isSelected = <bool>[ final List<bool> isSelected = <bool>[
themeMode == ThemeMode.light, themeMode == ThemeMode.light,
themeMode == ThemeMode.system, themeMode == ThemeMode.system,
@@ -28,10 +32,19 @@ class ThemeModeSwitch extends StatelessWidget {
onChanged(ThemeMode.dark); onChanged(ThemeMode.dark);
} }
}, },
children: const <Widget>[ children: <Widget>[
Icon(Icons.wb_sunny), Icon(
Icon(Icons.phone_iphone), Icons.wb_sunny,
Icon(Icons.bedtime), 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,
),
], ],
); );
} }

View File

@@ -9,6 +9,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "60.0.0" 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: analyzer:
dependency: "direct overridden" dependency: "direct overridden"
description: description:

View File

@@ -1,10 +1,10 @@
name: hiddify name: hiddify
description: A Proxy Frontend. description: A Proxy Frontend.
publish_to: 'none' publish_to: "none"
version: 0.1.0 version: 0.1.0
environment: environment:
sdk: '>=3.0.5 <4.0.0' sdk: ">=3.0.5 <4.0.0"
dependencies: dependencies:
flutter: flutter:
@@ -65,6 +65,7 @@ dependencies:
dartx: ^1.2.0 dartx: ^1.2.0
uuid: ^3.0.7 uuid: ^3.0.7
tint: ^2.0.1 tint: ^2.0.1
accessibility_tools: ^1.0.0
# widgets # widgets
go_router: ^10.1.2 go_router: ^10.1.2
@@ -96,7 +97,7 @@ dev_dependencies:
icons_launcher: ^2.1.3 icons_launcher: ^2.1.3
dependency_overrides: dependency_overrides:
analyzer: '5.12.0' analyzer: "5.12.0"
flutter: flutter:
uses-material-design: true uses-material-design: true
@@ -152,9 +153,9 @@ flutter_native_splash:
image: assets/images/source/ic_launcher_foreground.png image: assets/images/source/ic_launcher_foreground.png
ffigen: ffigen:
name: 'SingboxNativeLibrary' name: "SingboxNativeLibrary"
description: 'Bindings to Singbox' description: "Bindings to Singbox"
output: 'lib/gen/singbox_generated_bindings.dart' output: "lib/gen/singbox_generated_bindings.dart"
headers: headers:
entry-points: entry-points:
- 'libcore/bin/libcore.h' - "libcore/bin/libcore.h"