Improve accessibility (WIP)

* Add Accessibility settings
* Support screen reader (WIP)
* Only support German/English/Chinese
This commit is contained in:
Calcitem 2021-08-28 20:50:20 +08:00
parent 28fd9fb0a2
commit ff7d860359
No known key found for this signature in database
GPG Key ID: F2F7C29E054CFB80
9 changed files with 178 additions and 35 deletions

View File

@ -31,6 +31,7 @@ class Config {
// Preferences
static bool toneEnabled = true;
static bool keepMuteWhenTakingBack = true;
static bool screenReaderSupport = false;
static bool aiMovesFirst = false;
static bool aiIsLazy = false;
static int skillLevel = 1;
@ -105,6 +106,7 @@ class Config {
// Preferences
Config.toneEnabled = settings['ToneEnabled'] ?? true;
Config.keepMuteWhenTakingBack = settings['KeepMuteWhenTakingBack'] ?? true;
Config.screenReaderSupport = settings['ScreenReaderSupport'] ?? false;
Config.aiMovesFirst = settings['AiMovesFirst'] ?? false;
Config.aiIsLazy = settings['AiIsLazy'] ?? false;
Config.skillLevel = settings['SkillLevel'] ?? 1;
@ -209,6 +211,7 @@ class Config {
// Preferences
settings['ToneEnabled'] = Config.toneEnabled;
settings['KeepMuteWhenTakingBack'] = Config.keepMuteWhenTakingBack;
settings['ScreenReaderSupport'] = Config.screenReaderSupport;
settings['AiMovesFirst'] = Config.aiMovesFirst;
settings['AiIsLazy'] = Config.aiIsLazy;
settings['SkillLevel'] = Config.skillLevel;

View File

@ -1128,5 +1128,21 @@
"drawIfNoRemovalWithinTenMovesWhenThreeLeft_Detail": "Wenn ein Spieler nur noch drei Steine übrig hat und keiner der Spieler einen gegnerischen Stein innerhalb von zehn Zügen entfernen kann, ist das Spiel unentschieden.",
"@drawIfNoRemovalWithinTenMovesWhenThreeLeft_Detail": {
"description": "When a player is down to three pieces, and neither player can remove an opponent's piece within ten moves, the game is a draw."
},
"mainMenu": "Hauptmenü",
"@mainMenu": {
"description": "Main menu"
},
"board": "Planke",
"@mainMenu": {
"description": "Board"
},
"accessibility": "Barrierefreiheit",
"@accessibility": {
"description": "Accessibility"
},
"screenReaderSupport": "Unterstützung für Bildschirmlesegeräte",
"@accessibility": {
"description": "Screen reader support"
}
}

View File

@ -1128,5 +1128,21 @@
"drawIfNoRemovalWithinTenMovesWhenThreeLeft_Detail": "When a player is down to three pieces, and neither player can remove an opponent's piece within ten moves, the game is a draw.",
"@drawIfNoRemovalWithinTenMovesWhenThreeLeft_Detail": {
"description": "When a player is down to three pieces, and neither player can remove an opponent's piece within ten moves, the game is a draw."
},
"mainMenu": "Main menu",
"@mainMenu": {
"description": "Main menu"
},
"board": "Board",
"@mainMenu": {
"description": "Board"
},
"accessibility": "Accessibility",
"@accessibility": {
"description": "Accessibility"
},
"screenReaderSupport": "Screen reader support",
"@accessibility": {
"description": "Screen reader support"
}
}

View File

@ -1128,5 +1128,21 @@
"drawIfNoRemovalWithinTenMovesWhenThreeLeft_Detail": "当一方只剩下三颗棋子时,而双方都不能在接下来十步之内吃掉对方的棋子,则判为和棋。",
"@drawIfNoRemovalWithinTenMovesWhenThreeLeft_Detail": {
"description": "When a player is down to three pieces, and neither player can remove an opponent's piece within ten moves, the game is a draw."
},
"mainMenu": "主菜单",
"@mainMenu": {
"description": "Main menu"
},
"board": "棋盘",
"@mainMenu": {
"description": "Board"
},
"accessibility": "无障碍",
"@accessibility": {
"description": "Accessibility"
},
"screenReaderSupport": "支持屏幕阅读器",
"@accessibility": {
"description": "Screen reader support"
}
}

