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({ Future<void> createAvailability({
required AvailabilityModel availability, required AvailabilityModel availability,
required DateTimeRange range, required DateTimeRange range,
required TimeOfDay startTime,
required TimeOfDay endTime,
}) async { }) async {
// apply the startTime and endTime to the availability model // apply the startTime and endTime to the availability model
var updatedAvailability = availability.copyWith( var updatedAvailability = availability.copyWith(
@ -31,15 +29,15 @@ class AvailabilityService {
range.start.year, range.start.year,
range.start.month, range.start.month,
range.start.day, range.start.day,
startTime.hour, availability.startDate.hour,
startTime.minute, availability.startDate.minute,
), ),
endDate: DateTime( endDate: DateTime(
range.start.year, range.start.year,
range.start.month, range.start.month,
range.start.day, range.start.day,
endTime.hour, availability.endDate.hour,
endTime.minute, availability.endDate.minute,
), ),
userId: userId, userId: userId,
); );

View file

@ -1,7 +1,6 @@
import "dart:collection";
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/availability_view_model.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/ui/widgets/availability_clear.dart"; import "package:flutter_availability/src/ui/widgets/availability_clear.dart";
import "package:flutter_availability/src/ui/widgets/availability_template_selection.dart"; import "package:flutter_availability/src/ui/widgets/availability_template_selection.dart";
@ -49,19 +48,8 @@ class AvailabilitiesModificationScreen extends StatefulWidget {
class _AvailabilitiesModificationScreenState class _AvailabilitiesModificationScreenState
extends State<AvailabilitiesModificationScreen> { extends State<AvailabilitiesModificationScreen> {
late AvailabilityModel _availability = late AvailabilityViewModel _availabilityViewModel =
widget.initialAvailabilities.getAvailabilities().firstOrNull ?? AvailabilityViewModel.fromModel(widget.initialAvailabilities);
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;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -74,7 +62,7 @@ class _AvailabilitiesModificationScreenState
// TODO(freek): the selected period might be longer than 1 month // TODO(freek): the selected period might be longer than 1 month
//so we need to get all the availabilites through a stream //so we need to get all the availabilites through a stream
Future<void> onSave() async { Future<void> onSave() async {
if (_clearAvailability) { if (_availabilityViewModel.clearAvailability) {
await service.clearAvailabilities( await service.clearAvailabilities(
widget.initialAvailabilities.getAvailabilities(), widget.initialAvailabilities.getAvailabilities(),
); );
@ -88,10 +76,8 @@ class _AvailabilitiesModificationScreenState
} }
await service.createAvailability( await service.createAvailability(
availability: _availability, availability: _availabilityViewModel.toModel(),
range: widget.dateRange, range: widget.dateRange,
startTime: _startTime!,
endTime: _endTime!,
); );
widget.onExit(); widget.onExit();
} }
@ -110,8 +96,7 @@ class _AvailabilitiesModificationScreenState
} }
} }
var canSave = var canSave = _availabilityViewModel.canSave;
_clearAvailability || (_startTime != null && _endTime != null);
var saveButton = options.primaryButtonBuilder( var saveButton = options.primaryButtonBuilder(
context, context,
canSave ? onClickSave : null, canSave ? onClickSave : null,
@ -121,7 +106,9 @@ class _AvailabilitiesModificationScreenState
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters
void onClearSection(bool isChecked) { void onClearSection(bool isChecked) {
setState(() { setState(() {
_clearAvailability = isChecked; _availabilityViewModel = _availabilityViewModel.copyWith(
clearAvailability: isChecked,
);
}); });
} }
@ -129,54 +116,56 @@ class _AvailabilitiesModificationScreenState
var template = await widget.onTemplateSelection(); var template = await widget.onTemplateSelection();
if (template != null) { if (template != null) {
setState(() { setState(() {
_selectedTemplates = [template]; _availabilityViewModel =
_availability = _availability.copyWith( _availabilityViewModel.copyWith(templates: [template]);
templateId: template.id,
);
}); });
} }
} }
void onTemplatesRemoved() { void onTemplatesRemoved() {
setState(() { setState(() {
_selectedTemplates = []; _availabilityViewModel = _availabilityViewModel.copyWith(
templates: [],
);
}); });
} }
void onStartChanged(TimeOfDay start) { void onStartChanged(TimeOfDay start) {
setState(() { setState(() {
_startTime = start; _availabilityViewModel = _availabilityViewModel.copyWith(
startTime: start,
);
}); });
} }
void onEndChanged(TimeOfDay end) { void onEndChanged(TimeOfDay end) {
setState(() { setState(() {
_endTime = end; _availabilityViewModel = _availabilityViewModel.copyWith(
endTime: end,
);
}); });
} }
void onBreaksChanged(List<BreakViewModel> breaks) { void onBreaksChanged(List<BreakViewModel> breaks) {
setState(() { setState(() {
_availability = _availability.copyWith( _availabilityViewModel = _availabilityViewModel.copyWith(
breaks: breaks.map((b) => b.toBreak()).toList(), breaks: breaks,
); );
}); });
} }
return _AvailabilitiesModificationScreenLayout( return _AvailabilitiesModificationScreenLayout(
dateRange: widget.dateRange, dateRange: widget.dateRange,
clearAvailability: _clearAvailability, clearAvailability: _availabilityViewModel.clearAvailability,
onClearSection: onClearSection, onClearSection: onClearSection,
selectedTemplates: _selectedTemplates, selectedTemplates: _availabilityViewModel.templates,
onTemplateSelected: onTemplateSelected, onTemplateSelected: onTemplateSelected,
onTemplatesRemoved: onTemplatesRemoved, onTemplatesRemoved: onTemplatesRemoved,
startTime: _startTime, startTime: _availabilityViewModel.startTime,
endTime: _endTime, endTime: _availabilityViewModel.endTime,
onStartChanged: onStartChanged, onStartChanged: onStartChanged,
onEndChanged: onEndChanged, onEndChanged: onEndChanged,
breaks: _availability.breaks breaks: _availabilityViewModel.breaks,
.map(BreakViewModel.fromAvailabilityBreakModel)
.toList(),
onBreaksChanged: onBreaksChanged, onBreaksChanged: onBreaksChanged,
sidePadding: spacing.sidePadding, sidePadding: spacing.sidePadding,
bottomButtonPadding: spacing.bottomButtonPadding, bottomButtonPadding: spacing.bottomButtonPadding,

View file

@ -51,21 +51,16 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
var availabilitySnapshot = useStream(availabilityStream); var availabilitySnapshot = useStream(availabilityStream);
var selectedAvailabilities = <AvailabilityWithTemplate>[]; var selectedAvailabilities = [
if (_selectedRange != null) { if (_selectedRange != null) ...[
var availabilities = availabilitySnapshot.data ...?availabilitySnapshot.data?.where(
?.where( (item) => item.availabilityModel.isInRange(
(a) => _selectedRange!.start,
!a.availabilityModel.startDate _selectedRange!.end,
.isBefore(_selectedRange!.start) && ),
!a.availabilityModel.endDate.isAfter(_selectedRange!.end), ),
) ],
.toList(); ];
if (availabilities != null) {
selectedAvailabilities = availabilities;
}
}
var availabilitiesAreSelected = selectedAvailabilities.isNotEmpty; 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, hasAvailability: false,
), ),
); );
var dayIsSelected = selectedRange != null && specialDay = checkIfDayIsSelected(selectedRange, day, specialDay);
!day.isBefore(selectedRange.start) &&
!day.isAfter(selectedRange.end);
specialDay = specialDay.copyWith(
isSelected: dayIsSelected,
);
calendarDays.add(specialDay); calendarDays.add(specialDay);
} }
@ -303,3 +298,21 @@ List<CalendarDay> _generateCalendarDays(
return calendarDays; 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, endDate: endDate ?? this.endDate,
breaks: breaks ?? this.breaks, 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] /// A model defining the structure of a break within an [AvailabilityModel]