feat: add template selection for creating availabilities

This commit is contained in:
Freek van de Ven 2024-07-11 16:06:11 +02:00 committed by Bart Ribbers
parent fe910ef041
commit 04b843d2fd
5 changed files with 161 additions and 9 deletions

View file

@ -24,6 +24,8 @@ class AvailabilityTranslations {
required this.unavailableForDay, required this.unavailableForDay,
required this.unavailableForMultipleDays, required this.unavailableForMultipleDays,
required this.availabilityAddTemplateTitle, required this.availabilityAddTemplateTitle,
required this.availabilityUsedTemplate,
required this.availabilityUsedTemplates,
required this.availabilityTimeTitle, required this.availabilityTimeTitle,
required this.availabilitiesTimeTitle, required this.availabilitiesTimeTitle,
required this.availabilityDialogConfirmTitle, required this.availabilityDialogConfirmTitle,
@ -75,6 +77,8 @@ class AvailabilityTranslations {
this.unavailableForDay = "I am not available this day", this.unavailableForDay = "I am not available this day",
this.unavailableForMultipleDays = "I am not available these days", this.unavailableForMultipleDays = "I am not available these days",
this.availabilityAddTemplateTitle = "Add template to availability", this.availabilityAddTemplateTitle = "Add template to availability",
this.availabilityUsedTemplate = "Used template",
this.availabilityUsedTemplates = "Used templates",
this.availabilityTimeTitle = "Start and end time workday", this.availabilityTimeTitle = "Start and end time workday",
this.availabilitiesTimeTitle = "Start and end time workdays", this.availabilitiesTimeTitle = "Start and end time workdays",
this.availabilityDialogConfirmTitle = this.availabilityDialogConfirmTitle =
@ -156,6 +160,12 @@ class AvailabilityTranslations {
/// The title on the template selection section for adding availabilities /// The title on the template selection section for adding availabilities
final String availabilityAddTemplateTitle; final String availabilityAddTemplateTitle;
/// The title on the template selection section when a single template is used
final String availabilityUsedTemplate;
/// The title on the template selection section when more templates are used
final String availabilityUsedTemplates;
/// The title on the time selection section for adding a single availability /// The title on the time selection section for adding a single availability
final String availabilityTimeTitle; final String availabilityTimeTitle;

View file

@ -18,7 +18,8 @@ MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute(
); );
/// ///
MaterialPageRoute templateOverviewRoute() => MaterialPageRoute( MaterialPageRoute<AvailabilityTemplateModel?> templateOverviewRoute() =>
MaterialPageRoute(
builder: (context) => AvailabilityTemplateOverview( builder: (context) => AvailabilityTemplateOverview(
onExit: () => Navigator.of(context).pop(), onExit: () => Navigator.of(context).pop(),
onEditTemplate: (template) async { onEditTemplate: (template) async {
@ -31,6 +32,9 @@ MaterialPageRoute templateOverviewRoute() => MaterialPageRoute(
await Navigator.of(context).push(templateEditDayRoute(null)); await Navigator.of(context).push(templateEditDayRoute(null));
} }
}, },
onSelectTemplate: (template) async {
Navigator.of(context).pop(template);
},
), ),
); );
@ -52,8 +56,11 @@ MaterialPageRoute availabilityViewRoute(
builder: (context) => AvailabilitiesModificationScreen( builder: (context) => AvailabilitiesModificationScreen(
dateRange: dateRange, dateRange: dateRange,
initialAvailabilities: initialAvailabilities, initialAvailabilities: initialAvailabilities,
onExit: () { onTemplateSelection: () async {
Navigator.of(context).pop(); var selectedTemplate =
Navigator.of(context).push(templateOverviewRoute());
return selectedTemplate;
}, },
onExit: () => Navigator.of(context).pop(),
), ),
); );

View file

