Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A11y #8

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
## 1.2.0

Accessibility improvements

## 1.1.0

Minor improvements, such as
* Make `WeekdaySelector` stateless (it had no reason to be stateful to begin with)
* Add assertion to make sure that displayed days are in a valid format
Minor improvements, such as:
* Make `WeekdaySelector` stateless (it had no reason to be stateful to begin with).
* Add assertion to make sure that displayed days are in a valid format.

## 1.0.0

Expand All @@ -14,9 +18,9 @@ Migrate to null-safety.

## 0.4.0

Remove `DiagnosticableMixin` and use the `Diagnosticable` mixin. [`#2`](https://github.com/smaho-engineering/weekday_selector/pull/2)
Remove `DiagnosticableMixin` and use the `Diagnosticable` mixin. [`#2`](https://github.com/smaho-engineering/weekday_selector/pull/2).

Remove enormous example videos from the repository, use hashed URLs. For more info, see: [`pub-dev #3849`](https://github.com/dart-lang/pub-dev/issues/3849)
Remove enormous example videos from the repository, use hashed URLs. For more info, see: [`pub-dev #3849`](https://github.com/dart-lang/pub-dev/issues/3849).

## 0.3.1

Expand Down
8 changes: 1 addition & 7 deletions example/.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@ migration:
- platform: root
create_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
base_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
- platform: linux
create_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
base_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
- platform: macos
create_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
base_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
- platform: windows
- platform: ios
create_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a
base_revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a

Expand Down
71 changes: 60 additions & 11 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/date_symbols.dart';
Expand Down Expand Up @@ -83,8 +84,10 @@ class _MyAppState extends State<MyApp> {
locales = dateTimeSymbolMap()
.keys
.cast<String>()
.map((String k) => Locale(
k.split('_')[0], k.split('_').length > 1 ? k.split('_')[1] : null))
.map((k) => Locale(
k.split('_')[0],
k.split('_').length > 1 ? k.split('_')[1] : null,
))
.toList();
super.initState();
}
Expand Down Expand Up @@ -113,7 +116,8 @@ class UsageExamples extends StatelessWidget {
initializeDateFormatting();
final examples = <Widget>[
SimpleExampleWeekendsStatic(),
SelectedDaysUpdateExample(),
CheckboxLikeExample(),
RadioLikeExample(),
DisabledExample(),
DisplayedDaysExample(),
// TODO: use with setstate
Expand Down Expand Up @@ -202,7 +206,7 @@ printIntAsDay(int day) {

String intDayToEnglish(int day) {
if (day % 7 == DateTime.monday % 7) return 'Monday';
if (day % 7 == DateTime.tuesday % 7) return 'Tueday';
if (day % 7 == DateTime.tuesday % 7) return 'Tuesday';
if (day % 7 == DateTime.wednesday % 7) return 'Wednesday';
if (day % 7 == DateTime.thursday % 7) return 'Thursday';
if (day % 7 == DateTime.friday % 7) return 'Friday';
Expand Down Expand Up @@ -260,6 +264,10 @@ class _SimpleExampleWeekendsStaticState
// We display the last tapped value in the example app
onChanged: (v) {
printIntAsDay(v);
SemanticsService.announce(
'onChanged callback was last called with $v',
TextDirection.ltr,
);
setState(() => lastTapped = v);
},
values: [
Expand Down Expand Up @@ -311,26 +319,29 @@ class _DisabledExampleState extends State<DisabledExample> {
}
}

class SelectedDaysUpdateExample extends StatefulWidget {
class CheckboxLikeExample extends StatefulWidget {
@override
_SelectedDaysUpdateExampleState createState() =>
_SelectedDaysUpdateExampleState();
_CheckboxLikeExampleState createState() => _CheckboxLikeExampleState();
}

class _SelectedDaysUpdateExampleState extends State<SelectedDaysUpdateExample> {
class _CheckboxLikeExampleState extends State<CheckboxLikeExample> {
final values = List.filled(7, false);

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ExampleTitle('Stateful widget with selected days'),
ExampleTitle('Checkbox-like weekday selector'),
Text(
'When the user taps on a day, toggle the state! You can use stateful widgets, or any other methods for managing your state.'),
'When the user taps on a day, toggle the state! '
'You can use stateful widgets, or any other methods for managing your state.',
),
// Using v == true, as some values could be null!
Text(
'The days that are currently selected are: ${valuesToEnglishDays(values, true)}.'),
'The days that are currently selected are: '
'${valuesToEnglishDays(values, true)}.',
),
WeekdaySelector(
selectedFillColor: Colors.indigo,
onChanged: (v) {
Expand All @@ -340,6 +351,44 @@ class _SelectedDaysUpdateExampleState extends State<SelectedDaysUpdateExample> {
});
},
values: values,
semanticsWrapper: WeekdayButton.checkboxSemanticsWrapper,
),
],
);
}
}

class RadioLikeExample extends StatefulWidget {
@override
_RadioLikeExampleState createState() => _RadioLikeExampleState();
}

class _RadioLikeExampleState extends State<RadioLikeExample> {
var values = List.filled(7, false);

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ExampleTitle('Radio-like weekday selector'),
Text('When the user taps on a day, select that day.'),
// Using v == true, as some values could be null!
Text(
'Currently selected day:'
'${valuesToEnglishDays(values, true)}.',
),
WeekdaySelector(
selectedFillColor: Colors.blue,
onChanged: (v) {
printIntAsDay(v);
setState(() {
values = List.filled(7, false);
values[v % 7] = true;
});
},
values: values,
semanticsWrapper: WeekdayButton.radioSemanticsWrapper,
),
],
);
Expand Down
109 changes: 105 additions & 4 deletions lib/src/weekday_selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class WeekdaySelector extends StatelessWidget {
this.shape,
this.selectedShape,
this.disabledShape,
this.semanticsWrapper,
}) : assert(values.length == 7),
assert(shortWeekdays.length == 7),
assert(weekdays.length == 7),
Expand Down Expand Up @@ -244,6 +245,20 @@ class WeekdaySelector extends StatelessWidget {
/// state management library) so that the parent gets rebuilt.
final ValueChanged<int>? onChanged;

/// Provides a wrapper around the weekday buttons for improving accessibility.
///
/// For more info, check [WeekdayButton]'s `semanticsWrapper` field.
final WeekdayButtonSemanticsWrapper? semanticsWrapper;

static Widget checkbox(Widget weekdayButton, bool? selected, String label) {
return Semantics(
checked: selected,
label: label,
tooltip: label,
child: weekdayButton,
);
}

Widget buildButtonWith(int value) {
// In the arrays, element at index 0 correspond to Sunday...
final arrayIndex = value % 7;
Expand Down Expand Up @@ -278,6 +293,7 @@ class WeekdaySelector extends StatelessWidget {
shape: shape,
selectedShape: selectedShape,
disabledShape: disabledShape,
semanticsWrapper: semanticsWrapper,
);
}

Expand All @@ -288,14 +304,23 @@ class WeekdaySelector extends StatelessWidget {
return Row(
textDirection: textDirection,
children: days
.where((d) => displayedIndices.contains(d))
.where(displayedIndices.contains)
.map((i) => i + firstDayOfWeek)
.map(buildButtonWith)
.toList(),
);
}
}

/// Function signature for wrapping individual weekday buttons.
typedef WeekdayButtonSemanticsWrapper = Widget Function(
BuildContext context,
String label,
bool? selected,
VoidCallback? onPressed,
Widget child,
);

/// A single button that holds a weekday.
///
/// This widget is used in the [WeekdaySelector] widget,
Expand Down Expand Up @@ -331,6 +356,7 @@ class WeekdayButton extends StatelessWidget {
this.shape,
this.selectedShape,
this.disabledShape,
this.semanticsWrapper,
}) : assert(text.length != 0),
assert(tooltip.length != 0),
super(key: key);
Expand Down Expand Up @@ -437,6 +463,75 @@ class WeekdayButton extends StatelessWidget {
/// The shape of the disabled day button's [Material].
final ShapeBorder? disabledShape;

/// Provides a wrapper around the weekday buttons for improving accessibility.
///
/// See [WeekdayButton.checkboxSemanticsWrapper],
/// [WeekdayButton.radioSemanticsWrapper] and
/// [WeekdayButton.tooltipWrapper] for examples.
///
/// If these examples don't match your use case, you can create your own.
/// See [WeekdayButtonSemanticsWrapper] for more info.
///
/// If omitted, defaults to [WeekdayButton.tooltipWrapper].
final WeekdayButtonSemanticsWrapper? semanticsWrapper;

static WeekdayButtonSemanticsWrapper checkboxSemanticsWrapper = (
BuildContext context,
String label,
bool? selected,
VoidCallback? onPressed,
Widget child,
) {
return Semantics(
label: label,
checked: selected,
enabled: onPressed != null,
onTap: onPressed,
inMutuallyExclusiveGroup: false,
child: Tooltip(
message: label,
child: ExcludeSemantics(
child: child,
),
),
);
};

static WeekdayButtonSemanticsWrapper radioSemanticsWrapper = (
BuildContext context,
String label,
bool? selected,
VoidCallback? onPressed,
Widget child,
) {
return Semantics(
label: label,
checked: selected,
enabled: onPressed != null,
onTap: onPressed,
inMutuallyExclusiveGroup: true,
child: Tooltip(
message: label,
child: ExcludeSemantics(
child: child,
),
),
);
};

static WeekdayButtonSemanticsWrapper tooltipWrapper = (
BuildContext context,
String label,
bool? selected,
VoidCallback? onPressed,
Widget child,
) {
return Tooltip(
message: label,
child: child,
);
};

@override
Widget build(BuildContext context) {
Color currentColor;
Expand Down Expand Up @@ -512,10 +607,16 @@ class WeekdayButton extends StatelessWidget {
theme.textTheme.bodyText2!.copyWith(color: currentColor);
}

final semanticsWrapper = this.semanticsWrapper ?? tooltipWrapper;
final label = tooltip;

return Expanded(
child: Tooltip(
message: tooltip,
child: RawMaterialButton(
child: semanticsWrapper(
context,
label,
selected,
onPressed,
RawMaterialButton(
textStyle: currentTextStyle,
elevation: currentElevation ?? 0.0,
disabledElevation: currentDisabledElevation ?? 0.0,
Expand Down