From 20104160ebdd1f971fc7e022ea3754eb3b2aa7ec Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Wed, 10 Jul 2024 11:06:10 +0200 Subject: [PATCH] feat: add availability screen individual components --- .../src/config/availability_translations.dart | 71 ++++++++++++++++--- .../src/ui/widgets/availability_clear.dart | 68 ++++++++++++++++++ .../availability_template_selection.dart | 16 +++++ .../widgets/availabillity_time_selection.dart | 52 ++++++++++++++ .../ui/widgets/generic_time_selection.dart | 8 ++- 5 files changed, 201 insertions(+), 14 deletions(-) create mode 100644 packages/flutter_availability/lib/src/ui/widgets/availability_clear.dart create mode 100644 packages/flutter_availability/lib/src/ui/widgets/availability_template_selection.dart create mode 100644 packages/flutter_availability/lib/src/ui/widgets/availabillity_time_selection.dart diff --git a/packages/flutter_availability/lib/src/config/availability_translations.dart b/packages/flutter_availability/lib/src/config/availability_translations.dart index e9717d9..5a03993 100644 --- a/packages/flutter_availability/lib/src/config/availability_translations.dart +++ b/packages/flutter_availability/lib/src/config/availability_translations.dart @@ -18,6 +18,11 @@ class AvailabilityTranslations { required this.availabilityWithoutTemplateLabel, required this.overviewScreenTitle, required this.createTemplateButton, + required this.unavailableForDay, + required this.unavailableForMultipleDays, + required this.availabilityAddTemplateTitle, + required this.availabilityTimeTitle, + required this.availabilitiesTimeTitle, required this.templateScreenTitle, required this.dayTemplates, required this.weekTemplates, @@ -41,6 +46,8 @@ class AvailabilityTranslations { required this.saveButton, required this.addButton, required this.timeFormatter, + required this.dayMonthFormatter, + required this.periodFormatter, required this.monthYearFormatter, required this.weekDayAbbreviatedFormatter, }); @@ -54,6 +61,11 @@ class AvailabilityTranslations { this.templateSelectionLabel = "Selected day(s)", this.availabilityWithoutTemplateLabel = "Availabilty without template", this.createTemplateButton = "Create a new template", + this.unavailableForDay = "I am not available this day", + this.unavailableForMultipleDays = "I am not available these days", + this.availabilityAddTemplateTitle = "Add template to availability", + this.availabilityTimeTitle = "Start and end time workday", + this.availabilitiesTimeTitle = "Start and end time workdays", this.overviewScreenTitle = "Availability", this.templateScreenTitle = "Templates", this.dayTemplates = "Day templates", @@ -79,6 +91,8 @@ class AvailabilityTranslations { "Select between which times you want to take a break", this.saveButton = "Save", this.addButton = "Add", + this.dayMonthFormatter = _defaultDayMonthFormatter, + this.periodFormatter = _defaultPeriodFormatter, this.monthYearFormatter = _defaultMonthYearFormatter, this.weekDayAbbreviatedFormatter = _defaultWeekDayAbbreviatedFormatter, this.timeFormatter = _defaultTimeFormatter, @@ -105,6 +119,21 @@ class AvailabilityTranslations { /// The label on the button to go to the template screen final String createTemplateButton; + /// The text shown to clear the availability for a day + final String unavailableForDay; + + /// The text shown to clear the availability for multiple days + final String unavailableForMultipleDays; + + /// The title on the template selection section for adding availabilities + final String availabilityAddTemplateTitle; + + /// The title on the time selection section for adding a single availability + final String availabilityTimeTitle; + + /// The title on the time selection section for adding multiple availabilities + final String availabilitiesTimeTitle; + /// The title on the template screen final String templateScreenTitle; @@ -172,6 +201,16 @@ class AvailabilityTranslations { /// The text on the add button final String addButton; + /// Gets the day and month formatted as a string + /// + /// The default implementation is `Dayname day monthname` in english + final String Function(BuildContext, DateTime) dayMonthFormatter; + + /// Gets the period between two dates formatted as a string + /// + /// The default implementation is `day monthname to day monthname` in english + final String Function(BuildContext, DateTimeRange) periodFormatter; + /// Gets the month and year formatted as a string /// /// The default implementation is `MonthName Year` in english @@ -190,8 +229,15 @@ class AvailabilityTranslations { } String _defaultTimeFormatter(BuildContext context, DateTime date) => - "${date.hour}:${date.minute}"; + "${date.hour.toString().padLeft(2, '0')}:" + "${date.minute.toString().padLeft(2, '0')}"; +String _defaultDayMonthFormatter(BuildContext context, DateTime date) => + "${_getDayName(date.weekday)} ${date.day} ${_getMonthName(date.month)}"; + +String _defaultPeriodFormatter(BuildContext context, DateTimeRange range) => + "${range.start.day} ${_getMonthName(range.start.month)} to " + "${range.end.day} ${_getMonthName(range.end.month)}"; String _defaultWeekDayAbbreviatedFormatter( BuildContext context, DateTime date, @@ -201,16 +247,8 @@ String _defaultWeekDayAbbreviatedFormatter( String _defaultMonthYearFormatter(BuildContext context, DateTime date) => "${_getMonthName(date.month)} ${date.year}"; -String _getWeekDayAbbreviation(int weekday) => switch (weekday) { - 1 => "Mo", - 2 => "Tu", - 3 => "We", - 4 => "Th", - 5 => "Fr", - 6 => "Sa", - 7 => "Su", - _ => "", - }; +String _getWeekDayAbbreviation(int weekday) => + _getDayName(weekday).substring(0, 2); String _getMonthName(int month) => switch (month) { 1 => "January", @@ -227,3 +265,14 @@ String _getMonthName(int month) => switch (month) { 12 => "December", _ => "", }; + +String _getDayName(int day) => switch (day) { + 1 => "Monday", + 2 => "Tuesday", + 3 => "Wednesday", + 4 => "Thursday", + 5 => "Friday", + 6 => "Saturday", + 7 => "Sunday", + _ => "", + }; diff --git a/packages/flutter_availability/lib/src/ui/widgets/availability_clear.dart b/packages/flutter_availability/lib/src/ui/widgets/availability_clear.dart new file mode 100644 index 0000000..43b071c --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/widgets/availability_clear.dart @@ -0,0 +1,68 @@ +import "package:flutter/material.dart"; +import "package:flutter_availability/src/util/scope.dart"; + +/// +class AvailabilityClearSection extends StatelessWidget { + /// + const AvailabilityClearSection({ + required this.range, + required this.clearAvailable, + required this.onChanged, + super.key, + }); + + /// The date range for which the availabilities can be cleared + /// If the range is a single day, the range will be formatted as a single day + /// If the range is multiple days, the range will be formatted as a period + final DateTimeRange range; + + /// Whether the clear available checkbox should be checked + final bool clearAvailable; + + /// Callback for when the clear available checkbox is toggled + // ignore: avoid_positional_boolean_parameters + final void Function(bool isChecked) onChanged; + + @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 isSingleDay = range.start.isAtSameMomentAs(range.end); + + var titleText = isSingleDay + ? translations.dayMonthFormatter(context, range.start) + : translations.periodFormatter(context, range); + var unavailableText = isSingleDay + ? translations.unavailableForDay + : translations.unavailableForMultipleDays; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + titleText, + style: textTheme.titleMedium, + ), + Row( + children: [ + Checkbox( + value: clearAvailable, + onChanged: (value) { + if (value == null) return; + onChanged(value); + }, + ), + Text( + unavailableText, + style: textTheme.bodyMedium, + ), + ], + ), + ], + ); + } +} diff --git a/packages/flutter_availability/lib/src/ui/widgets/availability_template_selection.dart b/packages/flutter_availability/lib/src/ui/widgets/availability_template_selection.dart new file mode 100644 index 0000000..4b9a31e --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/widgets/availability_template_selection.dart @@ -0,0 +1,16 @@ +import "package:flutter/material.dart"; + +/// Selection of the template to use for the availability +/// +/// This can show multiple templates when the user selects a date range. +/// When updating the templates for a date range where there are multiple +/// different templates used, the user first needs to remove the existing +/// templates. +class AvailabilityTemplateSelection extends StatelessWidget { + /// Constructor + const AvailabilityTemplateSelection({super.key}); + + @override + Widget build(BuildContext context) => + const SizedBox(height: 50, child: Placeholder()); +} diff --git a/packages/flutter_availability/lib/src/ui/widgets/availabillity_time_selection.dart b/packages/flutter_availability/lib/src/ui/widgets/availabillity_time_selection.dart new file mode 100644 index 0000000..69a0bff --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/widgets/availabillity_time_selection.dart @@ -0,0 +1,52 @@ +import "package:flutter/material.dart"; +import "package:flutter_availability/src/ui/widgets/generic_time_selection.dart"; +import "package:flutter_availability/src/util/scope.dart"; + +/// +class AvailabilityTimeSelection extends StatelessWidget { + /// + const AvailabilityTimeSelection({ + required this.startTime, + required this.endTime, + required this.onStartChanged, + required this.onEndChanged, + required this.dateRange, + super.key, + }); + + /// + final DateTime? startTime; + + /// + final DateTime? endTime; + + /// + final void Function(DateTime) onStartChanged; + + /// + final void Function(DateTime) 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 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, + ); + } +} diff --git a/packages/flutter_availability/lib/src/ui/widgets/generic_time_selection.dart b/packages/flutter_availability/lib/src/ui/widgets/generic_time_selection.dart index 858148e..92dc6c6 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/generic_time_selection.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/generic_time_selection.dart @@ -20,7 +20,7 @@ class TimeSelection extends StatelessWidget { final String title; /// The description of the time selection - final String description; + final String? description; /// the axis aligment for the column final CrossAxisAlignment crossAxisAlignment; @@ -49,8 +49,10 @@ class TimeSelection extends StatelessWidget { crossAxisAlignment: crossAxisAlignment, children: [ Text(title, style: textTheme.titleMedium), - const SizedBox(height: 4), - Text(description, style: textTheme.bodyMedium), + if (description != null) ...[ + const SizedBox(height: 4), + Text(description!, style: textTheme.bodyMedium), + ], const SizedBox(height: 8), Row( children: [