implemented comprehensive accessibility (A11y) support across the app
This commit is contained in:
@@ -27,6 +27,7 @@ class CustomEventsView extends StatelessWidget {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
tooltip: 'Termin erstellen',
|
||||
onPressed: () => _openCreateDialog(context),
|
||||
),
|
||||
],
|
||||
@@ -67,6 +68,7 @@ class CustomEventsView extends StatelessWidget {
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
tooltip: 'Bearbeiten',
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) =>
|
||||
@@ -75,6 +77,7 @@ class CustomEventsView extends StatelessWidget {
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
tooltip: 'Löschen',
|
||||
onPressed: () =>
|
||||
showDeleteCustomEventDialog(context, e),
|
||||
),
|
||||
|
||||
@@ -103,6 +103,7 @@ class LessonSheet {
|
||||
static Widget _roomTile(BuildContext context, McTimetableEntry lesson) {
|
||||
final trailing = IconButton(
|
||||
icon: const Icon(Icons.house_outlined),
|
||||
tooltip: 'Raumplan öffnen',
|
||||
onPressed: () => AppRoutes.openRoomplan(context),
|
||||
);
|
||||
|
||||
|
||||
@@ -14,12 +14,17 @@ class SearchSubjectColors extends SearchDelegate<void> {
|
||||
@override
|
||||
List<Widget>? buildActions(BuildContext context) => [
|
||||
if (query.isNotEmpty)
|
||||
IconButton(onPressed: () => query = '', icon: const Icon(Icons.clear)),
|
||||
IconButton(
|
||||
onPressed: () => query = '',
|
||||
tooltip: 'Leeren',
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget? buildLeading(BuildContext context) => IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
tooltip: 'Zurück',
|
||||
onPressed: () => close(context, null),
|
||||
);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class SubjectColorsView extends StatelessWidget {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: 'Suchen',
|
||||
onPressed: () =>
|
||||
showSearch(context: context, delegate: SearchSubjectColors()),
|
||||
),
|
||||
|
||||
@@ -110,6 +110,7 @@ class _TimetableState extends State<Timetable> {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
tooltip: 'Zur aktuellen Woche',
|
||||
onPressed: atToday ? null : _jumpToToday,
|
||||
),
|
||||
PopupMenuButton<_CalendarAction>(
|
||||
@@ -179,6 +180,7 @@ class _TimetableState extends State<Timetable> {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
tooltip: 'Zur aktuellen Woche',
|
||||
onPressed: atToday ? null : _jumpToToday,
|
||||
),
|
||||
if (canViewForeign)
|
||||
|
||||
@@ -88,7 +88,7 @@ class _OutsideDayColumn extends StatelessWidget {
|
||||
}
|
||||
|
||||
static String _subtitleFor(Appointment a) {
|
||||
if (isAllDayLike(a)) return 'Ganztägig';
|
||||
if (isAllDayLike(a)) return A11yLabels.allDay;
|
||||
return '${a.startTime.formatHm()}–${a.endTime.formatHm()}';
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ class _OutsideDayColumn extends StatelessWidget {
|
||||
height: kOutsideChipHeight,
|
||||
child: _OutsideChip(
|
||||
appointment: visible[i],
|
||||
crossedOut: isCrossedOut(visible[i]),
|
||||
onTap: () => onAppointmentTap(visible[i]),
|
||||
),
|
||||
),
|
||||
@@ -144,15 +145,28 @@ class _OutsideDayColumn extends StatelessWidget {
|
||||
|
||||
class _OutsideChip extends StatelessWidget {
|
||||
final Appointment appointment;
|
||||
final bool crossedOut;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _OutsideChip({required this.appointment, required this.onTap});
|
||||
const _OutsideChip({
|
||||
required this.appointment,
|
||||
required this.crossedOut,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final allDay = isAllDayLike(appointment);
|
||||
final timeLabel = allDay ? null : appointment.startTime.formatHm();
|
||||
final semanticsLabel = [
|
||||
appointment.subject,
|
||||
if (allDay)
|
||||
A11yLabels.allDay
|
||||
else
|
||||
'${appointment.startTime.formatHm()}–${appointment.endTime.formatHm()}',
|
||||
if (crossedOut) A11yLabels.cancelled,
|
||||
].join(', ');
|
||||
|
||||
// Past chips fade further, future/ongoing ones get a more saturated tint
|
||||
// so the strip no longer reads as one uniform grey block.
|
||||
@@ -163,47 +177,54 @@ class _OutsideChip extends StatelessWidget {
|
||||
: theme.colorScheme.onSurface;
|
||||
final subjectWeight = isPast ? FontWeight.w400 : FontWeight.w600;
|
||||
|
||||
return Material(
|
||||
color: appointment.color.withAlpha(backgroundAlpha),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(7)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
appointment.subject,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: subjectColor,
|
||||
fontWeight: subjectWeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (timeLabel != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
timeLabel,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
fontSize: 10,
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: semanticsLabel,
|
||||
onTap: onTap,
|
||||
child: ExcludeSemantics(
|
||||
child: Material(
|
||||
color: appointment.color.withAlpha(backgroundAlpha),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(7)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
appointment.subject,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: subjectColor,
|
||||
fontWeight: subjectWeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
if (timeLabel != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
timeLabel,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -220,18 +241,25 @@ class _OutsideOverflowChip extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Material(
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'+$count weitere',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
fontWeight: FontWeight.w600,
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: A11yLabels.moreAppointments(count),
|
||||
onTap: onTap,
|
||||
child: ExcludeSemantics(
|
||||
child: Material(
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'+$count weitere',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -112,6 +112,7 @@ class _PeriodLabel extends StatelessWidget {
|
||||
Icons.coffee_outlined,
|
||||
size: 12,
|
||||
color: secondaryTextColor.withAlpha(180),
|
||||
semanticLabel: A11yLabels.breakTime,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -328,18 +329,39 @@ class _DayColumn extends StatelessWidget {
|
||||
left: cell.lane * width / cell.laneCount,
|
||||
width: width / cell.laneCount,
|
||||
child: switch (cell) {
|
||||
LaidOutAppointment(:final appointment) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => onAppointmentTap(appointment),
|
||||
child: AppointmentTile(
|
||||
appointment: appointment,
|
||||
LaidOutAppointment(:final appointment) => Semantics(
|
||||
button: true,
|
||||
label: A11yLabels.appointmentLabel(
|
||||
subject: appointment.subject,
|
||||
location: appointment.location ?? '',
|
||||
start: appointment.startTime,
|
||||
end: appointment.endTime,
|
||||
crossedOut: isCrossedOut(appointment),
|
||||
),
|
||||
onTap: () => onAppointmentTap(appointment),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => onAppointmentTap(appointment),
|
||||
child: AppointmentTile(
|
||||
appointment: appointment,
|
||||
crossedOut: isCrossedOut(appointment),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
LaidOutOverflow(:final appointments) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
LaidOutOverflow(:final appointments) => Semantics(
|
||||
button: true,
|
||||
label: A11yLabels.moreAppointments(appointments.length),
|
||||
onTap: () => _showOverflowSheet(context, appointments),
|
||||
child: _OverflowTile(count: appointments.length),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () =>
|
||||
_showOverflowSheet(context, appointments),
|
||||
child: _OverflowTile(count: appointments.length),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:syncfusion_flutter_calendar/calendar.dart';
|
||||
|
||||
import '../../../../extensions/date_time.dart';
|
||||
import '../../../../utils/haptics.dart';
|
||||
import '../../../../widget/a11y/a11y_labels.dart';
|
||||
import '../../../../widget/details_bottom_sheet.dart';
|
||||
import '../data/calendar_layout.dart';
|
||||
import '../data/calendar_logic.dart';
|
||||
|
||||
Reference in New Issue
Block a user