Compare commits

...

2 commits

Author SHA1 Message Date
Freek van de Ven
79d292cf4a fix: improve the UI for small devices 2025-05-11 13:56:44 +02:00
Freek van de Ven
9487cf2e57 chore: replace the CustomSemantics with the new one from flutter_accessibility 2025-03-06 08:15:04 +01:00
20 changed files with 90 additions and 129 deletions

View file

@ -1,3 +1,11 @@
## 1.2.0
* Improve the UI for smaller screens to prevent overflows
## 1.1.1
* Removed custom definition of CustomSemantics to use the one from flutter_accessibility instead
## 1.1.0 ## 1.1.0
* Added CustomSemantics widget that is used to wrap all the buttons, textfields and dynamic texts to make the userstory accessible for e2e testing. * Added CustomSemantics widget that is used to wrap all the buttons, textfields and dynamic texts to make the userstory accessible for e2e testing.

View file

@ -1,4 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/flutter_availability.dart"; import "package:flutter_availability/flutter_availability.dart";
import "package:flutter_availability/src/ui/view_models/availability_view_model.dart"; import "package:flutter_availability/src/ui/view_models/availability_view_model.dart";
import "package:flutter_availability/src/ui/view_models/break_view_model.dart"; import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
@ -7,7 +8,6 @@ import "package:flutter_availability/src/ui/widgets/availability_template_select
import "package:flutter_availability/src/ui/widgets/availabillity_time_selection.dart"; import "package:flutter_availability/src/ui/widgets/availabillity_time_selection.dart";
import "package:flutter_availability/src/ui/widgets/base_page.dart"; import "package:flutter_availability/src/ui/widgets/base_page.dart";
import "package:flutter_availability/src/ui/widgets/pause_selection.dart"; import "package:flutter_availability/src/ui/widgets/pause_selection.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability/src/util/utils.dart"; import "package:flutter_availability/src/util/utils.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";

View file

@ -1,7 +1,7 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/ui/widgets/base_page.dart"; import "package:flutter_availability/src/ui/widgets/base_page.dart";
import "package:flutter_availability/src/ui/widgets/calendar.dart"; import "package:flutter_availability/src/ui/widgets/calendar.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/ui/widgets/template_legend.dart"; import "package:flutter_availability/src/ui/widgets/template_legend.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";

View file

@ -1,10 +1,10 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/service/errors.dart"; import "package:flutter_availability/src/service/errors.dart";
import "package:flutter_availability/src/ui/view_models/day_template_view_model.dart"; import "package:flutter_availability/src/ui/view_models/day_template_view_model.dart";
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart"; import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
import "package:flutter_availability/src/ui/widgets/base_page.dart"; import "package:flutter_availability/src/ui/widgets/base_page.dart";
import "package:flutter_availability/src/ui/widgets/color_selection.dart"; import "package:flutter_availability/src/ui/widgets/color_selection.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/ui/widgets/template_name_input.dart"; import "package:flutter_availability/src/ui/widgets/template_name_input.dart";
import "package:flutter_availability/src/ui/widgets/template_time_break.dart"; import "package:flutter_availability/src/ui/widgets/template_time_break.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/ui/widgets/base_page.dart"; import "package:flutter_availability/src/ui/widgets/base_page.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";

View file

@ -1,9 +1,9 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/service/errors.dart"; import "package:flutter_availability/src/service/errors.dart";
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart"; import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
import "package:flutter_availability/src/ui/view_models/week_template_view_models.dart"; import "package:flutter_availability/src/ui/view_models/week_template_view_models.dart";
import "package:flutter_availability/src/ui/widgets/color_selection.dart"; import "package:flutter_availability/src/ui/widgets/color_selection.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/ui/widgets/template_name_input.dart"; import "package:flutter_availability/src/ui/widgets/template_name_input.dart";
import "package:flutter_availability/src/ui/widgets/template_time_break.dart"; import "package:flutter_availability/src/ui/widgets/template_time_break.dart";
import "package:flutter_availability/src/ui/widgets/template_week_day_selection.dart"; import "package:flutter_availability/src/ui/widgets/template_week_day_selection.dart";

View file

