diff --git a/lib/components/TaskBottomSheet.dart b/lib/components/TaskBottomSheet.dart index 379b8b5..0d2cb17 100644 --- a/lib/components/TaskBottomSheet.dart +++ b/lib/components/TaskBottomSheet.dart @@ -1,18 +1,30 @@ +import 'package:datetime_picker_formfield/datetime_picker_formfield.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +import 'package:provider/provider.dart'; +import 'package:vikunja_app/utils/priority.dart'; +import '../models/label.dart'; import '../models/task.dart'; +import '../pages/list/task_edit.dart'; +import '../stores/project_store.dart'; +import '../theme/constants.dart'; +import 'label.dart'; class TaskBottomSheet extends StatefulWidget { final Task task; final bool showInfo; final bool loading; + final Function onEdit; final ValueSetter? onMarkedAsDone; + final ProjectProvider taskState; const TaskBottomSheet({ Key? key, required this.task, + required this.onEdit, + required this.taskState, this.loading = false, this.showInfo = false, this.onMarkedAsDone, @@ -35,28 +47,96 @@ class TaskBottomSheetState extends State { @override Widget build(BuildContext context) { + ThemeData theme = Theme.of(context); return Container( height: MediaQuery.of(context).size.height * 0.9, - child: Center( - child: Padding( - padding: EdgeInsets.fromLTRB(10, 10, 10, 10), + child: Padding( + padding: EdgeInsets.fromLTRB(20, 10, 10, 20), child: Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + // Title and edit button mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(_currentTask.title), - BackButton(), + Text(_currentTask.title, style: theme.textTheme.headlineLarge), + IconButton(onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (buildContext) => TaskEditPage( + task: _currentTask, + taskState: widget.taskState, + ), + ), + ) + .then((task) => setState(() { + if (task != null) _currentTask = task; + })) + .whenComplete(() => widget.onEdit()); + }, icon: Icon(Icons.edit)), + ], + ), + Wrap( + spacing: 10, + children: _currentTask.labels.map((Label label) { + return LabelComponent( + label: label, + ); + }).toList()), + + // description with html rendering + Text("Description", style: theme.textTheme.headlineSmall), + Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0), + child: HtmlWidget(_currentTask.description.isNotEmpty ? _currentTask.description : "No description"), + ), + // Due date + Row( + children: [ + Icon(Icons.access_time), + Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)), + Text(_currentTask.dueDate != null ? vDateFormatShort.format(_currentTask.dueDate!.toLocal()) : "No due date"), + ], + ), + // start date + Row( + children: [ + Icon(Icons.play_arrow_rounded), + Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)), + Text(_currentTask.startDate != null ? vDateFormatShort.format(_currentTask.startDate!.toLocal()) : "No start date"), + ], + ), + // end date + Row( + children: [ + Icon(Icons.stop_rounded), + Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)), + Text(_currentTask.endDate != null ? vDateFormatShort.format(_currentTask.endDate!.toLocal()) : "No end date"), + ], + ), + // priority + Row( + children: [ + Icon(Icons.priority_high), + Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)), + Text(_currentTask.priority != null ? priorityToString(_currentTask.priority) : "No priority"), + ], + ), + // progress + Row( + children: [ + Icon(Icons.percent), + Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)), + Text(_currentTask.percent_done != null ? (_currentTask.percent_done! * 100).toInt().toString() + "%" : "Unset"), ], ), - HtmlWidget(_currentTask.description), ], ), ) - ), + ); } diff --git a/lib/components/TaskTile.dart b/lib/components/TaskTile.dart index 36873ae..1184d5a 100644 --- a/lib/components/TaskTile.dart +++ b/lib/components/TaskTile.dart @@ -92,7 +92,7 @@ class TaskTileState extends State with AutomaticKeepAliveClientMixin { ? null : HtmlWidget(_currentTask.description), trailing: IconButton( - icon: Icon(Icons.settings), + icon: Icon(Icons.edit), onPressed: () {}, ), ); @@ -110,7 +110,7 @@ class TaskTileState extends State with AutomaticKeepAliveClientMixin { showModalBottomSheet( context: context, builder: (BuildContext context) { - return TaskBottomSheet(task: widget.task); + return TaskBottomSheet(task: widget.task, onEdit: widget.onEdit, taskState: taskState); }); }, title: widget.showInfo @@ -137,7 +137,7 @@ class TaskTileState extends State with AutomaticKeepAliveClientMixin { }, ), trailing: IconButton( - icon: Icon(Icons.settings), + icon: Icon(Icons.edit), onPressed: () { Navigator.push( context, diff --git a/lib/components/label.dart b/lib/components/label.dart index 7cac52b..a07ee1e 100644 --- a/lib/components/label.dart +++ b/lib/components/label.dart @@ -3,9 +3,9 @@ import 'package:vikunja_app/models/label.dart'; class LabelComponent extends StatelessWidget { final Label label; - final VoidCallback onDelete; + final VoidCallback? onDelete; - const LabelComponent({Key? key, required this.label, required this.onDelete}) + const LabelComponent({Key? key, required this.label, this.onDelete}) : super(key: key); @override diff --git a/lib/models/task.dart b/lib/models/task.dart index 44a4861..d7ac5ec 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -20,6 +20,7 @@ class Task { final bool done; Color? color; final double? kanbanPosition; + final double? percent_done; final User createdBy; Duration? repeatAfter; final List subtasks; @@ -45,6 +46,7 @@ class Task { this.repeatAfter, this.color, this.kanbanPosition, + this.percent_done, this.subtasks = const [], this.labels = const [], this.attachments = const [], @@ -90,6 +92,9 @@ class Task { kanbanPosition = json['kanban_position'] is int ? json['kanban_position'].toDouble() : json['kanban_position'], + percent_done = json['percent_done'] is int + ? json['percent_done'].toDouble() + : json['percent_done'], labels = json['labels'] != null ? (json['labels'] as List) .map((label) => Label.fromJson(label)) @@ -128,6 +133,7 @@ class Task { 'repeat_after': repeatAfter?.inSeconds, 'hex_color': color?.value.toRadixString(16).padLeft(8, '0').substring(2), 'kanban_position': kanbanPosition, + 'percent_done': percent_done, 'project_id': projectId, 'labels': labels.map((label) => label.toJSON()).toList(), 'subtasks': subtasks.map((subtask) => subtask.toJSON()).toList(), @@ -157,6 +163,7 @@ class Task { bool? done, Color? color, double? kanbanPosition, + double? percent_done, User? createdBy, Duration? repeatAfter, List? subtasks, @@ -182,6 +189,7 @@ class Task { done: done ?? this.done, color: color ?? this.color, kanbanPosition: kanbanPosition ?? this.kanbanPosition, + percent_done: percent_done ?? this.percent_done, createdBy: createdBy ?? this.createdBy, repeatAfter: repeatAfter ?? this.repeatAfter, subtasks: subtasks ?? this.subtasks, diff --git a/lib/pages/list/task_edit.dart b/lib/pages/list/task_edit.dart index 56271fb..1824733 100644 --- a/lib/pages/list/task_edit.dart +++ b/lib/pages/list/task_edit.dart @@ -252,7 +252,7 @@ class _TaskEditPageState extends State { _reminderDates.add(DateTime(0)); var currentIndex = _reminderDates.length - 1; - // FIXME: Why does putting this into a row fails? + // FIXME: Why does putting this into a row fail? setState(() => _reminderInputs.add( VikunjaDateTimePicker( label: 'Reminder', diff --git a/lib/theme/constants.dart b/lib/theme/constants.dart index f1b7c42..0bd1760 100644 --- a/lib/theme/constants.dart +++ b/lib/theme/constants.dart @@ -30,4 +30,5 @@ const vStandardVerticalPadding = EdgeInsets.symmetric(vertical: 5.0); const vStandardHorizontalPadding = EdgeInsets.symmetric(horizontal: 5.0); const vStandardPadding = EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0); -var vDateFormatLong = DateFormat("EEEE, MMMM d, yyyy 'at' H:mm"); \ No newline at end of file +var vDateFormatLong = DateFormat("EEEE, MMMM d, yyyy 'at' H:mm"); +var vDateFormatShort = DateFormat("d MMM yyyy, H:mm"); \ No newline at end of file