@ -16,6 +16,7 @@ class AvailabilitiesModificationScreen extends StatefulWidget {
required this.dateRange, required this.dateRange,
required this.onExit, required this.onExit,
required this.initialAvailabilities, required this.initialAvailabilities,
required this.onTemplateSelection,
super.key, super.key,
}); });
@ -33,6 +34,10 @@ class AvailabilitiesModificationScreen extends StatefulWidget {
/// availabilities have been saved /// availabilities have been saved
final VoidCallback onExit; final VoidCallback onExit;
/// Callback for when the user wants to go to the template overview screen to
/// select a template
final Future<AvailabilityTemplateModel?> Function() onTemplateSelection;
@override @override
State<AvailabilitiesModificationScreen> createState() => State<AvailabilitiesModificationScreen> createState() =>
_AvailabilitiesModificationScreenState(); _AvailabilitiesModificationScreenState();
@ -41,6 +46,7 @@ class AvailabilitiesModificationScreen extends StatefulWidget {
class _AvailabilitiesModificationScreenState class _AvailabilitiesModificationScreenState
extends State<AvailabilitiesModificationScreen> { extends State<AvailabilitiesModificationScreen> {
late AvailabilityModel _availability; late AvailabilityModel _availability;
late List<AvailabilityTemplateModel> _selectedTemplates;
bool _clearAvailability = false; bool _clearAvailability = false;
TimeOfDay? _startTime; TimeOfDay? _startTime;
TimeOfDay? _endTime; TimeOfDay? _endTime;
@ -56,6 +62,7 @@ class _AvailabilitiesModificationScreenState
endDate: widget.dateRange.end, endDate: widget.dateRange.end,
breaks: [], breaks: [],
); );
_selectedTemplates = widget.initialAvailabilities.getUniqueTemplates();
} }
@override @override
@ -121,7 +128,22 @@ class _AvailabilitiesModificationScreenState
}, },
); );
var templateSelection = const AvailabilityTemplateSelection(); var templateSelection = AvailabilityTemplateSelection(
selectedTemplates: _selectedTemplates,
onTemplateAdd: () async {
var template = await widget.onTemplateSelection();
if (template != null) {
setState(() {
_selectedTemplates = [template];
});
}
},
onTemplatesRemoved: () {
setState(() {
_selectedTemplates = [];
});
},
);
var timeSelection = AvailabilityTimeSelection( var timeSelection = AvailabilityTimeSelection(
dateRange: widget.dateRange, dateRange: widget.dateRange,

View file

@ -10,6 +10,7 @@ class AvailabilityTemplateOverview extends HookWidget {
required this.onExit, required this.onExit,
required this.onEditTemplate, required this.onEditTemplate,
required this.onAddTemplate, required this.onAddTemplate,
this.onSelectTemplate,
super.key, super.key,
}); });
@ -22,6 +23,9 @@ class AvailabilityTemplateOverview extends HookWidget {
/// Callback for when the user goes to create a new template /// Callback for when the user goes to create a new template
final void Function(AvailabilityTemplateType type) onAddTemplate; final void Function(AvailabilityTemplateType type) onAddTemplate;
/// Callback for when the user selects a template
final void Function(AvailabilityTemplateModel template)? onSelectTemplate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
@ -48,6 +52,7 @@ class AvailabilityTemplateOverview extends HookWidget {
sectionTitle: translations.dayTemplates, sectionTitle: translations.dayTemplates,
createButtonText: translations.createDayTemplate, createButtonText: translations.createDayTemplate,
onEditTemplate: onEditTemplate, onEditTemplate: onEditTemplate,
onSelectTemplate: onSelectTemplate,
onAddTemplate: () => onAddTemplate(AvailabilityTemplateType.day), onAddTemplate: () => onAddTemplate(AvailabilityTemplateType.day),
templatesSnapshot: dayTemplatesSnapshot, templatesSnapshot: dayTemplatesSnapshot,
); );
@ -57,6 +62,7 @@ class AvailabilityTemplateOverview extends HookWidget {
createButtonText: translations.createWeekTemplate, createButtonText: translations.createWeekTemplate,
templatesSnapshot: weekTemplatesSnapshot, templatesSnapshot: weekTemplatesSnapshot,
onEditTemplate: onEditTemplate, onEditTemplate: onEditTemplate,
onSelectTemplate: onSelectTemplate,
onAddTemplate: () => onAddTemplate(AvailabilityTemplateType.week), onAddTemplate: () => onAddTemplate(AvailabilityTemplateType.week),
); );
@ -89,6 +95,7 @@ class _TemplateListSection extends StatelessWidget {
required this.templatesSnapshot, required this.templatesSnapshot,
required this.onEditTemplate, required this.onEditTemplate,
required this.onAddTemplate, required this.onAddTemplate,
required this.onSelectTemplate,
}); });
final String sectionTitle; final String sectionTitle;
@ -96,6 +103,7 @@ class _TemplateListSection extends StatelessWidget {
final AsyncSnapshot<List<AvailabilityTemplateModel>> templatesSnapshot; final AsyncSnapshot<List<AvailabilityTemplateModel>> templatesSnapshot;
final void Function(AvailabilityTemplateModel template) onEditTemplate; final void Function(AvailabilityTemplateModel template) onEditTemplate;
final VoidCallback onAddTemplate; final VoidCallback onAddTemplate;
final void Function(AvailabilityTemplateModel template)? onSelectTemplate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -104,6 +112,12 @@ class _TemplateListSection extends StatelessWidget {
var availabilityScope = AvailabilityScope.of(context); var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options; var options = availabilityScope.options;
void onClickTemplate(AvailabilityTemplateModel template) {
// if the onSelectTemplate is set the user can select a template
// The user will need to click on the edit button to edit
(onSelectTemplate ?? onEditTemplate).call(template);
}
var templateCreationButton = InkWell( var templateCreationButton = InkWell(
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
onTap: onAddTemplate, onTap: onAddTemplate,
@ -136,7 +150,7 @@ class _TemplateListSection extends StatelessWidget {
for (var template for (var template
in templatesSnapshot.data ?? <AvailabilityTemplateModel>[]) ...[ in templatesSnapshot.data ?? <AvailabilityTemplateModel>[]) ...[
GestureDetector( GestureDetector(
onTap: () => onEditTemplate(template), onTap: () => onClickTemplate(template),
child: Container( child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(top: 8), margin: const EdgeInsets.only(top: 8),
@ -157,7 +171,10 @@ class _TemplateListSection extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text(template.name, style: textTheme.bodyLarge), Text(template.name, style: textTheme.bodyLarge),
const Spacer(), const Spacer(),
const Icon(Icons.edit), GestureDetector(
onTap: () => onEditTemplate(template),
child: const Icon(Icons.edit),
),
], ],
), ),
), ),

View file

@ -1,4 +1,6 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_availability/src/util/scope.dart";
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
/// Selection of the template to use for the availability /// Selection of the template to use for the availability
/// ///
@ -8,9 +10,103 @@ import "package:flutter/material.dart";
/// templates. /// templates.
class AvailabilityTemplateSelection extends StatelessWidget { class AvailabilityTemplateSelection extends StatelessWidget {
/// Constructor /// Constructor
const AvailabilityTemplateSelection({super.key}); const AvailabilityTemplateSelection({
required this.selectedTemplates,
required this.onTemplateAdd,
required this.onTemplatesRemoved,
super.key,
});
/// The currently selected templates
final List<AvailabilityTemplateModel> selectedTemplates;
/// Callback for when the user selects a template
final VoidCallback onTemplateAdd;
/// Callback for when the user wants to remove the templates
/// There might be multiple templates and they can only be removed all at once
final VoidCallback onTemplatesRemoved;
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) {
const SizedBox(height: 50, child: Placeholder()); var theme = Theme.of(context);
var textTheme = theme.textTheme;
var availabilityScope = AvailabilityScope.of(context);
var options = availabilityScope.options;
var translations = options.translations;
var titleText = selectedTemplates.isEmpty
? translations.availabilityAddTemplateTitle
: selectedTemplates.length > 1
? translations.availabilityUsedTemplates
: translations.availabilityUsedTemplate;
var addButton = options.bigTextButtonWrapperBuilder(
context,
onTemplateAdd,
options.bigTextButtonBuilder(
context,
onTemplateAdd,
Text(translations.addButton),
),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
titleText,
style: textTheme.titleMedium,
),
const SizedBox(height: 8),
if (selectedTemplates.isEmpty) ...[
addButton,
] else ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(
color: theme.colorScheme.primary,
width: 1,
),
borderRadius: BorderRadius.circular(5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var template in selectedTemplates) ...[
Row(
children: [
Container(
decoration: BoxDecoration(
color: Color(template.color),
borderRadius: BorderRadius.circular(5),
),
width: 20,
height: 20,
),
const SizedBox(width: 12),
Text(template.name, style: textTheme.bodyLarge),
],
),
if (template != selectedTemplates.last)
const SizedBox(height: 12),
],
],
),
GestureDetector(
onTap: onTemplatesRemoved,
child: const Icon(Icons.remove),
),
],
),
),
],
],
);
}
} }