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
|
class _AvailabilitiesModificationScreenState
|
||||||
extends State<AvailabilitiesModificationScreen> {
|
extends State<AvailabilitiesModificationScreen> {
|
||||||
late AvailabilityViewModel _availabilityViewModel =
|
late AvailabilityViewModel _availabilityViewModel =
|
||||||
AvailabilityViewModel.fromModel(widget.initialAvailabilities);
|
AvailabilityViewModel.fromModel(
|
||||||
|
widget.initialAvailabilities,
|
||||||
|
widget.dateRange,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -117,16 +120,14 @@ class _AvailabilitiesModificationScreenState
|
||||||
if (template != null) {
|
if (template != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_availabilityViewModel =
|
_availabilityViewModel =
|
||||||
_availabilityViewModel.copyWith(templates: [template]);
|
_availabilityViewModel.applyTemplate(template);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTemplatesRemoved() {
|
void onTemplatesRemoved() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_availabilityViewModel = _availabilityViewModel.copyWith(
|
_availabilityViewModel = _availabilityViewModel.removeTemplates();
|
||||||
templates: [],
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,62 +1,115 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_availability/src/service/availability_service.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/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";
|
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 {
|
class AvailabilityViewModel {
|
||||||
///
|
///
|
||||||
const AvailabilityViewModel({
|
const AvailabilityViewModel({
|
||||||
|
required this.selectedRange,
|
||||||
this.templates = const [],
|
this.templates = const [],
|
||||||
this.breaks = const [],
|
this.breaks = const [],
|
||||||
this.id,
|
this.ids = const [],
|
||||||
this.userId,
|
this.userId,
|
||||||
this.startTime,
|
this.startTime,
|
||||||
this.endTime,
|
this.endTime,
|
||||||
this.clearAvailability = false,
|
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(
|
factory AvailabilityViewModel.fromModel(
|
||||||
List<AvailabilityWithTemplate> models,
|
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(
|
return AvailabilityViewModel(
|
||||||
templates: models.getUniqueTemplates(),
|
templates: models.getUniqueTemplates(),
|
||||||
breaks: model?.breaks
|
breaks: breaks,
|
||||||
.map(BreakViewModel.fromAvailabilityBreakModel)
|
ids: models.map((e) => e.availabilityModel.id!).toList(),
|
||||||
.toList() ??
|
userId: userId,
|
||||||
[],
|
|
||||||
id: model?.id,
|
|
||||||
userId: model?.userId,
|
|
||||||
startTime: startTime,
|
startTime: startTime,
|
||||||
endTime: endTime,
|
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;
|
final List<AvailabilityTemplateModel> templates;
|
||||||
|
|
||||||
///
|
/// Whether the selected availability range should be cleared
|
||||||
final bool clearAvailability;
|
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;
|
final TimeOfDay? startTime;
|
||||||
|
|
||||||
///
|
/// The end time in the time selection while editing availabilities
|
||||||
final TimeOfDay? endTime;
|
final TimeOfDay? endTime;
|
||||||
|
|
||||||
///
|
/// The ids of the availabilities
|
||||||
final String? id;
|
final List<String>? ids;
|
||||||
|
|
||||||
///
|
/// The user id for which the availabilities are managed
|
||||||
final String? userId;
|
final String? userId;
|
||||||
|
|
||||||
///
|
/// The configured breaks while editing availabilities
|
||||||
final List<BreakViewModel> breaks;
|
final List<BreakViewModel> breaks;
|
||||||
|
|
||||||
/// Whether the availability is valid
|
/// Whether the availability is valid
|
||||||
|
@ -66,12 +119,63 @@ class AvailabilityViewModel {
|
||||||
bool get canSave =>
|
bool get canSave =>
|
||||||
clearAvailability || (startTime != null && endTime != null);
|
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
|
/// create a AvailabilityModel from the current AvailabilityViewModel
|
||||||
AvailabilityModel toModel() {
|
AvailabilityModel toModel() {
|
||||||
var startDate = DateTime.now();
|
var startDate = DateTime(
|
||||||
var endDate = DateTime.now();
|
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(
|
return AvailabilityModel(
|
||||||
id: id,
|
id: ids?.firstOrNull,
|
||||||
userId: userId!,
|
userId: userId!,
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
|
@ -85,18 +189,51 @@ class AvailabilityViewModel {
|
||||||
List<AvailabilityTemplateModel>? templates,
|
List<AvailabilityTemplateModel>? templates,
|
||||||
TimeOfDay? startTime,
|
TimeOfDay? startTime,
|
||||||
TimeOfDay? endTime,
|
TimeOfDay? endTime,
|
||||||
String? id,
|
List<String>? ids,
|
||||||
String? userId,
|
String? userId,
|
||||||
List<BreakViewModel>? breaks,
|
List<BreakViewModel>? breaks,
|
||||||
bool? clearAvailability,
|
bool? clearAvailability,
|
||||||
|
bool? conflictingPauses,
|
||||||
|
bool? conflictingTime,
|
||||||
|
bool? templateSelected,
|
||||||
|
DateTimeRange? selectedRange,
|
||||||
}) =>
|
}) =>
|
||||||
AvailabilityViewModel(
|
AvailabilityViewModel(
|
||||||
templates: templates ?? this.templates,
|
templates: templates ?? this.templates,
|
||||||
startTime: startTime ?? this.startTime,
|
startTime: startTime ?? this.startTime,
|
||||||
endTime: endTime ?? this.endTime,
|
endTime: endTime ?? this.endTime,
|
||||||
id: id ?? this.id,
|
ids: ids ?? this.ids,
|
||||||
userId: userId ?? this.userId,
|
userId: userId ?? this.userId,
|
||||||
breaks: breaks ?? this.breaks,
|
breaks: breaks ?? this.breaks,
|
||||||
clearAvailability: clearAvailability ?? this.clearAvailability,
|
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!,
|
availability.template!,
|
||||||
);
|
);
|
||||||
return CalendarDay(
|
return CalendarDay(
|
||||||
date: availability.availabilityModel.startDate,
|
date: DateUtils.dateOnly(availability.availabilityModel.startDate),
|
||||||
color: availability.template != null
|
color: availability.template != null
|
||||||
? Color(availability.template!.color)
|
? Color(availability.template!.color)
|
||||||
: null,
|
: null,
|
||||||
|
|
|
@ -250,8 +250,16 @@ List<CalendarDay> _generateCalendarDays(
|
||||||
}) {
|
}) {
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
var day = isNextMonth
|
var day = isNextMonth
|
||||||
? DateTime(startDay.year, startDay.month, startDay.day + i + 1)
|
? DateTime(
|
||||||
: DateTime(startDay.year, startDay.month, startDay.day - count + i);
|
startDay.year,
|
||||||
|
startDay.month,
|
||||||
|
startDay.day + i + 1,
|
||||||
|
)
|
||||||
|
: DateTime(
|
||||||
|
startDay.year,
|
||||||
|
startDay.month,
|
||||||
|
startDay.day - count + i,
|
||||||
|
);
|
||||||
var isSelected = selectedRange != null &&
|
var isSelected = selectedRange != null &&
|
||||||
!day.isBefore(selectedRange.start) &&
|
!day.isBefore(selectedRange.start) &&
|
||||||
!day.isAfter(selectedRange.end);
|
!day.isAfter(selectedRange.end);
|
||||||
|
@ -275,7 +283,7 @@ List<CalendarDay> _generateCalendarDays(
|
||||||
|
|
||||||
// Add days of the current month
|
// Add days of the current month
|
||||||
for (var i = 1; i <= daysInMonth; i++) {
|
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(
|
var specialDay = days.firstWhere(
|
||||||
(d) =>
|
(d) =>
|
||||||
d.date.day == i &&
|
d.date.day == i &&
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// ignore_for_file: Generated using data class generator
|
// 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
|
/// A model defining the data structure for an availability
|
||||||
class AvailabilityModel {
|
class AvailabilityModel {
|
||||||
/// Creates a new availability
|
/// Creates a new availability
|
||||||
|
@ -56,24 +58,29 @@ class AvailabilityModel {
|
||||||
/// returns true if the date of the availability overlaps with the given range
|
/// returns true if the date of the availability overlaps with the given range
|
||||||
/// This disregards the time of the date
|
/// This disregards the time of the date
|
||||||
bool isInRange(DateTime start, DateTime end) {
|
bool isInRange(DateTime start, DateTime end) {
|
||||||
var startDate = DateTime(start.year, start.month, start.day);
|
var startDate = start.date;
|
||||||
var endDate = DateTime(end.year, end.month, end.day);
|
var endDate = end.date;
|
||||||
var availabilityStartDate = DateTime(
|
var availabilityStartDate = this.startDate.date;
|
||||||
this.startDate.year,
|
var availabilityEndDate = this.endDate.date;
|
||||||
this.startDate.month,
|
|
||||||
this.startDate.day,
|
|
||||||
);
|
|
||||||
var availabilityEndDate = DateTime(
|
|
||||||
this.endDate.year,
|
|
||||||
this.endDate.month,
|
|
||||||
this.endDate.day,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (startDate.isBefore(availabilityEndDate) ||
|
return (startDate.isBefore(availabilityEndDate) ||
|
||||||
startDate.isAtSameMomentAs(availabilityEndDate)) &&
|
startDate.isAtSameMomentAs(availabilityEndDate)) &&
|
||||||
(endDate.isAfter(availabilityStartDate) ||
|
(endDate.isAfter(availabilityStartDate) ||
|
||||||
endDate.isAtSameMomentAs(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]
|
/// A model defining the structure of a break within an [AvailabilityModel]
|
||||||
|
@ -160,4 +167,14 @@ class AvailabilityBreakModel {
|
||||||
"endTime": endTime.millisecondsSinceEpoch,
|
"endTime": endTime.millisecondsSinceEpoch,
|
||||||
"duration": submittedDuration?.inMinutes,
|
"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