added fields to task view, changed icon of task editing.
This commit is contained in:
parent
aa0f56231f
commit
1ef9ed4e67
|
@ -1,18 +1,30 @@
|
||||||
|
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.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 '../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 {
|
class TaskBottomSheet extends StatefulWidget {
|
||||||
final Task task;
|
final Task task;
|
||||||
final bool showInfo;
|
final bool showInfo;
|
||||||
final bool loading;
|
final bool loading;
|
||||||
|
final Function onEdit;
|
||||||
final ValueSetter<bool>? onMarkedAsDone;
|
final ValueSetter<bool>? onMarkedAsDone;
|
||||||
|
final ProjectProvider taskState;
|
||||||
|
|
||||||
const TaskBottomSheet({
|
const TaskBottomSheet({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.task,
|
required this.task,
|
||||||
|
required this.onEdit,
|
||||||
|
required this.taskState,
|
||||||
this.loading = false,
|
this.loading = false,
|
||||||
this.showInfo = false,
|
this.showInfo = false,
|
||||||
this.onMarkedAsDone,
|
this.onMarkedAsDone,
|
||||||
|
@ -35,28 +47,96 @@ class TaskBottomSheetState extends State<TaskBottomSheet> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
ThemeData theme = Theme.of(context);
|
||||||
return Container(
|
return Container(
|
||||||
height: MediaQuery.of(context).size.height * 0.9,
|
height: MediaQuery.of(context).size.height * 0.9,
|
||||||
child: Center(
|
child: Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.fromLTRB(20, 10, 10, 20),
|
||||||
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
|
// Title and edit button
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(_currentTask.title),
|
Text(_currentTask.title, style: theme.textTheme.headlineLarge),
|
||||||
BackButton(),
|
IconButton(onPressed: () {
|
||||||
|
Navigator.push<Task>(
|
||||||
|
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),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
||||||
? null
|
? null
|
||||||
: HtmlWidget(_currentTask.description),
|
: HtmlWidget(_currentTask.description),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(Icons.settings),
|
icon: Icon(Icons.edit),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -110,7 +110,7 @@ class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return TaskBottomSheet(task: widget.task);
|
return TaskBottomSheet(task: widget.task, onEdit: widget.onEdit, taskState: taskState);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
title: widget.showInfo
|
title: widget.showInfo
|
||||||
|
@ -137,7 +137,7 @@ class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(Icons.settings),
|
icon: Icon(Icons.edit),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push<Task>(
|
Navigator.push<Task>(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -3,9 +3,9 @@ import 'package:vikunja_app/models/label.dart';
|
||||||
|
|
||||||
class LabelComponent extends StatelessWidget {
|
class LabelComponent extends StatelessWidget {
|
||||||
final Label label;
|
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);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -20,6 +20,7 @@ class Task {
|
||||||
final bool done;
|
final bool done;
|
||||||
Color? color;
|
Color? color;
|
||||||
final double? kanbanPosition;
|
final double? kanbanPosition;
|
||||||
|
final double? percent_done;
|
||||||
final User createdBy;
|
final User createdBy;
|
||||||
Duration? repeatAfter;
|
Duration? repeatAfter;
|
||||||
final List<Task> subtasks;
|
final List<Task> subtasks;
|
||||||
|
@ -45,6 +46,7 @@ class Task {
|
||||||
this.repeatAfter,
|
this.repeatAfter,
|
||||||
this.color,
|
this.color,
|
||||||
this.kanbanPosition,
|
this.kanbanPosition,
|
||||||
|
this.percent_done,
|
||||||
this.subtasks = const [],
|
this.subtasks = const [],
|
||||||
this.labels = const [],
|
this.labels = const [],
|
||||||
this.attachments = const [],
|
this.attachments = const [],
|
||||||
|
@ -90,6 +92,9 @@ class Task {
|
||||||
kanbanPosition = json['kanban_position'] is int
|
kanbanPosition = json['kanban_position'] is int
|
||||||
? json['kanban_position'].toDouble()
|
? json['kanban_position'].toDouble()
|
||||||
: json['kanban_position'],
|
: json['kanban_position'],
|
||||||
|
percent_done = json['percent_done'] is int
|
||||||
|
? json['percent_done'].toDouble()
|
||||||
|
: json['percent_done'],
|
||||||
labels = json['labels'] != null
|
labels = json['labels'] != null
|
||||||
? (json['labels'] as List<dynamic>)
|
? (json['labels'] as List<dynamic>)
|
||||||
.map((label) => Label.fromJson(label))
|
.map((label) => Label.fromJson(label))
|
||||||
|
@ -128,6 +133,7 @@ class Task {
|
||||||
'repeat_after': repeatAfter?.inSeconds,
|
'repeat_after': repeatAfter?.inSeconds,
|
||||||
'hex_color': color?.value.toRadixString(16).padLeft(8, '0').substring(2),
|
'hex_color': color?.value.toRadixString(16).padLeft(8, '0').substring(2),
|
||||||
'kanban_position': kanbanPosition,
|
'kanban_position': kanbanPosition,
|
||||||
|
'percent_done': percent_done,
|
||||||
'project_id': projectId,
|
'project_id': projectId,
|
||||||
'labels': labels.map((label) => label.toJSON()).toList(),
|
'labels': labels.map((label) => label.toJSON()).toList(),
|
||||||
'subtasks': subtasks.map((subtask) => subtask.toJSON()).toList(),
|
'subtasks': subtasks.map((subtask) => subtask.toJSON()).toList(),
|
||||||
|
@ -157,6 +163,7 @@ class Task {
|
||||||
bool? done,
|
bool? done,
|
||||||
Color? color,
|
Color? color,
|
||||||
double? kanbanPosition,
|
double? kanbanPosition,
|
||||||
|
double? percent_done,
|
||||||
User? createdBy,
|
User? createdBy,
|
||||||
Duration? repeatAfter,
|
Duration? repeatAfter,
|
||||||
List<Task>? subtasks,
|
List<Task>? subtasks,
|
||||||
|
@ -182,6 +189,7 @@ class Task {
|
||||||
done: done ?? this.done,
|
done: done ?? this.done,
|
||||||
color: color ?? this.color,
|
color: color ?? this.color,
|
||||||
kanbanPosition: kanbanPosition ?? this.kanbanPosition,
|
kanbanPosition: kanbanPosition ?? this.kanbanPosition,
|
||||||
|
percent_done: percent_done ?? this.percent_done,
|
||||||
createdBy: createdBy ?? this.createdBy,
|
createdBy: createdBy ?? this.createdBy,
|
||||||
repeatAfter: repeatAfter ?? this.repeatAfter,
|
repeatAfter: repeatAfter ?? this.repeatAfter,
|
||||||
subtasks: subtasks ?? this.subtasks,
|
subtasks: subtasks ?? this.subtasks,
|
||||||
|
|
|
@ -252,7 +252,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
_reminderDates.add(DateTime(0));
|
_reminderDates.add(DateTime(0));
|
||||||
var currentIndex = _reminderDates.length - 1;
|
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(
|
setState(() => _reminderInputs.add(
|
||||||
VikunjaDateTimePicker(
|
VikunjaDateTimePicker(
|
||||||
label: 'Reminder',
|
label: 'Reminder',
|
||||||
|
|
|
@ -30,4 +30,5 @@ const vStandardVerticalPadding = EdgeInsets.symmetric(vertical: 5.0);
|
||||||
const vStandardHorizontalPadding = EdgeInsets.symmetric(horizontal: 5.0);
|
const vStandardHorizontalPadding = EdgeInsets.symmetric(horizontal: 5.0);
|
||||||
const vStandardPadding = EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0);
|
const vStandardPadding = EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0);
|
||||||
|
|
||||||
var vDateFormatLong = DateFormat("EEEE, MMMM d, yyyy 'at' H:mm");
|
var vDateFormatLong = DateFormat("EEEE, MMMM d, yyyy 'at' H:mm");
|
||||||
|
var vDateFormatShort = DateFormat("d MMM yyyy, H:mm");
|
Loading…
Reference in New Issue