// Flutter imports: import 'package:flutter/material.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/task_model.dart'; import 'package:invoiceninja_flutter/ui/app/forms/date_picker.dart'; import 'package:invoiceninja_flutter/ui/app/forms/duration_picker.dart'; import 'package:invoiceninja_flutter/ui/app/forms/time_picker.dart'; import 'package:invoiceninja_flutter/ui/app/help_text.dart'; import 'package:invoiceninja_flutter/ui/app/responsive_padding.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/task/edit/task_edit_times_vm.dart'; import 'package:invoiceninja_flutter/ui/task/task_time_view.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; class TaskEditTimes extends StatefulWidget { const TaskEditTimes({ Key key, @required this.viewModel, }) : super(key: key); final TaskEditTimesVM viewModel; @override _TaskEditTimesState createState() => _TaskEditTimesState(); } class _TaskEditTimesState extends State { TaskTime selectedTaskTime; void _showTaskTimeEditor(TaskTime taskTime, BuildContext context) { showDialog( barrierDismissible: false, context: context, builder: (BuildContext context) { final viewModel = widget.viewModel; final task = viewModel.task; final taskTimes = task.getTaskTimes(); return TimeEditDetails( viewModel: viewModel, taskTime: taskTime, index: taskTimes.indexOf( taskTimes.firstWhere((time) => time.equalTo(taskTime))), ); }); } @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); final viewModel = widget.viewModel; final task = viewModel.task; final taskTimes = task.getTaskTimes(); final invalidTimes = task.getInvalidTimeIndices; final taskTime = viewModel.taskTimeIndex != null && taskTimes.length > viewModel.taskTimeIndex ? taskTimes[viewModel.taskTimeIndex] : null; if (taskTime != null && taskTime != selectedTaskTime) { viewModel.clearSelectedTaskTime(); WidgetsBinding.instance.addPostFrameCallback((duration) { _showTaskTimeEditor(taskTime, context); }); } if (task.getTaskTimes().isEmpty) { return HelpText(localization.clickPlusToAddTime); } final sortedTaskTimes = task.getTaskTimes().toList().reversed.toList(); final taskTimeWidgets = []; for (var i = 0; i < sortedTaskTimes.length; i++) { final taskTime = sortedTaskTimes[i]; taskTimeWidgets.add(TaskTimeListTile( task: task, taskTime: taskTime, onTap: (context) => _showTaskTimeEditor(taskTime, context), isValid: !invalidTimes.contains(sortedTaskTimes.length - i - 1), )); } return ScrollableListView( children: taskTimeWidgets.toList(), ); } } class TimeEditDetails extends StatefulWidget { const TimeEditDetails({ Key key, @required this.index, @required this.taskTime, @required this.viewModel, }) : super(key: key); final int index; final TaskTime taskTime; final TaskEditTimesVM viewModel; @override TimeEditDetailsState createState() => TimeEditDetailsState(); } class TimeEditDetailsState extends State { TaskTime _taskTime = TaskTime(); int _startDateUpdatedAt = 0; int _startTimeUpdatedAt = 0; int _endDateUpdatedAt = 0; int _endTimeUpdatedAt = 0; int _durationUpdateAt = 0; @override void didChangeDependencies() { _taskTime = widget.taskTime; super.didChangeDependencies(); } @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); final viewModel = widget.viewModel; final company = viewModel.company; final showEndDate = company.showTaskEndDate; return AlertDialog( content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ DatePicker( key: ValueKey('__date_${_startTimeUpdatedAt}__'), labelText: localization.date, selectedDate: _taskTime.startDate == null ? null : convertDateTimeToSqlDate(_taskTime.startDate.toLocal()), onSelected: (date, _) { setState(() { _taskTime = _taskTime.copyWithStartDate(date, syncDates: !showEndDate); viewModel.onUpdatedTaskTime(_taskTime, widget.index); _startDateUpdatedAt = DateTime.now().millisecondsSinceEpoch; }); }, ), TimePicker( key: ValueKey('__start_time_${_durationUpdateAt}__'), labelText: localization.startTime, selectedDateTime: _taskTime.startDate, onSelected: (timeOfDay) { setState(() { _taskTime = _taskTime.copyWithStartTime(timeOfDay); viewModel.onUpdatedTaskTime(_taskTime, widget.index); _startTimeUpdatedAt = DateTime.now().millisecondsSinceEpoch; }); }, ), TimePicker( key: ValueKey( '__end_time_${_endDateUpdatedAt}_${_durationUpdateAt}__'), labelText: localization.endTime, selectedDateTime: _taskTime.endDate, isEndTime: true, onSelected: (timeOfDay) { setState(() { _taskTime = _taskTime.copyWithEndTime(timeOfDay); viewModel.onUpdatedTaskTime(_taskTime, widget.index); _endTimeUpdatedAt = DateTime.now().millisecondsSinceEpoch; }); }, ), if (showEndDate) DatePicker( key: ValueKey( '__${_startDateUpdatedAt}_${_durationUpdateAt}_${_endTimeUpdatedAt}__'), selectedDate: _taskTime.startDate == null ? null : convertDateTimeToSqlDate(_taskTime.endDate.toLocal()), onSelected: (date, _) { setState(() { _taskTime = _taskTime.copyWithEndDate(date); viewModel.onUpdatedTaskTime(_taskTime, widget.index); _endDateUpdatedAt = DateTime.now().millisecondsSinceEpoch; }); }, ), DurationPicker( key: ValueKey( '__duration_${_startTimeUpdatedAt}_${_endTimeUpdatedAt}_${_startDateUpdatedAt}_${_endDateUpdatedAt}_'), labelText: localization.duration, onSelected: (Duration duration) { setState(() { _taskTime = _taskTime.copyWithDuration(duration); viewModel.onUpdatedTaskTime(_taskTime, widget.index); _durationUpdateAt = DateTime.now().millisecondsSinceEpoch; }); }, selectedDuration: (_taskTime.startDate == null || _taskTime.endDate == null) ? null : _taskTime.duration, ), ], ), ), actions: [ TextButton( child: Text(localization.remove.toUpperCase()), onPressed: () { widget.viewModel.onRemoveTaskTimePressed(widget.index); Navigator.of(context).pop(); }, ), TextButton( child: Text(localization.done.toUpperCase()), onPressed: () { widget.viewModel.onDoneTaskTimePressed(); Navigator.of(context).pop(); }, ) ], ); } }