@ -1,5 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart"; import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// ///
@ -70,10 +70,12 @@ class AvailabilityClearSection extends StatelessWidget {
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Expanded(
child: Text(
unavailableText, unavailableText,
style: textTheme.bodyMedium, style: textTheme.bodyMedium,
), ),
),
], ],
), ),
], ],

View file

@ -1,5 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart"; import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";

View file

@ -1,7 +1,7 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/flutter_availability.dart"; import "package:flutter_availability/flutter_availability.dart";
import "package:flutter_availability/src/ui/widgets/calendar_grid.dart"; import "package:flutter_availability/src/ui/widgets/calendar_grid.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// ///
@ -73,7 +73,14 @@ class CalendarView extends StatelessWidget {
(element) => element.templateDeviation, (element) => element.templateDeviation,
); );
var monthDateSelector = Row( var monthDateSelector = LayoutBuilder(
builder: (context, constraints) {
var monthWidth =
_calculateTextWidthOfLongestMonth(context, translations);
var sideSpace =
((constraints.maxWidth - monthWidth) / 2 - 44).clamp(0.0, 44.0);
return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CustomSemantics( CustomSemantics(
@ -82,15 +89,13 @@ class CalendarView extends StatelessWidget {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: const Icon(Icons.chevron_left), icon: const Icon(Icons.chevron_left),
onPressed: () { onPressed: () {
onMonthChanged( onMonthChanged(DateTime(month.year, month.month - 1));
DateTime(month.year, month.month - 1),
);
}, },
), ),
), ),
const SizedBox(width: 44), SizedBox(width: sideSpace),
SizedBox( SizedBox(
width: _calculateTextWidthOfLongestMonth(context, translations), width: monthWidth,
child: CustomSemantics( child: CustomSemantics(
identifier: identifiers.monthNameTextIdentifier, identifier: identifiers.monthNameTextIdentifier,
child: Text( child: Text(
@ -100,21 +105,21 @@ class CalendarView extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 44), SizedBox(width: sideSpace),
CustomSemantics( CustomSemantics(
identifier: identifiers.nextMonthButtonIdentifier, identifier: identifiers.nextMonthButtonIdentifier,
child: IconButton( child: IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: const Icon(Icons.chevron_right), icon: const Icon(Icons.chevron_right),
onPressed: () { onPressed: () {
onMonthChanged( onMonthChanged(DateTime(month.year, month.month + 1));
DateTime(month.year, month.month + 1),
);
}, },
), ),
), ),
], ],
); );
},
);
var calendarGrid = CalendarGrid( var calendarGrid = CalendarGrid(
month: month, month: month,

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/flutter_availability.dart"; import "package:flutter_availability/flutter_availability.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// Returns the days of the week as abbreviated strings /// Returns the days of the week as abbreviated strings
@ -167,9 +167,12 @@ class _CalendarDay extends StatelessWidget {
children: [ children: [
Center( Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: decoration, decoration: decoration,
child: Text(day.date.day.toString(), style: textStyle), child: Text(
day.date.day.toString(),
style: textStyle,
),
), ),
), ),
if (day.templateDeviation) ...[ if (day.templateDeviation) ...[

View file

@ -1,7 +1,7 @@
import "dart:math"; import "dart:math";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart"; import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// Widget for selecting a color for a template /// Widget for selecting a color for a template

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.dart"; import "package:flutter/services.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart"; import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// An input field for time selection /// An input field for time selection

View file

@ -1,10 +1,10 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/flutter_availability.dart"; import "package:flutter_availability/flutter_availability.dart";
import "package:flutter_availability/src/service/pop_handler.dart"; import "package:flutter_availability/src/service/pop_handler.dart";
import "package:flutter_availability/src/ui/view_models/break_view_model.dart"; import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
import "package:flutter_availability/src/ui/widgets/generic_time_selection.dart"; import "package:flutter_availability/src/ui/widgets/generic_time_selection.dart";
import "package:flutter_availability/src/ui/widgets/input_fields.dart"; import "package:flutter_availability/src/ui/widgets/input_fields.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// ///

View file

@ -1,62 +0,0 @@
import "dart:io";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
/// A wrapper that wraps a widget with a [Semantics] widget.
/// This is used for testing purposes to add a unique identifier to a widget.
/// Use this element for all widgets that have something that should be
/// selectable in tests (buttons, textfields, dynamic texts)
/// The [identifier] should be unique for the specific screen.
/// To make sure the accessibility ID is set for all platforms this widget
/// uses a label for android when it is not a textfield.
/// [container] is set to true to make sure the widget is always its own
/// accessibility element.
/// [excludeSemantics] is set to false to make sure that children of this widget
/// can still be interacted with.
/// [isTextField] can be set to true because for textField the
/// [excludeSemantics] modifies the behavior of the textfield in e2e tests.
class CustomSemantics extends StatelessWidget {
/// Creates a [CustomSemantics] widget.
/// The [identifier] should be unique for the specific screen.
/// [isTextField] if this widget is a TextField or TextFormField
/// [excludeSemantics] if the [Semantics] widget should exclude the semantics
/// of its children. This is done by default if it not a textfield.
const CustomSemantics({
required this.identifier,
required this.child,
this.excludeSemantics = false,
this.isTextField = false,
super.key,
});
/// The widget that should be wrapped with a [Semantics] widget.
final Widget child;
/// Identifier for the widget that should be unique for the specific screen.
final String? identifier;
/// If the [Semantics] widget should exclude the semantics of its children.
final bool excludeSemantics;
/// If the widget is a textfield, the [child] can't be excluded. This is
/// because otherwise the textfield doesn't work in e2e tests with Appium.
final bool isTextField;
@override
Widget build(BuildContext context) {
var isAndroid = !kIsWeb && Platform.isAndroid;
var excludeSemantics = (!isTextField && isAndroid) || this.excludeSemantics;
var label = !isTextField && isAndroid ? identifier : null;
return Semantics(
excludeSemantics: excludeSemantics,
container: true,
label: label,
identifier: identifier,
child: child,
);
}
}

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/config/availability_options.dart"; import "package:flutter_availability/src/config/availability_options.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
@ -76,10 +76,12 @@ class _TemplateLegendState extends State<TemplateLegend> {
const SizedBox(width: 12), const SizedBox(width: 12),
const Icon(Icons.add, size: 20), const Icon(Icons.add, size: 20),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Expanded(
child: Text(
translations.createTemplateButton, translations.createTemplateButton,
style: textTheme.bodyLarge, style: textTheme.bodyLarge,
), ),
),
], ],
), ),
), ),

