fix: compare only dates of availabilities for selected range

This commit is contained in:
Freek van de Ven 2024-07-19 14:35:42 +02:00 committed by FlutterJoey
parent 5931b4c29a
commit e1dd2a3520
6 changed files with 184 additions and 65 deletions

View file

@ -22,8 +22,6 @@ class AvailabilityService {
Future<void> 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,
);

View file

@ -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<AvailabilitiesModificationScreen> {
late AvailabilityModel _availability =
widget.initialAvailabilities.getAvailabilities().firstOrNull ??
AvailabilityModel(
userId: "",
startDate: widget.dateRange.start,
endDate: widget.dateRange.end,
breaks: [],
);
late List<AvailabilityTemplateModel> _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<void> 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<BreakViewModel> 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,

View file

@ -51,21 +51,16 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
var availabilitySnapshot = useStream(availabilityStream);
var selectedAvailabilities = <AvailabilityWithTemplate>[];
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;

View file

@ -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<AvailabilityWithTemplate> 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<AvailabilityTemplateModel> templates;
///
final bool clearAvailability;
///
final TimeOfDay? startTime;
///
final TimeOfDay? endTime;
///
final String? id;
///
final String? userId;
///
final List<BreakViewModel> 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<AvailabilityTemplateModel>? templates,
TimeOfDay? startTime,
TimeOfDay? endTime,
String? id,
String? userId,
List<BreakViewModel>? 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,
);
}

View file

@ -289,12 +289,7 @@ List<CalendarDay> _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<CalendarDay> _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,
);
}

View file

@ -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]