Add accessability semantics
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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": "حذف",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
15
pubspec.yaml
15
pubspec.yaml
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user