diff --git a/packages/flutter_availability/lib/src/config/availability_translations.dart b/packages/flutter_availability/lib/src/config/availability_translations.dart index de30ce2..51a8908 100644 --- a/packages/flutter_availability/lib/src/config/availability_translations.dart +++ b/packages/flutter_availability/lib/src/config/availability_translations.dart @@ -38,6 +38,8 @@ class AvailabilityTranslations { required this.createWeekTemplate, required this.deleteTemplateButton, required this.dayTemplateTitle, + required this.weekTemplateTitle, + required this.weekTemplateDayTitle, required this.templateTitleHintText, required this.templateTitleLabel, required this.templateColorLabel, @@ -61,6 +63,7 @@ class AvailabilityTranslations { required this.periodFormatter, required this.monthYearFormatter, required this.weekDayAbbreviatedFormatter, + required this.weekDayFormatter, }); /// AvailabilityTranslations constructor where everything is optional. @@ -97,6 +100,8 @@ class AvailabilityTranslations { this.createWeekTemplate = "Create week template", this.deleteTemplateButton = "Delete template", this.dayTemplateTitle = "Day template", + this.weekTemplateTitle = "Week template", + this.weekTemplateDayTitle = "When", this.templateTitleHintText = "What do you want to call this template?", this.templateTitleLabel = "Template Title", this.templateColorLabel = "Colorlabel", @@ -123,6 +128,7 @@ class AvailabilityTranslations { this.periodFormatter = _defaultPeriodFormatter, this.monthYearFormatter = _defaultMonthYearFormatter, this.weekDayAbbreviatedFormatter = _defaultWeekDayAbbreviatedFormatter, + this.weekDayFormatter = _defaultWeekDayFormatter, this.timeFormatter = _defaultTimeFormatter, }); @@ -208,6 +214,12 @@ class AvailabilityTranslations { /// The title for the day template edit screen final String dayTemplateTitle; + /// The title for the week template edit screen + final String weekTemplateTitle; + + /// The title above the section with the week template days + final String weekTemplateDayTitle; + /// The hint text for the template title input field final String templateTitleHintText; @@ -286,12 +298,20 @@ class AvailabilityTranslations { /// the weekday in english 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; + /// Get the time formatted as a string /// /// The default implementation is `HH:mm` final String Function(BuildContext, TimeOfDay) timeFormatter; } +String _defaultWeekDayFormatter(BuildContext context, DateTime date) => + _getDayName(date.weekday); + String _defaultTimeFormatter(BuildContext context, TimeOfDay time) => "${time.hour.toString().padLeft(2, '0')}:" "${time.minute.toString().padLeft(2, '0')}"; 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 c55bec3..a100960 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/calendar_grid.dart @@ -37,11 +37,8 @@ class CalendarGrid extends StatelessWidget { var calendarDays = _generateCalendarDays(month, days, selectedRange, colors, colorScheme); - // get the names of the days of the week - var dayNames = List.generate(7, (index) { - var day = DateTime(2024, 7, 8 + index); // this is a monday - return translations.weekDayAbbreviatedFormatter(context, day); - }); + var dayNames = + getDaysOfTheWeekAsAbbreviatedStrings(translations, context); var calendarDaysRow = Row( children: List.generate(13, (index) { @@ -122,6 +119,31 @@ class CalendarGrid extends StatelessWidget { } } +/// Returns the days of the week as abbreviated strings +/// The first day of the week is monday +List getDaysOfTheWeekAsAbbreviatedStrings( + AvailabilityTranslations translations, + BuildContext context, +) { + var dayNames = List.generate(7, (index) { + var day = DateTime(2024, 7, 8 + index); // this is a monday + return translations.weekDayAbbreviatedFormatter(context, day); + }); + return dayNames; +} + +/// Returns the days of the week as strings +List getDaysOfTheWeekAsStrings( + AvailabilityTranslations translations, + BuildContext context, +) { + var dayNames = List.generate(7, (index) { + var day = DateTime(2024, 7, 8 + index); // this is a monday + return translations.weekDayFormatter(context, day); + }); + return dayNames; +} + /// A Special day in the calendar that needs to be displayed differently class CalendarDay { /// diff --git a/packages/flutter_availability/lib/src/ui/widgets/template_week_day_selection.dart b/packages/flutter_availability/lib/src/ui/widgets/template_week_day_selection.dart new file mode 100644 index 0000000..0aedb5d --- /dev/null +++ b/packages/flutter_availability/lib/src/ui/widgets/template_week_day_selection.dart @@ -0,0 +1,150 @@ +// ignore_for_file: avoid_positional_boolean_parameters + +import "package:flutter/material.dart"; +import "package:flutter_availability/src/ui/widgets/calendar_grid.dart"; +import "package:flutter_availability/src/util/scope.dart"; + +/// A widget for selecting a day of the week +class TemplateWeekDaySelection extends StatefulWidget { + /// Creates a [TemplateWeekDaySelection] + const TemplateWeekDaySelection({ + required this.onDaySelected, + super.key, + }); + + /// Callback for when a day is selected + final void Function(int) onDaySelected; + + @override + State createState() => + _TemplateWeekDaySelectionState(); +} + +class _TemplateWeekDaySelectionState extends State { + int _selectedDayIndex = 0; + + @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 days = getDaysOfTheWeekAsAbbreviatedStrings(translations, context); + + void onDaySelected(bool selected, int index) { + if (!selected) return; + widget.onDaySelected(index); + setState(() { + _selectedDayIndex = index; + }); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(translations.weekTemplateDayTitle, style: textTheme.titleMedium), + const SizedBox(height: 8), + SizedBox( + height: 72, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + for (var day in days) ...[ + _DaySelectionCard( + day: day, + days: days, + selectedDayIndex: _selectedDayIndex, + onDaySelected: (selected) => + onDaySelected(selected, days.indexOf(day)), + ), + ], + ], + ), + ), + ), + ], + ); + } +} + +class _DaySelectionCard extends StatelessWidget { + const _DaySelectionCard({ + required this.selectedDayIndex, + required this.day, + required this.days, + required this.onDaySelected, + }); + + final String day; + final List days; + + final int selectedDayIndex; + + final void Function(bool) onDaySelected; + + @override + Widget build(BuildContext context) { + var index = days.indexOf(day); + var isSelected = index == selectedDayIndex; + + return _DaySelectionCardLayout( + day: day, + isSelected: isSelected, + onDaySelected: onDaySelected, + ); + } +} + +class _DaySelectionCardLayout extends StatelessWidget { + const _DaySelectionCardLayout({ + required this.day, + required this.isSelected, + required this.onDaySelected, + }); + + final String day; + final bool isSelected; + + final void Function(bool) onDaySelected; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + var textTheme = theme.textTheme; + var abbreviationTextStyle = textTheme.headlineMedium; + + abbreviationTextStyle = isSelected + ? abbreviationTextStyle?.copyWith( + color: theme.colorScheme.onPrimary, + ) + : abbreviationTextStyle; + + return Padding( + padding: const EdgeInsets.only(left: 8.0), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + height: isSelected ? 72 : 64, + width: isSelected ? 72 : 64, + child: ChoiceChip( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + padding: EdgeInsets.zero, + label: Center( + child: Text( + day.toUpperCase(), + style: abbreviationTextStyle, + ), + ), + selected: isSelected, + showCheckmark: theme.chipTheme.showCheckmark ?? false, + onSelected: onDaySelected, + ), + ), + ); + } +}