Compare commits

..

7 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
Freek van de Ven
bf591a45a2 feat: add Semantics widget to inputs and dynamic texts
This will add accessibility id and id to the inputfields, buttons and dynamic texts so they can be accessed in appium for automated tests
2025-02-03 08:10:11 +01:00
Freek van de Ven
38a9351bbb feat: add CustomSemantics widget 2025-02-03 08:10:11 +01:00
Freek van de Ven
78acd0e41a feat: add component release workflow 2025-02-03 08:10:11 +01:00
Freek van de Ven
4247cc6ba9 chore: update package configuration to flutter 3.24.3 2025-02-03 08:10:11 +01:00
Kiril Tijsma
bd8bc994f8 fix(availability-scope): compare between two different things 2025-01-28 13:37:39 +01:00
26 changed files with 810 additions and 311 deletions

2
.fvmrc
View file

@ -1,3 +1,3 @@
{ {
"flutter": "3.22.2" "flutter": "3.24.3"
} }

View file

@ -12,3 +12,4 @@ jobs:
permissions: write-all permissions: write-all
with: with:
subfolder: '.' # add optional subfolder to run workflow in subfolder: '.' # add optional subfolder to run workflow in
flutter_version: 3.24.3

14
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Iconica Standard Component Release Workflow
# Workflow Caller version: 1.0.0
on:
release:
types: [published]
workflow_dispatch:
jobs:
call-global-iconica-workflow:
uses: Iconica-Development/.github/.github/workflows/component-release.yml@master
secrets: inherit
permissions: write-all

View file

@ -1,3 +1,15 @@
## 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
* Added CustomSemantics widget that is used to wrap all the buttons, textfields and dynamic texts to make the userstory accessible for e2e testing.
## 1.0.0 ## 1.0.0
- T.B.D * First release of flutter_availability userstory

View file

@ -0,0 +1,202 @@
/// Accessibility identifiers for all widgets in the availability userstory that
/// need to be interacted with by e2e tests. This includes buttons, textfields,
/// and dynamic texts.
class AvailabilityAccessibilityIds {
/// default [AvailabilityAccessibilityIds] constructor where all the
/// identifiers are required. This is to ensure that apps automatically break
/// when new identifiers are added.
const AvailabilityAccessibilityIds({
required this.monthNameTextIdentifier,
required this.previousMonthButtonIdentifier,
required this.nextMonthButtonIdentifier,
required this.availabilityDateButtonIdentifier,
required this.createNewTemplateButtonIdentifier,
required this.viewAvailabilitiesButtonIdentifier,
required this.clearAvailabilitiesButtonIdentifier,
required this.toggleTemplateDrawerButtonIdentifier,
required this.availabilitiesPeriodTextIdentifier,
required this.selectUnavailableForPeriodButtonIdentifier,
required this.addTemplateToAvailabilitiesButtonIdentifier,
required this.removeTemplatesFromAvailabilitiesButtonIdentifier,
required this.createNewDayTemplateButtonIdentifier,
required this.createNewWeekTemplateButtonIdentifier,
required this.dayTemplateEditButtonIdentifier,
required this.weekTemplateEditButtonIdentifier,
required this.templateNameTextFieldIdentifier,
required this.startTimeTextFieldIdentifier,
required this.endTimeTextFieldIdentifier,
required this.durationTextFieldIdentifier,
required this.addBreaksButtonIdentifier,
required this.editBreakButtonIdentifier,
required this.deleteBreakButtonIdentifier,
required this.colorSelectionButtonIdentifier,
required this.colorSelectedButtonIdentifier,
required this.weekDayButtonIdentifier,
required this.weekDayTimeIdentifier,
required this.weekDayBreakIdentifier,
required this.templateNameIdentifier,
required this.deleteTemplateButtonIdentifier,
required this.saveButtonIdentifier,
required this.addButtonIdentifier,
required this.nextButtonIdentifier,
required this.closeButtonIdentifier,
});
/// Empty [AvailabilityAccessibilityIds] constructor where all the identifiers
/// are already set to their default values. You can override all or some of
/// the default values.
const AvailabilityAccessibilityIds.empty({
this.monthNameTextIdentifier = "text_month_name",
this.previousMonthButtonIdentifier = "button_previous_month",
this.nextMonthButtonIdentifier = "button_next_month",
this.availabilityDateButtonIdentifier = "button_availability_date",
this.createNewTemplateButtonIdentifier = "button_create_template",
this.viewAvailabilitiesButtonIdentifier = "button_view_availabilities",
this.clearAvailabilitiesButtonIdentifier = "button_clear_availabilities",
this.toggleTemplateDrawerButtonIdentifier = "button_toggle_template_drawer",
this.availabilitiesPeriodTextIdentifier = "text_availabilities_period",
this.selectUnavailableForPeriodButtonIdentifier =
"button_select_unavailable_for_period",
this.addTemplateToAvailabilitiesButtonIdentifier =
"button_add_template_to_availabilities",
this.removeTemplatesFromAvailabilitiesButtonIdentifier =
"button_remove_templates_from_availabilities",
this.createNewDayTemplateButtonIdentifier = "button_create_template_day",
this.createNewWeekTemplateButtonIdentifier = "button_create_template_week",
this.dayTemplateEditButtonIdentifier = "button_edit_template_day",
this.weekTemplateEditButtonIdentifier = "button_edit_template_week",
this.templateNameTextFieldIdentifier = "textfield_template_name",
this.startTimeTextFieldIdentifier = "textfield_start_time",
this.endTimeTextFieldIdentifier = "textfield_end_time",
this.durationTextFieldIdentifier = "textfield_duration",
this.addBreaksButtonIdentifier = "button_add_breaks",
this.editBreakButtonIdentifier = "button_edit_break",
this.deleteBreakButtonIdentifier = "button_delete_break",
this.colorSelectionButtonIdentifier = "button_select_color",
this.colorSelectedButtonIdentifier = "button_selected_color",
this.weekDayButtonIdentifier = "button_select_week_day",
this.weekDayTimeIdentifier = "text_week_day_time",
this.weekDayBreakIdentifier = "text_week_day_break",
this.templateNameIdentifier = "text_template_name",
this.deleteTemplateButtonIdentifier = "button_delete_template",
this.saveButtonIdentifier = "button_save",
this.addButtonIdentifier = "button_add",
this.nextButtonIdentifier = "button_next",
this.closeButtonIdentifier = "button_close",
});
/// The identifier for the text that displays the month that is being viewed
final String monthNameTextIdentifier;
/// The identifier for the button to navigate to the previous month
final String previousMonthButtonIdentifier;
/// The identifier for the button to navigate to the next month
final String nextMonthButtonIdentifier;
/// The identifier for the button to select a date in the availability view
/// The month and day are appended to this identifier
final String availabilityDateButtonIdentifier;
/// The identifier for the button to go template overview screen
final String createNewTemplateButtonIdentifier;
/// The identifier for the button to view availabilities
final String viewAvailabilitiesButtonIdentifier;
/// The identifier for the button to clear availabilities;
final String clearAvailabilitiesButtonIdentifier;
/// The identifier for the button to toggle the template drawer
final String toggleTemplateDrawerButtonIdentifier;
/// The identifier for the text that displays the period of availabilities
/// that are being viewed
final String availabilitiesPeriodTextIdentifier;
/// The identifier for the checkbox to clear all availabilities for a period
final String selectUnavailableForPeriodButtonIdentifier;
/// The identifier for the button to add a template to a selection of
/// availabilities
final String addTemplateToAvailabilitiesButtonIdentifier;
/// The identifier for the button to remove all templates from a selection of
/// availabilities
final String removeTemplatesFromAvailabilitiesButtonIdentifier;
/// The identifier for the button to create a new day template
final String createNewDayTemplateButtonIdentifier;
/// The identifier for the button to create a new week template
final String createNewWeekTemplateButtonIdentifier;
/// The identifier for the button to edit a specific day template, the index
/// of the item in the list is appended to this identifier
final String dayTemplateEditButtonIdentifier;
/// The identifier for the button to edit a specific week template, the index
/// of the item in the list is appended to this identifier
final String weekTemplateEditButtonIdentifier;
/// The identifier for the textfield to edit the name of a template
final String templateNameTextFieldIdentifier;
/// The identifier for the textfield to edit a start time
final String startTimeTextFieldIdentifier;
/// The identifier for the textfield to edit an end time
final String endTimeTextFieldIdentifier;
/// The identifier for the textfield to edit a duration
final String durationTextFieldIdentifier;
/// The identifier for the button to add new breaks
final String addBreaksButtonIdentifier;
/// The identifier for the break edit button to edit a specific break, the
/// index of the item in the list is appended to this identifier
final String editBreakButtonIdentifier;
/// The identifier for the break delete button to delete a specific break, the
/// index of the item in the list is appended to this identifier
final String deleteBreakButtonIdentifier;
/// The identifier for the button to select a color from the list of colors,
/// the index of the item in the list is appended to this identifier
final String colorSelectionButtonIdentifier;
/// The identifier for the button for the currently selected color from the
/// list of colors. This overrides [colorSelectionButtonIdentifier]
final String colorSelectedButtonIdentifier;
/// The identifier for the button to select a day of the week in the template
/// modification screen. The index of the day is appended to this identifier
final String weekDayButtonIdentifier;
/// The identifier for the time of a day in the template view
/// The index of the day is appended to this identifier
final String weekDayTimeIdentifier;
/// The identifier for the time of a break in the template view
/// The index of the day and time is appended to this identifier
final String weekDayBreakIdentifier;
/// The identifier for the name of a template
final String templateNameIdentifier;
/// The identifier for the button to delete a template
final String deleteTemplateButtonIdentifier;
/// The identifier for the button to save (templates or availabilities)
final String saveButtonIdentifier;
/// The identifier for the button to save breaks
final String addButtonIdentifier;
/// The identifier for the button to navigate to next step for week templates
final String nextButtonIdentifier;
/// The identifier for the button to close a dialog
final String closeButtonIdentifier;
}

