From fd81964b75cb52c8b09d4efa18aac5721203d8bc Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Tue, 16 Jul 2024 10:18:08 +0200 Subject: [PATCH] feat: add week template modification screen --- .../src/config/availability_translations.dart | 12 +- .../flutter_availability/lib/src/routes.dart | 42 ++- .../screens/template_week_modification.dart | 296 ++++++++++++++++++ .../lib/src/ui/widgets/calendar_grid.dart | 3 +- .../src/ui/widgets/template_time_break.dart | 2 +- 5 files changed, 341 insertions(+), 14 deletions(-) create mode 100644 packages/flutter_availability/lib/src/ui/screens/template_week_modification.dart diff --git a/packages/flutter_availability/lib/src/config/availability_translations.dart b/packages/flutter_availability/lib/src/config/availability_translations.dart index 95e30f7..ceded7f 100644 --- a/packages/flutter_availability/lib/src/config/availability_translations.dart +++ b/packages/flutter_availability/lib/src/config/availability_translations.dart @@ -37,6 +37,7 @@ class AvailabilityTranslations { required this.createDayTemplate, required this.createWeekTemplate, required this.deleteTemplateButton, + required this.editTemplateButton, required this.dayTemplateTitle, required this.weekTemplateTitle, required this.weekTemplateDayTitle, @@ -60,6 +61,7 @@ class AvailabilityTranslations { required this.pauseDialogPeriodDescription, required this.saveButton, required this.addButton, + required this.nextButton, required this.confirmText, required this.cancelText, required this.timeFormatter, @@ -103,6 +105,7 @@ class AvailabilityTranslations { this.createDayTemplate = "Create day template", this.createWeekTemplate = "Create week template", this.deleteTemplateButton = "Delete template", + this.editTemplateButton = "Edit template", this.dayTemplateTitle = "Day template", this.weekTemplateTitle = "Week template", this.weekTemplateDayTitle = "When", @@ -130,6 +133,7 @@ class AvailabilityTranslations { "Select between which times you want to take a break", this.saveButton = "Save", this.addButton = "Add", + this.nextButton = "Next", this.confirmText = "Yes", this.cancelText = "No", this.dayMonthFormatter = _defaultDayMonthFormatter, @@ -219,6 +223,9 @@ class AvailabilityTranslations { /// The label on the button to delete a template final String deleteTemplateButton; + /// The label on the button to edit a template + final String editTemplateButton; + /// The title for the day template edit screen final String dayTemplateTitle; @@ -291,6 +298,9 @@ class AvailabilityTranslations { /// The text on the add button final String addButton; + /// The text on the next button + final String nextButton; + /// The text on the confirm button in the confirmation dialog final String confirmText; @@ -319,7 +329,7 @@ class AvailabilityTranslations { final String Function(BuildContext, DateTime) weekDayAbbreviatedFormatter; /// Gets the weekday formatted as a string - /// + /// /// The default implementation is the full name of the weekday in english final String Function(BuildContext, DateTime) weekDayFormatter; diff --git a/packages/flutter_availability/lib/src/routes.dart b/packages/flutter_availability/lib/src/routes.dart index 51835de..1392588 100644 --- a/packages/flutter_availability/lib/src/routes.dart +++ b/packages/flutter_availability/lib/src/routes.dart @@ -4,6 +4,7 @@ import "package:flutter_availability/src/service/availability_service.dart"; import "package:flutter_availability/src/ui/screens/availability_modification.dart"; import "package:flutter_availability/src/ui/screens/template_day_modification.dart"; import "package:flutter_availability/src/ui/screens/template_overview.dart"; +import "package:flutter_availability/src/ui/screens/template_week_modification.dart"; import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; /// @@ -17,6 +18,17 @@ MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute( ), ); +Future _routeToTemplateEditScreen( + BuildContext context, + AvailabilityTemplateModel template, +) async { + if (template.templateType == AvailabilityTemplateType.day) { + await Navigator.of(context).push(templateEditDayRoute(template)); + } else if (template.templateType == AvailabilityTemplateType.week) { + await Navigator.of(context).push(templateEditWeekRoute(template)); + } +} + /// MaterialPageRoute templateOverviewRoute({ bool allowSelection = false, @@ -24,21 +36,22 @@ MaterialPageRoute templateOverviewRoute({ MaterialPageRoute( builder: (context) => AvailabilityTemplateOverview( onExit: () => Navigator.of(context).pop(), - onEditTemplate: (template) async { - if (template.templateType == AvailabilityTemplateType.day) { - await Navigator.of(context).push(templateEditDayRoute(template)); - } - }, + onEditTemplate: (template) async => + _routeToTemplateEditScreen(context, template), onAddTemplate: (type) async { if (type == AvailabilityTemplateType.day) { await Navigator.of(context).push(templateEditDayRoute(null)); + } else if (type == AvailabilityTemplateType.week) { + await Navigator.of(context).push(templateEditWeekRoute(null)); + } + }, + onSelectTemplate: (template) async { + if (allowSelection) { + Navigator.of(context).pop(template); + } else { + await _routeToTemplateEditScreen(context, template); } }, - onSelectTemplate: allowSelection - ? (template) async { - Navigator.of(context).pop(template); - } - : null, ), ); @@ -51,6 +64,15 @@ MaterialPageRoute templateEditDayRoute(AvailabilityTemplateModel? template) => ), ); +/// +MaterialPageRoute templateEditWeekRoute(AvailabilityTemplateModel? template) => + MaterialPageRoute( + builder: (context) => WeekTemplateModificationScreen( + template: template, + onExit: () => Navigator.of(context).pop(), + ), + ); + /// MaterialPageRoute availabilityViewRoute( DateTimeRange dateRange, diff --git a/packages/flutter_availability/lib/src/ui/screens/template_week_modification.dart b/packages/flutter_availability/lib/src/ui/screens/template_week_modification.dart new file mode 100644 index 0000000..1e3583d --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/screens/template_week_modification.dart @@ -0,0 +1,296 @@ +import "package:flutter/material.dart"; +import "package:flutter_availability/src/ui/models/view_template_daydata.dart"; +import "package:flutter_availability/src/ui/widgets/color_selection.dart"; +import "package:flutter_availability/src/ui/widgets/template_name_input.dart"; +import "package:flutter_availability/src/ui/widgets/template_time_break.dart"; +import "package:flutter_availability/src/ui/widgets/template_week_day_selection.dart"; +import "package:flutter_availability/src/ui/widgets/template_week_overview.dart"; +import "package:flutter_availability/src/util/scope.dart"; +import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; + +/// Page for creating or editing a day template +class WeekTemplateModificationScreen extends StatefulWidget { + /// Constructor + const WeekTemplateModificationScreen({ + required this.template, + required this.onExit, + super.key, + }); + + /// The week template to edit or null if creating a new one + final AvailabilityTemplateModel? template; + + /// Callback for when the user wants to navigate back + final VoidCallback onExit; + + @override + State createState() => + _WeekTemplateModificationScreenState(); +} + +class _WeekTemplateModificationScreenState + extends State { + late int? _selectedColor; + late AvailabilityTemplateModel _template; + bool _editing = true; + WeekDay _selectedDay = WeekDay.monday; + + @override + void initState() { + super.initState(); + _selectedColor = widget.template?.color; + _template = widget.template ?? + AvailabilityTemplateModel( + userId: "1", + name: "", + color: 0, + templateType: AvailabilityTemplateType.week, + templateData: WeekTemplateData.forDays(), + ); + } + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + var textTheme = theme.textTheme; + var availabilityScope = AvailabilityScope.of(context); + var service = availabilityScope.service; + var options = availabilityScope.options; + var translations = options.translations; + var spacing = options.spacing; + + var weekTemplateDate = _template.templateData as WeekTemplateData; + var selectedDayData = weekTemplateDate.data[_selectedDay]; + + Future onDeletePressed() async { + await service.deleteTemplate(_template); + widget.onExit(); + } + + void onNextPressed() { + setState(() { + _editing = false; + }); + } + + void onBackPressed() { + if (_editing) { + widget.onExit(); + } else { + setState(() { + _editing = true; + }); + } + } + + Future onSavePressed() async { + if (widget.template == null) { + await service.createTemplate(_template); + } else { + await service.updateTemplate(_template); + } + widget.onExit(); + } + + var canSave = _template.name.isNotEmpty && _selectedColor != null; + var nextButton = options.primaryButtonBuilder( + context, + canSave ? onNextPressed : null, + Text(translations.nextButton), + ); + + var saveButton = options.primaryButtonBuilder( + context, + canSave ? onSavePressed : null, + Text(translations.saveButton), + ); + + var deleteButton = options.bigTextButtonBuilder( + context, + onDeletePressed, + Text(translations.deleteTemplateButton), + ); + + var previousButton = options.bigTextButtonBuilder( + context, + onBackPressed, + Text(translations.editTemplateButton), + ); + + var title = Center( + child: Text( + translations.weekTemplateTitle, + style: theme.textTheme.displaySmall, + ), + ); + + var templateTitleSection = TemplateNameInput( + initialValue: _template.name, + onNameChanged: (name) { + setState(() { + _template = _template.copyWith(name: name); + }); + }, + ); + + var colorSection = TemplateColorSelection( + selectedColor: _selectedColor, + onColorSelected: (color) { + setState(() { + _selectedColor = color; + _template = _template.copyWith(color: color); + }); + }, + ); + + var editPage = [ + _WeekTemplateSidePadding(child: templateTitleSection), + const SizedBox(height: 24), + Padding( + padding: EdgeInsets.only(left: spacing.sidePadding), + child: TemplateWeekDaySelection( + onDaySelected: (day) { + setState(() { + _selectedDay = WeekDay.values[day]; + }); + }, + ), + ), + const SizedBox(height: 24), + _WeekTemplateSidePadding( + child: TemplateTimeAndBreakSection( + dayData: selectedDayData != null + ? ViewDayTemplateData.fromDayTemplateData( + selectedDayData, + ) + : const ViewDayTemplateData(), + onDayDataChanged: (data) { + setState(() { + _template = _template.copyWith( + templateData: + // create a copy of the week template data + WeekTemplateData( + data: { + for (var entry in weekTemplateDate.data.entries) + entry.key: entry.value, + _selectedDay: data.toDayTemplateData(), + }, + ), + ); + }); + }, + ), + ), + const SizedBox(height: 24), + _WeekTemplateSidePadding(child: colorSection), + ]; + + var overviewPage = _WeekTemplateSidePadding( + child: Column( + children: [ + Text(translations.templateTitleLabel, style: textTheme.titleMedium), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all( + color: theme.colorScheme.primary, + width: 1, + ), + borderRadius: BorderRadius.circular(5), + ), + child: 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), + ], + ), + ), + const SizedBox(height: 30), + TemplateWeekOverview( + template: _template, + ), + ], + ), + ); + + var body = CustomScrollView( + slivers: [ + SliverList.list( + children: [ + const SizedBox(height: 40), + _WeekTemplateSidePadding(child: title), + const SizedBox(height: 24), + if (_editing) ...[ + ...editPage, + ] else ...[ + overviewPage, + ], + const SizedBox(height: 32), + ], + ), + SliverFillRemaining( + fillOverscroll: false, + hasScrollBody: false, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: spacing.sidePadding, + ).copyWith( + bottom: spacing.bottomButtonPadding, + ), + child: Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_editing) ...[ + nextButton, + ] else ...[ + saveButton, + const SizedBox(height: 8), + previousButton, + ], + if (widget.template != null) ...[ + const SizedBox(height: 8), + deleteButton, + ], + ], + ), + ), + ), + ), + ], + ); + + return options.baseScreenBuilder(context, onBackPressed, body); + } +} + +/// One of the elements in the week template modification screen doesn't have a +/// padding around it that is why all the other elements are wrapped in +/// [_WeekTemplateSidePadding] +class _WeekTemplateSidePadding extends StatelessWidget { + const _WeekTemplateSidePadding({ + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + var availabilityScope = AvailabilityScope.of(context); + var sidePadding = availabilityScope.options.spacing.sidePadding; + return Padding( + padding: EdgeInsets.symmetric(horizontal: sidePadding), + child: child, + ); + } +} diff --git a/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart b/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart index a100960..0b81a8f 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart @@ -37,8 +37,7 @@ class CalendarGrid extends StatelessWidget { var calendarDays = _generateCalendarDays(month, days, selectedRange, colors, colorScheme); - var dayNames = - getDaysOfTheWeekAsAbbreviatedStrings(translations, context); + var dayNames = getDaysOfTheWeekAsAbbreviatedStrings(translations, context); var calendarDaysRow = Row( children: List.generate(13, (index) { diff --git a/packages/flutter_availability/lib/src/ui/widgets/template_time_break.dart b/packages/flutter_availability/lib/src/ui/widgets/template_time_break.dart index 8680768..e845170 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/template_time_break.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/template_time_break.dart @@ -3,7 +3,7 @@ import "package:flutter_availability/src/ui/models/view_template_daydata.dart"; import "package:flutter_availability/src/ui/widgets/pause_selection.dart"; import "package:flutter_availability/src/ui/widgets/template_time_selection.dart"; -/// Section for selecting the time and breaks for a single day +/// Section for selecting the time and breaks for a single day class TemplateTimeAndBreakSection extends StatelessWidget { /// const TemplateTimeAndBreakSection({