diff --git a/packages/flutter_availability/lib/src/service/availability_service.dart b/packages/flutter_availability/lib/src/service/availability_service.dart index 4ae9ef1..c92c30c 100644 --- a/packages/flutter_availability/lib/src/service/availability_service.dart +++ b/packages/flutter_availability/lib/src/service/availability_service.dart @@ -22,8 +22,6 @@ class AvailabilityService { Future createAvailability({ required AvailabilityModel availability, required DateTimeRange range, - required TimeOfDay startTime, - required TimeOfDay endTime, }) async { // apply the startTime and endTime to the availability model var updatedAvailability = availability.copyWith( @@ -31,15 +29,15 @@ class AvailabilityService { range.start.year, range.start.month, range.start.day, - startTime.hour, - startTime.minute, + availability.startDate.hour, + availability.startDate.minute, ), endDate: DateTime( range.start.year, range.start.month, range.start.day, - endTime.hour, - endTime.minute, + availability.endDate.hour, + availability.endDate.minute, ), userId: userId, ); 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 9c2ef78..b46b295 100644 --- a/packages/flutter_availability/lib/src/ui/screens/availability_modification.dart +++ b/packages/flutter_availability/lib/src/ui/screens/availability_modification.dart @@ -1,7 +1,6 @@ -import "dart:collection"; - import "package:flutter/material.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"; import "package:flutter_availability/src/ui/widgets/availability_clear.dart"; import "package:flutter_availability/src/ui/widgets/availability_template_selection.dart"; @@ -49,19 +48,8 @@ class AvailabilitiesModificationScreen extends StatefulWidget { class _AvailabilitiesModificationScreenState extends State { - late AvailabilityModel _availability = - widget.initialAvailabilities.getAvailabilities().firstOrNull ?? - AvailabilityModel( - userId: "", - startDate: widget.dateRange.start, - endDate: widget.dateRange.end, - breaks: [], - ); - late List _selectedTemplates = - widget.initialAvailabilities.getUniqueTemplates(); - bool _clearAvailability = false; - TimeOfDay? _startTime; - TimeOfDay? _endTime; + late AvailabilityViewModel _availabilityViewModel = + AvailabilityViewModel.fromModel(widget.initialAvailabilities); @override Widget build(BuildContext context) { @@ -74,7 +62,7 @@ class _AvailabilitiesModificationScreenState // TODO(freek): the selected period might be longer than 1 month //so we need to get all the availabilites through a stream Future onSave() async { - if (_clearAvailability) { + if (_availabilityViewModel.clearAvailability) { await service.clearAvailabilities( widget.initialAvailabilities.getAvailabilities(), ); @@ -88,10 +76,8 @@ class _AvailabilitiesModificationScreenState } await service.createAvailability( - availability: _availability, + availability: _availabilityViewModel.toModel(), range: widget.dateRange, - startTime: _startTime!, - endTime: _endTime!, ); widget.onExit(); } @@ -110,8 +96,7 @@ class _AvailabilitiesModificationScreenState } } - var canSave = - _clearAvailability || (_startTime != null && _endTime != null); + var canSave = _availabilityViewModel.canSave; var saveButton = options.primaryButtonBuilder( context, canSave ? onClickSave : null, @@ -121,7 +106,9 @@ class _AvailabilitiesModificationScreenState // ignore: avoid_positional_boolean_parameters void onClearSection(bool isChecked) { setState(() { - _clearAvailability = isChecked; + _availabilityViewModel = _availabilityViewModel.copyWith( + clearAvailability: isChecked, + ); }); } @@ -129,54 +116,56 @@ class _AvailabilitiesModificationScreenState var template = await widget.onTemplateSelection(); if (template != null) { setState(() { - _selectedTemplates = [template]; - _availability = _availability.copyWith( - templateId: template.id, - ); + _availabilityViewModel = + _availabilityViewModel.copyWith(templates: [template]); }); } } void onTemplatesRemoved() { setState(() { - _selectedTemplates = []; + _availabilityViewModel = _availabilityViewModel.copyWith( + templates: [], + ); }); } void onStartChanged(TimeOfDay start) { setState(() { - _startTime = start; + _availabilityViewModel = _availabilityViewModel.copyWith( + startTime: start, + ); }); } void onEndChanged(TimeOfDay end) { setState(() { - _endTime = end; + _availabilityViewModel = _availabilityViewModel.copyWith( + endTime: end, + ); }); } void onBreaksChanged(List breaks) { setState(() { - _availability = _availability.copyWith( - breaks: breaks.map((b) => b.toBreak()).toList(), + _availabilityViewModel = _availabilityViewModel.copyWith( + breaks: breaks, ); }); } return _AvailabilitiesModificationScreenLayout( dateRange: widget.dateRange, - clearAvailability: _clearAvailability, + clearAvailability: _availabilityViewModel.clearAvailability, onClearSection: onClearSection, - selectedTemplates: _selectedTemplates, + selectedTemplates: _availabilityViewModel.templates, onTemplateSelected: onTemplateSelected, onTemplatesRemoved: onTemplatesRemoved, - startTime: _startTime, - endTime: _endTime, + startTime: _availabilityViewModel.startTime, + endTime: _availabilityViewModel.endTime, onStartChanged: onStartChanged, onEndChanged: onEndChanged, - breaks: _availability.breaks - .map(BreakViewModel.fromAvailabilityBreakModel) - .toList(), + breaks: _availabilityViewModel.breaks, onBreaksChanged: onBreaksChanged, sidePadding: spacing.sidePadding, bottomButtonPadding: spacing.bottomButtonPadding, diff --git a/packages/flutter_availability/lib/src/ui/screens/availability_overview.dart b/packages/flutter_availability/lib/src/ui/screens/availability_overview.dart index f714a08..2cd7e6a 100644 --- a/packages/flutter_availability/lib/src/ui/screens/availability_overview.dart +++ b/packages/flutter_availability/lib/src/ui/screens/availability_overview.dart @@ -51,21 +51,16 @@ class _AvailabilityOverviewState extends State { var availabilitySnapshot = useStream(availabilityStream); - var selectedAvailabilities = []; - if (_selectedRange != null) { - var availabilities = availabilitySnapshot.data - ?.where( - (a) => - !a.availabilityModel.startDate - .isBefore(_selectedRange!.start) && - !a.availabilityModel.endDate.isAfter(_selectedRange!.end), - ) - .toList(); - - if (availabilities != null) { - selectedAvailabilities = availabilities; - } - } + var selectedAvailabilities = [ + if (_selectedRange != null) ...[ + ...?availabilitySnapshot.data?.where( + (item) => item.availabilityModel.isInRange( + _selectedRange!.start, + _selectedRange!.end, + ), + ), + ], + ]; var availabilitiesAreSelected = selectedAvailabilities.isNotEmpty; diff --git a/packages/flutter_availability/lib/src/ui/view_models/availability_view_model.dart b/packages/flutter_availability/lib/src/ui/view_models/availability_view_model.dart new file mode 100644 index 0000000..8915de7 --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/view_models/availability_view_model.dart @@ -0,0 +1,102 @@ +import "package:flutter/material.dart"; +import "package:flutter_availability/src/service/availability_service.dart"; +import "package:flutter_availability/src/ui/view_models/break_view_model.dart"; +import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; + +/// +class AvailabilityViewModel { + /// + const AvailabilityViewModel({ + this.templates = const [], + this.breaks = const [], + this.id, + this.userId, + this.startTime, + this.endTime, + this.clearAvailability = false, + }); + + /// + factory AvailabilityViewModel.fromModel( + List models, + ) { + var model = models.firstOrNull?.availabilityModel; + + var startTime = + model != null ? TimeOfDay.fromDateTime(model.startDate) : null; + var endTime = model != null ? TimeOfDay.fromDateTime(model.endDate) : null; + return AvailabilityViewModel( + templates: models.getUniqueTemplates(), + breaks: model?.breaks + .map(BreakViewModel.fromAvailabilityBreakModel) + .toList() ?? + [], + id: model?.id, + userId: model?.userId, + startTime: startTime, + endTime: endTime, + ); + } + + /// + final List templates; + + /// + final bool clearAvailability; + + /// + final TimeOfDay? startTime; + + /// + final TimeOfDay? endTime; + + /// + final String? id; + + /// + final String? userId; + + /// + final List breaks; + + /// Whether the availability is valid + bool get isValid => breaks.every((element) => element.isValid); + + /// Whether the save button should be enabled + bool get canSave => + clearAvailability || (startTime != null && endTime != null); + + /// create a AvailabilityModel from the current AvailabilityViewModel + AvailabilityModel toModel() { + var startDate = DateTime.now(); + var endDate = DateTime.now(); + return AvailabilityModel( + id: id, + userId: userId!, + startDate: startDate, + endDate: endDate, + breaks: breaks.map((e) => e.toBreak()).toList(), + ); + } + + /// Copies the current properties into a new instance + /// of [AvailabilityViewModel], + AvailabilityViewModel copyWith({ + List? templates, + TimeOfDay? startTime, + TimeOfDay? endTime, + String? id, + String? userId, + List? breaks, + bool? clearAvailability, + }) => + AvailabilityViewModel( + templates: templates ?? this.templates, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + id: id ?? this.id, + userId: userId ?? this.userId, + breaks: breaks ?? this.breaks, + clearAvailability: clearAvailability ?? this.clearAvailability, + ); +} diff --git a/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart b/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart index ac38649..25d7a9e 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart @@ -289,12 +289,7 @@ List _generateCalendarDays( hasAvailability: false, ), ); - var dayIsSelected = selectedRange != null && - !day.isBefore(selectedRange.start) && - !day.isAfter(selectedRange.end); - specialDay = specialDay.copyWith( - isSelected: dayIsSelected, - ); + specialDay = checkIfDayIsSelected(selectedRange, day, specialDay); calendarDays.add(specialDay); } @@ -303,3 +298,21 @@ List _generateCalendarDays( return calendarDays; } + +/// Checks if the day is in the selected range +/// Only the date part of the selected range is used +CalendarDay checkIfDayIsSelected( + DateTimeRange? selectedRange, + DateTime day, + CalendarDay specialDay, +) { + var dayIsSelected = false; + if (selectedRange != null) { + var startDate = DateUtils.dateOnly(selectedRange.start); + var endDate = DateUtils.dateOnly(selectedRange.end); + dayIsSelected = !day.isBefore(startDate) && !day.isAfter(endDate); + } + return specialDay.copyWith( + isSelected: dayIsSelected, + ); +} 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 0641b78..2899409 100644 --- a/packages/flutter_availability_data_interface/lib/src/models/availability.dart +++ b/packages/flutter_availability_data_interface/lib/src/models/availability.dart @@ -52,6 +52,28 @@ class AvailabilityModel { endDate: endDate ?? this.endDate, breaks: breaks ?? this.breaks, ); + + /// returns true if the date of the availability overlaps with the given range + /// This disregards the time of the date + bool isInRange(DateTime start, DateTime end) { + var startDate = DateTime(start.year, start.month, start.day); + var endDate = DateTime(end.year, end.month, end.day); + var availabilityStartDate = DateTime( + this.startDate.year, + this.startDate.month, + this.startDate.day, + ); + var availabilityEndDate = DateTime( + this.endDate.year, + this.endDate.month, + this.endDate.day, + ); + + return (startDate.isBefore(availabilityEndDate) || + startDate.isAtSameMomentAs(availabilityEndDate)) && + (endDate.isAfter(availabilityStartDate) || + endDate.isAtSameMomentAs(availabilityStartDate)); + } } /// A model defining the structure of a break within an [AvailabilityModel]