View file

@ -1,6 +1,7 @@
import "dart:async"; import "dart:async";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_availability/src/config/availability_accessibility_ids.dart";
import "package:flutter_availability/src/config/availability_translations.dart"; import "package:flutter_availability/src/config/availability_translations.dart";
import "package:flutter_availability/src/service/errors.dart"; import "package:flutter_availability/src/service/errors.dart";
import "package:flutter_availability/src/ui/widgets/defaults/default_base_screen.dart"; import "package:flutter_availability/src/ui/widgets/defaults/default_base_screen.dart";
@ -15,6 +16,7 @@ class AvailabilityOptions {
/// AvailabilityOptions constructor where everything is optional. /// AvailabilityOptions constructor where everything is optional.
AvailabilityOptions({ AvailabilityOptions({
this.translations = const AvailabilityTranslations.empty(), this.translations = const AvailabilityTranslations.empty(),
this.accessibilityIds = const AvailabilityAccessibilityIds.empty(),
this.baseScreenBuilder = DefaultBaseScreen.builder, this.baseScreenBuilder = DefaultBaseScreen.builder,
this.primaryButtonBuilder = DefaultPrimaryButton.builder, this.primaryButtonBuilder = DefaultPrimaryButton.builder,
this.secondaryButtonBuilder = DefaultSecondaryButton.builder, this.secondaryButtonBuilder = DefaultSecondaryButton.builder,
@ -39,6 +41,10 @@ class AvailabilityOptions {
/// The translations for the availability userstory /// The translations for the availability userstory
final AvailabilityTranslations translations; final AvailabilityTranslations translations;
/// All the accessibility ids for the availability userstory
/// These are used to add identifiers to the elements for testing
final AvailabilityAccessibilityIds accessibilityIds;
/// The implementation for communicating with the persistance layer /// The implementation for communicating with the persistance layer
final AvailabilityDataInterface dataInterface; final AvailabilityDataInterface dataInterface;

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";
@ -62,6 +63,7 @@ class _AvailabilitiesModificationScreenState
var options = availabilityScope.options; var options = availabilityScope.options;
var spacing = options.spacing; var spacing = options.spacing;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
useEffect(() { useEffect(() {
availabilityScope.popHandler.add(widget.onExit); availabilityScope.popHandler.add(widget.onExit);
@ -126,10 +128,13 @@ class _AvailabilitiesModificationScreenState
} }
var canSave = _availabilityViewModel.canSave; var canSave = _availabilityViewModel.canSave;
var saveButton = options.primaryButtonBuilder( var saveButton = CustomSemantics(
identifier: identifiers.saveButtonIdentifier,
child: options.primaryButtonBuilder(
context, context,
canSave ? onClickSave : null, canSave ? onClickSave : null,
Text(translations.saveButton), Text(translations.saveButton),
),
); );
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters

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/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/template_legend.dart"; import "package:flutter_availability/src/ui/widgets/template_legend.dart";
@ -43,6 +44,7 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
var service = availabilityScope.service; var service = availabilityScope.service;
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var availabilityStream = useMemoized( var availabilityStream = useMemoized(
() => service.getOverviewDataForMonth(_selectedDate), () => service.getOverviewDataForMonth(_selectedDate),
@ -127,16 +129,22 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
} }
} }
var clearSelectedButton = options.bigTextButtonBuilder( var clearSelectedButton = CustomSemantics(
identifier: identifiers.clearAvailabilitiesButtonIdentifier,
child: options.bigTextButtonBuilder(
context, context,
onClearButtonClicked, onClearButtonClicked,
Text(translations.clearAvailabilityButton), Text(translations.clearAvailabilityButton),
),
); );
var startEditButton = options.primaryButtonBuilder( var startEditButton = CustomSemantics(
identifier: identifiers.viewAvailabilitiesButtonIdentifier,
child: options.primaryButtonBuilder(
context, context,
onButtonPress, onButtonPress,
Text(translations.editAvailabilityButton), Text(translations.editAvailabilityButton),
),
); );
return options.baseScreenBuilder( return options.baseScreenBuilder(

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/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";
@ -51,6 +52,7 @@ class _DayTemplateModificationScreenState
var service = availabilityScope.service; var service = availabilityScope.service;
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
useEffect(() { useEffect(() {
availabilityScope.popHandler.add(widget.onExit); availabilityScope.popHandler.add(widget.onExit);
@ -101,10 +103,13 @@ class _DayTemplateModificationScreenState
var canSave = _viewModel.canSave; var canSave = _viewModel.canSave;
var deleteButton = options.bigTextButtonBuilder( var deleteButton = CustomSemantics(
identifier: identifiers.deleteTemplateButtonIdentifier,
child: options.bigTextButtonBuilder(
context, context,
onDeletePressed, onDeletePressed,
Text(translations.deleteTemplateButton), Text(translations.deleteTemplateButton),
),
); );
void onNameChanged(String name) { void onNameChanged(String name) {
@ -153,11 +158,14 @@ class _DayTemplateModificationScreenState
), ),
], ],
buttons: [ buttons: [
options.primaryButtonBuilder( CustomSemantics(
identifier: identifiers.saveButtonIdentifier,
child: options.primaryButtonBuilder(
context, context,
canSave ? onSavePressed : null, canSave ? onSavePressed : null,
Text(translations.saveButton), Text(translations.saveButton),
), ),
),
if (widget.template != null) ...[ if (widget.template != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
deleteButton, deleteButton,

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/src/ui/widgets/base_page.dart"; import "package:flutter_availability/src/ui/widgets/base_page.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";
@ -34,6 +35,7 @@ class AvailabilityTemplateOverview extends HookWidget {
var service = availabilityScope.service; var service = availabilityScope.service;
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var dayTemplateStream = useMemoized(() => service.getDayTemplates()); var dayTemplateStream = useMemoized(() => service.getDayTemplates());
var weekTemplateStream = useMemoized(() => service.getWeekTemplates()); var weekTemplateStream = useMemoized(() => service.getWeekTemplates());
@ -61,6 +63,7 @@ class AvailabilityTemplateOverview extends HookWidget {
var dayTemplateSection = _TemplateListSection( var dayTemplateSection = _TemplateListSection(
sectionTitle: translations.dayTemplates, sectionTitle: translations.dayTemplates,
createButtonText: translations.createDayTemplate, createButtonText: translations.createDayTemplate,
createButtonIdentifier: identifiers.createNewDayTemplateButtonIdentifier,
onEditTemplate: onEditTemplate, onEditTemplate: onEditTemplate,
onSelectTemplate: onSelectTemplate, onSelectTemplate: onSelectTemplate,
onAddTemplate: () => onAddTemplate(AvailabilityTemplateType.day), onAddTemplate: () => onAddTemplate(AvailabilityTemplateType.day),
@ -72,6 +75,7 @@ class AvailabilityTemplateOverview extends HookWidget {
var weekTemplateSection = _TemplateListSection( var weekTemplateSection = _TemplateListSection(
sectionTitle: translations.weekTemplates, sectionTitle: translations.weekTemplates,
createButtonText: translations.createWeekTemplate, createButtonText: translations.createWeekTemplate,
createButtonIdentifier: identifiers.createNewWeekTemplateButtonIdentifier,
templates: weekTemplates, templates: weekTemplates,
isLoading: isLoading:
weekTemplatesSnapshot.connectionState == ConnectionState.waiting, weekTemplatesSnapshot.connectionState == ConnectionState.waiting,
@ -99,6 +103,7 @@ class _TemplateListSection extends StatelessWidget {
const _TemplateListSection({ const _TemplateListSection({
required this.sectionTitle, required this.sectionTitle,
required this.createButtonText, required this.createButtonText,
required this.createButtonIdentifier,
required this.templates, required this.templates,
required this.isLoading, required this.isLoading,
required this.onEditTemplate, required this.onEditTemplate,
@ -108,6 +113,10 @@ class _TemplateListSection extends StatelessWidget {
final String sectionTitle; final String sectionTitle;
final String createButtonText; final String createButtonText;
/// The accessibility identifier for the create button
final String createButtonIdentifier;
// transform the stream to a snapshot as low as possible to reduce rebuilds // transform the stream to a snapshot as low as possible to reduce rebuilds
final List<AvailabilityTemplateModel> templates; final List<AvailabilityTemplateModel> templates;
final bool isLoading; final bool isLoading;
@ -140,13 +149,16 @@ class _TemplateListSection extends StatelessWidget {
children: [ children: [
const Icon(Icons.add), const Icon(Icons.add),
const SizedBox(width: 8), const SizedBox(width: 8),
options.smallTextButtonBuilder( CustomSemantics(
identifier: createButtonIdentifier,
child: options.smallTextButtonBuilder(
context, context,
onAddTemplate, onAddTemplate,
Text( Text(
createButtonText, createButtonText,
), ),
), ),
),
], ],
), ),
), ),
@ -157,9 +169,10 @@ class _TemplateListSection extends StatelessWidget {
Text(sectionTitle, style: textTheme.titleMedium), Text(sectionTitle, style: textTheme.titleMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
const Divider(height: 1), const Divider(height: 1),
for (var template in templates) ...[ for (var (index, template) in templates.indexed) ...[
_TemplateListSectionItem( _TemplateListSectionItem(
template: template, template: template,
index: index,
onTemplateClicked: onClickTemplate, onTemplateClicked: onClickTemplate,
onEditTemplate: onEditTemplate, onEditTemplate: onEditTemplate,
), ),
@ -177,12 +190,16 @@ class _TemplateListSection extends StatelessWidget {
class _TemplateListSectionItem extends StatelessWidget { class _TemplateListSectionItem extends StatelessWidget {
const _TemplateListSectionItem({ const _TemplateListSectionItem({
required this.template, required this.template,
required this.index,
required this.onTemplateClicked, required this.onTemplateClicked,
required this.onEditTemplate, required this.onEditTemplate,
}); });
final AvailabilityTemplateModel template; final AvailabilityTemplateModel template;
/// The index of the template in the list
final int index;
final void Function(AvailabilityTemplateModel template) onTemplateClicked; final void Function(AvailabilityTemplateModel template) onTemplateClicked;
final void Function(AvailabilityTemplateModel template) onEditTemplate; final void Function(AvailabilityTemplateModel template) onEditTemplate;
@ -191,8 +208,16 @@ class _TemplateListSectionItem extends StatelessWidget {
var theme = Theme.of(context); var theme = Theme.of(context);
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
var templateTypeIdentifer =
template.templateType == AvailabilityTemplateType.day
? identifiers.dayTemplateEditButtonIdentifier
: identifiers.weekTemplateEditButtonIdentifier;
var templateIdentifier = "${templateTypeIdentifer}_$index";
return InkWell( return CustomSemantics(
identifier: templateIdentifier,
child: InkWell(
onTap: () => onTemplateClicked(template), onTap: () => onTemplateClicked(template),
child: Container( child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
@ -230,6 +255,7 @@ class _TemplateListSectionItem extends StatelessWidget {
], ],
), ),
), ),
),
); );
} }
} }

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/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";
@ -53,6 +54,7 @@ class _WeekTemplateModificationScreenState
var service = availabilityScope.service; var service = availabilityScope.service;
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var spacing = options.spacing; var spacing = options.spacing;
var weekTemplateDate = _viewModel.data; var weekTemplateDate = _viewModel.data;
@ -134,22 +136,31 @@ class _WeekTemplateModificationScreenState
}); });
var canSave = _viewModel.canSave; var canSave = _viewModel.canSave;
var nextButton = options.primaryButtonBuilder( var nextButton = CustomSemantics(
identifier: identifiers.nextButtonIdentifier,
child: options.primaryButtonBuilder(
context, context,
canSave ? onNextPressed : null, canSave ? onNextPressed : null,
Text(translations.nextButton), Text(translations.nextButton),
),
); );
var saveButton = options.primaryButtonBuilder( var saveButton = CustomSemantics(
identifier: identifiers.saveButtonIdentifier,
child: options.primaryButtonBuilder(
context, context,
canSave ? onSavePressed : null, canSave ? onSavePressed : null,
Text(translations.saveButton), Text(translations.saveButton),
),
); );
var deleteButton = options.bigTextButtonBuilder( var deleteButton = CustomSemantics(
identifier: identifiers.deleteTemplateButtonIdentifier,
child: options.bigTextButtonBuilder(
context, context,
onDeletePressed, onDeletePressed,
Text(translations.deleteTemplateButton), Text(translations.deleteTemplateButton),
),
); );
var title = Center( var title = Center(
@ -230,12 +241,15 @@ class _WeekTemplateModificationScreenState
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: CustomSemantics(
identifier: identifiers.templateNameIdentifier,
child: Text( child: Text(
_viewModel.name ?? "", _viewModel.name ?? "",
style: textTheme.bodyLarge, style: textTheme.bodyLarge,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
),
], ],
), ),
), ),

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/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// ///
@ -30,6 +31,7 @@ class AvailabilityClearSection extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var isSingleDay = range.start.isAtSameMomentAs(range.end); var isSingleDay = range.start.isAtSameMomentAs(range.end);
@ -43,14 +45,20 @@ class AvailabilityClearSection extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( CustomSemantics(
identifier: identifiers.availabilitiesPeriodTextIdentifier,
child: Text(
titleText, titleText,
style: textTheme.titleMedium, style: textTheme.titleMedium,
), ),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
Checkbox( CustomSemantics(
identifier:
identifiers.selectUnavailableForPeriodButtonIdentifier,
child: Checkbox(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
splashRadius: 0, splashRadius: 0,
@ -60,11 +68,14 @@ class AvailabilityClearSection extends StatelessWidget {
onChanged(value); onChanged(value);
}, },
), ),
),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Expanded(
child: Text(
unavailableText, unavailableText,
style: textTheme.bodyMedium, style: textTheme.bodyMedium,
), ),
),
], ],
), ),
], ],

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/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";
@ -34,6 +35,7 @@ class AvailabilityTemplateSelection extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var titleText = translations.availabilityAddTemplateTitle; var titleText = translations.availabilityAddTemplateTitle;
if (selectedTemplates.isNotEmpty) { if (selectedTemplates.isNotEmpty) {
@ -47,11 +49,14 @@ class AvailabilityTemplateSelection extends StatelessWidget {
var addButton = options.bigTextButtonWrapperBuilder( var addButton = options.bigTextButtonWrapperBuilder(
context, context,
onTemplateAdd, onTemplateAdd,
options.bigTextButtonBuilder( CustomSemantics(
identifier: identifiers.addTemplateToAvailabilitiesButtonIdentifier,
child: options.bigTextButtonBuilder(
context, context,
onTemplateAdd, onTemplateAdd,
Text(translations.addButton), Text(translations.addButton),
), ),
),
); );
return Column( return Column(
@ -89,6 +94,7 @@ class _TemplateList extends StatelessWidget {
var theme = Theme.of(context); var theme = Theme.of(context);
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
@ -107,8 +113,8 @@ class _TemplateList extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
for (var template in selectedTemplates) ...[ for (var (index, template) in selectedTemplates.indexed) ...[
_TemplateListItem(template: template), _TemplateListItem(template: template, index: index),
if (template != selectedTemplates.last) ...[ if (template != selectedTemplates.last) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
], ],
@ -117,10 +123,14 @@ class _TemplateList extends StatelessWidget {
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
InkWell( CustomSemantics(
identifier:
identifiers.removeTemplatesFromAvailabilitiesButtonIdentifier,
child: InkWell(
onTap: onTemplatesRemoved, onTap: onTemplatesRemoved,
child: const Icon(Icons.remove), child: const Icon(Icons.remove),
), ),
),
], ],
), ),
); );
@ -128,15 +138,22 @@ class _TemplateList extends StatelessWidget {
} }
class _TemplateListItem extends StatelessWidget { class _TemplateListItem extends StatelessWidget {
const _TemplateListItem({required this.template}); const _TemplateListItem({
required this.template,
required this.index,
});
final AvailabilityTemplateModel template; final AvailabilityTemplateModel template;
/// The index of the template in the list of selected templates
final int index;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
return Row( return Row(
children: [ children: [
@ -150,12 +167,15 @@ class _TemplateListItem extends StatelessWidget {
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: CustomSemantics(
identifier: "${identifiers.templateNameIdentifier}_$index",
child: Text( child: Text(
template.name, template.name,
style: theme.textTheme.bodyLarge, style: theme.textTheme.bodyLarge,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
),
], ],
); );
} }

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/widgets/calendar_grid.dart"; import "package:flutter_availability/src/ui/widgets/calendar_grid.dart";
import "package:flutter_availability/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
@ -65,45 +66,60 @@ class CalendarView extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var mappedCalendarDays = _mapAvailabilitiesToCalendarDays(availabilities); var mappedCalendarDays = _mapAvailabilitiesToCalendarDays(availabilities);
var existsTemplateDeviations = mappedCalendarDays.any( var existsTemplateDeviations = mappedCalendarDays.any(
(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: [
IconButton( CustomSemantics(
identifier: identifiers.previousMonthButtonIdentifier,
child: IconButton(
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(
identifier: identifiers.monthNameTextIdentifier,
child: Text( child: Text(
translations.monthYearFormatter(context, month), translations.monthYearFormatter(context, month),
style: textTheme.titleMedium, style: textTheme.titleMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
const SizedBox(width: 44), ),
IconButton( SizedBox(width: sideSpace),
CustomSemantics(
identifier: identifiers.nextMonthButtonIdentifier,
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,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/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
@ -119,6 +120,7 @@ class _CalendarDay extends StatelessWidget {
var colorScheme = theme.colorScheme; var colorScheme = theme.colorScheme;
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
var colors = options.colors; var colors = options.colors;
var dayColor = day.color ?? var dayColor = day.color ??
@ -134,6 +136,10 @@ class _CalendarDay extends StatelessWidget {
textStyle = textTheme.titleMedium?.copyWith(color: textColor); textStyle = textTheme.titleMedium?.copyWith(color: textColor);
} }
var dayIdentifier =
"${identifiers.availabilityDateButtonIdentifier}_${day.date.year}_"
"${day.date.month}_${day.date.day}";
var decoration = day.outsideMonth var decoration = day.outsideMonth
? null ? null
: BoxDecoration( : BoxDecoration(
@ -145,7 +151,9 @@ class _CalendarDay extends StatelessWidget {
), ),
); );
return InkWell( return CustomSemantics(
identifier: dayIdentifier,
child: InkWell(
onTap: () => onDayTap(day.date), onTap: () => onDayTap(day.date),
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -159,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) ...[
@ -173,6 +184,7 @@ class _CalendarDay extends StatelessWidget {
], ],
), ),
), ),
),
); );
} }
} }

View file

@ -1,6 +1,7 @@
import "dart:math"; import "dart:math";
import "package:flutter/material.dart"; import "package:flutter/material.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
@ -40,9 +41,10 @@ class TemplateColorSelection extends StatelessWidget {
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: [ children: [
for (var color in colors.templateColors) ...[ for (var (index, color) in colors.templateColors.indexed) ...[
_TemplateColorItem( _TemplateColorItem(
color: color, color: color,
index: index,
selectedColor: selectedColor, selectedColor: selectedColor,
onColorSelected: onColorSelected, onColorSelected: onColorSelected,
), ),
@ -57,6 +59,7 @@ class TemplateColorSelection extends StatelessWidget {
class _TemplateColorItem extends StatelessWidget { class _TemplateColorItem extends StatelessWidget {
const _TemplateColorItem({ const _TemplateColorItem({
required this.color, required this.color,
required this.index,
required this.selectedColor, required this.selectedColor,
required this.onColorSelected, required this.onColorSelected,
}); });
@ -66,14 +69,20 @@ class _TemplateColorItem extends StatelessWidget {
final Color color; final Color color;
/// The index of the color in the list of colors
final int index;
final int? selectedColor; final int? selectedColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
var colors = options.colors; var colors = options.colors;
var isSelected = selectedColor == color.value;
/// If the color is selected, deselect it, otherwise select it /// If the color is selected, deselect it, otherwise select it
void onColorClick(Color color) => onColorSelected( void onColorClick(Color color) => onColorSelected(
color.value == selectedColor ? null : color.value, color.value == selectedColor ? null : color.value,
@ -83,11 +92,15 @@ class _TemplateColorItem extends StatelessWidget {
? colors.templateColorLightCheckmarkColor ? colors.templateColorLightCheckmarkColor
: colors.templateColorDarkCheckmarkColor; : colors.templateColorDarkCheckmarkColor;
var icon = selectedColor == color.value var icon = isSelected ? Icon(Icons.check, color: checkMarkColor) : null;
? Icon(Icons.check, color: checkMarkColor)
: null;
return GestureDetector( var colorIdentifier = isSelected
? identifiers.colorSelectedButtonIdentifier
: "${identifiers.colorSelectionButtonIdentifier}_$index";
return CustomSemantics(
identifier: colorIdentifier,
child: GestureDetector(
onTap: () => onColorClick(color), onTap: () => onColorClick(color),
child: Container( child: Container(
width: 40, width: 40,
@ -98,6 +111,7 @@ class _TemplateColorItem extends StatelessWidget {
), ),
child: icon, child: icon,
), ),
),
); );
} }
} }

View file

@ -44,6 +44,7 @@ class TimeSelection extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
return Column( return Column(
crossAxisAlignment: crossAxisAlignment, crossAxisAlignment: crossAxisAlignment,
@ -63,6 +64,7 @@ class TimeSelection extends StatelessWidget {
Expanded( Expanded(
flex: 2, flex: 2,
child: TimeInputField( child: TimeInputField(
identifier: identifiers.startTimeTextFieldIdentifier,
initialValue: startTime, initialValue: startTime,
onTimeChanged: onStartChanged, onTimeChanged: onStartChanged,
), ),
@ -78,6 +80,7 @@ class TimeSelection extends StatelessWidget {
Expanded( Expanded(
flex: 2, flex: 2,
child: TimeInputField( child: TimeInputField(
identifier: identifiers.endTimeTextFieldIdentifier,
initialValue: endTime, initialValue: endTime,
onTimeChanged: onEndChanged, onTimeChanged: onEndChanged,
), ),

View file

@ -1,5 +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_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
@ -8,12 +9,16 @@ class TimeInputField extends StatelessWidget {
const TimeInputField({ const TimeInputField({
required this.initialValue, required this.initialValue,
required this.onTimeChanged, required this.onTimeChanged,
required this.identifier,
super.key, super.key,
}); });
/// ///
final TimeOfDay? initialValue; final TimeOfDay? initialValue;
/// The accessibility identifier for this input field
final String identifier;
/// ///
final void Function(TimeOfDay) onTimeChanged; final void Function(TimeOfDay) onTimeChanged;
@ -37,7 +42,10 @@ class TimeInputField extends StatelessWidget {
} }
} }
return TextFormField( return CustomSemantics(
identifier: identifier,
isTextField: true,
child: TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
suffixIcon: const Icon(Icons.access_time), suffixIcon: const Icon(Icons.access_time),
hintText: translations.time, hintText: translations.time,
@ -52,6 +60,7 @@ class TimeInputField extends StatelessWidget {
readOnly: true, readOnly: true,
style: options.textStyles.inputFieldTextStyle, style: options.textStyles.inputFieldTextStyle,
onTap: onFieldtap, onTap: onFieldtap,
),
); );
} }
} }
@ -122,6 +131,7 @@ class _DurationInputFieldState extends State<DurationInputField> {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
return Focus( return Focus(
onFocusChange: (hasFocus) { onFocusChange: (hasFocus) {
@ -131,6 +141,9 @@ class _DurationInputFieldState extends State<DurationInputField> {
_removeOverlay(); _removeOverlay();
} }
}, },
child: CustomSemantics(
identifier: identifiers.durationTextFieldIdentifier,
isTextField: true,
child: TextFormField( child: TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
labelText: translations.time, labelText: translations.time,
@ -148,6 +161,7 @@ class _DurationInputFieldState extends State<DurationInputField> {
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
], ],
), ),
),
); );
} }
} }

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/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";
@ -32,6 +33,7 @@ class PauseSelection extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var popHandler = availabilityScope.popHandler; var popHandler = availabilityScope.popHandler;
Future<BreakViewModel?> openBreakDialog( Future<BreakViewModel?> openBreakDialog(
@ -73,7 +75,9 @@ class PauseSelection extends StatelessWidget {
var sortedBreaks = breaks.toList()..sort((a, b) => a.compareTo(b)); var sortedBreaks = breaks.toList()..sort((a, b) => a.compareTo(b));
var addButton = options.bigTextButtonWrapperBuilder( var addButton = CustomSemantics(
identifier: identifiers.addBreaksButtonIdentifier,
child: options.bigTextButtonWrapperBuilder(
context, context,
onClickAddBreak, onClickAddBreak,
options.bigTextButtonBuilder( options.bigTextButtonBuilder(
@ -81,6 +85,7 @@ class PauseSelection extends StatelessWidget {
onClickAddBreak, onClickAddBreak,
Text(translations.addButton), Text(translations.addButton),
), ),
),
); );
return Column( return Column(
@ -99,10 +104,11 @@ class PauseSelection extends StatelessWidget {
), ),
], ],
), ),
for (var breakModel in sortedBreaks) ...[ for (var (index, breakModel) in sortedBreaks.indexed) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
BreakDisplay( BreakDisplay(
breakModel: breakModel, breakModel: breakModel,
index: index,
onRemove: () => onDeleteBreak(breakModel), onRemove: () => onDeleteBreak(breakModel),
onClick: () async => onEditBreak(breakModel), onClick: () async => onEditBreak(breakModel),
), ),
@ -119,6 +125,7 @@ class BreakDisplay extends StatelessWidget {
/// Creates a new break display /// Creates a new break display
const BreakDisplay({ const BreakDisplay({
required this.breakModel, required this.breakModel,
required this.index,
required this.onRemove, required this.onRemove,
required this.onClick, required this.onClick,
super.key, super.key,
@ -127,6 +134,9 @@ class BreakDisplay extends StatelessWidget {
/// The break to display /// The break to display
final BreakViewModel breakModel; final BreakViewModel breakModel;
/// The index of the break in the list
final int index;
/// Callback for when the minus button is clicked /// Callback for when the minus button is clicked
final VoidCallback onRemove; final VoidCallback onRemove;
@ -140,6 +150,7 @@ class BreakDisplay extends StatelessWidget {
var options = availabilityScope.options; var options = availabilityScope.options;
var colors = options.colors; var colors = options.colors;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var starTime = translations.timeFormatter( var starTime = translations.timeFormatter(
context, context,
@ -151,6 +162,9 @@ class BreakDisplay extends StatelessWidget {
); );
var breakDuration = breakModel.duration.inMinutes; var breakDuration = breakModel.duration.inMinutes;
var editBreakIdentifier = "${identifiers.editBreakButtonIdentifier}_$index";
var deleteBreakIdentifier =
"${identifiers.deleteBreakButtonIdentifier}_$index";
return InkWell( return InkWell(
onTap: onClick, onTap: onClick,
@ -163,17 +177,23 @@ class BreakDisplay extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row( child: Row(
children: [ children: [
Text( CustomSemantics(
identifier: editBreakIdentifier,
child: Text(
"$breakDuration " "$breakDuration "
"${translations.timeMinutes} | " "${translations.timeMinutes} | "
"$starTime - " "$starTime - "
"$endTime", "$endTime",
), ),
),
const Spacer(), const Spacer(),
InkWell( CustomSemantics(
identifier: deleteBreakIdentifier,
child: InkWell(
onTap: onRemove, onTap: onRemove,
child: const Icon(Icons.remove), child: const Icon(Icons.remove),
), ),
),
], ],
), ),
), ),
@ -251,6 +271,7 @@ class _AvailabilityBreakSelectionDialogState
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var spacing = options.spacing; var spacing = options.spacing;
void onUpdateDuration(Duration? duration) { void onUpdateDuration(Duration? duration) {
@ -299,7 +320,9 @@ class _AvailabilityBreakSelectionDialogState
var onSaveButtonPress = canSave ? onSave : null; var onSaveButtonPress = canSave ? onSave : null;
var saveButton = options.primaryButtonBuilder( var saveButton = CustomSemantics(
identifier: identifiers.addButtonIdentifier,
child: options.primaryButtonBuilder(
context, context,
onSaveButtonPress, onSaveButtonPress,
Text( Text(
@ -307,6 +330,7 @@ class _AvailabilityBreakSelectionDialogState
? translations.addButton ? translations.addButton
: translations.saveButton, : translations.saveButton,
), ),
),
); );
var descriptionText = widget.editingTemplate var descriptionText = widget.editingTemplate
@ -381,12 +405,15 @@ class _AvailabilityBreakSelectionDialogState
Positioned( Positioned(
right: 0, right: 0,
top: 0, top: 0,
child: CustomSemantics(
identifier: identifiers.closeButtonIdentifier,
child: IconButton( child: IconButton(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
), ),
),
], ],
); );
} }

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/src/config/availability_options.dart"; import "package:flutter_availability/src/config/availability_options.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";
@ -36,6 +37,7 @@ class _TemplateLegendState extends State<TemplateLegend> {
var options = availabilityScope.options; var options = availabilityScope.options;
var colors = options.colors; var colors = options.colors;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var featureSet = options.featureSet; var featureSet = options.featureSet;
var templatesLoading = var templatesLoading =
@ -63,7 +65,9 @@ class _TemplateLegendState extends State<TemplateLegend> {
}); });
} }
var createNewTemplateButton = GestureDetector( var createNewTemplateButton = CustomSemantics(
identifier: identifiers.createNewTemplateButtonIdentifier,
child: GestureDetector(
onTap: () => widget.onViewTemplates(), onTap: () => widget.onViewTemplates(),
child: ColoredBox( child: ColoredBox(
color: Colors.transparent, color: Colors.transparent,
@ -72,13 +76,16 @@ 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,
), ),
),
], ],
), ),
), ),
),
); );
Widget body = const Divider( Widget body = const Divider(
@ -104,6 +111,7 @@ class _TemplateLegendState extends State<TemplateLegend> {
left: 12, left: 12,
), ),
child: _TemplateLegendItem( child: _TemplateLegendItem(
index: 0,
name: translations.templateSelectionLabel, name: translations.templateSelectionLabel,
backgroundColor: Colors.white, backgroundColor: Colors.white,
borderColor: colorScheme.primary, borderColor: colorScheme.primary,
@ -116,6 +124,7 @@ class _TemplateLegendState extends State<TemplateLegend> {
left: 12, left: 12,
), ),
child: _TemplateLegendItem( child: _TemplateLegendItem(
index: 1,
name: translations.availabilityWithoutTemplateLabel, name: translations.availabilityWithoutTemplateLabel,
backgroundColor: colors.customAvailabilityColor ?? backgroundColor: colors.customAvailabilityColor ??
colorScheme.secondary, colorScheme.secondary,
@ -123,13 +132,14 @@ class _TemplateLegendState extends State<TemplateLegend> {
), ),
], ],
if (featureSet.require(AvailabilityFeature.templates)) ...[ if (featureSet.require(AvailabilityFeature.templates)) ...[
for (var template in templates) ...[ for (var (index, template) in templates.indexed) ...[
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 10, top: 10,
left: 12, left: 12,
), ),
child: _TemplateLegendItem( child: _TemplateLegendItem(
index: index + 2,
name: template.name, name: template.name,
backgroundColor: Color(template.color), backgroundColor: Color(template.color),
), ),
@ -149,7 +159,9 @@ class _TemplateLegendState extends State<TemplateLegend> {
return Column( return Column(
children: [ children: [
// a button to open/close a drawer with all the templates // a button to open/close a drawer with all the templates
GestureDetector( CustomSemantics(
identifier: identifiers.toggleTemplateDrawerButtonIdentifier,
child: GestureDetector(
onTap: onDrawerHeaderClick, onTap: onDrawerHeaderClick,
child: ColoredBox( child: ColoredBox(
color: Colors.transparent, color: Colors.transparent,
@ -173,6 +185,7 @@ class _TemplateLegendState extends State<TemplateLegend> {
), ),
), ),
), ),
),
const SizedBox(height: 4), const SizedBox(height: 4),
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
@ -208,11 +221,17 @@ class _TemplateLegendItem extends StatelessWidget {
const _TemplateLegendItem({ const _TemplateLegendItem({
required this.name, required this.name,
required this.backgroundColor, required this.backgroundColor,
required this.index,
this.borderColor, this.borderColor,
}); });
final String name; final String name;
/// The index of the color in the list of colors (index 0 is the selected
/// color template, index 1 is the color for availabilities without a
/// template)
final int index;
final Color backgroundColor; final Color backgroundColor;
final Color? borderColor; final Color? borderColor;
@ -222,6 +241,9 @@ class _TemplateLegendItem extends StatelessWidget {
var theme = Theme.of(context); var theme = Theme.of(context);
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
var templateIdentifier = "${identifiers.templateNameIdentifier}_$index";
return Row( return Row(
children: [ children: [
@ -238,12 +260,15 @@ class _TemplateLegendItem extends StatelessWidget {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: CustomSemantics(
identifier: templateIdentifier,
child: Text( child: Text(
name, name,
style: theme.textTheme.bodyLarge, style: theme.textTheme.bodyLarge,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
),
], ],
); );
} }

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/src/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
/// Input section for the template name /// Input section for the template name
@ -23,6 +24,7 @@ class TemplateNameInput extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -32,7 +34,10 @@ class TemplateNameInput extends StatelessWidget {
style: textTheme.titleMedium, style: textTheme.titleMedium,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
TextFormField( CustomSemantics(
identifier: identifiers.templateNameTextFieldIdentifier,
isTextField: true,
child: TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
hintText: translations.templateTitleHintText, hintText: translations.templateTitleHintText,
hintStyle: theme.inputDecorationTheme.hintStyle, hintStyle: theme.inputDecorationTheme.hintStyle,
@ -46,6 +51,7 @@ class TemplateNameInput extends StatelessWidget {
style: options.textStyles.inputFieldTextStyle, style: options.textStyles.inputFieldTextStyle,
onChanged: onNameChanged, onChanged: onNameChanged,
), ),
),
], ],
); );
} }

View file

@ -1,6 +1,7 @@
// 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/util/scope.dart"; import "package:flutter_availability/src/util/scope.dart";
@ -58,10 +59,10 @@ class _TemplateWeekDaySelectionState extends State<TemplateWeekDaySelection> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
for (var day in days) ...[ for (var (index, day) in days.indexed) ...[
_DaySelectionCard( _DaySelectionCard(
day: day, day: day,
days: days, index: index,
selectedDayIndex: _selectedDayIndex, selectedDayIndex: _selectedDayIndex,
onDaySelected: (selected) => onDaySelected: (selected) =>
onDaySelected(selected, days.indexOf(day)), onDaySelected(selected, days.indexOf(day)),
@ -81,12 +82,12 @@ class _DaySelectionCard extends StatelessWidget {
const _DaySelectionCard({ const _DaySelectionCard({
required this.selectedDayIndex, required this.selectedDayIndex,
required this.day, required this.day,
required this.days, required this.index,
required this.onDaySelected, required this.onDaySelected,
}); });
final String day; final String day;
final List<String> days; final int index;
final int selectedDayIndex; final int selectedDayIndex;
@ -94,11 +95,11 @@ class _DaySelectionCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var index = days.indexOf(day);
var isSelected = index == selectedDayIndex; var isSelected = index == selectedDayIndex;
return _DaySelectionCardLayout( return _DaySelectionCardLayout(
day: day, day: day,
index: index,
isSelected: isSelected, isSelected: isSelected,
onDaySelected: onDaySelected, onDaySelected: onDaySelected,
); );
@ -108,6 +109,7 @@ class _DaySelectionCard extends StatelessWidget {
class _DaySelectionCardLayout extends StatelessWidget { class _DaySelectionCardLayout extends StatelessWidget {
const _DaySelectionCardLayout({ const _DaySelectionCardLayout({
required this.day, required this.day,
required this.index,
required this.isSelected, required this.isSelected,
required this.onDaySelected, required this.onDaySelected,
}); });
@ -115,6 +117,9 @@ class _DaySelectionCardLayout extends StatelessWidget {
final String day; final String day;
final bool isSelected; final bool isSelected;
/// The index of the day in the list of days
final int index;
final void Function(bool) onDaySelected; final void Function(bool) onDaySelected;
@override @override
@ -124,6 +129,7 @@ class _DaySelectionCardLayout extends StatelessWidget {
var abbreviationTextStyle = textTheme.headlineMedium; var abbreviationTextStyle = textTheme.headlineMedium;
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var identifiers = options.accessibilityIds;
abbreviationTextStyle = isSelected abbreviationTextStyle = isSelected
? abbreviationTextStyle?.copyWith( ? abbreviationTextStyle?.copyWith(
@ -131,10 +137,14 @@ class _DaySelectionCardLayout extends StatelessWidget {
) )
: abbreviationTextStyle; : abbreviationTextStyle;
var identifier = "${identifiers.weekDayButtonIdentifier}_$index";
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
height: isSelected ? 72 : 64, height: isSelected ? 72 : 64,
width: isSelected ? 72 : 64, width: isSelected ? 72 : 64,
child: CustomSemantics(
identifier: identifier,
child: ChoiceChip( child: ChoiceChip(
shape: RoundedRectangleBorder(borderRadius: options.borderRadius), shape: RoundedRectangleBorder(borderRadius: options.borderRadius),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -148,6 +158,7 @@ class _DaySelectionCardLayout extends StatelessWidget {
showCheckmark: theme.chipTheme.showCheckmark ?? false, showCheckmark: theme.chipTheme.showCheckmark ?? false,
onSelected: onDaySelected, onSelected: onDaySelected,
), ),
),
); );
} }
} }

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/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";
@ -28,12 +29,24 @@ class TemplateWeekOverview extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var colors = options.colors; var colors = options.colors;
var dayNames = getDaysOfTheWeekAsStrings(translations, context); var dayNames = getDaysOfTheWeekAsStrings(translations, context);
var templateData = template.data; var templateData = template.data;
var editButton = CustomSemantics(
identifier: identifiers.weekTemplateEditButtonIdentifier,
child: options.smallTextButtonBuilder(
context,
onClickEdit,
Text(
translations.editTemplateButton,
),
),
);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -46,13 +59,7 @@ class TemplateWeekOverview extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
options.smallTextButtonBuilder( editButton,
context,
onClickEdit,
Text(
translations.editTemplateButton,
),
),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -67,12 +74,13 @@ class TemplateWeekOverview extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
for (var day in WeekDay.values) ...[ for (var (index, day) in WeekDay.values.indexed) ...[
_TemplateDayDetailRow( _TemplateDayDetailRow(
dayName: dayNames[day.index], dayName: dayNames[index],
dayData: dayData:
templateData.containsKey(day) ? templateData[day] : null, templateData.containsKey(day) ? templateData[day] : null,
isOdd: day.index.isOdd, index: index,
isOdd: index.isOdd,
), ),
], ],
], ],
@ -88,6 +96,7 @@ class _TemplateDayDetailRow extends StatelessWidget {
required this.dayName, required this.dayName,
required this.dayData, required this.dayData,
required this.isOdd, required this.isOdd,
required this.index,
}); });
/// The name of the day /// The name of the day
@ -97,6 +106,9 @@ class _TemplateDayDetailRow extends StatelessWidget {
/// This causes a layered effect /// This causes a layered effect
final bool isOdd; final bool isOdd;
/// The index of the day
final int index;
/// The data of the day /// The data of the day
final DayTemplateDataViewModel? dayData; final DayTemplateDataViewModel? dayData;
@ -107,6 +119,7 @@ class _TemplateDayDetailRow extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var startTime = dayData?.startTime; var startTime = dayData?.startTime;
var endTime = dayData?.endTime; var endTime = dayData?.endTime;
@ -119,6 +132,8 @@ class _TemplateDayDetailRow extends StatelessWidget {
dayPeriod = translations.unavailable; dayPeriod = translations.unavailable;
} }
var dayPeriodIdentifier = "${identifiers.weekDayTimeIdentifier}_$index";
var breaks = dayData?.breaks ?? <BreakViewModel>[]; var breaks = dayData?.breaks ?? <BreakViewModel>[];
BoxDecoration? boxDecoration; BoxDecoration? boxDecoration;
@ -145,13 +160,23 @@ class _TemplateDayDetailRow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(dayName, style: textTheme.bodyLarge), Text(dayName, style: textTheme.bodyLarge),
Text(dayPeriod, style: textTheme.bodyLarge), CustomSemantics(
identifier: dayPeriodIdentifier,
child: Text(
dayPeriod,
style: textTheme.bodyLarge,
),
),
], ],
), ),
// for each break add a line // for each break add a line
for (var dayBreak in breaks) ...[ for (var (breakIndex, dayBreak) in breaks.indexed) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
_TemplateDayDetailPauseRow(dayBreakViewModel: dayBreak), _TemplateDayDetailPauseRow(
dayBreakViewModel: dayBreak,
dayIndex: index,
breakIndex: breakIndex,
),
], ],
], ],
), ),
@ -162,10 +187,20 @@ class _TemplateDayDetailRow extends StatelessWidget {
class _TemplateDayDetailPauseRow extends StatelessWidget { class _TemplateDayDetailPauseRow extends StatelessWidget {
const _TemplateDayDetailPauseRow({ const _TemplateDayDetailPauseRow({
required this.dayBreakViewModel, required this.dayBreakViewModel,
required this.dayIndex,
required this.breakIndex,
}); });
final BreakViewModel dayBreakViewModel; final BreakViewModel dayBreakViewModel;
/// The index of the day in the list of days
/// This is used to create unique identifiers when there are multiple days
/// with breaks
final int dayIndex;
/// The index of the break in the list of breaks
final int breakIndex;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
@ -173,6 +208,7 @@ class _TemplateDayDetailPauseRow extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
var translations = options.translations; var translations = options.translations;
var identifiers = options.accessibilityIds;
var dayBreak = dayBreakViewModel.toBreak(); var dayBreak = dayBreakViewModel.toBreak();
var startTime = TimeOfDay.fromDateTime(dayBreak.startTime); var startTime = TimeOfDay.fromDateTime(dayBreak.startTime);
@ -186,6 +222,9 @@ class _TemplateDayDetailPauseRow extends StatelessWidget {
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
); );
var breakIdentifier =
"${identifiers.weekDayBreakIdentifier}_${dayIndex}_$breakIndex";
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -196,10 +235,13 @@ class _TemplateDayDetailPauseRow extends StatelessWidget {
style: pauseTextStyle, style: pauseTextStyle,
), ),
), ),
Text( CustomSemantics(
identifier: breakIdentifier,
child: Text(
pausePeriod, pausePeriod,
style: pauseTextStyle, style: pauseTextStyle,
), ),
),
], ],
); );
} }

View file

@ -29,7 +29,7 @@ class AvailabilityScope extends InheritedWidget {
@override @override
bool updateShouldNotify(AvailabilityScope oldWidget) => bool updateShouldNotify(AvailabilityScope oldWidget) =>
oldWidget.userId != userId || options != options; oldWidget.userId != userId || oldWidget.options != options;
/// ///
static AvailabilityScope of(BuildContext context) => static AvailabilityScope of(BuildContext context) =>

View file

@ -1,6 +1,6 @@
name: flutter_availability name: flutter_availability
description: "Flutter availability userstory package" description: "Flutter availability userstory package"
version: 1.0.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.0.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:
@ -25,4 +28,3 @@ dev_dependencies:
url: https://github.com/Iconica-Development/flutter_iconica_analysis url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0 ref: 7.0.0
flutter:

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.0.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