View File

@ -191,7 +191,9 @@ class Audios {
static playTone(var soundId) async {
Chain.capture(() async {
if (!Config.toneEnabled || isTemporaryMute) {
if (!Config.toneEnabled ||
isTemporaryMute ||
Config.screenReaderSupport) {
return;
}

View File

@ -40,10 +40,35 @@ class Board extends StatelessWidget {
Widget build(BuildContext context) {
var padding = AppTheme.boardPadding;
var coordinates = [];
for (var file in ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
for (var rank in ['7', '6', '5', '4', '3', '2', '1']) {
coordinates.add("$file$rank");
}
}
var container = Container(
margin: EdgeInsets.symmetric(
vertical: padding,
horizontal: (width - padding * 2) / 6 / 2 + padding,
vertical: 0,
horizontal: 0,
),
child: GridView(
scrollDirection: Axis.horizontal,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
),
children: List.generate(7 * 7, (index) {
return Center(
child: Text(
coordinates[index],
style: TextStyle(
fontSize: Config.fontSize,
color: Config.developerMode ? Colors.red : Colors.transparent,
),
),
);
}),
),
);
@ -70,6 +95,12 @@ class Board extends StatelessWidget {
);
return GestureDetector(
/*
child: Semantics(
label: S.of(context).board,
child: boardContainer,
),
*/
child: boardContainer,
onTapUp: (d) {
final gridWidth = (width - padding * 2);

View File

@ -18,6 +18,7 @@
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/home_drawer.dart';
@ -127,10 +128,13 @@ class _DrawerUserControllerState extends State<DrawerUserController>
// if you use your own menu view UI you add form initialization
child: widget.menuView != null
? widget.menuView
: AnimatedIcon(
icon: widget.animatedIconData,
color: AppTheme.drawerAnimationIconColor,
progress: iconAnimationController),
: Semantics(
label: S.of(context).mainMenu,
child: AnimatedIcon(
icon: widget.animatedIconData,
color: AppTheme.drawerAnimationIconColor,
progress: iconAnimationController),
),
),
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());

View File

@ -144,8 +144,12 @@ class _GamePageState extends State<GamePage>
showTip(String? tip) {
if (!mounted) return;
if (tip != null) print("[tip] $tip");
if (tip != null) {
print("[tip] $tip");
if (Config.screenReaderSupport) {
//showSnackBar(context, tip);
}
}
setState(() => _tip = tip);
}
@ -429,6 +433,19 @@ class _GamePageState extends State<GamePage>
position.recorder.prune();
position.recorder.moveIn(m, position);
if (Config.screenReaderSupport && m.notation != null) {
/*
var playerName = "";
if (position.sideToMove() == PieceColor.white) {
playerName = S.of(context).player + " 1";
} else if (position.sideToMove() == PieceColor.black) {
playerName = S.of(context).player + " 2";
}
*/
showSnackBar(context, S.of(context).human + ": " + m.notation!);
}
setState(() {});
if (position.winner == PieceColor.nobody) {
@ -518,6 +535,9 @@ class _GamePageState extends State<GamePage>
Game.instance.doMove(move.move);
showTips();
if (Config.screenReaderSupport && move.notation != null) {
showSnackBar(context, S.of(context).ai + ": " + move.notation!);
}
break;
case 'timeout':
if (mounted) {
@ -1511,11 +1531,14 @@ class _GamePageState extends State<GamePage>
child: Column(
// Replace with a Row for horizontal icon + text
children: <Widget>[
Icon(
ltr
? FluentIcons.arrow_previous_24_regular
: FluentIcons.arrow_next_24_regular,
color: Color(Config.navigationToolbarIconColor),
Semantics(
label: S.of(context).takeBackAll,
child: Icon(
ltr
? FluentIcons.arrow_previous_24_regular
: FluentIcons.arrow_next_24_regular,
color: Color(Config.navigationToolbarIconColor),
),
),
],
),
@ -1526,11 +1549,14 @@ class _GamePageState extends State<GamePage>
child: Column(
// Replace with a Row for horizontal icon + text
children: <Widget>[
Icon(
ltr
? FluentIcons.chevron_left_24_regular
: FluentIcons.chevron_right_24_regular,
color: Color(Config.navigationToolbarIconColor),
Semantics(
label: S.of(context).takeBack,
child: Icon(
ltr
? FluentIcons.chevron_left_24_regular
: FluentIcons.chevron_right_24_regular,
color: Color(Config.navigationToolbarIconColor),
),
),
],
),
@ -1541,11 +1567,14 @@ class _GamePageState extends State<GamePage>
child: Column(
// Replace with a Row for horizontal icon + text
children: <Widget>[
Icon(
ltr
? FluentIcons.chevron_right_24_regular
: FluentIcons.chevron_left_24_regular,
color: Color(Config.navigationToolbarIconColor),
Semantics(
label: S.of(context).stepForward,
child: Icon(
ltr
? FluentIcons.chevron_right_24_regular
: FluentIcons.chevron_left_24_regular,
color: Color(Config.navigationToolbarIconColor),
),
),
],
),
@ -1556,11 +1585,14 @@ class _GamePageState extends State<GamePage>
child: Column(
// Replace with a Row for horizontal icon + text
children: <Widget>[
Icon(
ltr
? FluentIcons.arrow_next_24_regular
: FluentIcons.arrow_previous_24_regular,
color: Color(Config.navigationToolbarIconColor),
Semantics(
label: S.of(context).stepForwardAll,
child: Icon(
ltr
? FluentIcons.arrow_next_24_regular
: FluentIcons.arrow_previous_24_regular,
color: Color(Config.navigationToolbarIconColor),
),
),
],
),
@ -1612,7 +1644,7 @@ class _GamePageState extends State<GamePage>
return Scaffold(
backgroundColor: Color(Config.darkBackgroundColor),
body: Column(children: <Widget>[
header,
BlockSemantics(child: header),
board,
Config.isHistoryNavigationToolbarShown
? historyNavToolbar

View File

@ -183,6 +183,20 @@ class _GameSettingsPageState extends State<GameSettingsPage> {
List<Widget> children(BuildContext context) {
return <Widget>[
Text(S.of(context).whoMovesFirst, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: !Config.aiMovesFirst,
onChanged: setWhoMovesFirst,
titleString:
Config.aiMovesFirst ? S.of(context).ai : S.of(context).human,
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).difficulty, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
@ -261,16 +275,15 @@ class _GameSettingsPageState extends State<GameSettingsPage> {
)
: Container(height: 0.0, width: 0.0),
SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).whoMovesFirst, style: AppTheme.settingsHeaderStyle),
Text(S.of(context).accessibility, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: !Config.aiMovesFirst,
onChanged: setWhoMovesFirst,
titleString:
Config.aiMovesFirst ? S.of(context).ai : S.of(context).human,
value: Config.screenReaderSupport,
onChanged: setScreenReaderSupport,
titleString: S.of(context).screenReaderSupport,
),
],
),
@ -483,6 +496,16 @@ class _GameSettingsPageState extends State<GameSettingsPage> {
Config.save();
}
setScreenReaderSupport(bool value) async {
setState(() {
Config.screenReaderSupport = value;
});
print("[config] screenReaderSupport: $value");
Config.save();
}
setDeveloperMode(bool value) async {
setState(() {
Config.developerMode = value;