diff --git a/packages/flutter_availability/lib/src/service/availability_service.dart b/packages/flutter_availability/lib/src/service/availability_service.dart index 2449fa2..956d024 100644 --- a/packages/flutter_availability/lib/src/service/availability_service.dart +++ b/packages/flutter_availability/lib/src/service/availability_service.dart @@ -42,6 +42,8 @@ class AvailabilityService { userId: userId, ); + availability.validate(); + await dataInterface.setAvailabilitiesForUser( userId: userId, availability: updatedAvailability, @@ -163,6 +165,9 @@ class AvailabilityService { var updatedTemplate = template.copyWith( userId: userId, ); + + template.validate(); + await dataInterface.createTemplateForUser( userId, updatedTemplate, @@ -171,6 +176,8 @@ class AvailabilityService { /// Updates a template Future updateTemplate(AvailabilityTemplateModel template) async { + template.validate(); + await dataInterface.updateTemplateForUser( userId, template.id!, diff --git a/packages/flutter_availability/lib/src/service/errors.dart b/packages/flutter_availability/lib/src/service/errors.dart index 039f35c..cf005ce 100644 --- a/packages/flutter_availability/lib/src/service/errors.dart +++ b/packages/flutter_availability/lib/src/service/errors.dart @@ -1,8 +1,31 @@ /// A set of errors enum AvailabilityError { - /// Error identifier when checking break mismatch - breakMismatchWithAvailability( - "The break needs to be within the timeframe of the availability", + /// Error identifier for when a submitted break ends before the availability + /// starts + breakEndBeforeStart( + "The break ends before the start of the break", + ), + + /// Error identifier for when the submitted duration of a break is longer than + /// the difference between the start and end time + breakSubmittedDurationTooLong( + "The submitted duration is longer than the difference between the start " + "and end time", + ), + + /// Error identifier for when the end of the day is before the start + endBeforeStart("The end of the day is before the start"), + + /// Error identifier for when one of the submitted breaks starts before the + /// availability + templateBreakBeforeStart( + "One of the submitted breaks starts before the start of the availability", + ), + + /// Error identifier for when one of the submitted breaks ends after the + /// availability + templateBreakAfterEnd( + "One of the submitted breaks ends after the end of the availability", ); const AvailabilityError(this.description); diff --git a/packages/flutter_availability/lib/src/ui/screens/availability_modification.dart b/packages/flutter_availability/lib/src/ui/screens/availability_modification.dart index 84c476f..9af134c 100644 --- a/packages/flutter_availability/lib/src/ui/screens/availability_modification.dart +++ b/packages/flutter_availability/lib/src/ui/screens/availability_modification.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:flutter_availability/flutter_availability.dart"; import "package:flutter_availability/src/service/availability_service.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"; @@ -8,7 +9,6 @@ import "package:flutter_availability/src/ui/widgets/availabillity_time_selection 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/util/scope.dart"; -import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_hooks/flutter_hooks.dart"; /// Screen for modifying the availabilities for a specific daterange @@ -78,18 +78,35 @@ class _AvailabilitiesModificationScreenState widget.onExit(); return; } - if (_availabilityViewModel.templateSelected) { - await service.applyTemplate( - template: _availabilityViewModel.templates.first, - range: widget.dateRange, - ); - } else { - await service.createAvailability( - availability: _availabilityViewModel.toModel(), - range: widget.dateRange, + + AvailabilityError? error; + try { + if (_availabilityViewModel.templateSelected) { + await service.applyTemplate( + template: _availabilityViewModel.templates.first, + range: widget.dateRange, + ); + } else { + await service.createAvailability( + availability: _availabilityViewModel.toModel(), + range: widget.dateRange, + ); + } + widget.onExit(); + } on AvailabilityEndBeforeStartException { + error = AvailabilityError.endBeforeStart; + } on BreakEndBeforeStartException { + error = AvailabilityError.breakEndBeforeStart; + } on BreakSubmittedDurationTooLongException { + error = AvailabilityError.breakSubmittedDurationTooLong; + } + + if (error != null && context.mounted) { + await options.errorDisplayBuilder( + context, + error, ); } - widget.onExit(); } Future onClickSave() async { diff --git a/packages/flutter_availability/lib/src/ui/screens/template_day_modification.dart b/packages/flutter_availability/lib/src/ui/screens/template_day_modification.dart index 91130b7..3839513 100644 --- a/packages/flutter_availability/lib/src/ui/screens/template_day_modification.dart +++ b/packages/flutter_availability/lib/src/ui/screens/template_day_modification.dart @@ -1,4 +1,5 @@ import "package:flutter/material.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/template_daydata_view_model.dart"; import "package:flutter_availability/src/ui/widgets/base_page.dart"; @@ -70,12 +71,32 @@ class _DayTemplateModificationScreenState Future onSavePressed() async { var template = _viewModel.toTemplate(); - if (widget.template == null) { - await service.createTemplate(template); - } else { - await service.updateTemplate(template); + AvailabilityError? error; + try { + if (widget.template == null) { + await service.createTemplate(template); + } else { + await service.updateTemplate(template); + } + widget.onExit(); + } on BreakEndBeforeStartException { + error = AvailabilityError.breakEndBeforeStart; + } on BreakSubmittedDurationTooLongException { + error = AvailabilityError.breakSubmittedDurationTooLong; + } on TemplateEndBeforeStartException { + error = AvailabilityError.endBeforeStart; + } on TemplateBreakBeforeStartException { + error = AvailabilityError.templateBreakBeforeStart; + } on TemplateBreakAfterEndException { + error = AvailabilityError.templateBreakAfterEnd; + } + + if (error != null && context.mounted) { + await options.errorDisplayBuilder( + context, + error, + ); } - widget.onExit(); } var canSave = _viewModel.canSave; diff --git a/packages/flutter_availability/lib/src/ui/widgets/defaults/default_error_display.dart b/packages/flutter_availability/lib/src/ui/widgets/defaults/default_error_display.dart index 0689244..839a8b7 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/defaults/default_error_display.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/defaults/default_error_display.dart @@ -23,11 +23,13 @@ class DefaultErrorDisplayDialog extends StatelessWidget { } @override - Widget build(BuildContext context) => Column( - children: [ - Text(_error.name), - const SizedBox(height: 20), - Text(_error.description), - ], + Widget build(BuildContext context) => Scaffold( + body: Column( + children: [ + Text(_error.name), + const SizedBox(height: 20), + Text(_error.description), + ], + ), ); } diff --git a/packages/flutter_availability_data_interface/lib/src/models/availability.dart b/packages/flutter_availability_data_interface/lib/src/models/availability.dart index 83e2dd7..0567905 100644 --- a/packages/flutter_availability_data_interface/lib/src/models/availability.dart +++ b/packages/flutter_availability_data_interface/lib/src/models/availability.dart @@ -1,6 +1,16 @@ // ignore_for_file: Generated using data class generator import "package:flutter_availability_data_interface/src/utils.dart"; +/// Exception thrown when the end is before the start +class BreakEndBeforeStartException implements Exception {} + +/// Exception thrown when the submitted duration is longer than the difference +/// between the start and end time +class BreakSubmittedDurationTooLongException implements Exception {} + +/// Exception thrown when the end is before the start +class AvailabilityEndBeforeStartException implements Exception {} + /// A model defining the data structure for an availability class AvailabilityModel { /// Creates a new availability @@ -81,6 +91,17 @@ class AvailabilityModel { } return true; } + + /// Verify the validity of this availability + void validate() { + if (startDate.compareTo(endDate) < 0) { + throw AvailabilityEndBeforeStartException(); + } + + for (var breakData in breaks) { + breakData.validate(); + } + } } /// A model defining the structure of a break within an [AvailabilityModel] @@ -177,4 +198,19 @@ class AvailabilityBreakModel { endTime.hour == other.endTime.hour && endTime.minute == other.endTime.minute && submittedDuration == other.submittedDuration; + + /// Verify the validity of this break + void validate() { + if (endTime.compareTo(startTime) < 0) { + throw BreakEndBeforeStartException(); + } + + if (submittedDuration != null) { + var calculatedDuration = endTime.difference(startTime); + + if (calculatedDuration < submittedDuration!) { + throw BreakSubmittedDurationTooLongException(); + } + } + } } diff --git a/packages/flutter_availability_data_interface/lib/src/models/templates.dart b/packages/flutter_availability_data_interface/lib/src/models/templates.dart index 2283b42..d8e0662 100644 --- a/packages/flutter_availability_data_interface/lib/src/models/templates.dart +++ b/packages/flutter_availability_data_interface/lib/src/models/templates.dart @@ -1,6 +1,17 @@ import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; import "package:flutter_availability_data_interface/src/models/availability.dart"; +/// Exception thrown when the end is before the start +class TemplateEndBeforeStartException implements Exception {} + +/// Exception thrown when the start of a break is before the start of the +/// availability +class TemplateBreakBeforeStartException implements Exception {} + +/// Exception thrown when the end of a break is after the end of the +/// availability +class TemplateBreakAfterEndException implements Exception {} + /// A limited set of different availability template types enum AvailabilityTemplateType { /// A template that applies to any day, regardless of when it is. @@ -103,6 +114,11 @@ class AvailabilityTemplateModel { /// Get the end time for the specified day in the week for this template DateTime? getEndTimeForDayOfWeek(WeekDay weekDay) => templateData.getEndTimeForDayOfWeek(weekDay); + + /// Verify the validity of this template + void validate() { + templateData.validate(); + } } /// Used as the key for defining week-based templates @@ -167,6 +183,9 @@ abstract interface class TemplateData { /// Get the end time for the specified day in the week for this template DateTime? getEndTimeForDayOfWeek(WeekDay weekDay); + + /// Verify the validity of the data in this template + void validate(); } /// A week based template data structure @@ -261,6 +280,13 @@ class WeekTemplateData implements TemplateData { /// Get the end time for the specified day in the week for this template @override DateTime? getEndTimeForDayOfWeek(WeekDay weekDay) => _data[weekDay]?.endTime; + + @override + void validate() { + for (var dayData in _data.entries) { + dayData.value.validate(); + } + } } /// A day based template data structure @@ -371,6 +397,25 @@ class DayTemplateData implements TemplateData { /// Get the end time for the specified day in the week for this template @override DateTime? getEndTimeForDayOfWeek(WeekDay weekDay) => endTime; + + @override + void validate() { + if (endTime.compareTo(startTime) < 0) { + throw TemplateEndBeforeStartException(); + } + + for (var breakData in breaks) { + breakData.validate(); + + if (breakData.startTime.compareTo(startTime) < 0) { + throw TemplateBreakBeforeStartException(); + } + + if (breakData.endTime.compareTo(endTime) > 0) { + throw TemplateBreakAfterEndException(); + } + } + } } List _getDatesBetween(DateTime startDate, DateTime endDate) {