mirror of
https://github.com/Iconica-Development/flutter_availability.git
synced 2025-05-19 05:03:44 +02:00
feat: add method for applying a template in the availability viewmodel
This code determines if a selected range of availabilities has the same start and end times and looks at the breaks.
This commit is contained in:
parent
e1dd2a3520
commit
53fb9a2e2c
6 changed files with 219 additions and 47 deletions
|
@ -49,7 +49,10 @@ class AvailabilitiesModificationScreen extends StatefulWidget {
|
|||
class _AvailabilitiesModificationScreenState
|
||||
extends State<AvailabilitiesModificationScreen> {
|
||||
late AvailabilityViewModel _availabilityViewModel =
|
||||
AvailabilityViewModel.fromModel(widget.initialAvailabilities);
|
||||
AvailabilityViewModel.fromModel(
|
||||
widget.initialAvailabilities,
|
||||
widget.dateRange,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -117,16 +120,14 @@ class _AvailabilitiesModificationScreenState
|
|||
if (template != null) {
|
||||
setState(() {
|
||||
_availabilityViewModel =
|
||||
_availabilityViewModel.copyWith(templates: [template]);
|
||||
_availabilityViewModel.applyTemplate(template);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onTemplatesRemoved() {
|
||||
setState(() {
|
||||
_availabilityViewModel = _availabilityViewModel.copyWith(
|
||||
templates: [],
|
||||
);
|
||||
_availabilityViewModel = _availabilityViewModel.removeTemplates();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,62 +1,115 @@
|
|||
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/src/util/utils.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
|
||||
///
|
||||
/// The view model for the availability modification screen
|
||||
/// This view model is used to manage the state of the availabilities while
|
||||
/// editing them or creating new ones
|
||||
class AvailabilityViewModel {
|
||||
///
|
||||
const AvailabilityViewModel({
|
||||
required this.selectedRange,
|
||||
this.templates = const [],
|
||||
this.breaks = const [],
|
||||
this.id,
|
||||
this.ids = const [],
|
||||
this.userId,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.clearAvailability = false,
|
||||
this.conflictingPauses = false,
|
||||
this.conflictingTime = false,
|
||||
this.templateSelected = false,
|
||||
});
|
||||
|
||||
///
|
||||
/// This constructor creates a [AvailabilityViewModel] from a list of
|
||||
/// [AvailabilityWithTemplate] models
|
||||
/// It will check if the models have the same start and end time, breaks and
|
||||
/// if the entire selected range is covered by the models
|
||||
factory AvailabilityViewModel.fromModel(
|
||||
List<AvailabilityWithTemplate> models,
|
||||
DateTimeRange range,
|
||||
) {
|
||||
var model = models.firstOrNull?.availabilityModel;
|
||||
var coveredByAvailabilities = models.length == (range.duration.inDays + 1);
|
||||
var userId = models.firstOrNull?.availabilityModel.userId;
|
||||
// if there is no availability there is no conflicting time or pauses
|
||||
var conflictingPauses = models.isNotEmpty;
|
||||
var conflictingTime = models.isNotEmpty;
|
||||
TimeOfDay? startTime;
|
||||
TimeOfDay? endTime;
|
||||
var breaks = <BreakViewModel>[];
|
||||
|
||||
if (coveredByAvailabilities) {
|
||||
var availabilities = models.getAvailabilities();
|
||||
if (_availabilityTimesAreEqual(availabilities)) {
|
||||
conflictingTime = false;
|
||||
startTime = TimeOfDay.fromDateTime(availabilities.first.startDate);
|
||||
endTime = TimeOfDay.fromDateTime(availabilities.first.endDate);
|
||||
}
|
||||
if (_availabilityBreaksAreEqual(availabilities)) {
|
||||
conflictingPauses = false;
|
||||
breaks = availabilities.first.breaks
|
||||
.map(BreakViewModel.fromAvailabilityBreakModel)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
breaks: breaks,
|
||||
ids: models.map((e) => e.availabilityModel.id!).toList(),
|
||||
userId: userId,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
conflictingPauses: conflictingPauses,
|
||||
conflictingTime: conflictingTime,
|
||||
selectedRange: range,
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// The templates are selected for the availability range
|
||||
/// There can be multiple templates used in a selected range but only one
|
||||
/// template can be applied at a time
|
||||
final List<AvailabilityTemplateModel> templates;
|
||||
|
||||
///
|
||||
/// Whether the selected availability range should be cleared
|
||||
final bool clearAvailability;
|
||||
|
||||
///
|
||||
/// Whether the initial selected range has different pauses.
|
||||
/// If true an indication will be shown to the user that there are different
|
||||
/// pauses and the pause section will be empty. If the user then selects a new
|
||||
/// pause all the availabilities will be updated with the new pause.
|
||||
final bool conflictingPauses;
|
||||
|
||||
/// Whether the initial selected range has different times
|
||||
/// If true an indication will be shown to the user that there are different
|
||||
/// times and the time section will be empty. If the user then selects a new
|
||||
/// time all the availabilities will be updated with the new time.
|
||||
final bool conflictingTime;
|
||||
|
||||
/// Whether a new template has been selected for the availability
|
||||
/// If true a template will be applied, if false the availability will be
|
||||
/// changed but the template will not be applied again
|
||||
final bool templateSelected;
|
||||
|
||||
/// The selected range for the availabilities
|
||||
/// This is used for applying templates
|
||||
final DateTimeRange selectedRange;
|
||||
|
||||
/// The start time in the time selection while editing availabilities
|
||||
final TimeOfDay? startTime;
|
||||
|
||||
///
|
||||
/// The end time in the time selection while editing availabilities
|
||||
final TimeOfDay? endTime;
|
||||
|
||||
///
|
||||
final String? id;
|
||||
/// The ids of the availabilities
|
||||
final List<String>? ids;
|
||||
|
||||
///
|
||||
/// The user id for which the availabilities are managed
|
||||
final String? userId;
|
||||
|
||||
///
|
||||
/// The configured breaks while editing availabilities
|
||||
final List<BreakViewModel> breaks;
|
||||
|
||||
/// Whether the availability is valid
|
||||
|
@ -66,12 +119,63 @@ class AvailabilityViewModel {
|
|||
bool get canSave =>
|
||||
clearAvailability || (startTime != null && endTime != null);
|
||||
|
||||
///
|
||||
AvailabilityViewModel applyTemplate(AvailabilityTemplateModel template) {
|
||||
TimeOfDay? startTime;
|
||||
TimeOfDay? endTime;
|
||||
var conflictingPauses = true;
|
||||
var conflictingTime = true;
|
||||
var breaks = <BreakViewModel>[];
|
||||
var appliedAvailabilities =
|
||||
template.apply(selectedRange.start, selectedRange.end);
|
||||
|
||||
if (_availabilityTimesAreEqual(appliedAvailabilities)) {
|
||||
conflictingTime = false;
|
||||
startTime = TimeOfDay.fromDateTime(appliedAvailabilities.first.startDate);
|
||||
endTime = TimeOfDay.fromDateTime(appliedAvailabilities.first.endDate);
|
||||
}
|
||||
if (_availabilityBreaksAreEqual(appliedAvailabilities)) {
|
||||
conflictingPauses = false;
|
||||
breaks = appliedAvailabilities.first.breaks
|
||||
.map(BreakViewModel.fromAvailabilityBreakModel)
|
||||
.toList();
|
||||
}
|
||||
|
||||
return copyWith(
|
||||
templates: [template],
|
||||
breaks: breaks,
|
||||
conflictingPauses: conflictingPauses,
|
||||
conflictingTime: conflictingTime,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
templateSelected: true,
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
AvailabilityViewModel removeTemplates() => copyWith(
|
||||
templates: [],
|
||||
templateSelected: false,
|
||||
);
|
||||
|
||||
/// create a AvailabilityModel from the current AvailabilityViewModel
|
||||
AvailabilityModel toModel() {
|
||||
var startDate = DateTime.now();
|
||||
var endDate = DateTime.now();
|
||||
var startDate = DateTime(
|
||||
selectedRange.start.year,
|
||||
selectedRange.start.month,
|
||||
selectedRange.start.day,
|
||||
startTime!.hour,
|
||||
startTime!.minute,
|
||||
);
|
||||
var endDate = DateTime(
|
||||
selectedRange.start.year,
|
||||
selectedRange.start.month,
|
||||
selectedRange.start.day,
|
||||
endTime!.hour,
|
||||
endTime!.minute,
|
||||
);
|
||||
return AvailabilityModel(
|
||||
id: id,
|
||||
id: ids?.firstOrNull,
|
||||
userId: userId!,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
|
@ -85,18 +189,51 @@ class AvailabilityViewModel {
|
|||
List<AvailabilityTemplateModel>? templates,
|
||||
TimeOfDay? startTime,
|
||||
TimeOfDay? endTime,
|
||||
String? id,
|
||||
List<String>? ids,
|
||||
String? userId,
|
||||
List<BreakViewModel>? breaks,
|
||||
bool? clearAvailability,
|
||||
bool? conflictingPauses,
|
||||
bool? conflictingTime,
|
||||
bool? templateSelected,
|
||||
DateTimeRange? selectedRange,
|
||||
}) =>
|
||||
AvailabilityViewModel(
|
||||
templates: templates ?? this.templates,
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
id: id ?? this.id,
|
||||
ids: ids ?? this.ids,
|
||||
userId: userId ?? this.userId,
|
||||
breaks: breaks ?? this.breaks,
|
||||
clearAvailability: clearAvailability ?? this.clearAvailability,
|
||||
conflictingPauses: conflictingPauses ?? this.conflictingPauses,
|
||||
conflictingTime: conflictingTime ?? this.conflictingTime,
|
||||
templateSelected: templateSelected ?? this.templateSelected,
|
||||
selectedRange: selectedRange ?? this.selectedRange,
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if the availability times are equal
|
||||
bool _availabilityTimesAreEqual(List<AvailabilityModel> availabilityModels) {
|
||||
var firstModel = availabilityModels.firstOrNull;
|
||||
if (firstModel == null) {
|
||||
return false;
|
||||
}
|
||||
var startDate = firstModel.startDate;
|
||||
var endDate = firstModel.endDate;
|
||||
return availabilityModels.every(
|
||||
(model) =>
|
||||
isAtSameTime(startDate, model.startDate) &&
|
||||
isAtSameTime(endDate, model.endDate),
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if the availability breaks are equal
|
||||
bool _availabilityBreaksAreEqual(List<AvailabilityModel> availabilityModels) {
|
||||
var firstModel = availabilityModels.firstOrNull;
|
||||
if (firstModel == null) {
|
||||
return false;
|
||||
}
|
||||
var breaks = firstModel.breaks;
|
||||
return availabilityModels.every((model) => model.breaksEqual(breaks));
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ List<CalendarDay> _mapAvailabilitiesToCalendarDays(
|
|||
availability.template!,
|
||||
);
|
||||
return CalendarDay(
|
||||
date: availability.availabilityModel.startDate,
|
||||
date: DateUtils.dateOnly(availability.availabilityModel.startDate),
|
||||
color: availability.template != null
|
||||
? Color(availability.template!.color)
|
||||
: null,
|
||||
|
|
|
@ -250,8 +250,16 @@ List<CalendarDay> _generateCalendarDays(
|
|||
}) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
var day = isNextMonth
|
||||
? DateTime(startDay.year, startDay.month, startDay.day + i + 1)
|
||||
: DateTime(startDay.year, startDay.month, startDay.day - count + i);
|
||||
? DateTime(
|
||||
startDay.year,
|
||||
startDay.month,
|
||||
startDay.day + i + 1,
|
||||
)
|
||||
: DateTime(
|
||||
startDay.year,
|
||||
startDay.month,
|
||||
startDay.day - count + i,
|
||||
);
|
||||
var isSelected = selectedRange != null &&
|
||||
!day.isBefore(selectedRange.start) &&
|
||||
!day.isAfter(selectedRange.end);
|
||||
|
@ -275,7 +283,7 @@ List<CalendarDay> _generateCalendarDays(
|
|||
|
||||
// Add days of the current month
|
||||
for (var i = 1; i <= daysInMonth; i++) {
|
||||
var day = DateTime(month.year, month.month, i);
|
||||
var day = DateTime(month.year, month.month, i, 0, 0);
|
||||
var specialDay = days.firstWhere(
|
||||
(d) =>
|
||||
d.date.day == i &&
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// ignore_for_file: Generated using data class generator
|
||||
import "package:flutter_availability_data_interface/src/utils.dart";
|
||||
|
||||
/// A model defining the data structure for an availability
|
||||
class AvailabilityModel {
|
||||
/// Creates a new availability
|
||||
|
@ -56,24 +58,29 @@ class AvailabilityModel {
|
|||
/// 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,
|
||||
);
|
||||
var startDate = start.date;
|
||||
var endDate = end.date;
|
||||
var availabilityStartDate = this.startDate.date;
|
||||
var availabilityEndDate = this.endDate.date;
|
||||
|
||||
return (startDate.isBefore(availabilityEndDate) ||
|
||||
startDate.isAtSameMomentAs(availabilityEndDate)) &&
|
||||
(endDate.isAfter(availabilityStartDate) ||
|
||||
endDate.isAtSameMomentAs(availabilityStartDate));
|
||||
}
|
||||
|
||||
/// Compares this AvailabilityModel breaks to another AvailabilityModel breaks
|
||||
bool breaksEqual(List<AvailabilityBreakModel> otherBreaks) {
|
||||
if (breaks.length != otherBreaks.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < breaks.length; i++) {
|
||||
if (!breaks[i].equals(otherBreaks[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// A model defining the structure of a break within an [AvailabilityModel]
|
||||
|
@ -160,4 +167,14 @@ class AvailabilityBreakModel {
|
|||
"endTime": endTime.millisecondsSinceEpoch,
|
||||
"duration": submittedDuration?.inMinutes,
|
||||
};
|
||||
|
||||
/// Compares this AvailabilityBreakModel to another AvailabilityBreakModel
|
||||
/// This only compares the start time, end time and submitted duration,
|
||||
/// it ignores the date of the DateTime objects
|
||||
bool equals(AvailabilityBreakModel other) =>
|
||||
startTime.hour == other.startTime.hour &&
|
||||
startTime.minute == other.startTime.minute &&
|
||||
endTime.hour == other.endTime.hour &&
|
||||
endTime.minute == other.endTime.minute &&
|
||||
submittedDuration == other.submittedDuration;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/// Utility datetime functions for the availability data interface
|
||||
extension DateUtils on DateTime {
|
||||
/// Gets the date without the time
|
||||
DateTime get date => DateTime(year, month, day);
|
||||
|
||||
/// Returns true if the time of the date matches the time of the other date
|
||||
bool timeMatches(DateTime other) =>
|
||||
other.hour == hour && other.minute == minute;
|
||||
}
|
Loading…
Reference in a new issue