View file

@ -1,5 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart"; import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// Input section for the template name /// Input section for the template name

View file

@ -1,8 +1,8 @@
// ignore_for_file: avoid_positional_boolean_parameters // ignore_for_file: avoid_positional_boolean_parameters
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/ui/widgets/calendar_grid.dart"; import "package:flutter_availability/src/ui/widgets/calendar_grid.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// A widget for selecting a day of the week /// A widget for selecting a day of the week

View file

@ -1,9 +1,9 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_accessibility/flutter_accessibility.dart";
import "package:flutter_availability/src/ui/view_models/break_view_model.dart"; import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart"; import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
import "package:flutter_availability/src/ui/view_models/week_template_view_models.dart"; import "package:flutter_availability/src/ui/view_models/week_template_view_models.dart";
import "package:flutter_availability/src/ui/widgets/calendar_grid.dart"; import "package:flutter_availability/src/ui/widgets/calendar_grid.dart";
import "package:flutter_availability/src/ui/widgets/semantic_widget.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";

View file

@ -1,6 +1,6 @@
name: flutter_availability name: flutter_availability
description: "Flutter availability userstory package" description: "Flutter availability userstory package"
version: 1.1.0 version: 1.2.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
@ -14,7 +14,10 @@ dependencies:
flutter_hooks: ^0.20.5 flutter_hooks: ^0.20.5
flutter_availability_data_interface: flutter_availability_data_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^1.1.0 version: ^1.2.0
flutter_accessibility:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^0.0.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -1,6 +1,6 @@
name: flutter_availability_data_interface name: flutter_availability_data_interface
description: "The data interface for the flutter_availability component" description: "The data interface for the flutter_availability component"
version: 1.1.0 version: 1.2.0
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub