mirror of
https://github.com/Iconica-Development/flutter_availability.git
synced 2025-05-20 13:43:43 +02:00
feat: add day template edit/creation page
This commit is contained in:
parent
f2e279a592
commit
65e638d899
10 changed files with 594 additions and 4 deletions
|
@ -16,6 +16,7 @@ class AvailabilityOptions {
|
||||||
this.primaryButtonBuilder = DefaultPrimaryButton.builder,
|
this.primaryButtonBuilder = DefaultPrimaryButton.builder,
|
||||||
this.textButtonBuilder = DefaultTextButton.builder,
|
this.textButtonBuilder = DefaultTextButton.builder,
|
||||||
this.spacing = const AvailabilitySpacing(),
|
this.spacing = const AvailabilitySpacing(),
|
||||||
|
this.textStyles = const AvailabilityTextStyles(),
|
||||||
this.colors = const AvailabilityColors(),
|
this.colors = const AvailabilityColors(),
|
||||||
AvailabilityDataInterface? dataInterface,
|
AvailabilityDataInterface? dataInterface,
|
||||||
}) : dataInterface = dataInterface ?? LocalAvailabilityDataInterface();
|
}) : dataInterface = dataInterface ?? LocalAvailabilityDataInterface();
|
||||||
|
@ -42,6 +43,9 @@ class AvailabilityOptions {
|
||||||
/// The spacing between elements
|
/// The spacing between elements
|
||||||
final AvailabilitySpacing spacing;
|
final AvailabilitySpacing spacing;
|
||||||
|
|
||||||
|
/// The configurable text styles in the userstory
|
||||||
|
final AvailabilityTextStyles textStyles;
|
||||||
|
|
||||||
/// The colors used in the userstory
|
/// The colors used in the userstory
|
||||||
final AvailabilityColors colors;
|
final AvailabilityColors colors;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +65,18 @@ class AvailabilitySpacing {
|
||||||
final double sidePadding;
|
final double sidePadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All customizable text styles for the availability userstory
|
||||||
|
/// If text styles are not provided the text styles will be taken from the theme
|
||||||
|
class AvailabilityTextStyles {
|
||||||
|
/// Constructor for the AvailabilityTextStyles
|
||||||
|
const AvailabilityTextStyles({
|
||||||
|
this.inputFieldTextStyle,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The text style for the filled in text on all the input fields
|
||||||
|
final TextStyle? inputFieldTextStyle;
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains all the customizable colors for the availability userstory
|
/// Contains all the customizable colors for the availability userstory
|
||||||
///
|
///
|
||||||
/// If colors are not provided the colors will be taken from the theme
|
/// If colors are not provided the colors will be taken from the theme
|
||||||
|
@ -74,7 +90,14 @@ class AvailabilityColors {
|
||||||
this.outsideMonthTextColor,
|
this.outsideMonthTextColor,
|
||||||
this.textDarkColor,
|
this.textDarkColor,
|
||||||
this.textLightColor,
|
this.textLightColor,
|
||||||
this.templateColors,
|
this.templateColors = const [
|
||||||
|
Color(0xFF9bb8f2),
|
||||||
|
Color(0xFF4b77d0),
|
||||||
|
Color(0xFF283a5e),
|
||||||
|
Color(0xFF57947d),
|
||||||
|
Color(0xFFef6c75),
|
||||||
|
Color(0xFFb7198b),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The color of the text for the days that are not in the current month
|
/// The color of the text for the days that are not in the current month
|
||||||
|
@ -98,7 +121,7 @@ class AvailabilityColors {
|
||||||
final Color? textDarkColor;
|
final Color? textDarkColor;
|
||||||
|
|
||||||
/// The colors that are used for the templates
|
/// The colors that are used for the templates
|
||||||
final List<Color>? templateColors;
|
final List<Color> templateColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder definition for providing a base screen surrounding each page
|
/// Builder definition for providing a base screen surrounding each page
|
||||||
|
|
|
@ -22,6 +22,18 @@ class AvailabilityTranslations {
|
||||||
required this.weekTemplates,
|
required this.weekTemplates,
|
||||||
required this.createDayTemplate,
|
required this.createDayTemplate,
|
||||||
required this.createWeekTemplate,
|
required this.createWeekTemplate,
|
||||||
|
required this.deleteTemplateButton,
|
||||||
|
required this.dayTemplateTitle,
|
||||||
|
required this.templateTitleHintText,
|
||||||
|
required this.templateTitleLabel,
|
||||||
|
required this.templateColorLabel,
|
||||||
|
required this.time,
|
||||||
|
required this.timeSeparator,
|
||||||
|
required this.templateTimeLabel,
|
||||||
|
required this.pauseSectionTitle,
|
||||||
|
required this.saveButton,
|
||||||
|
required this.addButton,
|
||||||
|
required this.timeFormatter,
|
||||||
required this.monthYearFormatter,
|
required this.monthYearFormatter,
|
||||||
required this.weekDayAbbreviatedFormatter,
|
required this.weekDayAbbreviatedFormatter,
|
||||||
});
|
});
|
||||||
|
@ -40,8 +52,20 @@ class AvailabilityTranslations {
|
||||||
this.weekTemplates = "Week templates",
|
this.weekTemplates = "Week templates",
|
||||||
this.createDayTemplate = "Create day template",
|
this.createDayTemplate = "Create day template",
|
||||||
this.createWeekTemplate = "Create week template",
|
this.createWeekTemplate = "Create week template",
|
||||||
|
this.deleteTemplateButton = "Delete template",
|
||||||
|
this.dayTemplateTitle = "Day template",
|
||||||
|
this.templateTitleHintText = "What do you want to call this template?",
|
||||||
|
this.templateTitleLabel = "Template Title",
|
||||||
|
this.templateColorLabel = "Colorlabel",
|
||||||
|
this.time = "Time",
|
||||||
|
this.timeSeparator = "to",
|
||||||
|
this.templateTimeLabel = "When are you available?",
|
||||||
|
this.pauseSectionTitle = "Add a pause (optional)",
|
||||||
|
this.saveButton = "Save",
|
||||||
|
this.addButton = "Add",
|
||||||
this.monthYearFormatter = _defaultMonthYearFormatter,
|
this.monthYearFormatter = _defaultMonthYearFormatter,
|
||||||
this.weekDayAbbreviatedFormatter = _defaultWeekDayAbbreviatedFormatter,
|
this.weekDayAbbreviatedFormatter = _defaultWeekDayAbbreviatedFormatter,
|
||||||
|
this.timeFormatter = _defaultTimeFormatter,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The title shown above the calendar
|
/// The title shown above the calendar
|
||||||
|
@ -77,6 +101,39 @@ class AvailabilityTranslations {
|
||||||
/// The label for the button to create a new week template
|
/// The label for the button to create a new week template
|
||||||
final String createWeekTemplate;
|
final String createWeekTemplate;
|
||||||
|
|
||||||
|
/// The label on the button to delete a template
|
||||||
|
final String deleteTemplateButton;
|
||||||
|
|
||||||
|
/// The title for the day template edit screen
|
||||||
|
final String dayTemplateTitle;
|
||||||
|
|
||||||
|
/// The hint text for the template title input field
|
||||||
|
final String templateTitleHintText;
|
||||||
|
|
||||||
|
/// The label for the template title input field
|
||||||
|
final String templateTitleLabel;
|
||||||
|
|
||||||
|
/// The title above the color selection for templates
|
||||||
|
final String templateColorLabel;
|
||||||
|
|
||||||
|
/// The title for time sections
|
||||||
|
final String time;
|
||||||
|
|
||||||
|
/// The text between start and end time
|
||||||
|
final String timeSeparator;
|
||||||
|
|
||||||
|
/// The label for the template time input
|
||||||
|
final String templateTimeLabel;
|
||||||
|
|
||||||
|
/// The title for pause configuration sections
|
||||||
|
final String pauseSectionTitle;
|
||||||
|
|
||||||
|
/// The text on the save button
|
||||||
|
final String saveButton;
|
||||||
|
|
||||||
|
/// The text on the add button
|
||||||
|
final String addButton;
|
||||||
|
|
||||||
/// Gets the month and year formatted as a string
|
/// Gets the month and year formatted as a string
|
||||||
///
|
///
|
||||||
/// The default implementation is `MonthName Year` in english
|
/// The default implementation is `MonthName Year` in english
|
||||||
|
@ -87,8 +144,16 @@ class AvailabilityTranslations {
|
||||||
/// The default implementation is the first 2 letters of
|
/// The default implementation is the first 2 letters of
|
||||||
/// the weekday in english
|
/// the weekday in english
|
||||||
final String Function(BuildContext, DateTime) weekDayAbbreviatedFormatter;
|
final String Function(BuildContext, DateTime) weekDayAbbreviatedFormatter;
|
||||||
|
|
||||||
|
/// Get the time formatted as a string
|
||||||
|
///
|
||||||
|
/// The default implementation is `HH:mm`
|
||||||
|
final String Function(BuildContext, DateTime) timeFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _defaultTimeFormatter(BuildContext context, DateTime date) =>
|
||||||
|
"${date.hour}:${date.minute}";
|
||||||
|
|
||||||
String _defaultWeekDayAbbreviatedFormatter(
|
String _defaultWeekDayAbbreviatedFormatter(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
DateTime date,
|
DateTime date,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_availability/flutter_availability.dart";
|
import "package:flutter_availability/flutter_availability.dart";
|
||||||
import "package:flutter_availability/src/ui/screens/template_availability_day_overview.dart";
|
import "package:flutter_availability/src/ui/screens/template_availability_day_overview.dart";
|
||||||
|
import "package:flutter_availability/src/ui/screens/template_day_edit.dart";
|
||||||
import "package:flutter_availability/src/ui/screens/template_overview.dart";
|
import "package:flutter_availability/src/ui/screens/template_overview.dart";
|
||||||
|
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||||
|
|
||||||
///
|
///
|
||||||
MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute(
|
MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute(
|
||||||
|
@ -18,8 +20,25 @@ MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute(
|
||||||
MaterialPageRoute templateOverviewRoute() => MaterialPageRoute(
|
MaterialPageRoute templateOverviewRoute() => MaterialPageRoute(
|
||||||
builder: (context) => AvailabilityTemplateOverview(
|
builder: (context) => AvailabilityTemplateOverview(
|
||||||
onExit: () => Navigator.of(context).pop(),
|
onExit: () => Navigator.of(context).pop(),
|
||||||
onEditTemplate: (template) {},
|
onEditTemplate: (template) async {
|
||||||
onAddTemplate: (type) {},
|
if (template.templateType == AvailabilityTemplateType.day) {
|
||||||
|
await Navigator.of(context).push(templateEditDayRoute(template));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAddTemplate: (type) async {
|
||||||
|
if (type == AvailabilityTemplateType.day) {
|
||||||
|
await Navigator.of(context).push(templateEditDayRoute(null));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
///
|
||||||
|
MaterialPageRoute templateEditDayRoute(AvailabilityTemplateModel? template) =>
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => AvailabilityDayTemplateEdit(
|
||||||
|
template: template,
|
||||||
|
onExit: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,31 @@ class AvailabilityService {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new template
|
||||||
|
Future<void> createTemplate(AvailabilityTemplateModel template) async {
|
||||||
|
await dataInterface.createTemplateForUser(
|
||||||
|
userId,
|
||||||
|
template,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates a template
|
||||||
|
Future<void> updateTemplate(AvailabilityTemplateModel template) async {
|
||||||
|
await dataInterface.updateTemplateForUser(
|
||||||
|
userId,
|
||||||
|
template.id!,
|
||||||
|
template,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a template
|
||||||
|
Future<void> deleteTemplate(AvailabilityTemplateModel template) async {
|
||||||
|
await dataInterface.deleteTemplateForUser(
|
||||||
|
userId,
|
||||||
|
template.id!,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A combination of availability and template for a single day
|
/// A combination of availability and template for a single day
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
import "package:flutter/material.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_selection.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 AvailabilityDayTemplateEdit extends StatefulWidget {
|
||||||
|
///
|
||||||
|
const AvailabilityDayTemplateEdit({
|
||||||
|
required this.template,
|
||||||
|
required this.onExit,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The day 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<AvailabilityDayTemplateEdit> createState() =>
|
||||||
|
_AvailabilityDayTemplateEditState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AvailabilityDayTemplateEditState
|
||||||
|
extends State<AvailabilityDayTemplateEdit> {
|
||||||
|
late int? _selectedColor;
|
||||||
|
late AvailabilityTemplateModel _template;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_selectedColor = widget.template?.color;
|
||||||
|
_template = widget.template ??
|
||||||
|
AvailabilityTemplateModel(
|
||||||
|
userId: "1",
|
||||||
|
name: "",
|
||||||
|
color: 0,
|
||||||
|
templateType: AvailabilityTemplateType.day,
|
||||||
|
templateData: DayTemplateData(
|
||||||
|
startTime: DateTime.now(),
|
||||||
|
endTime: DateTime.now(),
|
||||||
|
breaks: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
var availabilityScope = AvailabilityScope.of(context);
|
||||||
|
var service = availabilityScope.service;
|
||||||
|
var options = availabilityScope.options;
|
||||||
|
var translations = options.translations;
|
||||||
|
var spacing = options.spacing;
|
||||||
|
|
||||||
|
Future<void> onDeletePressed() async {
|
||||||
|
await service.deleteTemplate(_template);
|
||||||
|
widget.onExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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 saveButton = options.primaryButtonBuilder(
|
||||||
|
context,
|
||||||
|
canSave ? onSavePressed : null,
|
||||||
|
Text(translations.saveButton),
|
||||||
|
);
|
||||||
|
|
||||||
|
var deleteButton = options.textButtonBuilder(
|
||||||
|
context,
|
||||||
|
onDeletePressed,
|
||||||
|
Text(translations.deleteTemplateButton),
|
||||||
|
);
|
||||||
|
|
||||||
|
var title = Center(
|
||||||
|
child: Text(
|
||||||
|
translations.dayTemplateTitle,
|
||||||
|
style: theme.textTheme.displaySmall,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
var templateTitleSection = TemplateNameInput(
|
||||||
|
initialValue: _template.name,
|
||||||
|
onNameChanged: (name) {
|
||||||
|
setState(() {
|
||||||
|
_template = _template.copyWith(name: name);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var timeSection = TemplateTimeSelection(
|
||||||
|
key: ValueKey(_template.templateData),
|
||||||
|
startTime: (_template.templateData as DayTemplateData).startTime,
|
||||||
|
endTime: (_template.templateData as DayTemplateData).endTime,
|
||||||
|
onStartChanged: (start) {
|
||||||
|
setState(() {
|
||||||
|
_template = _template.copyWith(
|
||||||
|
templateData: (_template.templateData as DayTemplateData).copyWith(
|
||||||
|
startTime: start,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEndChanged: (end) {
|
||||||
|
setState(() {
|
||||||
|
_template = _template.copyWith(
|
||||||
|
templateData: (_template.templateData as DayTemplateData).copyWith(
|
||||||
|
endTime: end,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var colorSection = TemplateColorSelection(
|
||||||
|
selectedColor: _selectedColor,
|
||||||
|
onColorSelected: (color) {
|
||||||
|
setState(() {
|
||||||
|
_selectedColor = color;
|
||||||
|
_template = _template.copyWith(color: color);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var pauseSection = const SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Placeholder(),
|
||||||
|
);
|
||||||
|
|
||||||
|
var body = CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: spacing.sidePadding),
|
||||||
|
sliver: SliverList.list(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
title,
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
templateTitleSection,
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
timeSection,
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
colorSection,
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
pauseSection,
|
||||||
|
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(
|
||||||
|
children: [
|
||||||
|
saveButton,
|
||||||
|
if (widget.template != null) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
deleteButton,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return options.baseScreenBuilder(context, widget.onExit, body);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_availability/src/util/scope.dart";
|
||||||
|
|
||||||
|
/// Widget for selecting a color for a template
|
||||||
|
/// All available colors for the templates are displayed in a wrap layout
|
||||||
|
class TemplateColorSelection extends StatelessWidget {
|
||||||
|
///
|
||||||
|
const TemplateColorSelection({
|
||||||
|
required this.selectedColor,
|
||||||
|
required this.onColorSelected,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The selected color for the template
|
||||||
|
/// If null, no color is selected
|
||||||
|
final int? selectedColor;
|
||||||
|
|
||||||
|
/// Callback for when a color is selected or deselected
|
||||||
|
final void Function(int?) onColorSelected;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
translations.templateColorLabel,
|
||||||
|
style: textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
for (var color in colors.templateColors)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => _onColorClick(color),
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(
|
||||||
|
color: color.value == selectedColor
|
||||||
|
? Colors.black
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: selectedColor == color.value
|
||||||
|
? const Icon(Icons.check)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the color is selected, deselect it, otherwise select it
|
||||||
|
void _onColorClick(Color color) => onColorSelected(
|
||||||
|
color.value == selectedColor ? null : color.value,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_availability/src/util/scope.dart";
|
||||||
|
|
||||||
|
/// An input field for time selection
|
||||||
|
class TimeInputField extends StatelessWidget {
|
||||||
|
///
|
||||||
|
const TimeInputField({
|
||||||
|
required this.initialValue,
|
||||||
|
required this.onTimeChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
///
|
||||||
|
final DateTime? initialValue;
|
||||||
|
|
||||||
|
///
|
||||||
|
final void Function(DateTime) onTimeChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
var availabilityScope = AvailabilityScope.of(context);
|
||||||
|
var options = availabilityScope.options;
|
||||||
|
var translations = options.translations;
|
||||||
|
|
||||||
|
Future<void> onFieldtap() async {
|
||||||
|
var time = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: TimeOfDay.fromDateTime(initialValue ?? DateTime.now()),
|
||||||
|
);
|
||||||
|
if (time != null) {
|
||||||
|
onTimeChanged(
|
||||||
|
DateTime(
|
||||||
|
initialValue?.year ?? DateTime.now().year,
|
||||||
|
initialValue?.month ?? DateTime.now().month,
|
||||||
|
initialValue?.day ?? DateTime.now().day,
|
||||||
|
time.hour,
|
||||||
|
time.minute,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: const Icon(Icons.access_time),
|
||||||
|
hintText: translations.time,
|
||||||
|
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
initialValue: initialValue != null
|
||||||
|
? translations.timeFormatter(context, initialValue!)
|
||||||
|
: null,
|
||||||
|
readOnly: true,
|
||||||
|
style: options.textStyles.inputFieldTextStyle,
|
||||||
|
onTap: onFieldtap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_availability/src/util/scope.dart";
|
||||||
|
|
||||||
|
/// Input section for the template name
|
||||||
|
class TemplateNameInput extends StatelessWidget {
|
||||||
|
///
|
||||||
|
const TemplateNameInput({
|
||||||
|
required this.initialValue,
|
||||||
|
required this.onNameChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The initial value for the template name
|
||||||
|
final String? initialValue;
|
||||||
|
|
||||||
|
/// callback for when the template name is changed
|
||||||
|
final void Function(String) onNameChanged;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
translations.templateTitleLabel,
|
||||||
|
style: textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: translations.templateTitleHintText,
|
||||||
|
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
initialValue: initialValue,
|
||||||
|
style: options.textStyles.inputFieldTextStyle,
|
||||||
|
onChanged: onNameChanged,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_availability/src/ui/widgets/input_fields.dart";
|
||||||
|
import "package:flutter_availability/src/util/scope.dart";
|
||||||
|
|
||||||
|
///
|
||||||
|
class TemplateTimeSelection extends StatelessWidget {
|
||||||
|
///
|
||||||
|
const TemplateTimeSelection({
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
required this.onStartChanged,
|
||||||
|
required this.onEndChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
///
|
||||||
|
final DateTime? startTime;
|
||||||
|
|
||||||
|
///
|
||||||
|
final DateTime? endTime;
|
||||||
|
|
||||||
|
///
|
||||||
|
final void Function(DateTime) onStartChanged;
|
||||||
|
|
||||||
|
///
|
||||||
|
final void Function(DateTime) onEndChanged;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(translations.time, style: textTheme.titleMedium),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(translations.templateTimeLabel, style: textTheme.bodyMedium),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: TimeInputField(
|
||||||
|
initialValue: startTime,
|
||||||
|
onTimeChanged: onStartChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
translations.timeSeparator,
|
||||||
|
style: textTheme.bodyMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: TimeInputField(
|
||||||
|
initialValue: endTime,
|
||||||
|
onTimeChanged: onEndChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -317,6 +317,18 @@ class DayTemplateData implements TemplateData {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// copy the current instance with new values
|
||||||
|
DayTemplateData copyWith({
|
||||||
|
DateTime? startTime,
|
||||||
|
DateTime? endTime,
|
||||||
|
List<AvailabilityBreakModel>? breaks,
|
||||||
|
}) =>
|
||||||
|
DayTemplateData(
|
||||||
|
startTime: startTime ?? this.startTime,
|
||||||
|
endTime: endTime ?? this.endTime,
|
||||||
|
breaks: breaks ?? this.breaks,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
"startTime": startTime.toIso8601String(),
|
"startTime": startTime.toIso8601String(),
|
||||||
|
|
Loading…
Reference in a new issue