feat: validate availabilities and templates locally before submitting

This commit is contained in:
Bart Ribbers 2024-07-25 17:06:00 +02:00
parent c20a1a603c
commit ec47ed4696
7 changed files with 176 additions and 25 deletions

View file

@ -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<void> updateTemplate(AvailabilityTemplateModel template) async {
template.validate();
await dataInterface.updateTemplateForUser(
userId,
template.id!,

View file

@ -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);

View file

@ -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,6 +78,9 @@ class _AvailabilitiesModificationScreenState
widget.onExit();
return;
}
AvailabilityError? error;
try {
if (_availabilityViewModel.templateSelected) {
await service.applyTemplate(
template: _availabilityViewModel.templates.first,
@ -90,6 +93,20 @@ class _AvailabilitiesModificationScreenState
);
}
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,
);
}
}
Future<void> onClickSave() async {

View file

@ -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<void> onSavePressed() async {
var template = _viewModel.toTemplate();
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,
);
}
}
var canSave = _viewModel.canSave;

View file

@ -23,11 +23,13 @@ class DefaultErrorDisplayDialog extends StatelessWidget {
}
@override
Widget build(BuildContext context) => Column(
Widget build(BuildContext context) => Scaffold(
body: Column(
children: [
Text(_error.name),
const SizedBox(height: 20),
Text(_error.description),
],
),
);
}

View file

@ -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();
}
}
}
}

View file

@ -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<DateTime> _getDatesBetween(DateTime startDate, DateTime endDate) {