mirror of
https://github.com/go-vikunja/app
synced 2024-06-14 08:24:18 +00:00
edit task color, update edited task, some cleanup
This commit is contained in:
parent
5d6120a7ac
commit
c3a7962679
|
@ -174,12 +174,14 @@ class _BucketTaskCardState extends State<BucketTaskCard> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push<Task>(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => TaskEditPage(
|
MaterialPageRoute(builder: (context) => TaskEditPage(
|
||||||
task: _currentTask,
|
task: _currentTask,
|
||||||
)),
|
)),
|
||||||
),
|
).then((task) => setState(() {
|
||||||
|
if (task != null) _currentTask = task;
|
||||||
|
})),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,14 +81,16 @@ class TaskTileState extends State<TaskTile> {
|
||||||
: Text(_currentTask.description),
|
: Text(_currentTask.description),
|
||||||
secondary:
|
secondary:
|
||||||
IconButton(icon: Icon(Icons.settings), onPressed: () {
|
IconButton(icon: Icon(Icons.settings), onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push<Task>(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => TaskEditPage(
|
builder: (context) => TaskEditPage(
|
||||||
task: _currentTask,
|
task: _currentTask,
|
||||||
))).whenComplete(() {
|
),
|
||||||
widget.onEdit();
|
),
|
||||||
});
|
).then((task) => setState(() {
|
||||||
|
if (task != null) _currentTask = task;
|
||||||
|
})).whenComplete(() => widget.onEdit());
|
||||||
}),
|
}),
|
||||||
onChanged: _change,
|
onChanged: _change,
|
||||||
);
|
);
|
||||||
|
@ -107,15 +109,9 @@ class TaskTileState extends State<TaskTile> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Task> _updateTask(Task task, bool checked) {
|
Future<Task> _updateTask(Task task, bool checked) {
|
||||||
// TODO use copyFrom
|
return VikunjaGlobal.of(context).taskService.update(task.copyWith(
|
||||||
return VikunjaGlobal.of(context).taskService.update(Task(
|
done: checked,
|
||||||
id: task.id,
|
));
|
||||||
done: checked,
|
|
||||||
title: task.title,
|
|
||||||
description: task.description,
|
|
||||||
createdBy: task.createdBy,
|
|
||||||
dueDate: task.dueDate
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,4 +107,42 @@ class Task {
|
||||||
Color get textColor => color != null
|
Color get textColor => color != null
|
||||||
? color.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
? color.computeLuminance() > 0.5 ? Colors.black : Colors.white
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
Task copyWith({
|
||||||
|
int id, int parentTaskId, int priority, int listId, int bucketId,
|
||||||
|
DateTime created, DateTime updated, DateTime dueDate, DateTime startDate, DateTime endDate,
|
||||||
|
List<DateTime> reminderDates,
|
||||||
|
String title, String description,
|
||||||
|
bool done,
|
||||||
|
Color color,
|
||||||
|
bool resetColor,
|
||||||
|
User createdBy,
|
||||||
|
Duration repeatAfter,
|
||||||
|
List<Task> subtasks,
|
||||||
|
List<Label> labels,
|
||||||
|
List<TaskAttachment> attachments,
|
||||||
|
}) {
|
||||||
|
return Task(
|
||||||
|
id: id ?? this.id,
|
||||||
|
parentTaskId: parentTaskId ?? this.parentTaskId,
|
||||||
|
priority: priority ?? this.priority,
|
||||||
|
listId: listId ?? this.listId,
|
||||||
|
bucketId: bucketId ?? this.bucketId,
|
||||||
|
created: created ?? this.created,
|
||||||
|
updated: updated ?? this.updated,
|
||||||
|
dueDate: dueDate ?? this.dueDate,
|
||||||
|
startDate: startDate ?? this.startDate,
|
||||||
|
endDate: endDate ?? this.endDate,
|
||||||
|
reminderDates: reminderDates ?? this.reminderDates,
|
||||||
|
title: title ?? this.title,
|
||||||
|
description: description ?? this.description,
|
||||||
|
done: done ?? this.done,
|
||||||
|
color: (resetColor ?? false) ? null : color ?? this.color,
|
||||||
|
createdBy: createdBy ?? this.createdBy,
|
||||||
|
repeatAfter: repeatAfter ?? this.repeatAfter,
|
||||||
|
subtasks: subtasks ?? this.subtasks,
|
||||||
|
labels: labels ?? this.labels,
|
||||||
|
attachments: attachments ?? this.attachments,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,14 +224,16 @@ class _ListPageState extends State<ListPage> {
|
||||||
onReorderStart: (oldIndex) => setState(() => _draggedBucketIndex = oldIndex),
|
onReorderStart: (oldIndex) => setState(() => _draggedBucketIndex = oldIndex),
|
||||||
onReorder: (oldIndex, newIndex) {},
|
onReorder: (oldIndex, newIndex) {},
|
||||||
onReorderEnd: (newIndex) => setState(() {
|
onReorderEnd: (newIndex) => setState(() {
|
||||||
if (newIndex > _draggedBucketIndex) newIndex -= 1;
|
|
||||||
taskState.buckets.insert(newIndex, taskState.buckets.removeAt(_draggedBucketIndex));
|
|
||||||
bool indexUpdated = false;
|
bool indexUpdated = false;
|
||||||
|
if (newIndex > _draggedBucketIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
indexUpdated = true;
|
||||||
|
}
|
||||||
|
taskState.buckets.insert(newIndex, taskState.buckets.removeAt(_draggedBucketIndex));
|
||||||
if (newIndex == 0) {
|
if (newIndex == 0) {
|
||||||
taskState.buckets[0].position = 0;
|
taskState.buckets[0].position = 0;
|
||||||
_updateBucket(context, taskState.buckets[0]);
|
_updateBucket(context, taskState.buckets[0]);
|
||||||
newIndex = 1;
|
newIndex = 1;
|
||||||
indexUpdated = true;
|
|
||||||
}
|
}
|
||||||
taskState.buckets[newIndex].position = newIndex == taskState.buckets.length - 1
|
taskState.buckets[newIndex].position = newIndex == taskState.buckets.length - 1
|
||||||
? taskState.buckets[newIndex - 1].position + 1
|
? taskState.buckets[newIndex - 1].position + 1
|
||||||
|
@ -239,7 +241,9 @@ class _ListPageState extends State<ListPage> {
|
||||||
+ taskState.buckets[newIndex + 1].position) / 2.0;
|
+ taskState.buckets[newIndex + 1].position) / 2.0;
|
||||||
_updateBucket(context, taskState.buckets[newIndex]);
|
_updateBucket(context, taskState.buckets[newIndex]);
|
||||||
_draggedBucketIndex = null;
|
_draggedBucketIndex = null;
|
||||||
_pageController.jumpToPage(indexUpdated ? 0 : newIndex);
|
if (indexUpdated)
|
||||||
|
_pageController.jumpToPage((newIndex
|
||||||
|
/ (deviceData.orientation == Orientation.portrait ? 1 : 2)).floor());
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,12 @@ import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
import 'package:vikunja_app/components/datetimePicker.dart';
|
import 'package:vikunja_app/components/datetimePicker.dart';
|
||||||
import 'package:vikunja_app/components/label.dart';
|
import 'package:vikunja_app/components/label.dart';
|
||||||
import 'package:vikunja_app/global.dart';
|
import 'package:vikunja_app/global.dart';
|
||||||
import 'package:vikunja_app/models/label.dart';
|
import 'package:vikunja_app/models/label.dart';
|
||||||
import 'package:vikunja_app/models/task.dart';
|
import 'package:vikunja_app/models/task.dart';
|
||||||
import 'package:vikunja_app/theme/button.dart';
|
|
||||||
import 'package:vikunja_app/theme/buttonText.dart';
|
|
||||||
import 'package:vikunja_app/utils/repeat_after_parse.dart';
|
import 'package:vikunja_app/utils/repeat_after_parse.dart';
|
||||||
|
|
||||||
class TaskEditPage extends StatefulWidget {
|
class TaskEditPage extends StatefulWidget {
|
||||||
|
@ -22,6 +21,7 @@ class TaskEditPage extends StatefulWidget {
|
||||||
|
|
||||||
class _TaskEditPageState extends State<TaskEditPage> {
|
class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _listKey = GlobalKey();
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
bool _changed = false;
|
bool _changed = false;
|
||||||
|
|
||||||
|
@ -35,6 +35,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
List<Label> _suggestedLabels;
|
List<Label> _suggestedLabels;
|
||||||
var _reminderInputs = <Widget>[];
|
var _reminderInputs = <Widget>[];
|
||||||
final _labelTypeAheadController = TextEditingController();
|
final _labelTypeAheadController = TextEditingController();
|
||||||
|
Color _color;
|
||||||
|
Color _pickerColor;
|
||||||
|
bool _resetColor = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext ctx) {
|
Widget build(BuildContext ctx) {
|
||||||
|
@ -44,282 +47,322 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
|
|
||||||
_reminderDates?.asMap()?.forEach((i, time) =>
|
_reminderDates?.asMap()?.forEach((i, time) =>
|
||||||
setState(() => _reminderInputs?.add(VikunjaDateTimePicker(
|
setState(() => _reminderInputs?.add(VikunjaDateTimePicker(
|
||||||
initialValue: time,
|
initialValue: time,
|
||||||
label: 'Reminder',
|
label: 'Reminder',
|
||||||
onSaved: (reminder) => _reminderDates[i] = reminder,
|
onSaved: (reminder) => _reminderDates[i] = reminder,
|
||||||
))));
|
)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_labels == null) {
|
if (_labels == null) {
|
||||||
_labels = widget.task.labels ?? [];
|
_labels = widget.task.labels ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return WillPopScope(onWillPop: () {
|
return WillPopScope(
|
||||||
if(_changed) {
|
onWillPop: () {
|
||||||
return _showConfirmationDialog();
|
if(_changed) {
|
||||||
}
|
return _showConfirmationDialog();
|
||||||
return new Future(() => true);
|
}
|
||||||
},
|
return new Future(() => true);
|
||||||
child: Scaffold(
|
},
|
||||||
appBar: AppBar(
|
child: Scaffold(
|
||||||
title: Text('Edit Task'),
|
appBar: AppBar(
|
||||||
),
|
title: Text('Edit Task'),
|
||||||
body: Builder(
|
),
|
||||||
builder: (BuildContext context) => SafeArea(
|
body: Builder(
|
||||||
child: Form(
|
builder: (BuildContext context) => SafeArea(
|
||||||
key: _formKey,
|
child: Form(
|
||||||
child: ListView(padding: const EdgeInsets.all(16.0), children: <
|
key: _formKey,
|
||||||
Widget>[
|
child: ListView(
|
||||||
Padding(
|
key: _listKey,
|
||||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: TextFormField(
|
children: <Widget>[
|
||||||
maxLines: null,
|
Padding(
|
||||||
keyboardType: TextInputType.multiline,
|
padding: EdgeInsets.symmetric(vertical: 10.0),
|
||||||
initialValue: widget.task.title,
|
|
||||||
onSaved: (title) => _title = title,
|
|
||||||
onChanged: (_) => _changed = true,
|
|
||||||
validator: (title) {
|
|
||||||
if (title.length < 3 || title.length > 250) {
|
|
||||||
return 'The title needs to have between 3 and 250 characters.';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
decoration: new InputDecoration(
|
|
||||||
labelText: 'Title',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
|
||||||
child: TextFormField(
|
|
||||||
maxLines: null,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
initialValue: widget.task.description,
|
|
||||||
onSaved: (description) => _description = description,
|
|
||||||
onChanged: (_) => _changed = true,
|
|
||||||
validator: (description) {
|
|
||||||
if (description.length > 1000) {
|
|
||||||
return 'The description can have a maximum of 1000 characters.';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
decoration: new InputDecoration(
|
|
||||||
labelText: 'Description',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VikunjaDateTimePicker(
|
|
||||||
icon: Icon(Icons.access_time),
|
|
||||||
label: 'Due Date',
|
|
||||||
initialValue: widget.task.dueDate,
|
|
||||||
onSaved: (duedate) => _dueDate = duedate,
|
|
||||||
onChanged: (_) => _changed = true,
|
|
||||||
),
|
|
||||||
VikunjaDateTimePicker(
|
|
||||||
label: 'Start Date',
|
|
||||||
initialValue: widget.task.startDate,
|
|
||||||
onSaved: (startDate) => _startDate = startDate,
|
|
||||||
onChanged: (_) => _changed = true,
|
|
||||||
),
|
|
||||||
VikunjaDateTimePicker(
|
|
||||||
label: 'End Date',
|
|
||||||
initialValue: widget.task.endDate,
|
|
||||||
onSaved: (endDate) => _endDate = endDate,
|
|
||||||
onChanged: (_) => _changed = true,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
keyboardType: TextInputType.number,
|
maxLines: null,
|
||||||
initialValue: getRepeatAfterValueFromDuration(
|
keyboardType: TextInputType.multiline,
|
||||||
widget.task.repeatAfter)
|
initialValue: widget.task.title,
|
||||||
?.toString(),
|
onSaved: (title) => _title = title,
|
||||||
onSaved: (repeatAfter) => _repeatAfter =
|
|
||||||
getDurationFromType(repeatAfter, _repeatAfterType),
|
|
||||||
onChanged: (_) => _changed = true,
|
onChanged: (_) => _changed = true,
|
||||||
|
validator: (title) {
|
||||||
|
if (title.length < 3 || title.length > 250) {
|
||||||
|
return 'The title needs to have between 3 and 250 characters.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
decoration: new InputDecoration(
|
decoration: new InputDecoration(
|
||||||
labelText: 'Repeat after',
|
labelText: 'Title',
|
||||||
border: InputBorder.none,
|
border: OutlineInputBorder(),
|
||||||
icon: Icon(Icons.repeat),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Padding(
|
||||||
child: DropdownButton<String>(
|
padding: EdgeInsets.symmetric(vertical: 10.0),
|
||||||
|
child: TextFormField(
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
initialValue: widget.task.description,
|
||||||
|
onSaved: (description) => _description = description,
|
||||||
|
onChanged: (_) => _changed = true,
|
||||||
|
validator: (description) {
|
||||||
|
if (description.length > 1000) {
|
||||||
|
return 'The description can have a maximum of 1000 characters.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
decoration: new InputDecoration(
|
||||||
|
labelText: 'Description',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VikunjaDateTimePicker(
|
||||||
|
icon: Icon(Icons.access_time),
|
||||||
|
label: 'Due Date',
|
||||||
|
initialValue: widget.task.dueDate,
|
||||||
|
onSaved: (duedate) => _dueDate = duedate,
|
||||||
|
onChanged: (_) => _changed = true,
|
||||||
|
),
|
||||||
|
VikunjaDateTimePicker(
|
||||||
|
label: 'Start Date',
|
||||||
|
initialValue: widget.task.startDate,
|
||||||
|
onSaved: (startDate) => _startDate = startDate,
|
||||||
|
onChanged: (_) => _changed = true,
|
||||||
|
),
|
||||||
|
VikunjaDateTimePicker(
|
||||||
|
label: 'End Date',
|
||||||
|
initialValue: widget.task.endDate,
|
||||||
|
onSaved: (endDate) => _endDate = endDate,
|
||||||
|
onChanged: (_) => _changed = true,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
initialValue: getRepeatAfterValueFromDuration(
|
||||||
|
widget.task.repeatAfter)?.toString(),
|
||||||
|
onSaved: (repeatAfter) => _repeatAfter =
|
||||||
|
getDurationFromType(repeatAfter, _repeatAfterType),
|
||||||
|
onChanged: (_) => _changed = true,
|
||||||
|
decoration: new InputDecoration(
|
||||||
|
labelText: 'Repeat after',
|
||||||
|
border: InputBorder.none,
|
||||||
|
icon: Icon(Icons.repeat),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: DropdownButton<String>(
|
||||||
|
isExpanded: true,
|
||||||
|
isDense: true,
|
||||||
|
value: _repeatAfterType ??
|
||||||
|
getRepeatAfterTypeFromDuration(
|
||||||
|
widget.task.repeatAfter),
|
||||||
|
onChanged: (String newValue) {
|
||||||
|
setState(() {
|
||||||
|
_repeatAfterType = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
items: <String>[
|
||||||
|
'Hours',
|
||||||
|
'Days',
|
||||||
|
'Weeks',
|
||||||
|
'Months',
|
||||||
|
'Years'
|
||||||
|
].map<DropdownMenuItem<String>>((String value) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: Text(value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: _reminderInputs,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 15, left: 2),
|
||||||
|
child: Icon(
|
||||||
|
Icons.alarm_add,
|
||||||
|
color: Colors.grey,
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
'Add a reminder',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
// We add a new entry every time we add a new input, to make sure all inputs have a place where they can put their value.
|
||||||
|
_reminderDates.add(null);
|
||||||
|
var currentIndex = _reminderDates.length - 1;
|
||||||
|
|
||||||
|
// FIXME: Why does putting this into a row fails?
|
||||||
|
setState(() => _reminderInputs.add(
|
||||||
|
VikunjaDateTimePicker(
|
||||||
|
label: 'Reminder',
|
||||||
|
onSaved: (reminder) =>
|
||||||
|
_reminderDates[currentIndex] = reminder,
|
||||||
|
onChanged: (_) => _changed = true,
|
||||||
|
initialValue: DateTime.now(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
InputDecorator(
|
||||||
|
isEmpty: _priority == null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
icon: const Icon(Icons.flag),
|
||||||
|
labelText: 'Priority',
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
child: new DropdownButton<String>(
|
||||||
|
value: _priorityToString(_priority),
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
value: _repeatAfterType ??
|
|
||||||
getRepeatAfterTypeFromDuration(
|
|
||||||
widget.task.repeatAfter),
|
|
||||||
onChanged: (String newValue) {
|
onChanged: (String newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_repeatAfterType = newValue;
|
_priority = _priorityFromString(newValue);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
items: <String>[
|
items: ['Unset', 'Low', 'Medium', 'High', 'Urgent', 'DO NOW']
|
||||||
'Hours',
|
.map((String value) {
|
||||||
'Days',
|
return new DropdownMenuItem(
|
||||||
'Weeks',
|
|
||||||
'Months',
|
|
||||||
'Years'
|
|
||||||
].map<DropdownMenuItem<String>>((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: value,
|
value: value,
|
||||||
child: Text(value),
|
child: new Text(value),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
Wrap(
|
||||||
),
|
spacing: 10,
|
||||||
Column(
|
children: _labels.map((Label label) {
|
||||||
children: _reminderInputs,
|
return LabelComponent(
|
||||||
),
|
label: label,
|
||||||
GestureDetector(
|
onDelete: () {
|
||||||
child: Padding(
|
_removeLabel(label);
|
||||||
padding: EdgeInsets.symmetric(vertical: 10),
|
},
|
||||||
|
);
|
||||||
|
}).toList()),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width - 80,
|
||||||
|
child: TypeAheadFormField(
|
||||||
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
|
controller: _labelTypeAheadController,
|
||||||
|
decoration:
|
||||||
|
InputDecoration(labelText: 'Add a new label')),
|
||||||
|
suggestionsCallback: (pattern) => _searchLabel(pattern),
|
||||||
|
itemBuilder: (context, suggestion) {
|
||||||
|
return Text(suggestion);
|
||||||
|
},
|
||||||
|
transitionBuilder: (context, suggestionsBox, controller) {
|
||||||
|
return suggestionsBox;
|
||||||
|
},
|
||||||
|
onSuggestionSelected: (suggestion) {
|
||||||
|
_addLabel(suggestion);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () =>
|
||||||
|
_createAndAddLabel(_labelTypeAheadController.text),
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 15),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 15, left: 2),
|
padding: const EdgeInsets.only(right: 15, left: 2),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.alarm_add,
|
Icons.palette,
|
||||||
color: Colors.grey,
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
'Add a reminder',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
fontSize: 16,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(
|
||||||
|
'Color',
|
||||||
|
style: _resetColor || (_color ?? widget.task.color) == null ? null : TextStyle(
|
||||||
|
color: (_color ?? widget.task.color)
|
||||||
|
.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: _resetColor ? null : ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty
|
||||||
|
.resolveWith((_) => _color ?? widget.task.color),
|
||||||
|
),
|
||||||
|
onPressed: _onColorEdit,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 15),
|
||||||
|
child: () {
|
||||||
|
String colorString = (_resetColor ? null : (_color ?? widget.task.color))?.toString();
|
||||||
|
colorString = colorString?.substring(10, colorString.length - 1)?.toUpperCase();
|
||||||
|
colorString = colorString != null ? '#$colorString' : 'None';
|
||||||
|
return Text(
|
||||||
|
'$colorString',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
// We add a new entry every time we add a new input, to make sure all inputs have a place where they can put their value.
|
|
||||||
_reminderDates.add(null);
|
|
||||||
var currentIndex = _reminderDates.length - 1;
|
|
||||||
|
|
||||||
// FIXME: Why does putting this into a row fails?
|
|
||||||
setState(() => _reminderInputs.add(
|
|
||||||
VikunjaDateTimePicker(
|
|
||||||
label: 'Reminder',
|
|
||||||
onSaved: (reminder) =>
|
|
||||||
_reminderDates[currentIndex] = reminder,
|
|
||||||
onChanged: (_) => _changed = true,
|
|
||||||
initialValue: DateTime.now(),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}),
|
|
||||||
InputDecorator(
|
|
||||||
isEmpty: _priority == null,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
icon: const Icon(Icons.flag),
|
|
||||||
labelText: 'Priority',
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
child: new DropdownButton<String>(
|
|
||||||
value: _priorityToString(_priority),
|
|
||||||
isExpanded: true,
|
|
||||||
isDense: true,
|
|
||||||
onChanged: (String newValue) {
|
|
||||||
setState(() {
|
|
||||||
_priority = _priorityFromString(newValue);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
items: ['Unset', 'Low', 'Medium', 'High', 'Urgent', 'DO NOW']
|
|
||||||
.map((String value) {
|
|
||||||
return new DropdownMenuItem(
|
|
||||||
value: value,
|
|
||||||
child: new Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Wrap(
|
|
||||||
spacing: 10,
|
|
||||||
children: _labels.map((Label label) {
|
|
||||||
return LabelComponent(
|
|
||||||
label: label,
|
|
||||||
onDelete: () {
|
|
||||||
_removeLabel(label);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).toList()),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: MediaQuery.of(context).size.width - 80,
|
|
||||||
child: TypeAheadFormField(
|
|
||||||
textFieldConfiguration: TextFieldConfiguration(
|
|
||||||
controller: _labelTypeAheadController,
|
|
||||||
decoration:
|
|
||||||
InputDecoration(labelText: 'Add a new label')),
|
|
||||||
suggestionsCallback: (pattern) => _searchLabel(pattern),
|
|
||||||
itemBuilder: (context, suggestion) {
|
|
||||||
return Text(suggestion);
|
|
||||||
},
|
|
||||||
transitionBuilder: (context, suggestionsBox, controller) {
|
|
||||||
return suggestionsBox;
|
|
||||||
},
|
|
||||||
onSuggestionSelected: (suggestion) {
|
|
||||||
_addLabel(suggestion);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () =>
|
|
||||||
_createAndAddLabel(_labelTypeAheadController.text),
|
|
||||||
icon: Icon(Icons.add),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Builder(
|
),
|
||||||
builder: (context) => Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 10.0),
|
|
||||||
child: FancyButton(
|
|
||||||
onPressed: !_loading
|
|
||||||
? () {
|
|
||||||
if (_formKey.currentState.validate()) {
|
|
||||||
Form.of(context).save();
|
|
||||||
_saveTask(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: _loading
|
|
||||||
? CircularProgressIndicator()
|
|
||||||
: VikunjaButtonText('Save'),
|
|
||||||
))),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: !_loading ? () {
|
||||||
|
if (_formKey.currentState.validate()) {
|
||||||
|
Form.of(_listKey.currentContext).save();
|
||||||
|
_saveTask(_listKey.currentContext);
|
||||||
|
}
|
||||||
|
} : null,
|
||||||
|
child: Icon(Icons.save),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveTask(BuildContext context) async {
|
_saveTask(BuildContext context) async {
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
|
|
||||||
// Removes all reminders with no value set.
|
// Removes all reminders with no value set.
|
||||||
_reminderDates.removeWhere((d) => d == null);
|
_reminderDates.removeWhere((d) => d == null);
|
||||||
|
|
||||||
Task updatedTask = Task(
|
Task updatedTask = widget.task.copyWith(
|
||||||
id: widget.task.id,
|
|
||||||
title: _title,
|
title: _title,
|
||||||
description: _description,
|
description: _description,
|
||||||
done: widget.task.done,
|
|
||||||
reminderDates: _reminderDates,
|
reminderDates: _reminderDates,
|
||||||
createdBy: widget.task.createdBy,
|
|
||||||
dueDate: _dueDate,
|
dueDate: _dueDate,
|
||||||
startDate: _startDate,
|
startDate: _startDate,
|
||||||
endDate: _endDate,
|
endDate: _endDate,
|
||||||
priority: _priority,
|
priority: _priority,
|
||||||
repeatAfter: _repeatAfter,
|
repeatAfter: _repeatAfter,
|
||||||
|
color: _resetColor ? null : (_color ?? widget.task.color),
|
||||||
|
resetColor: _resetColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
// update the labels
|
// update the labels
|
||||||
|
@ -335,11 +378,12 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
VikunjaGlobal.of(context).taskService.update(updatedTask).then((_) {
|
VikunjaGlobal.of(context).taskService.update(updatedTask).then((result) {
|
||||||
setState(() { _loading = false; _changed = false;});
|
setState(() { _loading = false; _changed = false;});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: Text('The task was updated successfully!'),
|
content: Text('The task was updated successfully!'),
|
||||||
));
|
));
|
||||||
|
Navigator.of(context).pop(result);
|
||||||
}).catchError((err) {
|
}).catchError((err) {
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
@ -361,15 +405,13 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
|
|
||||||
_searchLabel(String query) {
|
_searchLabel(String query) {
|
||||||
return VikunjaGlobal.of(context)
|
return VikunjaGlobal.of(context)
|
||||||
.labelService
|
.labelService.getAll(query: query).then((labels) {
|
||||||
.getAll(query: query)
|
|
||||||
.then((labels) {
|
|
||||||
log("searched");
|
log("searched");
|
||||||
// Only show those labels which aren't already added to the task
|
// Only show those labels which aren't already added to the task
|
||||||
labels.removeWhere((labelToRemove) => _labels.contains(labelToRemove));
|
labels.removeWhere((labelToRemove) => _labels.contains(labelToRemove));
|
||||||
_suggestedLabels = labels;
|
_suggestedLabels = labels;
|
||||||
return labels.map((label) => label.title).toList();
|
return labels.map((label) => label.title).toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_addLabel(String labelTitle) {
|
_addLabel(String labelTitle) {
|
||||||
|
@ -444,6 +486,61 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onColorEdit() {
|
||||||
|
_pickerColor = _resetColor || (_color ?? widget.task.color) == null
|
||||||
|
? Colors.black
|
||||||
|
: _color ?? widget.task.color;
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Task Color'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ColorPicker(
|
||||||
|
pickerColor: _pickerColor,
|
||||||
|
enableAlpha: false,
|
||||||
|
labelTypes: const [ColorLabelType.hsl, ColorLabelType.rgb],
|
||||||
|
paletteType: PaletteType.hslWithLightness,
|
||||||
|
hexInputBar: true,
|
||||||
|
onColorChanged: (color) => setState(() => _pickerColor = color),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <TextButton>[
|
||||||
|
TextButton(
|
||||||
|
child: Text('Cancel'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text('Reset'),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_color = null;
|
||||||
|
_resetColor = true;
|
||||||
|
_changed = _color != widget.task.color;
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text('Ok'),
|
||||||
|
onPressed: () {
|
||||||
|
if (_pickerColor != Colors.black) setState(() {
|
||||||
|
_color = _pickerColor;
|
||||||
|
_resetColor = false;
|
||||||
|
_changed = _color != widget.task.color;
|
||||||
|
});
|
||||||
|
else setState(() {
|
||||||
|
_color = null;
|
||||||
|
_resetColor = true;
|
||||||
|
_changed = _color != widget.task.color;
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> _showConfirmationDialog() async {
|
Future<bool> _showConfirmationDialog() async {
|
||||||
return showDialog<bool>(
|
return showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -174,6 +174,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_colorpicker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_colorpicker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
flutter_keyboard_visibility:
|
flutter_keyboard_visibility:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -24,6 +24,7 @@ dependencies:
|
||||||
petitparser: ^5.0.0
|
petitparser: ^5.0.0
|
||||||
provider: ^6.0.3
|
provider: ^6.0.3
|
||||||
webview_flutter: ^3.0.4
|
webview_flutter: ^3.0.4
|
||||||
|
flutter_colorpicker: ^1.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user