mirror of
https://github.com/Iconica-Development/flutter_availability.git
synced 2025-05-19 05:03:44 +02:00
fix: use template viewmodels for the UI state
This commit is contained in:
parent
fd81964b75
commit
313a402409
11 changed files with 387 additions and 169 deletions
|
@ -161,7 +161,7 @@ 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
|
||||
/// 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;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/service/availability_service.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/availability_clear.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/availability_template_selection.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/availabillity_time_selection.dart";
|
||||
|
@ -172,12 +173,16 @@ class _AvailabilitiesModificationScreenState
|
|||
);
|
||||
|
||||
var pauseSelection = PauseSelection(
|
||||
breaks: _availability.breaks,
|
||||
breaks: _availability.breaks
|
||||
.map(BreakViewModel.fromAvailabilityBreakModel)
|
||||
.toList(),
|
||||
editingTemplate: false,
|
||||
// TODO(Joey): Extract these
|
||||
onBreaksChanged: (breaks) {
|
||||
setState(() {
|
||||
_availability = _availability.copyWith(breaks: breaks);
|
||||
_availability = _availability.copyWith(
|
||||
breaks: breaks.map((b) => b.toBreak()).toList(),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/ui/models/view_template_daydata.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/day_template_view_model.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";
|
||||
|
@ -28,25 +28,16 @@ class DayTemplateModificationScreen extends StatefulWidget {
|
|||
|
||||
class _DayTemplateModificationScreenState
|
||||
extends State<DayTemplateModificationScreen> {
|
||||
late int? _selectedColor;
|
||||
late AvailabilityTemplateModel _template;
|
||||
late DayTemplateViewModel _viewModel;
|
||||
|
||||
@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: [],
|
||||
),
|
||||
);
|
||||
if (widget.template != null) {
|
||||
_viewModel = DayTemplateViewModel.fromTemplate(widget.template!);
|
||||
} else {
|
||||
_viewModel = const DayTemplateViewModel();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -59,20 +50,21 @@ class _DayTemplateModificationScreenState
|
|||
var spacing = options.spacing;
|
||||
|
||||
Future<void> onDeletePressed() async {
|
||||
await service.deleteTemplate(_template);
|
||||
await service.deleteTemplate(widget.template!);
|
||||
widget.onExit();
|
||||
}
|
||||
|
||||
Future<void> onSavePressed() async {
|
||||
var template = _viewModel.toTemplate();
|
||||
if (widget.template == null) {
|
||||
await service.createTemplate(_template);
|
||||
await service.createTemplate(template);
|
||||
} else {
|
||||
await service.updateTemplate(_template);
|
||||
await service.updateTemplate(template);
|
||||
}
|
||||
widget.onExit();
|
||||
}
|
||||
|
||||
var canSave = _template.name.isNotEmpty && _selectedColor != null;
|
||||
var canSave = _viewModel.canSave;
|
||||
|
||||
var saveButton = options.primaryButtonBuilder(
|
||||
context,
|
||||
|
@ -94,34 +86,29 @@ class _DayTemplateModificationScreenState
|
|||
);
|
||||
|
||||
var templateTitleSection = TemplateNameInput(
|
||||
initialValue: _template.name,
|
||||
initialValue: _viewModel.name,
|
||||
onNameChanged: (name) {
|
||||
setState(() {
|
||||
_template = _template.copyWith(name: name);
|
||||
_viewModel = _viewModel.copyWith(name: name);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
var colorSection = TemplateColorSelection(
|
||||
selectedColor: _selectedColor,
|
||||
selectedColor: _viewModel.color,
|
||||
// TODO(Joey): Extract this
|
||||
onColorSelected: (color) {
|
||||
setState(() {
|
||||
_selectedColor = color;
|
||||
_template = _template.copyWith(color: color);
|
||||
_viewModel = _viewModel.copyWith(color: color);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
var availabilitySection = TemplateTimeAndBreakSection(
|
||||
dayData: ViewDayTemplateData.fromDayTemplateData(
|
||||
_template.templateData as DayTemplateData,
|
||||
),
|
||||
dayData: _viewModel.data,
|
||||
onDayDataChanged: (data) {
|
||||
setState(() {
|
||||
_template = _template.copyWith(
|
||||
templateData: data.toDayTemplateData(),
|
||||
);
|
||||
_viewModel = _viewModel.copyWith(data: data);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/ui/models/view_template_daydata.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/week_template_view_models.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";
|
||||
|
@ -30,23 +31,16 @@ class WeekTemplateModificationScreen extends StatefulWidget {
|
|||
|
||||
class _WeekTemplateModificationScreenState
|
||||
extends State<WeekTemplateModificationScreen> {
|
||||
late int? _selectedColor;
|
||||
late AvailabilityTemplateModel _template;
|
||||
bool _editing = true;
|
||||
WeekDay _selectedDay = WeekDay.monday;
|
||||
late WeekTemplateViewModel _viewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedColor = widget.template?.color;
|
||||
_template = widget.template ??
|
||||
AvailabilityTemplateModel(
|
||||
userId: "1",
|
||||
name: "",
|
||||
color: 0,
|
||||
templateType: AvailabilityTemplateType.week,
|
||||
templateData: WeekTemplateData.forDays(),
|
||||
);
|
||||
_viewModel = widget.template != null
|
||||
? WeekTemplateViewModel.fromTemplate(widget.template!)
|
||||
: const WeekTemplateViewModel();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -59,11 +53,31 @@ class _WeekTemplateModificationScreenState
|
|||
var translations = options.translations;
|
||||
var spacing = options.spacing;
|
||||
|
||||
var weekTemplateDate = _template.templateData as WeekTemplateData;
|
||||
var selectedDayData = weekTemplateDate.data[_selectedDay];
|
||||
var weekTemplateDate = _viewModel.data;
|
||||
var selectedDayData = weekTemplateDate[_selectedDay];
|
||||
|
||||
void onDayDataChanged(DayTemplateDataViewModel data) {
|
||||
setState(() {
|
||||
// create a new copy of an unmodifiable map that can be modified
|
||||
var updatedDays =
|
||||
Map<WeekDay, DayTemplateDataViewModel>.from(weekTemplateDate);
|
||||
if (data.isEmpty) {
|
||||
updatedDays.remove(_selectedDay);
|
||||
} else {
|
||||
updatedDays[_selectedDay] = data;
|
||||
}
|
||||
_viewModel = _viewModel.copyWith(data: updatedDays);
|
||||
});
|
||||
}
|
||||
|
||||
void onDaySelected(int day) {
|
||||
setState(() {
|
||||
_selectedDay = WeekDay.values[day];
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> onDeletePressed() async {
|
||||
await service.deleteTemplate(_template);
|
||||
await service.deleteTemplate(widget.template!);
|
||||
widget.onExit();
|
||||
}
|
||||
|
||||
|
@ -84,15 +98,20 @@ class _WeekTemplateModificationScreenState
|
|||
}
|
||||
|
||||
Future<void> onSavePressed() async {
|
||||
if (!_viewModel.isValid) {
|
||||
// TODO(freek): show error message
|
||||
return;
|
||||
}
|
||||
var template = _viewModel.toTemplate();
|
||||
if (widget.template == null) {
|
||||
await service.createTemplate(_template);
|
||||
await service.createTemplate(template);
|
||||
} else {
|
||||
await service.updateTemplate(_template);
|
||||
await service.updateTemplate(template);
|
||||
}
|
||||
widget.onExit();
|
||||
}
|
||||
|
||||
var canSave = _template.name.isNotEmpty && _selectedColor != null;
|
||||
var canSave = _viewModel.canSave;
|
||||
var nextButton = options.primaryButtonBuilder(
|
||||
context,
|
||||
canSave ? onNextPressed : null,
|
||||
|
@ -125,20 +144,19 @@ class _WeekTemplateModificationScreenState
|
|||
);
|
||||
|
||||
var templateTitleSection = TemplateNameInput(
|
||||
initialValue: _template.name,
|
||||
initialValue: _viewModel.name,
|
||||
onNameChanged: (name) {
|
||||
setState(() {
|
||||
_template = _template.copyWith(name: name);
|
||||
_viewModel = _viewModel.copyWith(name: name);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
var colorSection = TemplateColorSelection(
|
||||
selectedColor: _selectedColor,
|
||||
selectedColor: _viewModel.color,
|
||||
onColorSelected: (color) {
|
||||
setState(() {
|
||||
_selectedColor = color;
|
||||
_template = _template.copyWith(color: color);
|
||||
_viewModel = _viewModel.copyWith(color: color);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -148,37 +166,13 @@ class _WeekTemplateModificationScreenState
|
|||
const SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: spacing.sidePadding),
|
||||
child: TemplateWeekDaySelection(
|
||||
onDaySelected: (day) {
|
||||
setState(() {
|
||||
_selectedDay = WeekDay.values[day];
|
||||
});
|
||||
},
|
||||
),
|
||||
child: TemplateWeekDaySelection(onDaySelected: onDaySelected),
|
||||
),
|
||||
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(),
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
dayData: selectedDayData ?? const DayTemplateDataViewModel(),
|
||||
onDayDataChanged: onDayDataChanged,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
@ -203,20 +197,20 @@ class _WeekTemplateModificationScreenState
|
|||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(_template.color),
|
||||
color: Color(_viewModel.color ?? 0),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(_template.name, style: textTheme.bodyLarge),
|
||||
Text(_viewModel.name ?? "", style: textTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
TemplateWeekOverview(
|
||||
template: _template,
|
||||
template: _viewModel,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
|
||||
/// View model to represent the data during the modification of a break
|
||||
class BreakViewModel {
|
||||
/// Constructor
|
||||
const BreakViewModel({
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.duration,
|
||||
});
|
||||
|
||||
/// Create a [BreakViewModel] from a [AvailabilityBreakModel]
|
||||
factory BreakViewModel.fromAvailabilityBreakModel(
|
||||
AvailabilityBreakModel data,
|
||||
) =>
|
||||
BreakViewModel(
|
||||
startTime: TimeOfDay.fromDateTime(data.startTime),
|
||||
endTime: TimeOfDay.fromDateTime(data.endTime),
|
||||
duration: data.duration,
|
||||
);
|
||||
|
||||
/// The start time for this break
|
||||
final TimeOfDay? startTime;
|
||||
|
||||
/// The end time for this break
|
||||
final TimeOfDay? endTime;
|
||||
|
||||
/// The full duration of the actual break.
|
||||
///
|
||||
/// This is allowed to diverge from the difference between [startTime] and
|
||||
/// [endTime] to indicate that the break is somewhere between [startTime] and
|
||||
/// [endTime]
|
||||
final Duration? duration;
|
||||
|
||||
/// Returns true if the break is valid
|
||||
/// The start is before the end and the duration is equal or lower than the
|
||||
/// difference between the start and end
|
||||
bool get isValid {
|
||||
if (startTime == null || endTime == null) {
|
||||
return false;
|
||||
}
|
||||
var startDateTime = DateTime(0, 1, 1, startTime!.hour, startTime!.minute);
|
||||
var endDateTime = DateTime(0, 1, 1, endTime!.hour, endTime!.minute);
|
||||
|
||||
if (startDateTime.isAfter(endDateTime)) {
|
||||
return false;
|
||||
}
|
||||
if (duration == null) {
|
||||
return true;
|
||||
}
|
||||
var actualDuration = endDateTime.difference(startDateTime);
|
||||
return duration! <= actualDuration;
|
||||
}
|
||||
|
||||
/// Whether the save/next button should be enabled
|
||||
bool get canSave => startTime != null && endTime != null;
|
||||
|
||||
/// Convert to [AvailabilityBreakModel] for saving
|
||||
AvailabilityBreakModel toBreak() => AvailabilityBreakModel(
|
||||
startTime: DateTime(0, 0, 0, startTime!.hour, startTime!.minute),
|
||||
endTime: DateTime(0, 0, 0, endTime!.hour, endTime!.minute),
|
||||
duration: duration,
|
||||
);
|
||||
|
||||
/// Create a copy with new values
|
||||
BreakViewModel copyWith({
|
||||
TimeOfDay? startTime,
|
||||
TimeOfDay? endTime,
|
||||
Duration? duration,
|
||||
}) =>
|
||||
BreakViewModel(
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
duration: duration ?? this.duration,
|
||||
);
|
||||
|
||||
/// compareto method to order two breaks based on their start time
|
||||
/// multiples hours are converted to minutes and added to the minutes
|
||||
int compareTo(BreakViewModel other) {
|
||||
var difference = startTime!.hour * 60 +
|
||||
startTime!.minute -
|
||||
other.startTime!.hour * 60 -
|
||||
other.startTime!.minute;
|
||||
return difference;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
|
||||
/// View model to represent the data during the modification of a day template
|
||||
class DayTemplateViewModel {
|
||||
/// Constructor
|
||||
const DayTemplateViewModel({
|
||||
this.data = const DayTemplateDataViewModel(),
|
||||
this.id,
|
||||
this.name,
|
||||
this.color,
|
||||
});
|
||||
|
||||
/// Create a [WeekTemplateViewModel] from a [AvailabilityTemplateModel]
|
||||
factory DayTemplateViewModel.fromTemplate(
|
||||
AvailabilityTemplateModel template,
|
||||
) {
|
||||
var data = template.templateData as DayTemplateData;
|
||||
return DayTemplateViewModel(
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
color: template.color,
|
||||
data: DayTemplateDataViewModel.fromDayTemplateData(data),
|
||||
);
|
||||
}
|
||||
|
||||
/// The identifier for this template
|
||||
final String? id;
|
||||
|
||||
/// The name by which the template can be visually identified
|
||||
final String? name;
|
||||
|
||||
/// The color by which the template can be visually identified
|
||||
final int? color;
|
||||
|
||||
/// The data for the template day
|
||||
final DayTemplateDataViewModel data;
|
||||
|
||||
/// Whether the data is valid for saving
|
||||
bool get isValid => data.isValid;
|
||||
|
||||
/// Whether the save/next button should be enabled
|
||||
bool get canSave =>
|
||||
color != null && (name?.isNotEmpty ?? false) && data.canSave;
|
||||
|
||||
/// Convert to [AvailabilityTemplateModel] for saving
|
||||
AvailabilityTemplateModel toTemplate() => AvailabilityTemplateModel(
|
||||
id: id,
|
||||
userId: "",
|
||||
name: name!,
|
||||
color: color!,
|
||||
templateType: AvailabilityTemplateType.day,
|
||||
templateData: data.toDayTemplateData(),
|
||||
);
|
||||
|
||||
/// Create a copy with new values
|
||||
DayTemplateViewModel copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
int? color,
|
||||
DayTemplateDataViewModel? data,
|
||||
}) =>
|
||||
DayTemplateViewModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
data: data ?? this.data,
|
||||
);
|
||||
}
|
|
@ -1,21 +1,23 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
|
||||
/// The data for creating or editing a day template
|
||||
class ViewDayTemplateData {
|
||||
class DayTemplateDataViewModel {
|
||||
/// Constructor
|
||||
const ViewDayTemplateData({
|
||||
const DayTemplateDataViewModel({
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.breaks = const [],
|
||||
});
|
||||
|
||||
/// Create a new instance from a [DayTemplateData]
|
||||
factory ViewDayTemplateData.fromDayTemplateData(DayTemplateData data) =>
|
||||
ViewDayTemplateData(
|
||||
factory DayTemplateDataViewModel.fromDayTemplateData(DayTemplateData data) =>
|
||||
DayTemplateDataViewModel(
|
||||
startTime: TimeOfDay.fromDateTime(data.startTime),
|
||||
endTime: TimeOfDay.fromDateTime(data.endTime),
|
||||
breaks: data.breaks,
|
||||
breaks:
|
||||
data.breaks.map(BreakViewModel.fromAvailabilityBreakModel).toList(),
|
||||
);
|
||||
|
||||
/// The start time to apply on a new availability
|
||||
|
@ -25,18 +27,28 @@ class ViewDayTemplateData {
|
|||
final TimeOfDay? endTime;
|
||||
|
||||
/// A list of breaks to apply to every new availability
|
||||
final List<AvailabilityBreakModel> breaks;
|
||||
final List<BreakViewModel> breaks;
|
||||
|
||||
/// Whether the data is valid for saving
|
||||
bool get isValid => startTime != null && endTime != null;
|
||||
/// Whether the data is valid
|
||||
/// The start is before the end
|
||||
/// There are no breaks that are invalid
|
||||
/// The breaks are not outside the start and end time
|
||||
/// The breaks are not overlapping
|
||||
bool get isValid => canSave && breaks.every((b) => b.isValid);
|
||||
|
||||
/// Whether the save/next button should be enabled
|
||||
bool get canSave => startTime != null && endTime != null;
|
||||
|
||||
/// Whether the day is empty and can be removed from the template when saving
|
||||
bool get isEmpty => startTime == null && endTime == null && breaks.isEmpty;
|
||||
|
||||
/// Create a copy with new values
|
||||
ViewDayTemplateData copyWith({
|
||||
DayTemplateDataViewModel copyWith({
|
||||
TimeOfDay? startTime,
|
||||
TimeOfDay? endTime,
|
||||
List<AvailabilityBreakModel>? breaks,
|
||||
List<BreakViewModel>? breaks,
|
||||
}) =>
|
||||
ViewDayTemplateData(
|
||||
DayTemplateDataViewModel(
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
breaks: breaks ?? this.breaks,
|
||||
|
@ -47,6 +59,6 @@ class ViewDayTemplateData {
|
|||
startTime:
|
||||
DateTime(0, 0, 0, startTime?.hour ?? 0, startTime?.minute ?? 0),
|
||||
endTime: DateTime(0, 0, 0, endTime?.hour ?? 0, endTime?.minute ?? 0),
|
||||
breaks: breaks,
|
||||
breaks: breaks.map((b) => b.toBreak()).toList(),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
|
||||
/// View model to represent the data during the modification of a week template
|
||||
class WeekTemplateViewModel {
|
||||
/// Constructor
|
||||
const WeekTemplateViewModel({
|
||||
this.data = const {},
|
||||
this.id,
|
||||
this.name,
|
||||
this.color,
|
||||
});
|
||||
|
||||
/// Create a [WeekTemplateViewModel] from a [AvailabilityTemplateModel]
|
||||
factory WeekTemplateViewModel.fromTemplate(
|
||||
AvailabilityTemplateModel template,
|
||||
) {
|
||||
var data = template.templateData as WeekTemplateData;
|
||||
return WeekTemplateViewModel(
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
color: template.color,
|
||||
data: {
|
||||
for (var day in WeekDay.values)
|
||||
day: data.data.containsKey(day)
|
||||
? DayTemplateDataViewModel.fromDayTemplateData(
|
||||
data.data[day]!,
|
||||
)
|
||||
: const DayTemplateDataViewModel(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// The identifier for this template
|
||||
final String? id;
|
||||
|
||||
/// The name by which the template can be visually identified
|
||||
final String? name;
|
||||
|
||||
/// The color by which the template can be visually identified
|
||||
final int? color;
|
||||
|
||||
/// The data for each day of the week
|
||||
final Map<WeekDay, DayTemplateDataViewModel> data;
|
||||
|
||||
/// Whether the data is valid for saving
|
||||
/// All days must be valid and there must be at least one day with data
|
||||
bool get isValid =>
|
||||
data.values.every((e) => e.isValid) && data.values.isNotEmpty;
|
||||
|
||||
/// Whether the save/next button should be enabled
|
||||
bool get canSave =>
|
||||
color != null && (name?.isNotEmpty ?? false) && data.values.isNotEmpty;
|
||||
|
||||
/// Convert to [AvailabilityTemplateModel] for saving
|
||||
AvailabilityTemplateModel toTemplate() => AvailabilityTemplateModel(
|
||||
id: id,
|
||||
userId: "",
|
||||
name: name!,
|
||||
color: color!,
|
||||
templateType: AvailabilityTemplateType.week,
|
||||
templateData: WeekTemplateData(
|
||||
data: {
|
||||
for (var entry in data.entries)
|
||||
entry.key: entry.value.toDayTemplateData(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
/// Create a copy with new values
|
||||
WeekTemplateViewModel copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
int? color,
|
||||
Map<WeekDay, DayTemplateDataViewModel>? data,
|
||||
}) =>
|
||||
WeekTemplateViewModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
data: data ?? this.data,
|
||||
);
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/flutter_availability.dart";
|
||||
import "package:flutter_availability/src/service/availability_service.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/generic_time_selection.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/input_fields.dart";
|
||||
import "package:flutter_availability/src/util/scope.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
|
||||
///
|
||||
class PauseSelection extends StatelessWidget {
|
||||
|
@ -17,10 +17,10 @@ class PauseSelection extends StatelessWidget {
|
|||
});
|
||||
|
||||
/// The breaks that are currently set
|
||||
final List<AvailabilityBreakModel> breaks;
|
||||
final List<BreakViewModel> breaks;
|
||||
|
||||
/// Callback for when the breaks are changed
|
||||
final void Function(List<AvailabilityBreakModel>) onBreaksChanged;
|
||||
final void Function(List<BreakViewModel>) onBreaksChanged;
|
||||
|
||||
/// Whether the pause selection is used for editing a template
|
||||
final bool editingTemplate;
|
||||
|
@ -33,11 +33,12 @@ class PauseSelection extends StatelessWidget {
|
|||
var options = availabilityScope.options;
|
||||
var translations = options.translations;
|
||||
|
||||
Future<AvailabilityBreakModel?> openBreakDialog(
|
||||
AvailabilityBreakModel? initialBreak,
|
||||
Future<BreakViewModel?> openBreakDialog(
|
||||
BreakViewModel? initialBreak,
|
||||
) async =>
|
||||
AvailabilityBreakSelectionDialog.show(
|
||||
context,
|
||||
initialBreak: initialBreak,
|
||||
userId: availabilityScope.userId,
|
||||
options: options,
|
||||
service: availabilityScope.service,
|
||||
|
@ -52,7 +53,7 @@ class PauseSelection extends StatelessWidget {
|
|||
onBreaksChanged(updatedBreaks);
|
||||
}
|
||||
|
||||
Future<void> onEditBreak(AvailabilityBreakModel availabilityBreak) async {
|
||||
Future<void> onEditBreak(BreakViewModel availabilityBreak) async {
|
||||
var updatedBreak = await openBreakDialog(availabilityBreak);
|
||||
if (updatedBreak == null) return;
|
||||
|
||||
|
@ -60,13 +61,12 @@ class PauseSelection extends StatelessWidget {
|
|||
onBreaksChanged(updatedBreaks);
|
||||
}
|
||||
|
||||
void onDeleteBreak(AvailabilityBreakModel availabilityBreak) {
|
||||
void onDeleteBreak(BreakViewModel availabilityBreak) {
|
||||
var updatedBreaks = breaks.where((b) => b != availabilityBreak).toList();
|
||||
onBreaksChanged(updatedBreaks);
|
||||
}
|
||||
|
||||
var sortedBreaks = breaks.toList()
|
||||
..sort((a, b) => a.startTime.compareTo(b.startTime));
|
||||
var sortedBreaks = breaks.toList()..sort((a, b) => a.compareTo(b));
|
||||
|
||||
var addButton = options.bigTextButtonWrapperBuilder(
|
||||
context,
|
||||
|
@ -120,7 +120,7 @@ class BreakDisplay extends StatelessWidget {
|
|||
});
|
||||
|
||||
/// The break to display
|
||||
final AvailabilityBreakModel breakModel;
|
||||
final BreakViewModel breakModel;
|
||||
|
||||
/// Callback for when the minus button is clicked
|
||||
final VoidCallback onRemove;
|
||||
|
@ -138,11 +138,11 @@ class BreakDisplay extends StatelessWidget {
|
|||
|
||||
var starTime = translations.timeFormatter(
|
||||
context,
|
||||
TimeOfDay.fromDateTime(breakModel.startTime),
|
||||
breakModel.startTime!,
|
||||
);
|
||||
var endTime = translations.timeFormatter(
|
||||
context,
|
||||
TimeOfDay.fromDateTime(breakModel.endTime),
|
||||
breakModel.endTime!,
|
||||
);
|
||||
|
||||
// TODO(Joey): Watch out with gesture detectors
|
||||
|
@ -158,7 +158,7 @@ class BreakDisplay extends StatelessWidget {
|
|||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${breakModel.duration.inMinutes} "
|
||||
"${breakModel.duration!.inMinutes} "
|
||||
"${translations.timeMinutes} | "
|
||||
"$starTime - "
|
||||
"$endTime",
|
||||
|
@ -183,22 +183,22 @@ class AvailabilityBreakSelectionDialog extends StatefulWidget {
|
|||
});
|
||||
|
||||
/// The initial break to show in the dialog if any
|
||||
final AvailabilityBreakModel? initialBreak;
|
||||
final BreakViewModel? initialBreak;
|
||||
|
||||
/// Whether the dialog is used to edit a template
|
||||
/// This will change the description of the dialog
|
||||
final bool editingTemplate;
|
||||
|
||||
/// Opens the dialog to add a break
|
||||
static Future<AvailabilityBreakModel?> show(
|
||||
static Future<BreakViewModel?> show(
|
||||
BuildContext context, {
|
||||
required AvailabilityOptions options,
|
||||
required String userId,
|
||||
required AvailabilityService service,
|
||||
required bool editingTemplate,
|
||||
AvailabilityBreakModel? initialBreak,
|
||||
BreakViewModel? initialBreak,
|
||||
}) async =>
|
||||
showModalBottomSheet<AvailabilityBreakModel>(
|
||||
showModalBottomSheet<BreakViewModel>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
@ -225,20 +225,12 @@ class AvailabilityBreakSelectionDialog extends StatefulWidget {
|
|||
|
||||
class _AvailabilityBreakSelectionDialogState
|
||||
extends State<AvailabilityBreakSelectionDialog> {
|
||||
late TimeOfDay? _startTime;
|
||||
late TimeOfDay? _endTime;
|
||||
late Duration? _duration;
|
||||
late BreakViewModel _breakViewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startTime = widget.initialBreak != null
|
||||
? TimeOfDay.fromDateTime(widget.initialBreak!.startTime)
|
||||
: null;
|
||||
_endTime = widget.initialBreak != null
|
||||
? TimeOfDay.fromDateTime(widget.initialBreak!.endTime)
|
||||
: null;
|
||||
_duration = widget.initialBreak?.duration;
|
||||
_breakViewModel = widget.initialBreak ?? const BreakViewModel();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -252,44 +244,31 @@ class _AvailabilityBreakSelectionDialogState
|
|||
|
||||
void onUpdateDuration(Duration duration) {
|
||||
setState(() {
|
||||
_duration = duration;
|
||||
_breakViewModel = _breakViewModel.copyWith(duration: duration);
|
||||
});
|
||||
}
|
||||
|
||||
void onUpdateStart(TimeOfDay start) {
|
||||
setState(() {
|
||||
_startTime = start;
|
||||
_breakViewModel = _breakViewModel.copyWith(startTime: start);
|
||||
});
|
||||
}
|
||||
|
||||
void onUpdateEnd(TimeOfDay end) {
|
||||
setState(() {
|
||||
_endTime = end;
|
||||
_breakViewModel = _breakViewModel.copyWith(endTime: end);
|
||||
});
|
||||
}
|
||||
|
||||
var canSave = _startTime != null && _endTime != null;
|
||||
var canSave = _breakViewModel.canSave;
|
||||
|
||||
var onSaveButtonPress = canSave
|
||||
? () {
|
||||
var breakModel = AvailabilityBreakModel(
|
||||
startTime: DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
_startTime!.hour,
|
||||
_startTime!.minute,
|
||||
),
|
||||
endTime: DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month,
|
||||
DateTime.now().day,
|
||||
_endTime!.hour,
|
||||
_endTime!.minute,
|
||||
),
|
||||
duration: _duration,
|
||||
);
|
||||
Navigator.of(context).pop(breakModel);
|
||||
if (_breakViewModel.isValid) {
|
||||
Navigator.of(context).pop(_breakViewModel);
|
||||
}
|
||||
debugPrint("Break is not valid");
|
||||
// TODO(freek): show error message
|
||||
}
|
||||
: null;
|
||||
|
||||
|
@ -333,7 +312,7 @@ class _AvailabilityBreakSelectionDialogState
|
|||
Expanded(
|
||||
flex: 2,
|
||||
child: DurationInputField(
|
||||
initialValue: _duration,
|
||||
initialValue: _breakViewModel.duration,
|
||||
onDurationChanged: onUpdateDuration,
|
||||
),
|
||||
),
|
||||
|
@ -342,13 +321,15 @@ class _AvailabilityBreakSelectionDialogState
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
TimeSelection(
|
||||
key: ValueKey(
|
||||
[_breakViewModel.startTime, _breakViewModel.endTime],
|
||||
),
|
||||
// rebuild the widget when the start or end time changes
|
||||
key: ValueKey([_startTime, _endTime]),
|
||||
title: translations.pauseDialogPeriodTitle,
|
||||
description: translations.pauseDialogPeriodDescription,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
startTime: _startTime,
|
||||
endTime: _endTime,
|
||||
startTime: _breakViewModel.startTime,
|
||||
endTime: _breakViewModel.endTime,
|
||||
onStartChanged: onUpdateStart,
|
||||
onEndChanged: onUpdateEnd,
|
||||
),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/ui/models/view_template_daydata.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/pause_selection.dart";
|
||||
import "package:flutter_availability/src/ui/widgets/template_time_selection.dart";
|
||||
|
||||
|
@ -8,15 +8,15 @@ class TemplateTimeAndBreakSection extends StatelessWidget {
|
|||
///
|
||||
const TemplateTimeAndBreakSection({
|
||||
required this.onDayDataChanged,
|
||||
this.dayData = const ViewDayTemplateData(),
|
||||
this.dayData = const DayTemplateDataViewModel(),
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The day data to display and edit
|
||||
final ViewDayTemplateData dayData;
|
||||
final DayTemplateDataViewModel dayData;
|
||||
|
||||
/// Callback for when the day data changes
|
||||
final void Function(ViewDayTemplateData data) onDayDataChanged;
|
||||
final void Function(DayTemplateDataViewModel data) onDayDataChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/ui/models/view_template_daydata.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/break_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/template_daydata_view_model.dart";
|
||||
import "package:flutter_availability/src/ui/view_models/week_template_view_models.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";
|
||||
|
@ -13,7 +15,7 @@ class TemplateWeekOverview extends StatelessWidget {
|
|||
});
|
||||
|
||||
/// The template to show
|
||||
final AvailabilityTemplateModel template;
|
||||
final WeekTemplateViewModel template;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -26,7 +28,7 @@ class TemplateWeekOverview extends StatelessWidget {
|
|||
|
||||
var dayNames = getDaysOfTheWeekAsStrings(translations, context);
|
||||
|
||||
var templateData = template.templateData as WeekTemplateData;
|
||||
var templateData = template.data;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -50,11 +52,8 @@ class TemplateWeekOverview extends StatelessWidget {
|
|||
for (var day in WeekDay.values) ...[
|
||||
_TemplateDayDetailRow(
|
||||
dayName: dayNames[day.index],
|
||||
dayData: templateData.data.containsKey(day)
|
||||
? ViewDayTemplateData.fromDayTemplateData(
|
||||
templateData.data[day]!,
|
||||
)
|
||||
: null,
|
||||
dayData:
|
||||
templateData.containsKey(day) ? templateData[day] : null,
|
||||
isOdd: day.index.isOdd,
|
||||
),
|
||||
],
|
||||
|
@ -81,7 +80,7 @@ class _TemplateDayDetailRow extends StatelessWidget {
|
|||
final bool isOdd;
|
||||
|
||||
/// The data of the day
|
||||
final ViewDayTemplateData? dayData;
|
||||
final DayTemplateDataViewModel? dayData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -102,7 +101,7 @@ class _TemplateDayDetailRow extends StatelessWidget {
|
|||
dayPeriod = translations.unavailable;
|
||||
}
|
||||
|
||||
var breaks = dayData?.breaks ?? <AvailabilityBreakModel>[];
|
||||
var breaks = dayData?.breaks ?? <BreakViewModel>[];
|
||||
|
||||
BoxDecoration? boxDecoration;
|
||||
if (isOdd) {
|
||||
|
@ -134,7 +133,7 @@ class _TemplateDayDetailRow extends StatelessWidget {
|
|||
// for each break add a line
|
||||
for (var dayBreak in breaks) ...[
|
||||
const SizedBox(height: 4),
|
||||
_TemplateDayDetailPauseRow(dayBreak: dayBreak),
|
||||
_TemplateDayDetailPauseRow(dayBreakViewModel: dayBreak),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
@ -144,10 +143,10 @@ class _TemplateDayDetailRow extends StatelessWidget {
|
|||
|
||||
class _TemplateDayDetailPauseRow extends StatelessWidget {
|
||||
const _TemplateDayDetailPauseRow({
|
||||
required this.dayBreak,
|
||||
required this.dayBreakViewModel,
|
||||
});
|
||||
|
||||
final AvailabilityBreakModel dayBreak;
|
||||
final BreakViewModel dayBreakViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -157,6 +156,7 @@ class _TemplateDayDetailPauseRow extends StatelessWidget {
|
|||
var options = availabilityScope.options;
|
||||
var translations = options.translations;
|
||||
|
||||
var dayBreak = dayBreakViewModel.toBreak();
|
||||
var startTime = TimeOfDay.fromDateTime(dayBreak.startTime);
|
||||
var endTime = TimeOfDay.fromDateTime(dayBreak.endTime);
|
||||
var startTimeString = translations.timeFormatter(context, startTime);
|
||||
|
|
Loading…
Reference in a new issue