mirror of
https://github.com/Iconica-Development/flutter_availability.git
synced 2025-05-18 20:53:45 +02:00
feat: add marking for template deviation in the availability modification screen
This commit is contained in:
parent
5eda42c9dd
commit
eb704f44cd
5 changed files with 160 additions and 41 deletions
|
@ -29,6 +29,9 @@ class AvailabilityTranslations {
|
|||
required this.availabilityUsedTemplates,
|
||||
required this.availabilityTimeTitle,
|
||||
required this.availabilitiesTimeTitle,
|
||||
required this.availabilityTemplateDeviationExplanation,
|
||||
required this.availabilitiesTemplateDeviationExplanation,
|
||||
required this.availabilitiesConflictingTimeExplanation,
|
||||
required this.availabilityDialogConfirmTitle,
|
||||
required this.availabilityDialogConfirmDescription,
|
||||
required this.templateScreenTitle,
|
||||
|
@ -95,6 +98,13 @@ class AvailabilityTranslations {
|
|||
this.availabilityUsedTemplates = "Used templates",
|
||||
this.availabilityTimeTitle = "Start and end time workday",
|
||||
this.availabilitiesTimeTitle = "Start and end time workdays",
|
||||
this.availabilityTemplateDeviationExplanation =
|
||||
"The start and end time are deviating from the template for this day",
|
||||
this.availabilitiesTemplateDeviationExplanation =
|
||||
"The start and end time are deviating from the template for these days",
|
||||
this.availabilitiesConflictingTimeExplanation =
|
||||
"There are conflicting times when applying this template "
|
||||
"for this period",
|
||||
this.availabilityDialogConfirmTitle =
|
||||
"Are you sure you want to save the changes?",
|
||||
this.availabilityDialogConfirmDescription =
|
||||
|
@ -204,6 +214,18 @@ class AvailabilityTranslations {
|
|||
/// The title on the time selection section for adding multiple availabilities
|
||||
final String availabilitiesTimeTitle;
|
||||
|
||||
/// The explainer text when the availability deviates from the used template
|
||||
/// on the availability modification screen
|
||||
final String availabilityTemplateDeviationExplanation;
|
||||
|
||||
/// The explainer text when one of the availabilities deviates from the used
|
||||
/// template on the availability modification screen
|
||||
final String availabilitiesTemplateDeviationExplanation;
|
||||
|
||||
/// The explainer text when the availabilities have conflicting times when
|
||||
/// applying a template and the start and end time have not been filled in
|
||||
final String availabilitiesConflictingTimeExplanation;
|
||||
|
||||
/// The title on the dialog for confirming the availability update
|
||||
final String availabilityDialogConfirmTitle;
|
||||
|
||||
|
|
|
@ -183,17 +183,13 @@ class _AvailabilitiesModificationScreenState
|
|||
}
|
||||
|
||||
return _AvailabilitiesModificationScreenLayout(
|
||||
dateRange: widget.dateRange,
|
||||
clearAvailability: _availabilityViewModel.clearAvailability,
|
||||
viewModel: _availabilityViewModel,
|
||||
onClearSection: onClearSection,
|
||||
selectedTemplates: _availabilityViewModel.templates,
|
||||
onTemplateSelected: onTemplateSelected,
|
||||
onTemplatesRemoved: onTemplatesRemoved,
|
||||
startTime: _availabilityViewModel.startTime,
|
||||
endTime: _availabilityViewModel.endTime,
|
||||
onStartChanged: onStartChanged,
|
||||
onEndChanged: onEndChanged,
|
||||
breaks: _availabilityViewModel.breaks,
|
||||
onBreaksChanged: onBreaksChanged,
|
||||
sidePadding: spacing.sidePadding,
|
||||
bottomButtonPadding: spacing.bottomButtonPadding,
|
||||
|
@ -205,17 +201,13 @@ class _AvailabilitiesModificationScreenState
|
|||
|
||||
class _AvailabilitiesModificationScreenLayout extends HookWidget {
|
||||
const _AvailabilitiesModificationScreenLayout({
|
||||
required this.dateRange,
|
||||
required this.clearAvailability,
|
||||
required this.viewModel,
|
||||
required this.onClearSection,
|
||||
required this.selectedTemplates,
|
||||
required this.onTemplateSelected,
|
||||
required this.onTemplatesRemoved,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.onStartChanged,
|
||||
required this.onEndChanged,
|
||||
required this.breaks,
|
||||
required this.onBreaksChanged,
|
||||
required this.sidePadding,
|
||||
required this.bottomButtonPadding,
|
||||
|
@ -223,8 +215,7 @@ class _AvailabilitiesModificationScreenLayout extends HookWidget {
|
|||
required this.onExit,
|
||||
});
|
||||
|
||||
final DateTimeRange dateRange;
|
||||
final bool clearAvailability;
|
||||
final AvailabilityViewModel viewModel;
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
final void Function(bool isChecked) onClearSection;
|
||||
|
||||
|
@ -232,12 +223,9 @@ class _AvailabilitiesModificationScreenLayout extends HookWidget {
|
|||
final void Function() onTemplateSelected;
|
||||
final void Function() onTemplatesRemoved;
|
||||
|
||||
final TimeOfDay? startTime;
|
||||
final TimeOfDay? endTime;
|
||||
final void Function(TimeOfDay start) onStartChanged;
|
||||
final void Function(TimeOfDay start) onEndChanged;
|
||||
|
||||
final List<BreakViewModel> breaks;
|
||||
final void Function(List<BreakViewModel> breaks) onBreaksChanged;
|
||||
|
||||
final double sidePadding;
|
||||
|
@ -258,11 +246,11 @@ class _AvailabilitiesModificationScreenLayout extends HookWidget {
|
|||
BasePage(
|
||||
body: [
|
||||
AvailabilityClearSection(
|
||||
range: dateRange,
|
||||
clearAvailable: clearAvailability,
|
||||
range: viewModel.selectedRange,
|
||||
clearAvailable: viewModel.clearAvailability,
|
||||
onChanged: onClearSection,
|
||||
),
|
||||
if (!clearAvailability) ...[
|
||||
if (!viewModel.clearAvailability) ...[
|
||||
const SizedBox(height: 24),
|
||||
AvailabilityTemplateSelection(
|
||||
selectedTemplates: selectedTemplates,
|
||||
|
@ -271,16 +259,14 @@ class _AvailabilitiesModificationScreenLayout extends HookWidget {
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
AvailabilityTimeSelection(
|
||||
dateRange: dateRange,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
key: ValueKey([startTime, endTime]),
|
||||
viewModel: viewModel,
|
||||
key: ValueKey(viewModel),
|
||||
onStartChanged: onStartChanged,
|
||||
onEndChanged: onEndChanged,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
PauseSelection(
|
||||
breaks: breaks,
|
||||
breaks: viewModel.breaks,
|
||||
editingTemplate: false,
|
||||
onBreaksChanged: onBreaksChanged,
|
||||
),
|
||||
|
|
|
@ -21,7 +21,8 @@ class AvailabilityViewModel {
|
|||
this.conflictingPauses = false,
|
||||
this.conflictingTime = false,
|
||||
this.templateSelected = false,
|
||||
});
|
||||
List<AvailabilityWithTemplate> initialModels = const [],
|
||||
}) : _initialModels = initialModels;
|
||||
|
||||
/// This constructor creates a [AvailabilityViewModel] from a list of
|
||||
/// [AvailabilityWithTemplate] models
|
||||
|
@ -59,6 +60,7 @@ class AvailabilityViewModel {
|
|||
}
|
||||
|
||||
return AvailabilityViewModel(
|
||||
initialModels: models,
|
||||
templates: models.getUniqueTemplates(),
|
||||
breaks: breaks,
|
||||
ids: models.map((e) => e.availabilityModel.id!).toList(),
|
||||
|
@ -71,6 +73,10 @@ class AvailabilityViewModel {
|
|||
);
|
||||
}
|
||||
|
||||
/// The initial models that were selected and can be checked for deviations
|
||||
/// from the templates
|
||||
final List<AvailabilityWithTemplate> _initialModels;
|
||||
|
||||
/// 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
|
||||
|
@ -124,6 +130,17 @@ class AvailabilityViewModel {
|
|||
templateSelected ||
|
||||
(startTime != null && endTime != null);
|
||||
|
||||
/// Whether a template deviation should be shown to the user
|
||||
bool get isDeviatingFromTemplate =>
|
||||
startTime != null &&
|
||||
endTime != null &&
|
||||
templates.isNotEmpty &&
|
||||
_isAnyTemplateWithDifferentTimeFromAvailability();
|
||||
|
||||
/// Checks whether any availability has a different time from their template
|
||||
bool _isAnyTemplateWithDifferentTimeFromAvailability() =>
|
||||
_initialModels.any(_availabilityTemplateDeviatesFromTime);
|
||||
|
||||
///
|
||||
AvailabilityViewModel applyTemplate(AvailabilityTemplateModel template) {
|
||||
TimeOfDay? startTime;
|
||||
|
@ -209,6 +226,24 @@ class AvailabilityViewModel {
|
|||
);
|
||||
}
|
||||
|
||||
/// Checks the current selected start and end time against the templates for
|
||||
/// the initial models to see if the time deviates from the template
|
||||
bool _availabilityTemplateDeviatesFromTime(AvailabilityWithTemplate model) {
|
||||
var template = model.template;
|
||||
var availability = model.availabilityModel;
|
||||
if (template == null) {
|
||||
return false;
|
||||
}
|
||||
var startDate = DateTime(0, 0, 0, startTime!.hour, startTime!.minute);
|
||||
var endDate = DateTime(0, 0, 0, endTime!.hour, endTime!.minute);
|
||||
|
||||
return template.availabilityDeviatesFromTemplate(
|
||||
availability,
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
}
|
||||
|
||||
/// Copies the current properties into a new instance
|
||||
/// of [AvailabilityViewModel],
|
||||
AvailabilityViewModel copyWith({
|
||||
|
@ -236,6 +271,7 @@ class AvailabilityViewModel {
|
|||
conflictingTime: conflictingTime ?? this.conflictingTime,
|
||||
templateSelected: templateSelected ?? this.templateSelected,
|
||||
selectedRange: selectedRange ?? this.selectedRange,
|
||||
initialModels: _initialModels,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/availability_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/generic_time_selection.dart";
|
||||
import "package:flutter_availability/src/util/scope.dart";
|
||||
|
||||
|
@ -6,19 +7,14 @@ import "package:flutter_availability/src/util/scope.dart";
|
|||
class AvailabilityTimeSelection extends StatelessWidget {
|
||||
///
|
||||
const AvailabilityTimeSelection({
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.viewModel,
|
||||
required this.onStartChanged,
|
||||
required this.onEndChanged,
|
||||
required this.dateRange,
|
||||
super.key,
|
||||
});
|
||||
|
||||
///
|
||||
final TimeOfDay? startTime;
|
||||
|
||||
///
|
||||
final TimeOfDay? endTime;
|
||||
final AvailabilityViewModel viewModel;
|
||||
|
||||
///
|
||||
final void Function(TimeOfDay) onStartChanged;
|
||||
|
@ -26,27 +22,65 @@ class AvailabilityTimeSelection extends StatelessWidget {
|
|||
///
|
||||
final void Function(TimeOfDay) onEndChanged;
|
||||
|
||||
/// The date range for which the availabilities are being managed
|
||||
final DateTimeRange dateRange;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var availabilityScope = AvailabilityScope.of(context);
|
||||
var options = availabilityScope.options;
|
||||
var translations = options.translations;
|
||||
|
||||
var dateRange = viewModel.selectedRange;
|
||||
|
||||
var isSingleDay = dateRange.start.isAtSameMomentAs(dateRange.end);
|
||||
var titleText = isSingleDay
|
||||
? translations.availabilityTimeTitle
|
||||
: translations.availabilitiesTimeTitle;
|
||||
|
||||
return TimeSelection(
|
||||
title: titleText,
|
||||
description: null,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
onStartChanged: onStartChanged,
|
||||
onEndChanged: onEndChanged,
|
||||
String? explanationText;
|
||||
if (viewModel.isDeviatingFromTemplate) {
|
||||
explanationText = isSingleDay
|
||||
? translations.availabilityTemplateDeviationExplanation
|
||||
: translations.availabilitiesTemplateDeviationExplanation;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
TimeSelection(
|
||||
title: titleText,
|
||||
description: null,
|
||||
startTime: viewModel.startTime,
|
||||
endTime: viewModel.endTime,
|
||||
onStartChanged: onStartChanged,
|
||||
onEndChanged: onEndChanged,
|
||||
),
|
||||
if (explanationText != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
_AvailabilityExplanation(
|
||||
explanation: explanationText,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AvailabilityExplanation extends StatelessWidget {
|
||||
const _AvailabilityExplanation({
|
||||
required this.explanation,
|
||||
});
|
||||
|
||||
final String explanation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
var textTheme = theme.textTheme;
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.info_outline),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(explanation, style: textTheme.bodyMedium)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
import "package:flutter_availability_data_interface/src/models/availability.dart";
|
||||
import "package:flutter_availability_data_interface/src/utils.dart";
|
||||
|
||||
/// Exception thrown when the end is before the start
|
||||
class TemplateEndBeforeStartException implements Exception {}
|
||||
|
@ -119,6 +120,15 @@ class AvailabilityTemplateModel {
|
|||
void validate() {
|
||||
templateData.validate();
|
||||
}
|
||||
|
||||
/// check if an availability's day corresponds to the template with the given
|
||||
/// [availability] and [start] and [end] dates
|
||||
bool availabilityDeviatesFromTemplate(
|
||||
AvailabilityModel availability,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
) =>
|
||||
templateData.availabilityDeviates(availability, start, end);
|
||||
}
|
||||
|
||||
/// Used as the key for defining week-based templates
|
||||
|
@ -186,6 +196,14 @@ abstract interface class TemplateData {
|
|||
|
||||
/// Verify the validity of the data in this template
|
||||
void validate();
|
||||
|
||||
/// Check if an availability's day corresponds to the template with the given
|
||||
/// [availability] and [start] and [end] dates
|
||||
bool availabilityDeviates(
|
||||
AvailabilityModel availability,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
);
|
||||
}
|
||||
|
||||
/// A week based template data structure
|
||||
|
@ -287,6 +305,21 @@ class WeekTemplateData implements TemplateData {
|
|||
dayData.value.validate();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool availabilityDeviates(
|
||||
AvailabilityModel availability,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
) {
|
||||
var dayOfWeek = WeekDay.values[availability.startDate.weekday];
|
||||
var data = _data[dayOfWeek];
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
// compare the start and end with the template
|
||||
return !start.timeMatches(data.startTime) || !end.timeMatches(data.endTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// A day based template data structure
|
||||
|
@ -416,6 +449,14 @@ class DayTemplateData implements TemplateData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool availabilityDeviates(
|
||||
AvailabilityModel availability,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
) =>
|
||||
!start.timeMatches(startTime) || !end.timeMatches(endTime);
|
||||
}
|
||||
|
||||
List<DateTime> _getDatesBetween(DateTime startDate, DateTime endDate) {
|
||||
|
|
Loading…
Reference in a new issue