mirror of
https://github.com/Iconica-Development/flutter_availability.git
synced 2025-05-19 13:13:44 +02:00
feat: update availability modification screen with new design
This commit is contained in:
parent
20104160eb
commit
4c75c8c260
8 changed files with 233 additions and 427 deletions
|
@ -1,5 +1,6 @@
|
||||||
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/service/availability_service.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_day_edit.dart";
|
||||||
import "package:flutter_availability/src/ui/screens/template_overview.dart";
|
import "package:flutter_availability/src/ui/screens/template_overview.dart";
|
||||||
|
@ -8,8 +9,8 @@ import "package:flutter_availability_data_interface/flutter_availability_data_in
|
||||||
///
|
///
|
||||||
MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute(
|
MaterialPageRoute homePageRoute(VoidCallback onExit) => MaterialPageRoute(
|
||||||
builder: (context) => AvailabilityOverview(
|
builder: (context) => AvailabilityOverview(
|
||||||
onEditDateRange: (range) async =>
|
onEditDateRange: (range, availabilities) async => Navigator.of(context)
|
||||||
Navigator.of(context).push(availabilityViewRoute(range.start)),
|
.push(availabilityViewRoute(range, availabilities)),
|
||||||
onViewTemplates: () async =>
|
onViewTemplates: () async =>
|
||||||
Navigator.of(context).push(templateOverviewRoute()),
|
Navigator.of(context).push(templateOverviewRoute()),
|
||||||
onExit: () => onExit(),
|
onExit: () => onExit(),
|
||||||
|
@ -44,12 +45,14 @@ MaterialPageRoute templateEditDayRoute(AvailabilityTemplateModel? template) =>
|
||||||
|
|
||||||
///
|
///
|
||||||
MaterialPageRoute availabilityViewRoute(
|
MaterialPageRoute availabilityViewRoute(
|
||||||
DateTime date,
|
DateTimeRange dateRange,
|
||||||
|
List<AvailabilityWithTemplate> initialAvailabilities,
|
||||||
) =>
|
) =>
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => AvailabilityDayOverview(
|
builder: (context) => AvailabilityModificationView(
|
||||||
date: date,
|
dateRange: dateRange,
|
||||||
onAvailabilitySaved: () {
|
initialAvailabilities: initialAvailabilities,
|
||||||
|
onExit: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -10,27 +10,59 @@ class AvailabilityService {
|
||||||
required this.dataInterface,
|
required this.dataInterface,
|
||||||
});
|
});
|
||||||
|
|
||||||
///
|
/// The user id for which the availabilities are managed
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
///
|
/// The data interface that is used to store and retrieve data
|
||||||
final AvailabilityDataInterface dataInterface;
|
final AvailabilityDataInterface dataInterface;
|
||||||
|
|
||||||
/// Creates a set of availabilities for the given [range], where every
|
/// Creates a set of availabilities for the given [range], where every
|
||||||
/// availability is a copy of [availability] with only date information
|
/// availability is a copy of [availability] with only date information
|
||||||
/// changed
|
/// changed
|
||||||
Future<void> createAvailability(
|
Future<void> createAvailability({
|
||||||
AvailabilityModel availability,
|
required AvailabilityModel availability,
|
||||||
DateTimeRange range,
|
required DateTimeRange range,
|
||||||
) async {
|
required TimeOfDay startTime,
|
||||||
|
required TimeOfDay endTime,
|
||||||
|
}) async {
|
||||||
|
// apply the startTime and endTime to the availability model
|
||||||
|
var updatedAvailability = availability.copyWith(
|
||||||
|
startDate: DateTime(
|
||||||
|
range.start.year,
|
||||||
|
range.start.month,
|
||||||
|
range.start.day,
|
||||||
|
startTime.hour,
|
||||||
|
startTime.minute,
|
||||||
|
),
|
||||||
|
endDate: DateTime(
|
||||||
|
range.start.year,
|
||||||
|
range.start.month,
|
||||||
|
range.start.day,
|
||||||
|
endTime.hour,
|
||||||
|
endTime.minute,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
await dataInterface.createAvailabilitiesForUser(
|
await dataInterface.createAvailabilitiesForUser(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
availability: availability,
|
availability: updatedAvailability,
|
||||||
start: range.start,
|
start: range.start,
|
||||||
end: range.end,
|
end: range.end,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// removes all the given [availabilities] from the data store
|
||||||
|
Future<void> clearAvailabilities(
|
||||||
|
List<AvailabilityModel> availabilities,
|
||||||
|
) async {
|
||||||
|
for (var availability in availabilities) {
|
||||||
|
await dataInterface.deleteAvailabilityForUser(
|
||||||
|
userId,
|
||||||
|
availability.id!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a stream where data from availabilities and templates are merged
|
/// Returns a stream where data from availabilities and templates are merged
|
||||||
Stream<List<AvailabilityWithTemplate>> getOverviewDataForMonth(
|
Stream<List<AvailabilityWithTemplate>> getOverviewDataForMonth(
|
||||||
DateTime dayInMonth,
|
DateTime dayInMonth,
|
||||||
|
@ -173,3 +205,11 @@ extension RetrieveUniqueTemplates on List<AvailabilityWithTemplate> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension to retrieve [AvailabilityModel] from a list
|
||||||
|
/// of [AvailabilityWithTemplate]
|
||||||
|
extension TransformAvailabilityWithTemplate on List<AvailabilityWithTemplate> {
|
||||||
|
/// Retrieve all availabilities from a list of [AvailabilityWithTemplate]
|
||||||
|
List<AvailabilityModel> getAvailabilities() =>
|
||||||
|
map((entry) => entry.availabilityModel).toList();
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_availability/src/service/availability_service.dart";
|
||||||
import "package:flutter_availability/src/ui/widgets/calendar.dart";
|
import "package:flutter_availability/src/ui/widgets/calendar.dart";
|
||||||
import "package:flutter_availability/src/ui/widgets/template_legend.dart";
|
import "package:flutter_availability/src/ui/widgets/template_legend.dart";
|
||||||
import "package:flutter_availability/src/util/scope.dart";
|
import "package:flutter_availability/src/util/scope.dart";
|
||||||
|
@ -14,8 +15,11 @@ class AvailabilityOverview extends StatefulHookWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Callback for when the user clicks on a day
|
/// Callback for when the user gives an availability range
|
||||||
final void Function(DateTimeRange range) onEditDateRange;
|
final void Function(
|
||||||
|
DateTimeRange range,
|
||||||
|
List<AvailabilityWithTemplate> selectedAvailabilities,
|
||||||
|
) onEditDateRange;
|
||||||
|
|
||||||
/// Callback for when the user wants to navigate to the overview of templates
|
/// Callback for when the user wants to navigate to the overview of templates
|
||||||
final VoidCallback onViewTemplates;
|
final VoidCallback onViewTemplates;
|
||||||
|
@ -74,12 +78,23 @@ class _AvailabilityOverviewState extends State<AvailabilityOverview> {
|
||||||
availabilities: availabilitySnapshot,
|
availabilities: availabilitySnapshot,
|
||||||
);
|
);
|
||||||
|
|
||||||
// if there is no range selected we want to disable the button
|
|
||||||
var onButtonPress = _selectedRange == null
|
var onButtonPress = _selectedRange == null
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
|
var availabilitesWithinSelectedRange = availabilitySnapshot.data
|
||||||
|
?.where(
|
||||||
|
(a) =>
|
||||||
|
a.availabilityModel.startDate
|
||||||
|
.isAfter(_selectedRange!.start) &&
|
||||||
|
a.availabilityModel.endDate
|
||||||
|
.isBefore(_selectedRange!.end),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
<AvailabilityWithTemplate>[];
|
||||||
|
|
||||||
widget.onEditDateRange(
|
widget.onEditDateRange(
|
||||||
DateTimeRange(start: DateTime(1), end: DateTime(2)),
|
_selectedRange!,
|
||||||
|
availabilitesWithinSelectedRange,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,448 +1,191 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_availability/src/service/availability_service.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";
|
||||||
|
import "package:flutter_availability/src/ui/widgets/pause_selection.dart";
|
||||||
import "package:flutter_availability/src/util/scope.dart";
|
import "package:flutter_availability/src/util/scope.dart";
|
||||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||||
import "package:intl/intl.dart";
|
|
||||||
|
|
||||||
///
|
///
|
||||||
class AvailabilityDayOverview extends StatefulWidget {
|
class AvailabilityModificationView extends StatefulWidget {
|
||||||
///
|
///
|
||||||
const AvailabilityDayOverview({
|
const AvailabilityModificationView({
|
||||||
required this.date,
|
required this.dateRange,
|
||||||
required this.onAvailabilitySaved,
|
required this.onExit,
|
||||||
this.initialAvailability,
|
required this.initialAvailabilities,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The date for which the availability is being managed
|
/// The date for which the availability is being managed
|
||||||
final DateTime date;
|
final DateTimeRange dateRange;
|
||||||
|
|
||||||
/// The initial availability for the day
|
/// The initial availabilities for the selected period
|
||||||
final AvailabilityModel? initialAvailability;
|
final List<AvailabilityWithTemplate> initialAvailabilities;
|
||||||
|
|
||||||
/// Callback for when the availability is saved
|
/// Callback for when the user wants to navigate back or the
|
||||||
final Function() onAvailabilitySaved;
|
/// availabilities have been saved
|
||||||
|
final VoidCallback onExit;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AvailabilityDayOverview> createState() =>
|
State<AvailabilityModificationView> createState() =>
|
||||||
_AvailabilityDayOverviewState();
|
_AvailabilityModificationViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AvailabilityDayOverviewState extends State<AvailabilityDayOverview> {
|
class _AvailabilityModificationViewState
|
||||||
late TextEditingController _startDateController;
|
extends State<AvailabilityModificationView> {
|
||||||
late TextEditingController _endDateController;
|
|
||||||
late AvailabilityModel _availability;
|
late AvailabilityModel _availability;
|
||||||
bool _clearAvailableToday = false;
|
bool _clearAvailability = false;
|
||||||
|
TimeOfDay? _startTime;
|
||||||
|
TimeOfDay? _endTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_availability = widget.initialAvailability ??
|
_availability =
|
||||||
AvailabilityModel(
|
widget.initialAvailabilities.getAvailabilities().firstOrNull ??
|
||||||
userId: "",
|
AvailabilityModel(
|
||||||
startDate: widget.date,
|
userId: "",
|
||||||
endDate: widget.date,
|
startDate: widget.dateRange.start,
|
||||||
breaks: [],
|
endDate: widget.dateRange.end,
|
||||||
);
|
breaks: [],
|
||||||
_startDateController = TextEditingController(
|
);
|
||||||
text: DateFormat("HH:mm").format(_availability.startDate),
|
|
||||||
);
|
|
||||||
_endDateController = TextEditingController(
|
|
||||||
text: DateFormat("HH:mm").format(_availability.endDate),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_startDateController.dispose();
|
|
||||||
_endDateController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<TimeOfDay?> _selectTime(TextEditingController controller) async {
|
|
||||||
var picked = await showTimePicker(
|
|
||||||
context: context,
|
|
||||||
initialTime: TimeOfDay.now(),
|
|
||||||
);
|
|
||||||
if (picked != null) {
|
|
||||||
setState(() {
|
|
||||||
controller.text = picked.format(context);
|
|
||||||
});
|
|
||||||
return picked;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var availabilityScope = AvailabilityScope.of(context);
|
var availabilityScope = AvailabilityScope.of(context);
|
||||||
var userId = availabilityScope.userId;
|
|
||||||
var service = availabilityScope.service;
|
var service = availabilityScope.service;
|
||||||
|
var options = availabilityScope.options;
|
||||||
|
var spacing = options.spacing;
|
||||||
|
var translations = options.translations;
|
||||||
|
|
||||||
Future<void> updateAvailabilityStart() async {
|
// TODO(freek): the selected period might be longer than 1 month
|
||||||
var selectedTime = await _selectTime(_startDateController);
|
//so we need to get all the availabilites through a stream
|
||||||
if (selectedTime == null) return;
|
|
||||||
|
|
||||||
var updatedStartDate = _availability.startDate.copyWith(
|
Future<void> onSave() async {
|
||||||
hour: selectedTime.hour,
|
if (_clearAvailability) {
|
||||||
minute: selectedTime.minute,
|
await service.clearAvailabilities(
|
||||||
);
|
widget.initialAvailabilities.getAvailabilities(),
|
||||||
setState(() {
|
);
|
||||||
_availability = _availability.copyWith(startDate: updatedStartDate);
|
widget.onExit();
|
||||||
});
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateAvailabilityEnd() async {
|
|
||||||
var selectedTime = await _selectTime(_endDateController);
|
|
||||||
if (selectedTime == null) return;
|
|
||||||
|
|
||||||
var updatedEndDate = _availability.endDate.copyWith(
|
|
||||||
hour: selectedTime.hour,
|
|
||||||
minute: selectedTime.minute,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
_availability = _availability.copyWith(endDate: updatedEndDate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> onClickAddPause() async {
|
|
||||||
var newBreak = await AvailabilityBreakSelectionDialog.show(context, null);
|
|
||||||
if (newBreak != null) {
|
|
||||||
setState(() {
|
|
||||||
_availability.breaks.add(newBreak);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
if (widget.initialAvailabilities.isNotEmpty) {
|
||||||
|
await service.clearAvailabilities(
|
||||||
Future<void> onClickSave() async {
|
widget.initialAvailabilities.getAvailabilities(),
|
||||||
if (_clearAvailableToday) {
|
|
||||||
// remove the availability for the user
|
|
||||||
if (_availability.id != null) {
|
|
||||||
await service.dataInterface.deleteAvailabilityForUser(
|
|
||||||
userId,
|
|
||||||
_availability.id!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// add an availability for the user
|
|
||||||
await service.dataInterface.createAvailabilitiesForUser(
|
|
||||||
userId: userId,
|
|
||||||
availability: _availability,
|
|
||||||
start: widget.date,
|
|
||||||
end: widget.date,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
|
||||||
widget.onAvailabilitySaved();
|
await service.createAvailability(
|
||||||
}
|
availability: _availability,
|
||||||
|
range: widget.dateRange,
|
||||||
|
startTime: _startTime!,
|
||||||
|
endTime: _endTime!,
|
||||||
|
);
|
||||||
|
widget.onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
var theme = Theme.of(context);
|
var canSave =
|
||||||
return Scaffold(
|
_clearAvailability || (_startTime != null && _endTime != null);
|
||||||
body: Padding(
|
var saveButton = options.primaryButtonBuilder(
|
||||||
padding: const EdgeInsets.all(16.0),
|
context,
|
||||||
child: Column(
|
canSave ? onSave : null,
|
||||||
children: [
|
Text(translations.saveButton),
|
||||||
Text(
|
);
|
||||||
DateFormat.yMMMMd().format(widget.date),
|
|
||||||
style: theme.textTheme.bodyLarge,
|
var clearSection = AvailabilityClearSection(
|
||||||
),
|
range: widget.dateRange,
|
||||||
Row(
|
clearAvailable: _clearAvailability,
|
||||||
children: [
|
onChanged: (isChecked) {
|
||||||
Checkbox(
|
setState(() {
|
||||||
value: _clearAvailableToday,
|
_clearAvailability = isChecked;
|
||||||
onChanged: (value) {
|
});
|
||||||
setState(() {
|
},
|
||||||
_clearAvailableToday = value!;
|
);
|
||||||
});
|
|
||||||
},
|
var templateSelection = const AvailabilityTemplateSelection();
|
||||||
),
|
|
||||||
const Text("Clear availability for today"),
|
var timeSelection = AvailabilityTimeSelection(
|
||||||
|
dateRange: widget.dateRange,
|
||||||
|
startTime: _startTime != null
|
||||||
|
? DateTime(
|
||||||
|
widget.dateRange.start.year,
|
||||||
|
widget.dateRange.start.month,
|
||||||
|
widget.dateRange.start.day,
|
||||||
|
_startTime!.hour,
|
||||||
|
_startTime!.minute,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
endTime: _endTime != null
|
||||||
|
? DateTime(
|
||||||
|
widget.dateRange.start.year,
|
||||||
|
widget.dateRange.start.month,
|
||||||
|
widget.dateRange.start.day,
|
||||||
|
_endTime!.hour,
|
||||||
|
_endTime!.minute,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
key: ValueKey([_startTime, _endTime]),
|
||||||
|
onStartChanged: (start) => setState(() {
|
||||||
|
_startTime = TimeOfDay.fromDateTime(start);
|
||||||
|
}),
|
||||||
|
onEndChanged: (end) => setState(() {
|
||||||
|
_endTime = TimeOfDay.fromDateTime(end);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
var pauseSelection = PauseSelection(
|
||||||
|
breaks: _availability.breaks,
|
||||||
|
onBreaksChanged: (breaks) {
|
||||||
|
setState(() {
|
||||||
|
_availability = _availability.copyWith(breaks: breaks);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var body = CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.symmetric(horizontal: options.spacing.sidePadding),
|
||||||
|
sliver: SliverList.list(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
clearSection,
|
||||||
|
if (!_clearAvailability) ...[
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
templateSelection,
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
timeSelection,
|
||||||
|
const SizedBox(height: 26),
|
||||||
|
pauseSelection,
|
||||||
],
|
],
|
||||||
),
|
],
|
||||||
Opacity(
|
|
||||||
opacity: _clearAvailableToday ? 0.5 : 1,
|
|
||||||
child: IgnorePointer(
|
|
||||||
ignoring: _clearAvailableToday,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: updateAvailabilityStart,
|
|
||||||
child: AbsorbPointer(
|
|
||||||
child: TextField(
|
|
||||||
controller: _startDateController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Begin tijd",
|
|
||||||
suffixIcon: Icon(Icons.access_time),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
const Text("tot"),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: updateAvailabilityEnd,
|
|
||||||
child: AbsorbPointer(
|
|
||||||
child: TextField(
|
|
||||||
controller: _endDateController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Eind tijd",
|
|
||||||
suffixIcon: Icon(Icons.access_time),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
const Text("Add pause (optional)"),
|
|
||||||
ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: _availability.breaks.map(
|
|
||||||
(breakModel) {
|
|
||||||
var start =
|
|
||||||
DateFormat("HH:mm").format(breakModel.startTime);
|
|
||||||
var end =
|
|
||||||
DateFormat("HH:mm").format(breakModel.endTime);
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
var updatedBreak =
|
|
||||||
await AvailabilityBreakSelectionDialog.show(
|
|
||||||
context,
|
|
||||||
breakModel,
|
|
||||||
);
|
|
||||||
if (updatedBreak != null) {
|
|
||||||
setState(() {
|
|
||||||
_availability.breaks.remove(breakModel);
|
|
||||||
_availability.breaks.add(updatedBreak);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.lightBlue,
|
|
||||||
),
|
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"${breakModel.duration.inMinutes}"
|
|
||||||
" minutes | ",
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"$start - "
|
|
||||||
"$end",
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_availability.breaks.remove(breakModel);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
).toList(),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: onClickAddPause,
|
|
||||||
child: const Text("Add"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async => onClickSave(),
|
|
||||||
child: const Text(""),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
class AvailabilityBreakSelectionDialog extends StatefulWidget {
|
|
||||||
///
|
|
||||||
const AvailabilityBreakSelectionDialog({
|
|
||||||
required this.initialBreak,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The initial break to show in the dialog if any
|
|
||||||
final AvailabilityBreakModel? initialBreak;
|
|
||||||
|
|
||||||
/// Opens the dialog to add a break
|
|
||||||
static Future<AvailabilityBreakModel?> show(
|
|
||||||
BuildContext context,
|
|
||||||
AvailabilityBreakModel? initialBreak,
|
|
||||||
) async =>
|
|
||||||
showDialog<AvailabilityBreakModel>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AvailabilityBreakSelectionDialog(
|
|
||||||
initialBreak: initialBreak,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AvailabilityBreakSelectionDialog> createState() =>
|
|
||||||
_AvailabilityBreakSelectionDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AvailabilityBreakSelectionDialogState
|
|
||||||
extends State<AvailabilityBreakSelectionDialog> {
|
|
||||||
late TextEditingController _durationController;
|
|
||||||
late TextEditingController _startPauseController;
|
|
||||||
late TextEditingController _endPauseController;
|
|
||||||
late AvailabilityBreakModel _breakModel;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_breakModel = widget.initialBreak ??
|
|
||||||
AvailabilityBreakModel(
|
|
||||||
startTime: DateTime.now(),
|
|
||||||
endTime: DateTime.now(),
|
|
||||||
);
|
|
||||||
_durationController = TextEditingController(
|
|
||||||
text: _breakModel.duration.inMinutes.toString(),
|
|
||||||
);
|
|
||||||
_startPauseController = TextEditingController(
|
|
||||||
text: DateFormat("HH:mm").format(_breakModel.startTime),
|
|
||||||
);
|
|
||||||
_endPauseController = TextEditingController(
|
|
||||||
text: DateFormat("HH:mm").format(_breakModel.endTime),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_durationController.dispose();
|
|
||||||
_startPauseController.dispose();
|
|
||||||
_endPauseController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<TimeOfDay?> _selectTime(
|
|
||||||
BuildContext context,
|
|
||||||
TextEditingController controller,
|
|
||||||
) async {
|
|
||||||
var picked = await showTimePicker(
|
|
||||||
context: context,
|
|
||||||
initialTime: TimeOfDay.now(),
|
|
||||||
);
|
|
||||||
if (picked != null) {
|
|
||||||
setState(() {
|
|
||||||
controller.text = picked.format(context);
|
|
||||||
});
|
|
||||||
return picked;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
void onUpdateDuration() {
|
|
||||||
var duration = int.tryParse(_durationController.text);
|
|
||||||
if (duration != null) {
|
|
||||||
setState(() {
|
|
||||||
_breakModel = _breakModel.copyWith(
|
|
||||||
duration: Duration(minutes: duration),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> onUpdateStart() async {
|
|
||||||
var selectedTime = await _selectTime(context, _startPauseController);
|
|
||||||
if (selectedTime == null) return;
|
|
||||||
|
|
||||||
var updatedStartTime = _breakModel.startTime.copyWith(
|
|
||||||
hour: selectedTime.hour,
|
|
||||||
minute: selectedTime.minute,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
_breakModel = _breakModel.copyWith(startTime: updatedStartTime);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> onUpdateEnd() async {
|
|
||||||
var selectedTime = await _selectTime(context, _endPauseController);
|
|
||||||
if (selectedTime == null) return;
|
|
||||||
|
|
||||||
var updatedEndTime = _breakModel.endTime.copyWith(
|
|
||||||
hour: selectedTime.hour,
|
|
||||||
minute: selectedTime.minute,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
_breakModel = _breakModel.copyWith(endTime: updatedEndTime);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text("Pauze toevoegen"),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
// textfield for duration in minutes
|
|
||||||
TextField(
|
|
||||||
controller: _durationController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Duration in minutes",
|
|
||||||
),
|
|
||||||
onChanged: (_) => onUpdateDuration(),
|
|
||||||
),
|
),
|
||||||
TextField(
|
|
||||||
controller: _startPauseController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Start time",
|
|
||||||
suffixIcon: Icon(Icons.access_time),
|
|
||||||
),
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () async => onUpdateStart(),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: _endPauseController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "End time",
|
|
||||||
suffixIcon: Icon(Icons.access_time),
|
|
||||||
),
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () async => onUpdateEnd(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: const Text("Cancel"),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
TextButton(
|
SliverFillRemaining(
|
||||||
child: const Text("Save"),
|
fillOverscroll: false,
|
||||||
onPressed: () {
|
hasScrollBody: false,
|
||||||
Navigator.of(context).pop(_breakModel);
|
child: Padding(
|
||||||
},
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: spacing.sidePadding,
|
||||||
|
).copyWith(
|
||||||
|
bottom: spacing.bottomButtonPadding,
|
||||||
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: saveButton,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return options.baseScreenBuilder(
|
||||||
|
context,
|
||||||
|
widget.onExit,
|
||||||
|
body,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,15 +47,20 @@ class AvailabilityClearSection extends StatelessWidget {
|
||||||
titleText,
|
titleText,
|
||||||
style: textTheme.titleMedium,
|
style: textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
splashRadius: 0,
|
||||||
value: clearAvailable,
|
value: clearAvailable,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
onChanged(value);
|
onChanged(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
unavailableText,
|
unavailableText,
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
|
|
|
@ -81,6 +81,7 @@ class PauseSelection extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
translations.pauseSectionTitle,
|
translations.pauseSectionTitle,
|
||||||
|
|
|
@ -10,7 +10,6 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: any
|
|
||||||
rxdart: ^0.27.0
|
rxdart: ^0.27.0
|
||||||
flutter_hooks: ^0.20.5
|
flutter_hooks: ^0.20.5
|
||||||
flutter_availability_data_interface:
|
flutter_availability_data_interface:
|
||||||
|
|
Loading…
Reference in a new issue