Improve accessibility (WIP)
* Add Accessibility settings * Support screen reader (WIP) * Only support German/English/Chinese
This commit is contained in:
parent
28fd9fb0a2
commit
ff7d860359
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,11 +128,14 @@ class _DrawerUserControllerState extends State<DrawerUserController>
|
|||
// if you use your own menu view UI you add form initialization
|
||||
child: widget.menuView != null
|
||||
? widget.menuView
|
||||
: AnimatedIcon(
|
||||
: Semantics(
|
||||
label: S.of(context).mainMenu,
|
||||
child: AnimatedIcon(
|
||||
icon: widget.animatedIconData,
|
||||
color: AppTheme.drawerAnimationIconColor,
|
||||
progress: iconAnimationController),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
onDrawerClick();
|
||||
|
|
|
@ -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,12 +1531,15 @@ class _GamePageState extends State<GamePage>
|
|||
child: Column(
|
||||
// Replace with a Row for horizontal icon + text
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Semantics(
|
||||
label: S.of(context).takeBackAll,
|
||||
child: Icon(
|
||||
ltr
|
||||
? FluentIcons.arrow_previous_24_regular
|
||||
: FluentIcons.arrow_next_24_regular,
|
||||
color: Color(Config.navigationToolbarIconColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => onTakeBackAllButtonPressed(pop: false),
|
||||
|
@ -1526,12 +1549,15 @@ class _GamePageState extends State<GamePage>
|
|||
child: Column(
|
||||
// Replace with a Row for horizontal icon + text
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Semantics(
|
||||
label: S.of(context).takeBack,
|
||||
child: Icon(
|
||||
ltr
|
||||
? FluentIcons.chevron_left_24_regular
|
||||
: FluentIcons.chevron_right_24_regular,
|
||||
color: Color(Config.navigationToolbarIconColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => onTakeBackButtonPressed(pop: false),
|
||||
|
@ -1541,12 +1567,15 @@ class _GamePageState extends State<GamePage>
|
|||
child: Column(
|
||||
// Replace with a Row for horizontal icon + text
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Semantics(
|
||||
label: S.of(context).stepForward,
|
||||
child: Icon(
|
||||
ltr
|
||||
? FluentIcons.chevron_right_24_regular
|
||||
: FluentIcons.chevron_left_24_regular,
|
||||
color: Color(Config.navigationToolbarIconColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => onStepForwardButtonPressed(pop: false),
|
||||
|
@ -1556,12 +1585,15 @@ class _GamePageState extends State<GamePage>
|
|||
child: Column(
|
||||
// Replace with a Row for horizontal icon + text
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Semantics(
|
||||
label: S.of(context).stepForwardAll,
|
||||
child: Icon(
|
||||
ltr
|
||||
? FluentIcons.arrow_next_24_regular
|
||||
: FluentIcons.arrow_previous_24_regular,
|
||||
color: Color(Config.navigationToolbarIconColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => onStepForwardAllButtonPressed(pop: false),
|
||||
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue