From c7ea82677c2e600d20b10cf88084a894867283dd Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Tue, 16 Jul 2024 10:17:45 +0200 Subject: [PATCH] feat: add week template overview --- .../lib/src/config/availability_options.dart | 6 + .../src/config/availability_translations.dart | 20 ++ .../ui/widgets/template_week_overview.dart | 188 ++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 packages/flutter_availability/lib/src/ui/widgets/template_week_overview.dart diff --git a/packages/flutter_availability/lib/src/config/availability_options.dart b/packages/flutter_availability/lib/src/config/availability_options.dart index c93decb..6e4d995 100644 --- a/packages/flutter_availability/lib/src/config/availability_options.dart +++ b/packages/flutter_availability/lib/src/config/availability_options.dart @@ -125,6 +125,7 @@ class AvailabilityColors { this.outsideMonthTextColor, this.textDarkColor, this.textLightColor, + this.templateWeekOverviewBackgroundColor, this.templateColors = const [ Color(0xFF9bb8f2), Color(0xFF4b77d0), @@ -160,6 +161,11 @@ class AvailabilityColors { /// If not provided the text color will be the theme's text color final Color? textDarkColor; + /// The color of the background in the template week overview that creates a + /// layered effect by interchanging a color and a transparent color + /// If not provided the color will be the theme's [ColorScheme.surface] + final Color? templateWeekOverviewBackgroundColor; + /// The colors that are used for the templates final List templateColors; } diff --git a/packages/flutter_availability/lib/src/config/availability_translations.dart b/packages/flutter_availability/lib/src/config/availability_translations.dart index 51a8908..95e30f7 100644 --- a/packages/flutter_availability/lib/src/config/availability_translations.dart +++ b/packages/flutter_availability/lib/src/config/availability_translations.dart @@ -43,9 +43,13 @@ class AvailabilityTranslations { required this.templateTitleHintText, required this.templateTitleLabel, required this.templateColorLabel, + required this.weekTemplateOverviewTitle, + required this.pause, + required this.unavailable, required this.time, required this.timeSeparator, required this.timeMinutes, + required this.timeMinutesShort, required this.templateTimeLabel, required this.pauseSectionTitle, required this.pauseSectionOptional, @@ -105,9 +109,13 @@ class AvailabilityTranslations { this.templateTitleHintText = "What do you want to call this template?", this.templateTitleLabel = "Template Title", this.templateColorLabel = "Colorlabel", + this.weekTemplateOverviewTitle = "Overview availability", + this.pause = "Pause", + this.unavailable = "Unavailable", this.time = "Time", this.timeSeparator = "to", this.timeMinutes = "minutes", + this.timeMinutesShort = "min", this.templateTimeLabel = "When are you available?", this.pauseSectionTitle = "Add a pause", this.pauseSectionOptional = "(Optional)", @@ -229,6 +237,15 @@ class AvailabilityTranslations { /// The title above the color selection for templates final String templateColorLabel; + /// The title for the week overview section + final String weekTemplateOverviewTitle; + + /// The label used for pause + final String pause; + + /// The label used for unavailable + final String unavailable; + /// The title for time sections final String time; @@ -238,6 +255,9 @@ class AvailabilityTranslations { /// The text used for minutes final String timeMinutes; + /// The text used for minutes in a short form + final String timeMinutesShort; + /// The label for the template time input final String templateTimeLabel; diff --git a/packages/flutter_availability/lib/src/ui/widgets/template_week_overview.dart b/packages/flutter_availability/lib/src/ui/widgets/template_week_overview.dart new file mode 100644 index 0000000..f9a72c4 --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/widgets/template_week_overview.dart @@ -0,0 +1,188 @@ +import "package:flutter/material.dart"; +import "package:flutter_availability/src/ui/models/view_template_daydata.dart"; +import "package:flutter_availability/src/ui/widgets/calendar_grid.dart"; +import "package:flutter_availability/src/util/scope.dart"; +import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart"; + +/// Shows an overview of the template of a week before saving it +class TemplateWeekOverview extends StatelessWidget { + /// + const TemplateWeekOverview({ + required this.template, + super.key, + }); + + /// The template to show + final AvailabilityTemplateModel template; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + var textTheme = theme.textTheme; + var availabilityScope = AvailabilityScope.of(context); + var options = availabilityScope.options; + var translations = options.translations; + var colors = options.colors; + + var dayNames = getDaysOfTheWeekAsStrings(translations, context); + + var templateData = template.templateData as WeekTemplateData; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + translations.weekTemplateOverviewTitle, + style: textTheme.titleMedium, + ), + const SizedBox(height: 8), + DecoratedBox( + decoration: BoxDecoration( + color: colors.templateWeekOverviewBackgroundColor ?? + theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: theme.dividerColor, + ), + ), + child: Column( + children: [ + for (var day in WeekDay.values) ...[ + _TemplateDayDetailRow( + dayName: dayNames[day.index], + dayData: templateData.data.containsKey(day) + ? ViewDayTemplateData.fromDayTemplateData( + templateData.data[day]!, + ) + : null, + isOdd: day.index.isOdd, + ), + ], + ], + ), + ), + ], + ); + } +} + +class _TemplateDayDetailRow extends StatelessWidget { + const _TemplateDayDetailRow({ + required this.dayName, + required this.dayData, + required this.isOdd, + }); + + /// The name of the day + final String dayName; + + /// There odd rows do not have a background color + /// This causes a layered effect + final bool isOdd; + + /// The data of the day + final ViewDayTemplateData? dayData; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + var textTheme = theme.textTheme; + var availabilityScope = AvailabilityScope.of(context); + var options = availabilityScope.options; + var translations = options.translations; + + var startTime = dayData?.startTime; + var endTime = dayData?.endTime; + var dayHasAvailability = startTime != null && endTime != null; + String? dayPeriod; + if (dayHasAvailability) { + dayPeriod = "${translations.timeFormatter(context, startTime)} - " + "${translations.timeFormatter(context, endTime)}"; + } else { + dayPeriod = translations.unavailable; + } + + var breaks = dayData?.breaks ?? []; + + BoxDecoration? boxDecoration; + if (isOdd) { + boxDecoration = BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide( + color: theme.dividerColor, + ), + right: BorderSide( + color: theme.dividerColor, + ), + ), + ); + } + + return Container( + decoration: boxDecoration, + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(dayName, style: textTheme.bodyLarge), + Text(dayPeriod, style: textTheme.bodyLarge), + ], + ), + // for each break add a line + for (var dayBreak in breaks) ...[ + const SizedBox(height: 4), + _TemplateDayDetailPauseRow(dayBreak: dayBreak), + ], + ], + ), + ); + } +} + +class _TemplateDayDetailPauseRow extends StatelessWidget { + const _TemplateDayDetailPauseRow({ + required this.dayBreak, + }); + + final AvailabilityBreakModel dayBreak; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + var textTheme = theme.textTheme; + var availabilityScope = AvailabilityScope.of(context); + var options = availabilityScope.options; + var translations = options.translations; + + var startTime = TimeOfDay.fromDateTime(dayBreak.startTime); + var endTime = TimeOfDay.fromDateTime(dayBreak.endTime); + var startTimeString = translations.timeFormatter(context, startTime); + var endTimeString = translations.timeFormatter(context, endTime); + var pausePeriod = + "$startTimeString - $endTimeString (${dayBreak.duration.inMinutes} " + "${translations.timeMinutesShort})"; + var pauseTextStyle = textTheme.bodyMedium?.copyWith( + fontStyle: FontStyle.italic, + ); + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16), + child: Text( + translations.pause, + style: pauseTextStyle, + ), + ), + Text( + pausePeriod, + style: pauseTextStyle, + ), + ], + ); + } +}