mirror of
https://github.com/go-vikunja/app
synced 2024-06-04 11:39:47 +00:00
Compare commits
3 Commits
f1ebc2c516
...
c37daa7e8b
Author | SHA1 | Date | |
---|---|---|---|
|
c37daa7e8b | ||
|
37009e2eaa | ||
|
ecd2006955 |
|
@ -12,15 +12,14 @@ class BucketAPIService extends APIService implements BucketService {
|
||||||
return client
|
return client
|
||||||
.put('/projects/$projectId/buckets', body: bucket.toJSON())
|
.put('/projects/$projectId/buckets', body: bucket.toJSON())
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Bucket.fromJSON(response.body);
|
return Bucket.fromJSON(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future delete(int projectId, int bucketId) {
|
Future delete(int projectId, int bucketId) {
|
||||||
return client
|
return client.delete('/projects/$projectId/buckets/$bucketId');
|
||||||
.delete('/projects/$projectId/buckets/$bucketId');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Not implemented in the Vikunja API
|
/* Not implemented in the Vikunja API
|
||||||
|
@ -35,13 +34,13 @@ class BucketAPIService extends APIService implements BucketService {
|
||||||
@override
|
@override
|
||||||
Future<Response?> getAllByList(int projectId,
|
Future<Response?> getAllByList(int projectId,
|
||||||
[Map<String, List<String>>? queryParameters]) {
|
[Map<String, List<String>>? queryParameters]) {
|
||||||
return client
|
return client.get('/projects/$projectId/buckets', queryParameters).then(
|
||||||
.get('/projects/$projectId/buckets', queryParameters)
|
(response) => response != null
|
||||||
.then((response) => response != null ? new Response(
|
? new Response(
|
||||||
convertList(response.body, (result) => Bucket.fromJSON(result)),
|
convertList(response.body, (result) => Bucket.fromJSON(result)),
|
||||||
response.statusCode,
|
response.statusCode,
|
||||||
response.headers
|
response.headers)
|
||||||
) : null);
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -51,10 +50,11 @@ class BucketAPIService extends APIService implements BucketService {
|
||||||
@override
|
@override
|
||||||
Future<Bucket?> update(Bucket bucket) {
|
Future<Bucket?> update(Bucket bucket) {
|
||||||
return client
|
return client
|
||||||
.post('/projects/${bucket.projectId}/buckets/${bucket.id}', body: bucket.toJSON())
|
.post('/projects/${bucket.projectId}/buckets/${bucket.id}',
|
||||||
|
body: bucket.toJSON())
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Bucket.fromJSON(response.body);
|
return Bucket.fromJSON(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class LabelTaskAPIService extends APIService implements LabelTaskService {
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Label.fromJson(response.body);
|
return Label.fromJson(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -22,9 +22,9 @@ class LabelTaskAPIService extends APIService implements LabelTaskService {
|
||||||
return client
|
return client
|
||||||
.delete('/tasks/${lt.task!.id}/labels/${lt.label.id}')
|
.delete('/tasks/${lt.task!.id}/labels/${lt.label.id}')
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Label.fromJson(response.body);
|
return Label.fromJson(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -32,10 +32,9 @@ class LabelTaskAPIService extends APIService implements LabelTaskService {
|
||||||
String? params =
|
String? params =
|
||||||
query == null ? null : '?s=' + Uri.encodeQueryComponent(query);
|
query == null ? null : '?s=' + Uri.encodeQueryComponent(query);
|
||||||
|
|
||||||
return client.get('/tasks/${lt.task!.id}/labels$params').then(
|
return client.get('/tasks/${lt.task!.id}/labels$params').then((label) {
|
||||||
(label) {
|
if (label == null) return null;
|
||||||
if (label == null) return null;
|
return convertList(label, (result) => Label.fromJson(result));
|
||||||
return convertList(label, (result) => Label.fromJson(result));
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,14 @@ class LabelTaskBulkAPIService extends APIService
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Label>?> update(Task task, List<Label>? labels) {
|
Future<List<Label>?> update(Task task, List<Label>? labels) {
|
||||||
if(labels == null)
|
if (labels == null) labels = [];
|
||||||
labels = [];
|
|
||||||
return client
|
return client
|
||||||
.post('/tasks/${task.id}/labels/bulk',
|
.post('/tasks/${task.id}/labels/bulk',
|
||||||
body: LabelTaskBulk(labels: labels).toJSON())
|
body: LabelTaskBulk(labels: labels).toJSON())
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return convertList(
|
return convertList(
|
||||||
response.body['labels'], (result) => Label.fromJson(result));
|
response.body['labels'], (result) => Label.fromJson(result));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,52 +8,43 @@ class LabelAPIService extends APIService implements LabelService {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Label?> create(Label label) {
|
Future<Label?> create(Label label) {
|
||||||
return client
|
return client.put('/labels', body: label.toJSON()).then((response) {
|
||||||
.put('/labels', body: label.toJSON())
|
if (response == null) return null;
|
||||||
.then((response) {
|
return Label.fromJson(response.body);
|
||||||
if (response == null) return null;
|
});
|
||||||
return Label.fromJson(response.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Label?> delete(Label label) {
|
Future<Label?> delete(Label label) {
|
||||||
return client
|
return client.delete('/labels/${label.id}').then((response) {
|
||||||
.delete('/labels/${label.id}')
|
if (response == null) return null;
|
||||||
.then((response) {
|
return Label.fromJson(response.body);
|
||||||
if (response == null) return null;
|
});
|
||||||
return Label.fromJson(response.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Label?> get(int labelID) {
|
Future<Label?> get(int labelID) {
|
||||||
return client
|
return client.get('/labels/$labelID').then((response) {
|
||||||
.get('/labels/$labelID')
|
if (response == null) return null;
|
||||||
.then((response) {
|
return Label.fromJson(response.body);
|
||||||
if (response == null) return null;
|
});
|
||||||
return Label.fromJson(response.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Label>?> getAll({String? query}) {
|
Future<List<Label>?> getAll({String? query}) {
|
||||||
String params =
|
String params =
|
||||||
query == null ? '' : '?s=' + Uri.encodeQueryComponent(query);
|
query == null ? '' : '?s=' + Uri.encodeQueryComponent(query);
|
||||||
return client.get('/labels$params').then(
|
return client.get('/labels$params').then((response) {
|
||||||
(response) {
|
if (response == null) return null;
|
||||||
if (response == null) return null;
|
return convertList(response.body, (result) => Label.fromJson(result));
|
||||||
return convertList(response.body, (result) => Label.fromJson(result));
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Label?> update(Label label) {
|
Future<Label?> update(Label label) {
|
||||||
return client
|
return client.post('/labels/${label.id}', body: label).then((response) {
|
||||||
.post('/labels/${label.id}', body: label)
|
if (response == null) return null;
|
||||||
.then((response) {
|
return Label.fromJson(response.body);
|
||||||
if (response == null) return null;
|
});
|
||||||
return Label.fromJson(response.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,8 +41,8 @@ class NamespaceAPIService extends APIService implements NamespaceService {
|
||||||
return client
|
return client
|
||||||
.post('/namespaces/${ns.id}', body: ns.toJSON())
|
.post('/namespaces/${ns.id}', body: ns.toJSON())
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Namespace.fromJson(response.body);
|
return Namespace.fromJson(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,7 @@ class ServerAPIService extends APIService implements ServerService {
|
||||||
@override
|
@override
|
||||||
Future<Server?> getInfo() {
|
Future<Server?> getInfo() {
|
||||||
return client.get('/info').then((value) {
|
return client.get('/info').then((value) {
|
||||||
if(value == null)
|
if (value == null) return null;
|
||||||
return null;
|
|
||||||
return Server.fromJson(value.body);
|
return Server.fromJson(value.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,25 +14,22 @@ class TaskAPIService extends APIService implements TaskService {
|
||||||
return client
|
return client
|
||||||
.put('/projects/$projectId/tasks', body: task.toJSON())
|
.put('/projects/$projectId/tasks', body: task.toJSON())
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Task.fromJson(response.body);
|
return Task.fromJson(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Task?> get(int listId) {
|
Future<Task?> get(int listId) {
|
||||||
return client
|
return client.get('/project/$listId/tasks').then((response) {
|
||||||
.get('/project/$listId/tasks')
|
if (response == null) return null;
|
||||||
.then((response) {
|
return Task.fromJson(response.body);
|
||||||
if (response == null) return null;
|
});
|
||||||
return Task.fromJson(response.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future delete(int taskId) {
|
Future delete(int taskId) {
|
||||||
return client
|
return client.delete('/tasks/$taskId');
|
||||||
.delete('/tasks/$taskId');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -40,36 +37,38 @@ class TaskAPIService extends APIService implements TaskService {
|
||||||
return client
|
return client
|
||||||
.post('/tasks/${task.id}', body: task.toJSON())
|
.post('/tasks/${task.id}', body: task.toJSON())
|
||||||
.then((response) {
|
.then((response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
return Task.fromJson(response.body);
|
return Task.fromJson(response.body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Task>?> getAll() {
|
Future<List<Task>?> getAll() {
|
||||||
return client
|
return client.get('/tasks/all').then((response) {
|
||||||
.get('/tasks/all')
|
int page_n = 0;
|
||||||
.then((response) {
|
if (response == null) return null;
|
||||||
int page_n = 0;
|
if (response.headers["x-pagination-total-pages"] != null) {
|
||||||
if (response == null) return null;
|
page_n = int.parse(response.headers["x-pagination-total-pages"]!);
|
||||||
if (response.headers["x-pagination-total-pages"] != null) {
|
} else {
|
||||||
page_n = int.parse(response.headers["x-pagination-total-pages"]!);
|
return Future.value(response.body);
|
||||||
} else {
|
}
|
||||||
return Future.value(response.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Future<void>> futureList = [];
|
List<Future<void>> futureList = [];
|
||||||
List<Task> taskList = [];
|
List<Task> taskList = [];
|
||||||
|
|
||||||
for(int i = 0; i < page_n; i++) {
|
for (int i = 0; i < page_n; i++) {
|
||||||
Map<String, List<String>> paramMap = {
|
Map<String, List<String>> paramMap = {
|
||||||
"page": [i.toString()]
|
"page": [i.toString()]
|
||||||
};
|
};
|
||||||
futureList.add(client.get('/tasks/all', paramMap).then((pageResponse) {convertList(pageResponse?.body, (result) {taskList.add(Task.fromJson(result));});}));
|
futureList.add(client.get('/tasks/all', paramMap).then((pageResponse) {
|
||||||
}
|
convertList(pageResponse?.body, (result) {
|
||||||
return Future.wait(futureList).then((value) {
|
taskList.add(Task.fromJson(result));
|
||||||
return taskList;
|
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return Future.wait(futureList).then((value) {
|
||||||
|
return taskList;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,14 +76,15 @@ class TaskAPIService extends APIService implements TaskService {
|
||||||
Future<Response?> getAllByProject(int projectId,
|
Future<Response?> getAllByProject(int projectId,
|
||||||
[Map<String, List<String>>? queryParameters]) {
|
[Map<String, List<String>>? queryParameters]) {
|
||||||
return client
|
return client
|
||||||
.get('/projects/$projectId/tasks', queryParameters).then(
|
.get('/projects/$projectId/tasks', queryParameters)
|
||||||
(response) {
|
.then((response) {
|
||||||
return response != null ?
|
return response != null
|
||||||
new Response(
|
? new Response(
|
||||||
convertList(response.body, (result) => Task.fromJson(result)),
|
convertList(response.body, (result) => Task.fromJson(result)),
|
||||||
response.statusCode,
|
response.statusCode,
|
||||||
response.headers) : null;
|
response.headers)
|
||||||
});
|
: null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -93,16 +93,13 @@ class TaskAPIService extends APIService implements TaskService {
|
||||||
//optionString = "?sort_by[]=due_date&sort_by[]=id&order_by[]=asc&order_by[]=desc&filter_by[]=done&filter_value[]=false&filter_comparator[]=equals&filter_concat=and&filter_include_nulls=false&page=1";
|
//optionString = "?sort_by[]=due_date&sort_by[]=id&order_by[]=asc&order_by[]=desc&filter_by[]=done&filter_value[]=false&filter_comparator[]=equals&filter_concat=and&filter_include_nulls=false&page=1";
|
||||||
//print(optionString);
|
//print(optionString);
|
||||||
|
|
||||||
return client
|
return client.get('/tasks/all', optionsMap).then((response) {
|
||||||
.get('/tasks/all', optionsMap)
|
if (response == null) return null;
|
||||||
.then((response) {
|
return convertList(response.body, (result) => Task.fromJson(result));
|
||||||
if (response == null) return null;
|
|
||||||
return convertList(response.body, (result) => Task.fromJson(result));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement maxPages
|
// TODO: implement maxPages
|
||||||
int get maxPages => maxPages;
|
int get maxPages => maxPages;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'dart:convert';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
|
||||||
class VersionChecker {
|
class VersionChecker {
|
||||||
GlobalKey<ScaffoldMessengerState> snackbarKey;
|
GlobalKey<ScaffoldMessengerState> snackbarKey;
|
||||||
VersionChecker(this.snackbarKey);
|
VersionChecker(this.snackbarKey);
|
||||||
|
@ -46,7 +45,8 @@ class VersionChecker {
|
||||||
content: Text("New version available: $latest"),
|
content: Text("New version available: $latest"),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: "View on Github",
|
label: "View on Github",
|
||||||
onPressed: () => launchUrl(Uri.parse(repo), mode: LaunchMode.externalApplication)),
|
onPressed: () => launchUrl(Uri.parse(repo),
|
||||||
|
mode: LaunchMode.externalApplication)),
|
||||||
);
|
);
|
||||||
snackbarKey.currentState?.showSnackBar(snackBar);
|
snackbarKey.currentState?.showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:vikunja_app/components/datetimePicker.dart';
|
import 'package:vikunja_app/components/datetimePicker.dart';
|
||||||
|
|
||||||
enum NewTaskDue {day,week, month, custom}
|
enum NewTaskDue { day, week, month, custom }
|
||||||
|
|
||||||
// TODO: add to enum above
|
// TODO: add to enum above
|
||||||
Map<NewTaskDue, Duration> newTaskDueToDuration = {
|
Map<NewTaskDue, Duration> newTaskDueToDuration = {
|
||||||
NewTaskDue.day: Duration(days: 1),
|
NewTaskDue.day: Duration(days: 1),
|
||||||
|
@ -13,11 +14,11 @@ class AddDialog extends StatefulWidget {
|
||||||
final ValueChanged<String>? onAdd;
|
final ValueChanged<String>? onAdd;
|
||||||
final void Function(String title, DateTime? dueDate)? onAddTask;
|
final void Function(String title, DateTime? dueDate)? onAddTask;
|
||||||
final InputDecoration? decoration;
|
final InputDecoration? decoration;
|
||||||
const AddDialog({Key? key, this.onAdd, this.decoration, this.onAddTask}) : super(key: key);
|
const AddDialog({Key? key, this.onAdd, this.decoration, this.onAddTask})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => AddDialogState();
|
State<StatefulWidget> createState() => AddDialogState();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddDialogState extends State<AddDialog> {
|
class AddDialogState extends State<AddDialog> {
|
||||||
|
@ -27,13 +28,11 @@ class AddDialogState extends State<AddDialog> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if(newTaskDue != NewTaskDue.custom)
|
if (newTaskDue != NewTaskDue.custom)
|
||||||
customDueDate = DateTime.now().add(newTaskDueToDuration[newTaskDue]!);
|
customDueDate = DateTime.now().add(newTaskDueToDuration[newTaskDue]!);
|
||||||
return new AlertDialog(
|
return new AlertDialog(
|
||||||
contentPadding: const EdgeInsets.all(16.0),
|
contentPadding: const EdgeInsets.all(16.0),
|
||||||
content: new Column(
|
content: new Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Row(children: <Widget>[
|
Row(children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: new TextField(
|
child: new TextField(
|
||||||
|
@ -43,14 +42,24 @@ class AddDialogState extends State<AddDialog> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
widget.onAddTask != null ? taskDueList("1 Day", NewTaskDue.day) : new Container(),
|
widget.onAddTask != null
|
||||||
widget.onAddTask != null ? taskDueList("1 Week", NewTaskDue.week) : new Container(),
|
? taskDueList("1 Day", NewTaskDue.day)
|
||||||
widget.onAddTask != null ? taskDueList("1 Month", NewTaskDue.month) : new Container(),
|
: new Container(),
|
||||||
widget.onAddTask != null ? VikunjaDateTimePicker(
|
widget.onAddTask != null
|
||||||
label: "Enter exact time",
|
? taskDueList("1 Week", NewTaskDue.week)
|
||||||
onChanged: (value) {setState(() => newTaskDue = NewTaskDue.custom); customDueDate = value;},
|
: new Container(),
|
||||||
|
widget.onAddTask != null
|
||||||
) : new Container(),
|
? taskDueList("1 Month", NewTaskDue.month)
|
||||||
|
: new Container(),
|
||||||
|
widget.onAddTask != null
|
||||||
|
? VikunjaDateTimePicker(
|
||||||
|
label: "Enter exact time",
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => newTaskDue = NewTaskDue.custom);
|
||||||
|
customDueDate = value;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: new Container(),
|
||||||
//],)
|
//],)
|
||||||
]),
|
]),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
|
@ -63,7 +72,7 @@ class AddDialogState extends State<AddDialog> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (widget.onAdd != null && textController.text.isNotEmpty)
|
if (widget.onAdd != null && textController.text.isNotEmpty)
|
||||||
widget.onAdd!(textController.text);
|
widget.onAdd!(textController.text);
|
||||||
if(widget.onAddTask != null && textController.text.isNotEmpty) {
|
if (widget.onAddTask != null && textController.text.isNotEmpty) {
|
||||||
widget.onAddTask!(textController.text, customDueDate);
|
widget.onAddTask!(textController.text, customDueDate);
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
@ -75,9 +84,15 @@ class AddDialogState extends State<AddDialog> {
|
||||||
|
|
||||||
Widget taskDueList(String name, NewTaskDue thisNewTaskDue) {
|
Widget taskDueList(String name, NewTaskDue thisNewTaskDue) {
|
||||||
return Row(children: [
|
return Row(children: [
|
||||||
Checkbox(value: newTaskDue == thisNewTaskDue, onChanged: (value) {
|
Checkbox(
|
||||||
newTaskDue = thisNewTaskDue;
|
value: newTaskDue == thisNewTaskDue,
|
||||||
setState(() => customDueDate = DateTime.now().add(newTaskDueToDuration[thisNewTaskDue]!));}, shape: CircleBorder(),),
|
onChanged: (value) {
|
||||||
|
newTaskDue = thisNewTaskDue;
|
||||||
|
setState(() => customDueDate =
|
||||||
|
DateTime.now().add(newTaskDueToDuration[thisNewTaskDue]!));
|
||||||
|
},
|
||||||
|
shape: CircleBorder(),
|
||||||
|
),
|
||||||
Text(name),
|
Text(name),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,13 +36,15 @@ class _BucketLimitDialogState extends State<BucketLimitDialog> {
|
||||||
inputFormatters: <TextInputFormatter>[
|
inputFormatters: <TextInputFormatter>[
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
],
|
],
|
||||||
onSubmitted: (text) => Navigator.of(context).pop(int.parse(text)),
|
onSubmitted: (text) =>
|
||||||
|
Navigator.of(context).pop(int.parse(text)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _controller.text = '${int.parse(_controller.text) + 1}',
|
onPressed: () =>
|
||||||
|
_controller.text = '${int.parse(_controller.text) + 1}',
|
||||||
icon: Icon(Icons.expand_less),
|
icon: Icon(Icons.expand_less),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -68,7 +70,8 @@ class _BucketLimitDialogState extends State<BucketLimitDialog> {
|
||||||
child: Text('Remove Limit'),
|
child: Text('Remove Limit'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(int.parse(_controller.text)),
|
onPressed: () =>
|
||||||
|
Navigator.of(context).pop(int.parse(_controller.text)),
|
||||||
child: Text('Done'),
|
child: Text('Done'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -20,20 +20,23 @@ class SliverBucketList extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverList(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
return index >= bucket.tasks.length ? null : BucketTaskCard(
|
return index >= bucket.tasks.length
|
||||||
key: ObjectKey(bucket.tasks[index]),
|
? null
|
||||||
task: bucket.tasks[index],
|
: BucketTaskCard(
|
||||||
index: index,
|
key: ObjectKey(bucket.tasks[index]),
|
||||||
onDragUpdate: onTaskDragUpdate,
|
task: bucket.tasks[index],
|
||||||
onAccept: (task, index) {
|
index: index,
|
||||||
_moveTaskToBucket(context, task, index);
|
onDragUpdate: onTaskDragUpdate,
|
||||||
},
|
onAccept: (task, index) {
|
||||||
);
|
_moveTaskToBucket(context, task, index);
|
||||||
|
},
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _moveTaskToBucket(BuildContext context, Task task, int index) async {
|
Future<void> _moveTaskToBucket(
|
||||||
|
BuildContext context, Task task, int index) async {
|
||||||
await Provider.of<ProjectProvider>(context, listen: false).moveTaskToBucket(
|
await Provider.of<ProjectProvider>(context, listen: false).moveTaskToBucket(
|
||||||
context: context,
|
context: context,
|
||||||
task: task,
|
task: task,
|
||||||
|
@ -42,7 +45,8 @@ class SliverBucketList extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: Text('\'${task.title}\' was moved to \'${bucket.title}\' successfully!'),
|
content: Text(
|
||||||
|
'\'${task.title}\' was moved to \'${bucket.title}\' successfully!'),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,14 @@ class SliverBucketPersistentHeader extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverPersistentHeader(
|
return SliverPersistentHeader(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
delegate: _SliverBucketPersistentHeaderDelegate(child, minExtent, maxExtent),
|
delegate:
|
||||||
|
_SliverBucketPersistentHeaderDelegate(child, minExtent, maxExtent),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SliverBucketPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
|
class _SliverBucketPersistentHeaderDelegate
|
||||||
|
extends SliverPersistentHeaderDelegate {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final double min;
|
final double min;
|
||||||
final double max;
|
final double max;
|
||||||
|
@ -29,7 +31,8 @@ class _SliverBucketPersistentHeaderDelegate extends SliverPersistentHeaderDelega
|
||||||
_SliverBucketPersistentHeaderDelegate(this.child, this.min, this.max);
|
_SliverBucketPersistentHeaderDelegate(this.child, this.min, this.max);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
Widget build(
|
||||||
|
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +43,10 @@ class _SliverBucketPersistentHeaderDelegate extends SliverPersistentHeaderDelega
|
||||||
double get minExtent => min;
|
double get minExtent => min;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRebuild(covariant _SliverBucketPersistentHeaderDelegate oldDelegate) {
|
bool shouldRebuild(
|
||||||
return oldDelegate.child != child || oldDelegate.min != min || oldDelegate.max != max;
|
covariant _SliverBucketPersistentHeaderDelegate oldDelegate) {
|
||||||
|
return oldDelegate.child != child ||
|
||||||
|
oldDelegate.min != min ||
|
||||||
|
oldDelegate.max != max;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
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:vikunja_app/components/label.dart';
|
||||||
|
import 'package:vikunja_app/models/task.dart';
|
||||||
|
import 'package:vikunja_app/pages/list/task_edit.dart';
|
||||||
|
import 'package:vikunja_app/stores/project_store.dart';
|
||||||
|
import 'package:vikunja_app/theme/constants.dart';
|
||||||
import 'package:vikunja_app/utils/priority.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 {
|
class TaskBottomSheet extends StatefulWidget {
|
||||||
final Task task;
|
final Task task;
|
||||||
final bool showInfo;
|
final bool showInfo;
|
||||||
|
@ -26,129 +24,131 @@ class TaskBottomSheet extends StatefulWidget {
|
||||||
this.showInfo = false,
|
this.showInfo = false,
|
||||||
this.onMarkedAsDone,
|
this.onMarkedAsDone,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
/*
|
|
||||||
@override
|
|
||||||
TaskTileState createState() {
|
|
||||||
return new TaskTileState(this.task, this.loading);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@override
|
@override
|
||||||
TaskBottomSheetState createState() => TaskBottomSheetState(this.task);
|
TaskBottomSheetState createState() => TaskBottomSheetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class TaskBottomSheetState extends State<TaskBottomSheet> {
|
class TaskBottomSheetState extends State<TaskBottomSheet> {
|
||||||
Task _currentTask;
|
late Task _currentTask;
|
||||||
|
|
||||||
TaskBottomSheetState(this._currentTask);
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentTask = widget.task;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ThemeData theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Container(
|
return SingleChildScrollView(
|
||||||
height: MediaQuery.of(context).size.height * 0.9,
|
child: Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.all(20),
|
||||||
padding: EdgeInsets.fromLTRB(20, 10, 10, 20),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: [
|
||||||
mainAxisSize: MainAxisSize.max,
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: [
|
||||||
Row(
|
Text(
|
||||||
// Title and edit button
|
_currentTask.title,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
style: theme.textTheme.headline6,
|
||||||
children: [
|
),
|
||||||
Text(_currentTask.title,
|
IconButton(
|
||||||
style: theme.textTheme.headlineLarge),
|
onPressed: _editTask,
|
||||||
IconButton(
|
icon: Icon(Icons.edit),
|
||||||
onPressed: () {
|
),
|
||||||
Navigator.push<Task>(
|
],
|
||||||
context,
|
),
|
||||||
MaterialPageRoute(
|
SizedBox(height: 16),
|
||||||
builder: (buildContext) => TaskEditPage(
|
Wrap(
|
||||||
task: _currentTask,
|
spacing: 8,
|
||||||
taskState: widget.taskState,
|
runSpacing: 8,
|
||||||
),
|
children: _currentTask.labels.map((label) {
|
||||||
),
|
return LabelComponent(label: label);
|
||||||
)
|
}).toList(),
|
||||||
.then((task) => setState(() {
|
),
|
||||||
if (task != null) _currentTask = task;
|
HtmlWidget(
|
||||||
}))
|
_currentTask.description.isNotEmpty
|
||||||
.whenComplete(() => widget.onEdit());
|
? _currentTask.description
|
||||||
},
|
: 'No description',
|
||||||
icon: Icon(Icons.edit)),
|
),
|
||||||
],
|
SizedBox(height: 16),
|
||||||
),
|
_buildRowWithIconAndText(
|
||||||
Wrap(
|
Icons.access_time,
|
||||||
spacing: 10,
|
_currentTask.dueDate != null
|
||||||
children: _currentTask.labels.map((Label label) {
|
? vDateFormatShort.format(_currentTask.dueDate!.toLocal())
|
||||||
return LabelComponent(
|
: 'No due date',
|
||||||
label: label,
|
),
|
||||||
);
|
_buildRowWithIconAndText(
|
||||||
}).toList()),
|
Icons.play_arrow_rounded,
|
||||||
|
_currentTask.startDate != null
|
||||||
|
? vDateFormatShort.format(_currentTask.startDate!.toLocal())
|
||||||
|
: 'No start date',
|
||||||
|
),
|
||||||
|
_buildRowWithIconAndText(
|
||||||
|
Icons.stop_rounded,
|
||||||
|
_currentTask.endDate != null
|
||||||
|
? vDateFormatShort.format(_currentTask.endDate!.toLocal())
|
||||||
|
: 'No end date',
|
||||||
|
),
|
||||||
|
_buildRowWithIconAndText(
|
||||||
|
Icons.priority_high,
|
||||||
|
_currentTask.priority != null
|
||||||
|
? priorityToString(_currentTask.priority)
|
||||||
|
: 'No priority',
|
||||||
|
),
|
||||||
|
_buildRowWithIconAndText(
|
||||||
|
Icons.percent,
|
||||||
|
_currentTask.percent_done != null
|
||||||
|
? '${(_currentTask.percent_done! * 100).toInt()}%'
|
||||||
|
: 'Unset',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// description with html rendering
|
void _editTask() {
|
||||||
Text("Description", style: theme.textTheme.headlineSmall),
|
Navigator.push<Task>(
|
||||||
Padding(
|
context,
|
||||||
padding: EdgeInsets.fromLTRB(10, 0, 0, 0),
|
MaterialPageRoute(
|
||||||
child: HtmlWidget(_currentTask.description.isNotEmpty
|
builder: (buildContext) => TaskEditPage(
|
||||||
? _currentTask.description
|
task: _currentTask,
|
||||||
: "No description"),
|
taskState: widget.taskState,
|
||||||
),
|
),
|
||||||
// Due date
|
),
|
||||||
Row(
|
).then((task) {
|
||||||
children: [
|
if (task != null) {
|
||||||
Icon(Icons.access_time),
|
setState(() {
|
||||||
Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)),
|
_currentTask = task;
|
||||||
Text(_currentTask.dueDate != null
|
});
|
||||||
? vDateFormatShort.format(_currentTask.dueDate!.toLocal())
|
}
|
||||||
: "No due date"),
|
widget.onEdit();
|
||||||
],
|
});
|
||||||
),
|
}
|
||||||
// start date
|
|
||||||
Row(
|
Widget _buildSectionTitle(String title) {
|
||||||
children: [
|
return Text(
|
||||||
Icon(Icons.play_arrow_rounded),
|
title,
|
||||||
Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)),
|
style: TextStyle(
|
||||||
Text(_currentTask.startDate != null
|
fontWeight: FontWeight.bold,
|
||||||
? vDateFormatShort
|
fontSize: 18,
|
||||||
.format(_currentTask.startDate!.toLocal())
|
),
|
||||||
: "No start date"),
|
);
|
||||||
],
|
}
|
||||||
),
|
|
||||||
// end date
|
Widget _buildRowWithIconAndText(IconData icon, String text) {
|
||||||
Row(
|
return Padding(
|
||||||
children: [
|
padding: EdgeInsets.symmetric(vertical: 4),
|
||||||
Icon(Icons.stop_rounded),
|
child: Row(
|
||||||
Padding(padding: EdgeInsets.fromLTRB(10, 0, 0, 0)),
|
children: [
|
||||||
Text(_currentTask.endDate != null
|
Icon(icon),
|
||||||
? vDateFormatShort.format(_currentTask.endDate!.toLocal())
|
SizedBox(width: 8),
|
||||||
: "No end date"),
|
Text(text),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// 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"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,160 +1,65 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
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:provider/provider.dart';
|
||||||
import 'package:vikunja_app/components/TaskBottomSheet.dart';
|
import 'package:vikunja_app/components/TaskBottomSheet.dart';
|
||||||
|
import 'package:vikunja_app/models/project.dart';
|
||||||
import 'package:vikunja_app/models/task.dart';
|
import 'package:vikunja_app/models/task.dart';
|
||||||
import 'package:vikunja_app/utils/misc.dart';
|
|
||||||
import 'package:vikunja_app/pages/list/task_edit.dart';
|
import 'package:vikunja_app/pages/list/task_edit.dart';
|
||||||
|
import 'package:vikunja_app/stores/project_store.dart';
|
||||||
|
import 'package:vikunja_app/utils/misc.dart';
|
||||||
import 'package:vikunja_app/utils/priority.dart';
|
import 'package:vikunja_app/utils/priority.dart';
|
||||||
|
|
||||||
import '../stores/project_store.dart';
|
|
||||||
|
|
||||||
class TaskTile extends StatefulWidget {
|
class TaskTile extends StatefulWidget {
|
||||||
final Task task;
|
final Task task;
|
||||||
final Function onEdit;
|
final Function onEdit;
|
||||||
final bool showInfo;
|
final bool showInfo;
|
||||||
final bool loading;
|
final bool loading;
|
||||||
final ValueSetter<bool>? onMarkedAsDone;
|
final ValueSetter<bool>? onMarkedAsDone;
|
||||||
|
final Map<int, Project>? projectsMap;
|
||||||
|
|
||||||
const TaskTile({
|
const TaskTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.task,
|
required this.task,
|
||||||
required this.onEdit,
|
required this.onEdit,
|
||||||
|
this.projectsMap,
|
||||||
this.loading = false,
|
this.loading = false,
|
||||||
this.showInfo = false,
|
this.showInfo = false,
|
||||||
this.onMarkedAsDone,
|
this.onMarkedAsDone,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
/*
|
|
||||||
@override
|
|
||||||
TaskTileState createState() {
|
|
||||||
return new TaskTileState(this.task, this.loading);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@override
|
@override
|
||||||
TaskTileState createState() => TaskTileState(this.task);
|
_TaskTileState createState() => _TaskTileState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? _buildTaskSubtitle(Task? task, bool showInfo, BuildContext context) {
|
class _TaskTileState extends State<TaskTile> {
|
||||||
Duration? durationUntilDue = task?.dueDate?.difference(DateTime.now());
|
late Task _currentTask;
|
||||||
|
|
||||||
if (task == null) return null;
|
@override
|
||||||
|
void initState() {
|
||||||
List<TextSpan> texts = [];
|
super.initState();
|
||||||
|
_currentTask = widget.task;
|
||||||
if (showInfo && task.hasDueDate) {
|
|
||||||
texts.add(TextSpan(
|
|
||||||
text: "Due " + durationToHumanReadable(durationUntilDue!),
|
|
||||||
style: durationUntilDue.isNegative
|
|
||||||
? TextStyle(color: Colors.red)
|
|
||||||
: Theme.of(context).textTheme.bodyMedium));
|
|
||||||
}
|
}
|
||||||
if (task.priority != null && task.priority != 0) {
|
|
||||||
texts.add(TextSpan(
|
|
||||||
text: " !" + priorityToString(task.priority),
|
|
||||||
style: TextStyle(color: Colors.orange)));
|
|
||||||
}
|
|
||||||
|
|
||||||
//if(texts.isEmpty && task.description.isNotEmpty) {
|
|
||||||
// return HtmlWidget(task.description);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (texts.isNotEmpty) {
|
|
||||||
return RichText(text: TextSpan(children: texts));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
|
||||||
Task _currentTask;
|
|
||||||
|
|
||||||
TaskTileState(this._currentTask);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
|
||||||
final taskState = Provider.of<ProjectProvider>(context);
|
final taskState = Provider.of<ProjectProvider>(context);
|
||||||
if (_currentTask.loading) {
|
return ListTile(
|
||||||
return ListTile(
|
onTap: () {
|
||||||
leading: Padding(
|
_showBottomSheet(context, taskState);
|
||||||
padding: const EdgeInsets.all(8.0),
|
},
|
||||||
child: SizedBox(
|
leading: _buildLeading(),
|
||||||
height: Checkbox.width,
|
title: _buildTitle(),
|
||||||
width: Checkbox.width,
|
subtitle: Column(
|
||||||
child: CircularProgressIndicator(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
strokeWidth: 2.0,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
)),
|
children: <Widget>[
|
||||||
),
|
_buildSubtitle(context),
|
||||||
title: Text(_currentTask.title),
|
SizedBox(height: 8),
|
||||||
subtitle: _currentTask.description.isEmpty
|
buildChip() ?? Container(),
|
||||||
? null
|
],
|
||||||
: HtmlWidget(_currentTask.description),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: Icon(Icons.edit),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return IntrinsicHeight(
|
|
||||||
child: Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
|
||||||
Container(
|
|
||||||
width: 4.0, // Adjust the width of the red line
|
|
||||||
color: widget.task.color,
|
|
||||||
//margin: EdgeInsets.only(left: 10.0),
|
|
||||||
),
|
),
|
||||||
Flexible(
|
trailing: _buildTrailing(context),
|
||||||
child: ListTile(
|
);
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return TaskBottomSheet(task: widget.task, onEdit: widget.onEdit, taskState: taskState);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
title: widget.showInfo
|
|
||||||
? RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: null,
|
|
||||||
children: <TextSpan>[
|
|
||||||
// TODO: get list name of task
|
|
||||||
//TextSpan(text: widget.task.list.title+" - ", style: TextStyle(color: Colors.grey)),
|
|
||||||
TextSpan(text: widget.task.title),
|
|
||||||
],
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
: Text(_currentTask.title),
|
|
||||||
subtitle: _buildTaskSubtitle(widget.task, widget.showInfo, context),
|
|
||||||
leading: Checkbox(
|
|
||||||
value: _currentTask.done,
|
|
||||||
onChanged: (bool? newValue) {
|
|
||||||
_change(newValue);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: Icon(Icons.edit),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push<Task>(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (buildContext) => TaskEditPage(
|
|
||||||
task: _currentTask,
|
|
||||||
taskState: taskState,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then((task) => setState(() {
|
|
||||||
if (task != null) _currentTask = task;
|
|
||||||
}))
|
|
||||||
.whenComplete(() => widget.onEdit());
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _change(bool? value) async {
|
void _change(bool? value) async {
|
||||||
|
@ -179,8 +84,123 @@ class TaskTileState extends State<TaskTile> with AutomaticKeepAliveClientMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget _buildLeading() {
|
||||||
bool get wantKeepAlive => _currentTask != widget.task;
|
return Checkbox(
|
||||||
}
|
value: _currentTask.done,
|
||||||
|
onChanged: (bool? newValue) {
|
||||||
|
_change(newValue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
typedef Future<void> TaskChanged(Task task, bool newValue);
|
Widget _buildTitle() {
|
||||||
|
return widget.showInfo
|
||||||
|
? RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: null,
|
||||||
|
children: <TextSpan>[
|
||||||
|
TextSpan(
|
||||||
|
text: widget.task.title,
|
||||||
|
style: TextStyle(
|
||||||
|
decoration:
|
||||||
|
_currentTask.done ? TextDecoration.lineThrough : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
_currentTask.title,
|
||||||
|
style: TextStyle(
|
||||||
|
decoration: _currentTask.done ? TextDecoration.lineThrough : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSubtitle(BuildContext context) {
|
||||||
|
final durationUntilDue = _currentTask.dueDate?.difference(DateTime.now());
|
||||||
|
if (widget.loading) {
|
||||||
|
return CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.0,
|
||||||
|
);
|
||||||
|
} else if (widget.showInfo && _currentTask.hasDueDate) {
|
||||||
|
return Text(
|
||||||
|
"Due " + durationToHumanReadable(durationUntilDue!),
|
||||||
|
style: TextStyle(
|
||||||
|
color: durationUntilDue.isNegative ? Colors.red : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (_currentTask.priority != null && _currentTask.priority != 0) {
|
||||||
|
return Text(
|
||||||
|
" !" + priorityToString(_currentTask.priority),
|
||||||
|
style: TextStyle(color: Colors.orange),
|
||||||
|
);
|
||||||
|
} else if (_currentTask.description.isNotEmpty) {
|
||||||
|
return HtmlWidget(_currentTask.description);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTrailing(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(Icons.edit),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push<Task>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (buildContext) => TaskEditPage(
|
||||||
|
task: _currentTask,
|
||||||
|
taskState: Provider.of<ProjectProvider>(context, listen: false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((task) {
|
||||||
|
if (task != null) {
|
||||||
|
setState(() {
|
||||||
|
_currentTask = task;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
widget.onEdit();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showBottomSheet(BuildContext context, ProjectProvider taskState) {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return TaskBottomSheet(
|
||||||
|
task: widget.task,
|
||||||
|
onEdit: widget.onEdit,
|
||||||
|
taskState: taskState,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget? buildChip() {
|
||||||
|
if (_currentTask.projectId == null || widget.projectsMap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Project? p = widget.projectsMap![_currentTask.projectId!];
|
||||||
|
if (p != null) {
|
||||||
|
return Transform(
|
||||||
|
transform: new Matrix4.identity()..scale(0.8),
|
||||||
|
child: Chip(
|
||||||
|
label: Text(p.title),
|
||||||
|
backgroundColor: p.color,
|
||||||
|
labelStyle: TextStyle(color: Colors.white),
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
visualDensity: VisualDensity(horizontal: 0.0, vertical: -4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -40,8 +40,7 @@ class VikunjaDateTimePicker extends StatelessWidget {
|
||||||
onSaved: onSaved,
|
onSaved: onSaved,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
onShowPicker: (context, currentValue) {
|
onShowPicker: (context, currentValue) {
|
||||||
if(currentValue == null)
|
if (currentValue == null) currentValue = DateTime.now();
|
||||||
currentValue = DateTime.now();
|
|
||||||
return _showDatePickerFuture(context, currentValue);
|
return _showDatePickerFuture(context, currentValue);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -51,26 +50,22 @@ class VikunjaDateTimePicker extends StatelessWidget {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => DatePickerDialog(
|
builder: (_) => DatePickerDialog(
|
||||||
initialDate: currentValue.year <= 1
|
initialDate:
|
||||||
? DateTime.now()
|
currentValue.year <= 1 ? DateTime.now() : currentValue,
|
||||||
: currentValue,
|
firstDate: DateTime(1900),
|
||||||
firstDate: DateTime(1900),
|
lastDate: DateTime(2100),
|
||||||
lastDate: DateTime(2100),
|
initialCalendarMode: DatePickerMode.day,
|
||||||
initialCalendarMode: DatePickerMode.day,
|
)).then((date) {
|
||||||
)).then((date) {
|
if (date == null) return null;
|
||||||
if(date == null)
|
return showDialog(
|
||||||
return null;
|
|
||||||
return showDialog(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) =>
|
builder: (_) => TimePickerDialog(
|
||||||
TimePickerDialog(
|
|
||||||
initialTime: TimeOfDay.fromDateTime(currentValue),
|
initialTime: TimeOfDay.fromDateTime(currentValue),
|
||||||
)
|
)).then((time) {
|
||||||
).then((time) {
|
if (time == null) return null;
|
||||||
if(time == null)
|
return DateTime(
|
||||||
return null;
|
date.year, date.month, date.day, time.hour, time.minute);
|
||||||
return DateTime(date.year,date.month, date.day,time.hour,time.minute);
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,12 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeUser(User newUser, {String? token, String? base}) async {
|
void changeUser(
|
||||||
|
User newUser, {
|
||||||
|
String? token,
|
||||||
|
String? base,
|
||||||
|
String? xClientToken,
|
||||||
|
}) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_loading = true;
|
_loading = true;
|
||||||
});
|
});
|
||||||
|
@ -145,6 +150,16 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||||
// Write new base to secure storage
|
// Write new base to secure storage
|
||||||
await _storage.write(key: "${newUser.id.toString()}_base", value: base);
|
await _storage.write(key: "${newUser.id.toString()}_base", value: base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (xClientToken == null) {
|
||||||
|
xClientToken =
|
||||||
|
await _storage.read(key: "${newUser.id.toString()}_x_client_token");
|
||||||
|
} else {
|
||||||
|
// Write new xClientToken to secure storage
|
||||||
|
await _storage.write(
|
||||||
|
key: "${newUser.id.toString()}_x_client_token", value: xClientToken);
|
||||||
|
}
|
||||||
|
|
||||||
// Set current user in storage
|
// Set current user in storage
|
||||||
await _storage.write(key: 'currentUser', value: newUser.id.toString());
|
await _storage.write(key: 'currentUser', value: newUser.id.toString());
|
||||||
client.configure(token: token, base: base, authenticated: true);
|
client.configure(token: token, base: base, authenticated: true);
|
||||||
|
@ -182,13 +197,20 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||||
}
|
}
|
||||||
var token = await _storage.read(key: currentUser);
|
var token = await _storage.read(key: currentUser);
|
||||||
var base = await _storage.read(key: '${currentUser}_base');
|
var base = await _storage.read(key: '${currentUser}_base');
|
||||||
|
var xClientToken =
|
||||||
|
await _storage.read(key: '${currentUser}_x_client_token');
|
||||||
if (token == null || base == null) {
|
if (token == null || base == null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_loading = false;
|
_loading = false;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.configure(token: token, base: base, authenticated: true);
|
client.configure(
|
||||||
|
token: token,
|
||||||
|
base: base,
|
||||||
|
authenticated: true,
|
||||||
|
xClientToken: xClientToken,
|
||||||
|
);
|
||||||
User loadedCurrentUser;
|
User loadedCurrentUser;
|
||||||
try {
|
try {
|
||||||
loadedCurrentUser = await UserAPIService(client).getCurrentUser();
|
loadedCurrentUser = await UserAPIService(client).getCurrentUser();
|
||||||
|
|
|
@ -47,15 +47,15 @@ class Bucket {
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
toJSON() => {
|
toJSON() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'list_id': projectId,
|
'list_id': projectId,
|
||||||
'title': title,
|
'title': title,
|
||||||
'position': position,
|
'position': position,
|
||||||
'limit': limit,
|
'limit': limit,
|
||||||
'is_done_bucket': isDoneBucket,
|
'is_done_bucket': isDoneBucket,
|
||||||
'created': created.toUtc().toIso8601String(),
|
'created': created.toUtc().toIso8601String(),
|
||||||
'updated': updated.toUtc().toIso8601String(),
|
'updated': updated.toUtc().toIso8601String(),
|
||||||
'created_by': createdBy.toJSON(),
|
'created_by': createdBy.toJSON(),
|
||||||
'tasks': tasks.map((task) => task.toJSON()).toList(),
|
'tasks': tasks.map((task) => task.toJSON()).toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ class Label {
|
||||||
final User createdBy;
|
final User createdBy;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
|
|
||||||
late final Color textColor = color != null && color!.computeLuminance() <= 0.5 ? vLabelLight : vLabelDark;
|
late final Color textColor = color != null && color!.computeLuminance() <= 0.5
|
||||||
|
? vLabelLight
|
||||||
|
: vLabelDark;
|
||||||
|
|
||||||
Label({
|
Label({
|
||||||
this.id = 0,
|
this.id = 0,
|
||||||
|
|
|
@ -33,9 +33,11 @@ class TaskList {
|
||||||
created = DateTime.parse(json['created']),
|
created = DateTime.parse(json['created']),
|
||||||
isFavorite = json['is_favorite'],
|
isFavorite = json['is_favorite'],
|
||||||
namespaceId = json['namespace_id'],
|
namespaceId = json['namespace_id'],
|
||||||
tasks = json['tasks'] == null ? [] : (json['tasks'] as List<dynamic>)
|
tasks = json['tasks'] == null
|
||||||
.map((taskJson) => Task.fromJson(taskJson))
|
? []
|
||||||
.toList();
|
: (json['tasks'] as List<dynamic>)
|
||||||
|
.map((taskJson) => Task.fromJson(taskJson))
|
||||||
|
.toList();
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -17,20 +17,19 @@ class Project {
|
||||||
Iterable<Project>? subprojects;
|
Iterable<Project>? subprojects;
|
||||||
|
|
||||||
Project(
|
Project(
|
||||||
{
|
{this.id = 0,
|
||||||
this.id = 0,
|
this.owner,
|
||||||
this.owner,
|
this.parentProjectId = 0,
|
||||||
this.parentProjectId = 0,
|
this.description = '',
|
||||||
this.description = '',
|
this.position = 0,
|
||||||
this.position = 0,
|
this.doneBucketId,
|
||||||
this.doneBucketId,
|
this.color,
|
||||||
this.color,
|
this.isArchived = false,
|
||||||
this.isArchived = false,
|
this.isFavourite = false,
|
||||||
this.isFavourite = false,
|
required this.title,
|
||||||
required this.title,
|
|
||||||
created,
|
created,
|
||||||
updated}) :
|
updated})
|
||||||
this.created = created ?? DateTime.now(),
|
: this.created = created ?? DateTime.now(),
|
||||||
this.updated = updated ?? DateTime.now();
|
this.updated = updated ?? DateTime.now();
|
||||||
|
|
||||||
Project.fromJson(Map<String, dynamic> json)
|
Project.fromJson(Map<String, dynamic> json)
|
||||||
|
@ -50,19 +49,20 @@ class Project {
|
||||||
owner = json['owner'] != null ? User.fromJson(json['owner']) : null;
|
owner = json['owner'] != null ? User.fromJson(json['owner']) : null;
|
||||||
|
|
||||||
Map<String, dynamic> toJSON() => {
|
Map<String, dynamic> toJSON() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'created': created.toUtc().toIso8601String(),
|
'created': created.toUtc().toIso8601String(),
|
||||||
'updated': updated.toUtc().toIso8601String(),
|
'updated': updated.toUtc().toIso8601String(),
|
||||||
'title': title,
|
'title': title,
|
||||||
'owner': owner?.toJSON(),
|
'owner': owner?.toJSON(),
|
||||||
'description': description,
|
'description': description,
|
||||||
'parent_project_id': parentProjectId,
|
'parent_project_id': parentProjectId,
|
||||||
'hex_color': color?.value.toRadixString(16).padLeft(8, '0').substring(2),
|
'hex_color':
|
||||||
'is_archived': isArchived,
|
color?.value.toRadixString(16).padLeft(8, '0').substring(2),
|
||||||
'is_favourite': isFavourite,
|
'is_archived': isArchived,
|
||||||
'done_bucket_id': doneBucketId,
|
'is_favourite': isFavourite,
|
||||||
'position': position
|
'done_bucket_id': doneBucketId,
|
||||||
};
|
'position': position
|
||||||
|
};
|
||||||
|
|
||||||
Project copyWith({
|
Project copyWith({
|
||||||
int? id,
|
int? id,
|
||||||
|
@ -77,21 +77,20 @@ class Project {
|
||||||
bool? isFavourite,
|
bool? isFavourite,
|
||||||
int? doneBucketId,
|
int? doneBucketId,
|
||||||
double? position,
|
double? position,
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
return Project(
|
return Project(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
created: created ?? this.created,
|
created: created ?? this.created,
|
||||||
updated: updated ?? this.updated,
|
updated: updated ?? this.updated,
|
||||||
title: title ?? this.title,
|
title: title ?? this.title,
|
||||||
owner: owner ?? this.owner,
|
owner: owner ?? this.owner,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
parentProjectId: parentProjectId ?? this.parentProjectId,
|
parentProjectId: parentProjectId ?? this.parentProjectId,
|
||||||
doneBucketId: doneBucketId ?? this.doneBucketId,
|
doneBucketId: doneBucketId ?? this.doneBucketId,
|
||||||
color: color ?? this.color,
|
color: color ?? this.color,
|
||||||
isArchived: isArchived ?? this.isArchived,
|
isArchived: isArchived ?? this.isArchived,
|
||||||
isFavourite: isFavourite ?? this.isFavourite,
|
isFavourite: isFavourite ?? this.isFavourite,
|
||||||
position: position ?? this.position,
|
position: position ?? this.position,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@ class Server {
|
||||||
String? version;
|
String? version;
|
||||||
|
|
||||||
Server.fromJson(Map<String, dynamic> json)
|
Server.fromJson(Map<String, dynamic> json)
|
||||||
:
|
: caldavEnabled = json['caldav_enabled'],
|
||||||
caldavEnabled = json['caldav_enabled'],
|
|
||||||
emailRemindersEnabled = json['email_reminders_enabled'],
|
emailRemindersEnabled = json['email_reminders_enabled'],
|
||||||
frontendUrl = json['frontend_url'],
|
frontendUrl = json['frontend_url'],
|
||||||
linkSharingEnabled = json['link_sharing_enabled'],
|
linkSharingEnabled = json['link_sharing_enabled'],
|
||||||
|
@ -26,4 +25,4 @@ class Server {
|
||||||
totpEnabled = json['totp_enabled'],
|
totpEnabled = json['totp_enabled'],
|
||||||
userDeletion = json['user_deletion'],
|
userDeletion = json['user_deletion'],
|
||||||
version = json['version'];
|
version = json['version'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ class Task {
|
||||||
}
|
}
|
||||||
return Colors.white;
|
return Colors.white;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasDueDate => dueDate?.year != 1;
|
bool get hasDueDate => dueDate?.year != 1;
|
||||||
|
|
||||||
Task.fromJson(Map<String, dynamic> json)
|
Task.fromJson(Map<String, dynamic> json)
|
||||||
|
@ -131,7 +132,8 @@ class Task {
|
||||||
'end_date': endDate?.toUtc().toIso8601String(),
|
'end_date': endDate?.toUtc().toIso8601String(),
|
||||||
'priority': priority,
|
'priority': priority,
|
||||||
'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,
|
'percent_done': percent_done,
|
||||||
'project_id': projectId,
|
'project_id': projectId,
|
||||||
|
|
|
@ -25,15 +25,14 @@ class TaskAttachmentFile {
|
||||||
size = json['size'];
|
size = json['size'];
|
||||||
|
|
||||||
toJSON() => {
|
toJSON() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'created': created.toUtc().toIso8601String(),
|
'created': created.toUtc().toIso8601String(),
|
||||||
'mime': mime,
|
'mime': mime,
|
||||||
'name': name,
|
'name': name,
|
||||||
'size': size,
|
'size': size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class TaskAttachment {
|
class TaskAttachment {
|
||||||
final int id, taskId;
|
final int id, taskId;
|
||||||
|
@ -58,10 +57,10 @@ class TaskAttachment {
|
||||||
createdBy = User.fromJson(json['created_by']);
|
createdBy = User.fromJson(json['created_by']);
|
||||||
|
|
||||||
toJSON() => {
|
toJSON() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'task_id': taskId,
|
'task_id': taskId,
|
||||||
'created': created.toUtc().toIso8601String(),
|
'created': created.toUtc().toIso8601String(),
|
||||||
'created_by': createdBy.toJSON(),
|
'created_by': createdBy.toJSON(),
|
||||||
'file': file.toJSON(),
|
'file': file.toJSON(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ import 'package:vikunja_app/global.dart';
|
||||||
|
|
||||||
class UserSettings {
|
class UserSettings {
|
||||||
final int default_project_id;
|
final int default_project_id;
|
||||||
final bool discoverable_by_email, discoverable_by_name, email_reminders_enabled;
|
final bool discoverable_by_email,
|
||||||
|
discoverable_by_name,
|
||||||
|
email_reminders_enabled;
|
||||||
final Map<String, dynamic>? frontend_settings;
|
final Map<String, dynamic>? frontend_settings;
|
||||||
final String language;
|
final String language;
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -34,24 +36,25 @@ class UserSettings {
|
||||||
frontend_settings = json['frontend_settings'],
|
frontend_settings = json['frontend_settings'],
|
||||||
language = json['language'],
|
language = json['language'],
|
||||||
name = json['name'],
|
name = json['name'],
|
||||||
overdue_tasks_reminders_enabled = json['overdue_tasks_reminders_enabled'],
|
overdue_tasks_reminders_enabled =
|
||||||
|
json['overdue_tasks_reminders_enabled'],
|
||||||
overdue_tasks_reminders_time = json['overdue_tasks_reminders_time'],
|
overdue_tasks_reminders_time = json['overdue_tasks_reminders_time'],
|
||||||
timezone = json['timezone'],
|
timezone = json['timezone'],
|
||||||
week_start = json['week_start'];
|
week_start = json['week_start'];
|
||||||
|
|
||||||
toJson() => {
|
toJson() => {
|
||||||
'default_project_id': default_project_id,
|
'default_project_id': default_project_id,
|
||||||
'discoverable_by_email': discoverable_by_email,
|
'discoverable_by_email': discoverable_by_email,
|
||||||
'discoverable_by_name': discoverable_by_name,
|
'discoverable_by_name': discoverable_by_name,
|
||||||
'email_reminders_enabled': email_reminders_enabled,
|
'email_reminders_enabled': email_reminders_enabled,
|
||||||
'frontend_settings': frontend_settings,
|
'frontend_settings': frontend_settings,
|
||||||
'language': language,
|
'language': language,
|
||||||
'name': name,
|
'name': name,
|
||||||
'overdue_tasks_reminders_enabled': overdue_tasks_reminders_enabled,
|
'overdue_tasks_reminders_enabled': overdue_tasks_reminders_enabled,
|
||||||
'overdue_tasks_reminders_time': overdue_tasks_reminders_time,
|
'overdue_tasks_reminders_time': overdue_tasks_reminders_time,
|
||||||
'timezone': timezone,
|
'timezone': timezone,
|
||||||
'week_start': week_start,
|
'week_start': week_start,
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSettings copyWith({
|
UserSettings copyWith({
|
||||||
int? default_project_id,
|
int? default_project_id,
|
||||||
|
@ -68,14 +71,18 @@ class UserSettings {
|
||||||
}) {
|
}) {
|
||||||
return UserSettings(
|
return UserSettings(
|
||||||
default_project_id: default_project_id ?? this.default_project_id,
|
default_project_id: default_project_id ?? this.default_project_id,
|
||||||
discoverable_by_email: discoverable_by_email ?? this.discoverable_by_email,
|
discoverable_by_email:
|
||||||
|
discoverable_by_email ?? this.discoverable_by_email,
|
||||||
discoverable_by_name: discoverable_by_name ?? this.discoverable_by_name,
|
discoverable_by_name: discoverable_by_name ?? this.discoverable_by_name,
|
||||||
email_reminders_enabled: email_reminders_enabled ?? this.email_reminders_enabled,
|
email_reminders_enabled:
|
||||||
|
email_reminders_enabled ?? this.email_reminders_enabled,
|
||||||
frontend_settings: frontend_settings ?? this.frontend_settings,
|
frontend_settings: frontend_settings ?? this.frontend_settings,
|
||||||
language: language ?? this.language,
|
language: language ?? this.language,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
overdue_tasks_reminders_enabled: overdue_tasks_reminders_enabled ?? this.overdue_tasks_reminders_enabled,
|
overdue_tasks_reminders_enabled: overdue_tasks_reminders_enabled ??
|
||||||
overdue_tasks_reminders_time: overdue_tasks_reminders_time ?? this.overdue_tasks_reminders_time,
|
this.overdue_tasks_reminders_enabled,
|
||||||
|
overdue_tasks_reminders_time:
|
||||||
|
overdue_tasks_reminders_time ?? this.overdue_tasks_reminders_time,
|
||||||
timezone: timezone ?? this.timezone,
|
timezone: timezone ?? this.timezone,
|
||||||
week_start: week_start ?? this.week_start,
|
week_start: week_start ?? this.week_start,
|
||||||
);
|
);
|
||||||
|
@ -104,9 +111,10 @@ class User {
|
||||||
username = json['username'],
|
username = json['username'],
|
||||||
created = DateTime.parse(json['created']),
|
created = DateTime.parse(json['created']),
|
||||||
updated = DateTime.parse(json['updated']) {
|
updated = DateTime.parse(json['updated']) {
|
||||||
if(json.containsKey('settings')){
|
if (json.containsKey('settings')) {
|
||||||
this.settings = UserSettings.fromJson(json['settings']);
|
this.settings = UserSettings.fromJson(json['settings']);
|
||||||
};
|
}
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() => {
|
toJSON() => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:after_layout/after_layout.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:vikunja_app/global.dart';
|
import 'package:vikunja_app/global.dart';
|
||||||
|
import 'package:vikunja_app/models/project.dart';
|
||||||
import 'package:vikunja_app/service/services.dart';
|
import 'package:vikunja_app/service/services.dart';
|
||||||
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
@ -33,6 +34,7 @@ class LandingPageState extends State<LandingPage>
|
||||||
int? defaultList;
|
int? defaultList;
|
||||||
bool onlyDueDate = true;
|
bool onlyDueDate = true;
|
||||||
List<Task> _tasks = [];
|
List<Task> _tasks = [];
|
||||||
|
Map<int, Project> _projectsMap = {};
|
||||||
PageStatus landingPageStatus = PageStatus.built;
|
PageStatus landingPageStatus = PageStatus.built;
|
||||||
static const platform = const MethodChannel('vikunja');
|
static const platform = const MethodChannel('vikunja');
|
||||||
|
|
||||||
|
@ -115,9 +117,10 @@ class LandingPageState extends State<LandingPage>
|
||||||
body = ListView(
|
body = ListView(
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
children:
|
children: ListTile.divideTiles(
|
||||||
ListTile.divideTiles(context: context, tiles: _listTasks(context))
|
context: context,
|
||||||
.toList(),
|
tiles: _listTasks(context),
|
||||||
|
).toList(),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -205,66 +208,66 @@ class LandingPageState extends State<LandingPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _listTasks(BuildContext context) {
|
List<Widget> _listTasks(BuildContext context) {
|
||||||
var tasks = (_tasks.map((task) => _buildTile(task, context))).toList();
|
return (_tasks.map((task) => _buildTile(task, context))).toList();
|
||||||
//tasks.addAll(_loadingTasks.map(_buildLoadingTile));
|
|
||||||
return tasks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskTile _buildTile(Task task, BuildContext context) {
|
TaskTile _buildTile(Task task, BuildContext context) {
|
||||||
// key: UniqueKey() seems like a weird workaround to fix the loading issue
|
|
||||||
// is there a better way?
|
|
||||||
return TaskTile(
|
return TaskTile(
|
||||||
key: UniqueKey(),
|
key: Key("task_${task.id}"),
|
||||||
|
projectsMap: _projectsMap,
|
||||||
task: task,
|
task: task,
|
||||||
onEdit: () => _loadList(context),
|
onEdit: () => _loadList(context),
|
||||||
showInfo: true,
|
showInfo: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadList(BuildContext context) {
|
Future<void> _loadList(BuildContext context) async {
|
||||||
_tasks = [];
|
_tasks = [];
|
||||||
|
_projectsMap = {};
|
||||||
landingPageStatus = PageStatus.loading;
|
landingPageStatus = PageStatus.loading;
|
||||||
// FIXME: loads and reschedules tasks each time list is updated
|
// FIXME: loads and reschedules tasks each time list is updated
|
||||||
VikunjaGlobal.of(context)
|
VikunjaGlobal.of(context)
|
||||||
.notifications
|
.notifications
|
||||||
.scheduleDueNotifications(VikunjaGlobal.of(context).taskService);
|
.scheduleDueNotifications(VikunjaGlobal.of(context).taskService);
|
||||||
return VikunjaGlobal.of(context)
|
bool showOnlyDueDateTasks = await VikunjaGlobal.of(context)
|
||||||
.settingsManager
|
.settingsManager
|
||||||
.getLandingPageOnlyDueDateTasks()
|
.getLandingPageOnlyDueDateTasks();
|
||||||
.then((showOnlyDueDateTasks) {
|
|
||||||
VikunjaGlobalState global = VikunjaGlobal.of(context);
|
|
||||||
Map<String, dynamic>? frontend_settings =
|
|
||||||
global.currentUser?.settings?.frontend_settings;
|
|
||||||
int? filterId = 0;
|
|
||||||
if (frontend_settings != null) {
|
|
||||||
if (frontend_settings["filter_id_used_on_overview"] != null)
|
|
||||||
filterId = frontend_settings["filter_id_used_on_overview"];
|
|
||||||
}
|
|
||||||
if (filterId != null && filterId != 0) {
|
|
||||||
return global.taskService.getAllByProject(filterId, {
|
|
||||||
"sort_by": ["due_date", "id"],
|
|
||||||
"order_by": ["asc", "desc"],
|
|
||||||
}).then<Future<void>?>((response) =>
|
|
||||||
_handleTaskList(response?.body, showOnlyDueDateTasks));
|
|
||||||
}
|
|
||||||
|
|
||||||
return global.taskService
|
VikunjaGlobalState global = VikunjaGlobal.of(context);
|
||||||
.getByOptions(TaskServiceOptions(newOptions: [
|
List<Project>? projects = await global.projectService.getAll();
|
||||||
TaskServiceOption<TaskServiceOptionSortBy>(
|
if (projects != null) {
|
||||||
"sort_by", ["due_date", "id"]),
|
projects.forEach((project) {
|
||||||
TaskServiceOption<TaskServiceOptionSortBy>(
|
_projectsMap[project.id] = project;
|
||||||
"order_by", ["asc", "desc"]),
|
});
|
||||||
TaskServiceOption<TaskServiceOptionFilterBy>("filter_by", "done"),
|
}
|
||||||
TaskServiceOption<TaskServiceOptionFilterValue>(
|
|
||||||
"filter_value", "false"),
|
Map<String, dynamic>? frontend_settings =
|
||||||
TaskServiceOption<TaskServiceOptionFilterComparator>(
|
global.currentUser?.settings?.frontend_settings;
|
||||||
"filter_comparator", "equals"),
|
int? filterId = 0;
|
||||||
TaskServiceOption<TaskServiceOptionFilterConcat>(
|
if (frontend_settings != null) {
|
||||||
"filter_concat", "and"),
|
if (frontend_settings["filter_id_used_on_overview"] != null)
|
||||||
], clearOther: true))
|
filterId = frontend_settings["filter_id_used_on_overview"];
|
||||||
.then<Future<void>?>(
|
}
|
||||||
(taskList) => _handleTaskList(taskList, showOnlyDueDateTasks));
|
if (filterId != null && filterId != 0) {
|
||||||
}); //.onError((error, stackTrace) {print("error");});
|
var response = await global.taskService.getAllByProject(filterId, {
|
||||||
|
"sort_by": ["due_date", "id"],
|
||||||
|
"order_by": ["asc", "desc"],
|
||||||
|
});
|
||||||
|
await _handleTaskList(response?.body, showOnlyDueDateTasks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskList =
|
||||||
|
await global.taskService.getByOptions(TaskServiceOptions(newOptions: [
|
||||||
|
TaskServiceOption<TaskServiceOptionSortBy>("sort_by", ["due_date", "id"]),
|
||||||
|
TaskServiceOption<TaskServiceOptionSortBy>("order_by", ["asc", "desc"]),
|
||||||
|
TaskServiceOption<TaskServiceOptionFilterBy>("filter_by", "done"),
|
||||||
|
TaskServiceOption<TaskServiceOptionFilterValue>("filter_value", "false"),
|
||||||
|
TaskServiceOption<TaskServiceOptionFilterComparator>(
|
||||||
|
"filter_comparator", "equals"),
|
||||||
|
TaskServiceOption<TaskServiceOptionFilterConcat>("filter_concat", "and"),
|
||||||
|
], clearOther: true));
|
||||||
|
await _handleTaskList(taskList, showOnlyDueDateTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleTaskList(
|
Future<void> _handleTaskList(
|
||||||
|
|
|
@ -65,7 +65,7 @@ class _ListPageState extends State<ListPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
taskState = Provider.of<ListProvider>(context);
|
taskState = Provider.of<ListProvider>(context);
|
||||||
//_kanban = KanbanClass(
|
//_kanban = KanbanClass(
|
||||||
// context, nullSetState, _onViewTapped, _addItemDialog, _list);
|
// context, nullSetState, _onViewTapped, _addItemDialog, _list);
|
||||||
|
|
||||||
Widget body;
|
Widget body;
|
||||||
|
|
||||||
|
@ -126,10 +126,8 @@ class _ListPageState extends State<ListPage> {
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case PageStatus.empty:
|
case PageStatus.empty:
|
||||||
body = new Stack(children: [
|
body = new Stack(
|
||||||
ListView(),
|
children: [ListView(), Center(child: Text("This view is empty"))]);
|
||||||
Center(child: Text("This view is empty"))
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,8 +239,8 @@ class _ListPageState extends State<ListPage> {
|
||||||
TaskTile _buildLoadingTile(Task task) {
|
TaskTile _buildLoadingTile(Task task) {
|
||||||
return TaskTile(
|
return TaskTile(
|
||||||
task: task,
|
task: task,
|
||||||
loading: true, onEdit: () {},
|
loading: true,
|
||||||
|
onEdit: () {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,13 +253,11 @@ class _ListPageState extends State<ListPage> {
|
||||||
_loadTasksForPage(1);
|
_loadTasksForPage(1);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
await _kanban
|
await _kanban.loadBucketsForPage(1);
|
||||||
.loadBucketsForPage(1);
|
|
||||||
// load all buckets to get length for RecordableListView
|
// load all buckets to get length for RecordableListView
|
||||||
while (_currentPage < taskState.maxPages) {
|
while (_currentPage < taskState.maxPages) {
|
||||||
_currentPage++;
|
_currentPage++;
|
||||||
await _kanban
|
await _kanban.loadBucketsForPage(_currentPage);
|
||||||
.loadBucketsForPage(_currentPage);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -271,12 +267,11 @@ class _ListPageState extends State<ListPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadTasksForPage(int page) {
|
Future<void> _loadTasksForPage(int page) {
|
||||||
return Provider.of<ListProvider>(context, listen: false)
|
return Provider.of<ListProvider>(context, listen: false).loadTasks(
|
||||||
.loadTasks(
|
context: context,
|
||||||
context: context,
|
listId: _list.id,
|
||||||
listId: _list.id,
|
page: page,
|
||||||
page: page,
|
displayDoneTasks: displayDoneTasks);
|
||||||
displayDoneTasks: displayDoneTasks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addItemDialog(BuildContext context, [Bucket? bucket]) {
|
Future<void> _addItemDialog(BuildContext context, [Bucket? bucket]) {
|
||||||
|
|
|
@ -22,16 +22,18 @@ class _ListEditPageState extends State<ListEditPage> {
|
||||||
late int listId;
|
late int listId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState(){
|
void initState() {
|
||||||
listId = widget.list.id;
|
listId = widget.list.id;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext ctx) {
|
Widget build(BuildContext ctx) {
|
||||||
if(displayDoneTasks == null)
|
if (displayDoneTasks == null)
|
||||||
VikunjaGlobal.of(context).listService.getDisplayDoneTasks(listId).then(
|
VikunjaGlobal.of(context)
|
||||||
(value) => setState(() => displayDoneTasks = value == "1"));
|
.listService
|
||||||
|
.getDisplayDoneTasks(listId)
|
||||||
|
.then((value) => setState(() => displayDoneTasks = value == "1"));
|
||||||
else
|
else
|
||||||
log("Display done tasks: " + displayDoneTasks.toString());
|
log("Display done tasks: " + displayDoneTasks.toString());
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -43,7 +45,7 @@ class _ListEditPageState extends State<ListEditPage> {
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
//reverse: true,
|
//reverse: true,
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -71,10 +73,10 @@ class _ListEditPageState extends State<ListEditPage> {
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
initialValue: widget.list.description,
|
initialValue: widget.list.description,
|
||||||
onSaved: (description) => _description = description ?? '',
|
onSaved: (description) =>
|
||||||
|
_description = description ?? '',
|
||||||
validator: (description) {
|
validator: (description) {
|
||||||
if(description == null)
|
if (description == null) return null;
|
||||||
return null;
|
|
||||||
if (description.length > 1000) {
|
if (description.length > 1000) {
|
||||||
return 'The description can have a maximum of 1000 characters.';
|
return 'The description can have a maximum of 1000 characters.';
|
||||||
}
|
}
|
||||||
|
@ -93,7 +95,9 @@ class _ListEditPageState extends State<ListEditPage> {
|
||||||
title: Text("Show done tasks"),
|
title: Text("Show done tasks"),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
value ??= false;
|
value ??= false;
|
||||||
VikunjaGlobal.of(context).listService.setDisplayDoneTasks(listId, value ? "1" : "0");
|
VikunjaGlobal.of(context)
|
||||||
|
.listService
|
||||||
|
.setDisplayDoneTasks(listId, value ? "1" : "0");
|
||||||
setState(() => displayDoneTasks = value);
|
setState(() => displayDoneTasks = value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -135,8 +139,7 @@ class _ListEditPageState extends State<ListEditPage> {
|
||||||
},)
|
},)
|
||||||
],
|
],
|
||||||
)*/
|
)*/
|
||||||
]
|
]),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -84,10 +84,8 @@ class _NamespacePageState extends State<NamespacePage> {
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case PageStatus.empty:
|
case PageStatus.empty:
|
||||||
body = new Stack(children: [
|
body = new Stack(
|
||||||
ListView(),
|
children: [ListView(), Center(child: Text("This view is empty"))]);
|
||||||
Center(child: Text("This view is empty"))
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
|
@ -112,7 +110,6 @@ class _NamespacePageState extends State<NamespacePage> {
|
||||||
onPressed: () => _addListDialog(context),
|
onPressed: () => _addListDialog(context),
|
||||||
child: const Icon(Icons.add))),
|
child: const Icon(Icons.add))),
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -7,7 +7,8 @@ import 'package:vikunja_app/theme/buttonText.dart';
|
||||||
class NamespaceEditPage extends StatefulWidget {
|
class NamespaceEditPage extends StatefulWidget {
|
||||||
final Namespace namespace;
|
final Namespace namespace;
|
||||||
|
|
||||||
NamespaceEditPage({required this.namespace}) : super(key: Key(namespace.toString()));
|
NamespaceEditPage({required this.namespace})
|
||||||
|
: super(key: Key(namespace.toString()));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _NamespaceEditPageState();
|
State<StatefulWidget> createState() => _NamespaceEditPageState();
|
||||||
|
@ -63,7 +64,8 @@ class _NamespaceEditPageState extends State<NamespaceEditPage> {
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
initialValue: widget.namespace.description,
|
initialValue: widget.namespace.description,
|
||||||
onSaved: (description) => _description = description ?? '',
|
onSaved: (description) =>
|
||||||
|
_description = description ?? '',
|
||||||
validator: (description) {
|
validator: (description) {
|
||||||
//if (description.length > 1000) {
|
//if (description.length > 1000) {
|
||||||
// return 'The description can have a maximum of 1000 characters.';
|
// return 'The description can have a maximum of 1000 characters.';
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -9,7 +9,8 @@ import '../../models/project.dart';
|
||||||
class ProjectEditPage extends StatefulWidget {
|
class ProjectEditPage extends StatefulWidget {
|
||||||
final Project project;
|
final Project project;
|
||||||
|
|
||||||
ProjectEditPage({required this.project}) : super(key: Key(project.toString()));
|
ProjectEditPage({required this.project})
|
||||||
|
: super(key: Key(project.toString()));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _ProjectEditPageState();
|
State<StatefulWidget> createState() => _ProjectEditPageState();
|
||||||
|
@ -23,16 +24,18 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
late int listId;
|
late int listId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState(){
|
void initState() {
|
||||||
listId = widget.project.id;
|
listId = widget.project.id;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext ctx) {
|
Widget build(BuildContext ctx) {
|
||||||
if(displayDoneTasks == null)
|
if (displayDoneTasks == null)
|
||||||
VikunjaGlobal.of(context).projectService.getDisplayDoneTasks(listId).then(
|
VikunjaGlobal.of(context)
|
||||||
(value) => setState(() => displayDoneTasks = value == "1"));
|
.projectService
|
||||||
|
.getDisplayDoneTasks(listId)
|
||||||
|
.then((value) => setState(() => displayDoneTasks = value == "1"));
|
||||||
else
|
else
|
||||||
log("Display done tasks: " + displayDoneTasks.toString());
|
log("Display done tasks: " + displayDoneTasks.toString());
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -44,7 +47,7 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
//reverse: true,
|
//reverse: true,
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -72,10 +75,10 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
initialValue: widget.project.description,
|
initialValue: widget.project.description,
|
||||||
onSaved: (description) => _description = description ?? '',
|
onSaved: (description) =>
|
||||||
|
_description = description ?? '',
|
||||||
validator: (description) {
|
validator: (description) {
|
||||||
if(description == null)
|
if (description == null) return null;
|
||||||
return null;
|
|
||||||
if (description.length > 1000) {
|
if (description.length > 1000) {
|
||||||
return 'The description can have a maximum of 1000 characters.';
|
return 'The description can have a maximum of 1000 characters.';
|
||||||
}
|
}
|
||||||
|
@ -94,7 +97,9 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
title: Text("Show done tasks"),
|
title: Text("Show done tasks"),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
value ??= false;
|
value ??= false;
|
||||||
VikunjaGlobal.of(context).listService.setDisplayDoneTasks(listId, value ? "1" : "0");
|
VikunjaGlobal.of(context)
|
||||||
|
.listService
|
||||||
|
.setDisplayDoneTasks(listId, value ? "1" : "0");
|
||||||
setState(() => displayDoneTasks = value);
|
setState(() => displayDoneTasks = value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -105,11 +110,11 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
child: FancyButton(
|
child: FancyButton(
|
||||||
onPressed: !_loading
|
onPressed: !_loading
|
||||||
? () {
|
? () {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
Form.of(context).save();
|
Form.of(context).save();
|
||||||
_saveList(context);
|
_saveList(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: () {},
|
: () {},
|
||||||
child: _loading
|
child: _loading
|
||||||
? CircularProgressIndicator()
|
? CircularProgressIndicator()
|
||||||
|
@ -136,8 +141,7 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
},)
|
},)
|
||||||
],
|
],
|
||||||
)*/
|
)*/
|
||||||
]
|
]),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -148,10 +152,8 @@ class _ProjectEditPageState extends State<ProjectEditPage> {
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
// FIXME: is there a way we can update the list without creating a new list object?
|
// FIXME: is there a way we can update the list without creating a new list object?
|
||||||
// aka updating the existing list we got from context (setters?)
|
// aka updating the existing list we got from context (setters?)
|
||||||
Project newProject = widget.project.copyWith(
|
Project newProject =
|
||||||
title: _title,
|
widget.project.copyWith(title: _title, description: _description);
|
||||||
description: _description
|
|
||||||
);
|
|
||||||
VikunjaGlobal.of(context).projectService.update(newProject).then((_) {
|
VikunjaGlobal.of(context).projectService.update(newProject).then((_) {
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
|
|
@ -95,42 +95,42 @@ class _ListPageState extends State<ListPage> {
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case PageStatus.success:
|
case PageStatus.success:
|
||||||
body = taskState.tasks.length > 0 || taskState.buckets.length > 0 || _project.subprojects!.length > 0
|
body = taskState.tasks.length > 0 ||
|
||||||
|
taskState.buckets.length > 0 ||
|
||||||
|
_project.subprojects!.length > 0
|
||||||
? ListenableProvider.value(
|
? ListenableProvider.value(
|
||||||
value: taskState,
|
value: taskState,
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: (ThemeData base) {
|
data: (ThemeData base) {
|
||||||
return base.copyWith(
|
return base.copyWith(
|
||||||
chipTheme: base.chipTheme.copyWith(
|
chipTheme: base.chipTheme.copyWith(
|
||||||
labelPadding: EdgeInsets.symmetric(horizontal: 2),
|
labelPadding: EdgeInsets.symmetric(horizontal: 2),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}(Theme.of(context)),
|
||||||
|
child: () {
|
||||||
|
switch (_viewIndex) {
|
||||||
|
case 0:
|
||||||
|
return _listView(context);
|
||||||
|
case 1:
|
||||||
|
return _kanban.kanbanView();
|
||||||
|
default:
|
||||||
|
return _listView(context);
|
||||||
|
}
|
||||||
|
}(),
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
}(Theme.of(context)),
|
|
||||||
child: () {
|
|
||||||
switch (_viewIndex) {
|
|
||||||
case 0:
|
|
||||||
return _listView(context);
|
|
||||||
case 1:
|
|
||||||
return _kanban.kanbanView();
|
|
||||||
default:
|
|
||||||
return _listView(context);
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Stack(children: [
|
: Stack(children: [
|
||||||
ListView(),
|
ListView(),
|
||||||
Center(child: Text('This list is empty.'))
|
Center(child: Text('This list is empty.'))
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case PageStatus.empty:
|
case PageStatus.empty:
|
||||||
body = new Stack(children: [
|
body = new Stack(
|
||||||
ListView(),
|
children: [ListView(), Center(child: Text("This view is empty"))]);
|
||||||
Center(child: Text("This view is empty"))
|
|
||||||
]);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,10 +154,10 @@ class _ListPageState extends State<ListPage> {
|
||||||
floatingActionButton: _viewIndex == 1
|
floatingActionButton: _viewIndex == 1
|
||||||
? null
|
? null
|
||||||
: Builder(
|
: Builder(
|
||||||
builder: (context) => FloatingActionButton(
|
builder: (context) => FloatingActionButton(
|
||||||
onPressed: () => _addItemDialog(context),
|
onPressed: () => _addItemDialog(context),
|
||||||
child: Icon(Icons.add)),
|
child: Icon(Icons.add)),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
items: const <BottomNavigationBarItem>[
|
items: const <BottomNavigationBarItem>[
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
|
@ -181,22 +181,23 @@ class _ListPageState extends State<ListPage> {
|
||||||
return Container(
|
return Container(
|
||||||
height: 80,
|
height: 80,
|
||||||
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
|
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||||
child:
|
child: ListView(
|
||||||
ListView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
//mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
//mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
...?widget.project.subprojects?.map((elem) =>
|
...?widget.project.subprojects?.map((elem) => InkWell(
|
||||||
InkWell(
|
onTap: () {
|
||||||
onTap: () {openList(context, elem);},
|
openList(context, elem);
|
||||||
child:
|
},
|
||||||
Container(
|
child: Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 100,
|
width: 100,
|
||||||
child:
|
child: Text(
|
||||||
Text(elem.title, overflow: TextOverflow.ellipsis,softWrap: false,)))
|
elem.title,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
)))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -213,39 +214,50 @@ class _ListPageState extends State<ListPage> {
|
||||||
|
|
||||||
Widget _listView(BuildContext context) {
|
Widget _listView(BuildContext context) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
if(widget.project.subprojects?.length != 0) {
|
if (widget.project.subprojects?.length != 0) {
|
||||||
children.add(Padding(child: Text("Projects", style: TextStyle(fontWeight: FontWeight.bold),), padding: EdgeInsets.fromLTRB(0, 10, 0, 0),));
|
children.add(Padding(
|
||||||
|
child: Text(
|
||||||
|
"Projects",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
|
||||||
|
));
|
||||||
children.add(buildSubProjectSelector());
|
children.add(buildSubProjectSelector());
|
||||||
|
|
||||||
}
|
}
|
||||||
if(taskState.tasks.length != 0) {
|
if (taskState.tasks.length != 0) {
|
||||||
children.add(Padding(child: Text("Tasks", style: TextStyle(fontWeight: FontWeight.bold),), padding: EdgeInsets.fromLTRB(0, 10, 0, 0),));
|
children.add(Padding(
|
||||||
|
child: Text(
|
||||||
|
"Tasks",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
|
||||||
|
));
|
||||||
children.add(Divider());
|
children.add(Divider());
|
||||||
children.add(Expanded(child:
|
children.add(Expanded(
|
||||||
ListView.builder(
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
itemCount: taskState.tasks.length * 2,
|
itemCount: taskState.tasks.length * 2,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
if (i.isOdd) return Divider();
|
if (i.isOdd) return Divider();
|
||||||
|
|
||||||
if (_loadingTasks.isNotEmpty) {
|
if (_loadingTasks.isNotEmpty) {
|
||||||
final loadingTask = _loadingTasks.removeLast();
|
final loadingTask = _loadingTasks.removeLast();
|
||||||
return _buildLoadingTile(loadingTask);
|
return _buildLoadingTile(loadingTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
final index = i ~/ 2;
|
final index = i ~/ 2;
|
||||||
|
|
||||||
if (taskState.maxPages == _currentPage &&
|
if (taskState.maxPages == _currentPage &&
|
||||||
index == taskState.tasks.length)
|
index == taskState.tasks.length)
|
||||||
throw Exception("Check itemCount attribute");
|
throw Exception("Check itemCount attribute");
|
||||||
|
|
||||||
if (index >= taskState.tasks.length &&
|
if (index >= taskState.tasks.length &&
|
||||||
_currentPage < taskState.maxPages) {
|
_currentPage < taskState.maxPages) {
|
||||||
_currentPage++;
|
_currentPage++;
|
||||||
_loadTasksForPage(_currentPage);
|
_loadTasksForPage(_currentPage);
|
||||||
}
|
}
|
||||||
return _buildTile(taskState.tasks[index]);
|
return _buildTile(taskState.tasks[index]);
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(children: children);
|
return Column(children: children);
|
||||||
|
@ -302,13 +314,11 @@ class _ListPageState extends State<ListPage> {
|
||||||
_loadTasksForPage(1);
|
_loadTasksForPage(1);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
await _kanban
|
await _kanban.loadBucketsForPage(1);
|
||||||
.loadBucketsForPage(1);
|
|
||||||
// load all buckets to get length for RecordableListView
|
// load all buckets to get length for RecordableListView
|
||||||
while (_currentPage < taskState.maxPages) {
|
while (_currentPage < taskState.maxPages) {
|
||||||
_currentPage++;
|
_currentPage++;
|
||||||
await _kanban
|
await _kanban.loadBucketsForPage(_currentPage);
|
||||||
.loadBucketsForPage(_currentPage);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -318,8 +328,7 @@ class _ListPageState extends State<ListPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadTasksForPage(int page) {
|
Future<void> _loadTasksForPage(int page) {
|
||||||
return Provider.of<ProjectProvider>(context, listen: false)
|
return Provider.of<ProjectProvider>(context, listen: false).loadTasks(
|
||||||
.loadTasks(
|
|
||||||
context: context,
|
context: context,
|
||||||
listId: _project.id,
|
listId: _project.id,
|
||||||
page: page,
|
page: page,
|
||||||
|
@ -333,7 +342,7 @@ class _ListPageState extends State<ListPage> {
|
||||||
onAdd: (title) => _addItem(title, context, bucket),
|
onAdd: (title) => _addItem(title, context, bucket),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText:
|
labelText:
|
||||||
(bucket != null ? '\'${bucket.title}\': ' : '') + 'New Task Name',
|
(bucket != null ? '\'${bucket.title}\': ' : '') + 'New Task Name',
|
||||||
hintText: 'eg. Milk',
|
hintText: 'eg. Milk',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -375,7 +384,6 @@ class _ListPageState extends State<ListPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
openList(BuildContext context, Project project) {
|
openList(BuildContext context, Project project) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => ChangeNotifierProvider<ProjectProvider>(
|
builder: (context) => ChangeNotifierProvider<ProjectProvider>(
|
||||||
|
@ -386,4 +394,4 @@ openList(BuildContext context, Project project) {
|
||||||
),
|
),
|
||||||
// ListPage(taskList: list)
|
// ListPage(taskList: list)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,7 +357,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newUser.error == 0)
|
if (newUser.error == 0)
|
||||||
vGlobal.changeUser(newUser.user!, token: newUser.token, base: _server);
|
vGlobal.changeUser(
|
||||||
|
newUser.user!,
|
||||||
|
token: newUser.token,
|
||||||
|
base: _server,
|
||||||
|
xClientToken: _xClientToken,
|
||||||
|
);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
print(ex);
|
print(ex);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -127,10 +127,9 @@ class _RegisterPageState extends State<RegisterPage> {
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
try {
|
try {
|
||||||
var vGlobal = VikunjaGlobal.of(context);
|
var vGlobal = VikunjaGlobal.of(context);
|
||||||
var newUserLoggedIn = await vGlobal
|
var newUserLoggedIn =
|
||||||
.newUserService
|
await vGlobal.newUserService?.register(_username!, _email, _password);
|
||||||
?.register(_username!, _email, _password);
|
if (newUserLoggedIn != null)
|
||||||
if(newUserLoggedIn != null)
|
|
||||||
vGlobal.changeUser(newUserLoggedIn.user!,
|
vGlobal.changeUser(newUserLoggedIn.user!,
|
||||||
token: newUserLoggedIn.token, base: _server!);
|
token: newUserLoggedIn.token, base: _server!);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:vikunja_app/models/task.dart';
|
import 'package:vikunja_app/models/task.dart';
|
||||||
import 'package:vikunja_app/models/bucket.dart';
|
import 'package:vikunja_app/models/bucket.dart';
|
||||||
|
@ -15,7 +14,6 @@ class ListProvider with ChangeNotifier {
|
||||||
List<Task> _tasks = [];
|
List<Task> _tasks = [];
|
||||||
List<Bucket> _buckets = [];
|
List<Bucket> _buckets = [];
|
||||||
|
|
||||||
|
|
||||||
bool get taskDragging => _taskDragging;
|
bool get taskDragging => _taskDragging;
|
||||||
|
|
||||||
set taskDragging(bool value) {
|
set taskDragging(bool value) {
|
||||||
|
@ -39,7 +37,6 @@ class ListProvider with ChangeNotifier {
|
||||||
|
|
||||||
List<Bucket> get buckets => _buckets;
|
List<Bucket> get buckets => _buckets;
|
||||||
|
|
||||||
|
|
||||||
PageStatus _pageStatus = PageStatus.built;
|
PageStatus _pageStatus = PageStatus.built;
|
||||||
|
|
||||||
PageStatus get pageStatus => _pageStatus;
|
PageStatus get pageStatus => _pageStatus;
|
||||||
|
@ -50,7 +47,11 @@ class ListProvider with ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadTasks({required BuildContext context, required int listId, int page = 1, bool displayDoneTasks = true}) {
|
Future<void> loadTasks(
|
||||||
|
{required BuildContext context,
|
||||||
|
required int listId,
|
||||||
|
int page = 1,
|
||||||
|
bool displayDoneTasks = true}) {
|
||||||
_tasks = [];
|
_tasks = [];
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ class ListProvider with ChangeNotifier {
|
||||||
"page": [page.toString()]
|
"page": [page.toString()]
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!displayDoneTasks) {
|
if (!displayDoneTasks) {
|
||||||
queryParams.addAll({
|
queryParams.addAll({
|
||||||
"filter_by": ["done"],
|
"filter_by": ["done"],
|
||||||
"filter_value": ["false"]
|
"filter_value": ["false"]
|
||||||
|
@ -81,7 +82,8 @@ class ListProvider with ChangeNotifier {
|
||||||
});*/
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadBuckets({required BuildContext context, required int listId, int page = 1}) {
|
Future<void> loadBuckets(
|
||||||
|
{required BuildContext context, required int listId, int page = 1}) {
|
||||||
_buckets = [];
|
_buckets = [];
|
||||||
pageStatus = PageStatus.loading;
|
pageStatus = PageStatus.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -90,8 +92,11 @@ class ListProvider with ChangeNotifier {
|
||||||
"page": [page.toString()]
|
"page": [page.toString()]
|
||||||
};
|
};
|
||||||
|
|
||||||
return VikunjaGlobal.of(context).bucketService.getAllByList(listId, queryParams).then((response) {
|
return VikunjaGlobal.of(context)
|
||||||
if(response == null) {
|
.bucketService
|
||||||
|
.getAllByList(listId, queryParams)
|
||||||
|
.then((response) {
|
||||||
|
if (response == null) {
|
||||||
pageStatus = PageStatus.error;
|
pageStatus = PageStatus.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +110,9 @@ class ListProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addTaskByTitle(
|
Future<void> addTaskByTitle(
|
||||||
{required BuildContext context, required String title, required int listId}) async{
|
{required BuildContext context,
|
||||||
|
required String title,
|
||||||
|
required int listId}) async {
|
||||||
final globalState = VikunjaGlobal.of(context);
|
final globalState = VikunjaGlobal.of(context);
|
||||||
if (globalState.currentUser == null) {
|
if (globalState.currentUser == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -120,13 +127,15 @@ class ListProvider with ChangeNotifier {
|
||||||
pageStatus = PageStatus.loading;
|
pageStatus = PageStatus.loading;
|
||||||
|
|
||||||
return globalState.taskService.add(listId, newTask).then((task) {
|
return globalState.taskService.add(listId, newTask).then((task) {
|
||||||
if(task != null)
|
if (task != null) _tasks.insert(0, task);
|
||||||
_tasks.insert(0, task);
|
|
||||||
pageStatus = PageStatus.success;
|
pageStatus = PageStatus.success;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addTask({required BuildContext context, required Task newTask, required int listId}) {
|
Future<void> addTask(
|
||||||
|
{required BuildContext context,
|
||||||
|
required Task newTask,
|
||||||
|
required int listId}) {
|
||||||
var globalState = VikunjaGlobal.of(context);
|
var globalState = VikunjaGlobal.of(context);
|
||||||
if (newTask.bucketId == null) pageStatus = PageStatus.loading;
|
if (newTask.bucketId == null) pageStatus = PageStatus.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -136,107 +145,129 @@ class ListProvider with ChangeNotifier {
|
||||||
pageStatus = PageStatus.error;
|
pageStatus = PageStatus.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_tasks.isNotEmpty)
|
if (_tasks.isNotEmpty) _tasks.insert(0, task);
|
||||||
_tasks.insert(0, task);
|
|
||||||
if (_buckets.isNotEmpty) {
|
if (_buckets.isNotEmpty) {
|
||||||
final bucket = _buckets[_buckets.indexWhere((b) => task.bucketId == b.id)];
|
final bucket =
|
||||||
|
_buckets[_buckets.indexWhere((b) => task.bucketId == b.id)];
|
||||||
bucket.tasks.add(task);
|
bucket.tasks.add(task);
|
||||||
}
|
}
|
||||||
pageStatus = PageStatus.success;
|
pageStatus = PageStatus.success;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Task?> updateTask({required BuildContext context, required Task task}) {
|
Future<Task?> updateTask(
|
||||||
|
{required BuildContext context, required Task task}) {
|
||||||
return VikunjaGlobal.of(context).taskService.update(task).then((task) {
|
return VikunjaGlobal.of(context).taskService.update(task).then((task) {
|
||||||
// FIXME: This is ugly. We should use a redux to not have to do these kind of things.
|
// FIXME: This is ugly. We should use a redux to not have to do these kind of things.
|
||||||
// This is enough for now (it works™) but we should definitely fix it later.
|
// This is enough for now (it works™) but we should definitely fix it later.
|
||||||
if(task == null)
|
if (task == null) return null;
|
||||||
return null;
|
|
||||||
_tasks.asMap().forEach((i, t) {
|
_tasks.asMap().forEach((i, t) {
|
||||||
if (task.id == t.id) {
|
if (task.id == t.id) {
|
||||||
_tasks[i] = task;
|
_tasks[i] = task;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_buckets.asMap().forEach((i, b) => b.tasks.asMap().forEach((v, t) {
|
_buckets.asMap().forEach((i, b) => b.tasks.asMap().forEach((v, t) {
|
||||||
if (task.id == t.id){
|
if (task.id == t.id) {
|
||||||
_buckets[i].tasks[v] = task;
|
_buckets[i].tasks[v] = task;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return task;
|
return task;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addBucket({required BuildContext context, required Bucket newBucket, required int listId}) {
|
Future<void> addBucket(
|
||||||
|
{required BuildContext context,
|
||||||
|
required Bucket newBucket,
|
||||||
|
required int listId}) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return VikunjaGlobal.of(context).bucketService.add(listId, newBucket)
|
return VikunjaGlobal.of(context)
|
||||||
|
.bucketService
|
||||||
|
.add(listId, newBucket)
|
||||||
.then((bucket) {
|
.then((bucket) {
|
||||||
if(bucket == null)
|
if (bucket == null) return null;
|
||||||
return null;
|
_buckets.add(bucket);
|
||||||
_buckets.add(bucket);
|
notifyListeners();
|
||||||
notifyListeners();
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateBucket({required BuildContext context, required Bucket bucket}) {
|
Future<void> updateBucket(
|
||||||
return VikunjaGlobal.of(context).bucketService.update(bucket)
|
{required BuildContext context, required Bucket bucket}) {
|
||||||
|
return VikunjaGlobal.of(context)
|
||||||
|
.bucketService
|
||||||
|
.update(bucket)
|
||||||
.then((rBucket) {
|
.then((rBucket) {
|
||||||
if(rBucket == null)
|
if (rBucket == null) return null;
|
||||||
return null;
|
_buckets[_buckets.indexWhere((b) => rBucket.id == b.id)] = rBucket;
|
||||||
_buckets[_buckets.indexWhere((b) => rBucket.id == b.id)] = rBucket;
|
_buckets.sort((a, b) => a.position!.compareTo(b.position!));
|
||||||
_buckets.sort((a, b) => a.position!.compareTo(b.position!));
|
notifyListeners();
|
||||||
notifyListeners();
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteBucket({required BuildContext context, required int listId, required int bucketId}) {
|
|
||||||
return VikunjaGlobal.of(context).bucketService.delete(listId, bucketId)
|
|
||||||
.then((_) {
|
|
||||||
_buckets.removeWhere((bucket) => bucket.id == bucketId);
|
|
||||||
notifyListeners();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveTaskToBucket({required BuildContext context, required Task? task, int? newBucketId, required int index}) async {
|
Future<void> deleteBucket(
|
||||||
if(task == null)
|
{required BuildContext context,
|
||||||
throw Exception("Task to be moved may not be null");
|
required int listId,
|
||||||
|
required int bucketId}) {
|
||||||
|
return VikunjaGlobal.of(context)
|
||||||
|
.bucketService
|
||||||
|
.delete(listId, bucketId)
|
||||||
|
.then((_) {
|
||||||
|
_buckets.removeWhere((bucket) => bucket.id == bucketId);
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> moveTaskToBucket(
|
||||||
|
{required BuildContext context,
|
||||||
|
required Task? task,
|
||||||
|
int? newBucketId,
|
||||||
|
required int index}) async {
|
||||||
|
if (task == null) throw Exception("Task to be moved may not be null");
|
||||||
final sameBucket = task.bucketId == newBucketId;
|
final sameBucket = task.bucketId == newBucketId;
|
||||||
final newBucketIndex = _buckets.indexWhere((b) => b.id == newBucketId);
|
final newBucketIndex = _buckets.indexWhere((b) => b.id == newBucketId);
|
||||||
if (sameBucket && index > _buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task?.id)) index--;
|
if (sameBucket &&
|
||||||
|
index >
|
||||||
|
_buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task?.id))
|
||||||
|
index--;
|
||||||
|
|
||||||
_buckets[_buckets.indexWhere((b) => b.id == task?.bucketId)].tasks.remove(task);
|
_buckets[_buckets.indexWhere((b) => b.id == task?.bucketId)]
|
||||||
|
.tasks
|
||||||
|
.remove(task);
|
||||||
if (index >= _buckets[newBucketIndex].tasks.length)
|
if (index >= _buckets[newBucketIndex].tasks.length)
|
||||||
_buckets[newBucketIndex].tasks.add(task);
|
_buckets[newBucketIndex].tasks.add(task);
|
||||||
else
|
else
|
||||||
_buckets[newBucketIndex].tasks.insert(index, task);
|
_buckets[newBucketIndex].tasks.insert(index, task);
|
||||||
|
|
||||||
task = await VikunjaGlobal.of(context).taskService.update(task.copyWith(
|
task = await VikunjaGlobal.of(context).taskService.update(task.copyWith(
|
||||||
bucketId: newBucketId,
|
bucketId: newBucketId,
|
||||||
kanbanPosition: calculateItemPosition(
|
kanbanPosition: calculateItemPosition(
|
||||||
positionBefore: index != 0
|
positionBefore: index != 0
|
||||||
? _buckets[newBucketIndex].tasks[index - 1].kanbanPosition : null,
|
? _buckets[newBucketIndex].tasks[index - 1].kanbanPosition
|
||||||
positionAfter: index < _buckets[newBucketIndex].tasks.length - 1
|
: null,
|
||||||
? _buckets[newBucketIndex].tasks[index + 1].kanbanPosition : null,
|
positionAfter: index < _buckets[newBucketIndex].tasks.length - 1
|
||||||
),
|
? _buckets[newBucketIndex].tasks[index + 1].kanbanPosition
|
||||||
));
|
: null,
|
||||||
if(task == null)
|
),
|
||||||
return;
|
));
|
||||||
|
if (task == null) return;
|
||||||
_buckets[newBucketIndex].tasks[index] = task;
|
_buckets[newBucketIndex].tasks[index] = task;
|
||||||
|
|
||||||
// make sure the first 2 tasks don't have 0 kanbanPosition
|
// make sure the first 2 tasks don't have 0 kanbanPosition
|
||||||
Task? secondTask;
|
Task? secondTask;
|
||||||
if (index == 0 && _buckets[newBucketIndex].tasks.length > 1
|
if (index == 0 &&
|
||||||
&& _buckets[newBucketIndex].tasks[1].kanbanPosition == 0) {
|
_buckets[newBucketIndex].tasks.length > 1 &&
|
||||||
secondTask = await VikunjaGlobal.of(context).taskService.update(
|
_buckets[newBucketIndex].tasks[1].kanbanPosition == 0) {
|
||||||
_buckets[newBucketIndex].tasks[1].copyWith(
|
secondTask = await VikunjaGlobal.of(context)
|
||||||
kanbanPosition: calculateItemPosition(
|
.taskService
|
||||||
positionBefore: task.kanbanPosition,
|
.update(_buckets[newBucketIndex].tasks[1].copyWith(
|
||||||
positionAfter: 1 < _buckets[newBucketIndex].tasks.length - 1
|
kanbanPosition: calculateItemPosition(
|
||||||
? _buckets[newBucketIndex].tasks[2].kanbanPosition : null,
|
positionBefore: task.kanbanPosition,
|
||||||
),
|
positionAfter: 1 < _buckets[newBucketIndex].tasks.length - 1
|
||||||
));
|
? _buckets[newBucketIndex].tasks[2].kanbanPosition
|
||||||
if(secondTask != null)
|
: null,
|
||||||
_buckets[newBucketIndex].tasks[1] = secondTask;
|
),
|
||||||
|
));
|
||||||
|
if (secondTask != null) _buckets[newBucketIndex].tasks[1] = secondTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_tasks.isNotEmpty) {
|
if (_tasks.isNotEmpty) {
|
||||||
|
@ -245,8 +276,12 @@ class ListProvider with ChangeNotifier {
|
||||||
_tasks[_tasks.indexWhere((t) => t.id == secondTask!.id)] = secondTask;
|
_tasks[_tasks.indexWhere((t) => t.id == secondTask!.id)] = secondTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buckets[newBucketIndex].tasks[_buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task?.id)] = task;
|
_buckets[newBucketIndex].tasks[_buckets[newBucketIndex]
|
||||||
_buckets[newBucketIndex].tasks.sort((a, b) => a.kanbanPosition!.compareTo(b.kanbanPosition!));
|
.tasks
|
||||||
|
.indexWhere((t) => t.id == task?.id)] = task;
|
||||||
|
_buckets[newBucketIndex]
|
||||||
|
.tasks
|
||||||
|
.sort((a, b) => a.kanbanPosition!.compareTo(b.kanbanPosition!));
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ class ProjectProvider with ChangeNotifier {
|
||||||
List<Task> _tasks = [];
|
List<Task> _tasks = [];
|
||||||
List<Bucket> _buckets = [];
|
List<Bucket> _buckets = [];
|
||||||
|
|
||||||
|
|
||||||
bool get taskDragging => _taskDragging;
|
bool get taskDragging => _taskDragging;
|
||||||
|
|
||||||
set taskDragging(bool value) {
|
set taskDragging(bool value) {
|
||||||
|
@ -38,7 +37,6 @@ class ProjectProvider with ChangeNotifier {
|
||||||
|
|
||||||
List<Bucket> get buckets => _buckets;
|
List<Bucket> get buckets => _buckets;
|
||||||
|
|
||||||
|
|
||||||
PageStatus _pageStatus = PageStatus.built;
|
PageStatus _pageStatus = PageStatus.built;
|
||||||
|
|
||||||
PageStatus get pageStatus => _pageStatus;
|
PageStatus get pageStatus => _pageStatus;
|
||||||
|
@ -49,7 +47,11 @@ class ProjectProvider with ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadTasks({required BuildContext context, required int listId, int page = 1, bool displayDoneTasks = true}) {
|
Future<void> loadTasks(
|
||||||
|
{required BuildContext context,
|
||||||
|
required int listId,
|
||||||
|
int page = 1,
|
||||||
|
bool displayDoneTasks = true}) {
|
||||||
_tasks = [];
|
_tasks = [];
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
@ -59,15 +61,18 @@ class ProjectProvider with ChangeNotifier {
|
||||||
"page": [page.toString()]
|
"page": [page.toString()]
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!displayDoneTasks) {
|
if (!displayDoneTasks) {
|
||||||
queryParams.addAll({
|
queryParams.addAll({
|
||||||
"filter_by": ["done"],
|
"filter_by": ["done"],
|
||||||
"filter_value": ["false"],
|
"filter_value": ["false"],
|
||||||
"sort_by": ["done"],
|
"sort_by": ["done"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return VikunjaGlobal.of(context).taskService.getAllByProject(listId, queryParams).then((response) {
|
return VikunjaGlobal.of(context)
|
||||||
if(response == null) {
|
.taskService
|
||||||
|
.getAllByProject(listId, queryParams)
|
||||||
|
.then((response) {
|
||||||
|
if (response == null) {
|
||||||
pageStatus = PageStatus.error;
|
pageStatus = PageStatus.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +84,8 @@ class ProjectProvider with ChangeNotifier {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadBuckets({required BuildContext context, required int listId, int page = 1}) {
|
Future<void> loadBuckets(
|
||||||
|
{required BuildContext context, required int listId, int page = 1}) {
|
||||||
_buckets = [];
|
_buckets = [];
|
||||||
pageStatus = PageStatus.loading;
|
pageStatus = PageStatus.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -88,8 +94,11 @@ class ProjectProvider with ChangeNotifier {
|
||||||
"page": [page.toString()]
|
"page": [page.toString()]
|
||||||
};
|
};
|
||||||
|
|
||||||
return VikunjaGlobal.of(context).bucketService.getAllByList(listId, queryParams).then((response) {
|
return VikunjaGlobal.of(context)
|
||||||
if(response == null) {
|
.bucketService
|
||||||
|
.getAllByList(listId, queryParams)
|
||||||
|
.then((response) {
|
||||||
|
if (response == null) {
|
||||||
pageStatus = PageStatus.error;
|
pageStatus = PageStatus.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +112,9 @@ class ProjectProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addTaskByTitle(
|
Future<void> addTaskByTitle(
|
||||||
{required BuildContext context, required String title, required int projectId}) async{
|
{required BuildContext context,
|
||||||
|
required String title,
|
||||||
|
required int projectId}) async {
|
||||||
final globalState = VikunjaGlobal.of(context);
|
final globalState = VikunjaGlobal.of(context);
|
||||||
if (globalState.currentUser == null) {
|
if (globalState.currentUser == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -118,13 +129,15 @@ class ProjectProvider with ChangeNotifier {
|
||||||
pageStatus = PageStatus.loading;
|
pageStatus = PageStatus.loading;
|
||||||
|
|
||||||
return globalState.taskService.add(projectId, newTask).then((task) {
|
return globalState.taskService.add(projectId, newTask).then((task) {
|
||||||
if(task != null)
|
if (task != null) _tasks.insert(0, task);
|
||||||
_tasks.insert(0, task);
|
|
||||||
pageStatus = PageStatus.success;
|
pageStatus = PageStatus.success;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addTask({required BuildContext context, required Task newTask, required int listId}) {
|
Future<void> addTask(
|
||||||
|
{required BuildContext context,
|
||||||
|
required Task newTask,
|
||||||
|
required int listId}) {
|
||||||
var globalState = VikunjaGlobal.of(context);
|
var globalState = VikunjaGlobal.of(context);
|
||||||
if (newTask.bucketId == null) pageStatus = PageStatus.loading;
|
if (newTask.bucketId == null) pageStatus = PageStatus.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -134,107 +147,129 @@ class ProjectProvider with ChangeNotifier {
|
||||||
pageStatus = PageStatus.error;
|
pageStatus = PageStatus.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_tasks.isNotEmpty)
|
if (_tasks.isNotEmpty) _tasks.insert(0, task);
|
||||||
_tasks.insert(0, task);
|
|
||||||
if (_buckets.isNotEmpty) {
|
if (_buckets.isNotEmpty) {
|
||||||
final bucket = _buckets[_buckets.indexWhere((b) => task.bucketId == b.id)];
|
final bucket =
|
||||||
|
_buckets[_buckets.indexWhere((b) => task.bucketId == b.id)];
|
||||||
bucket.tasks.add(task);
|
bucket.tasks.add(task);
|
||||||
}
|
}
|
||||||
pageStatus = PageStatus.success;
|
pageStatus = PageStatus.success;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Task?> updateTask({required BuildContext context, required Task task}) {
|
Future<Task?> updateTask(
|
||||||
|
{required BuildContext context, required Task task}) {
|
||||||
return VikunjaGlobal.of(context).taskService.update(task).then((task) {
|
return VikunjaGlobal.of(context).taskService.update(task).then((task) {
|
||||||
// FIXME: This is ugly. We should use a redux to not have to do these kind of things.
|
// FIXME: This is ugly. We should use a redux to not have to do these kind of things.
|
||||||
// This is enough for now (it works™) but we should definitely fix it later.
|
// This is enough for now (it works™) but we should definitely fix it later.
|
||||||
if(task == null)
|
if (task == null) return null;
|
||||||
return null;
|
|
||||||
_tasks.asMap().forEach((i, t) {
|
_tasks.asMap().forEach((i, t) {
|
||||||
if (task.id == t.id) {
|
if (task.id == t.id) {
|
||||||
_tasks[i] = task;
|
_tasks[i] = task;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_buckets.asMap().forEach((i, b) => b.tasks.asMap().forEach((v, t) {
|
_buckets.asMap().forEach((i, b) => b.tasks.asMap().forEach((v, t) {
|
||||||
if (task.id == t.id){
|
if (task.id == t.id) {
|
||||||
_buckets[i].tasks[v] = task;
|
_buckets[i].tasks[v] = task;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return task;
|
return task;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addBucket({required BuildContext context, required Bucket newBucket, required int listId}) {
|
Future<void> addBucket(
|
||||||
|
{required BuildContext context,
|
||||||
|
required Bucket newBucket,
|
||||||
|
required int listId}) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return VikunjaGlobal.of(context).bucketService.add(listId, newBucket)
|
return VikunjaGlobal.of(context)
|
||||||
|
.bucketService
|
||||||
|
.add(listId, newBucket)
|
||||||
.then((bucket) {
|
.then((bucket) {
|
||||||
if(bucket == null)
|
if (bucket == null) return null;
|
||||||
return null;
|
|
||||||
_buckets.add(bucket);
|
_buckets.add(bucket);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateBucket({required BuildContext context, required Bucket bucket}) {
|
Future<void> updateBucket(
|
||||||
return VikunjaGlobal.of(context).bucketService.update(bucket)
|
{required BuildContext context, required Bucket bucket}) {
|
||||||
|
return VikunjaGlobal.of(context)
|
||||||
|
.bucketService
|
||||||
|
.update(bucket)
|
||||||
.then((rBucket) {
|
.then((rBucket) {
|
||||||
if(rBucket == null)
|
if (rBucket == null) return null;
|
||||||
return null;
|
|
||||||
_buckets[_buckets.indexWhere((b) => rBucket.id == b.id)] = rBucket;
|
_buckets[_buckets.indexWhere((b) => rBucket.id == b.id)] = rBucket;
|
||||||
_buckets.sort((a, b) => a.position!.compareTo(b.position!));
|
_buckets.sort((a, b) => a.position!.compareTo(b.position!));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteBucket({required BuildContext context, required int listId, required int bucketId}) {
|
Future<void> deleteBucket(
|
||||||
return VikunjaGlobal.of(context).bucketService.delete(listId, bucketId)
|
{required BuildContext context,
|
||||||
|
required int listId,
|
||||||
|
required int bucketId}) {
|
||||||
|
return VikunjaGlobal.of(context)
|
||||||
|
.bucketService
|
||||||
|
.delete(listId, bucketId)
|
||||||
.then((_) {
|
.then((_) {
|
||||||
_buckets.removeWhere((bucket) => bucket.id == bucketId);
|
_buckets.removeWhere((bucket) => bucket.id == bucketId);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveTaskToBucket({required BuildContext context, required Task? task, int? newBucketId, required int index}) async {
|
Future<void> moveTaskToBucket(
|
||||||
if(task == null)
|
{required BuildContext context,
|
||||||
throw Exception("Task to be moved may not be null");
|
required Task? task,
|
||||||
|
int? newBucketId,
|
||||||
|
required int index}) async {
|
||||||
|
if (task == null) throw Exception("Task to be moved may not be null");
|
||||||
final sameBucket = task.bucketId == newBucketId;
|
final sameBucket = task.bucketId == newBucketId;
|
||||||
final newBucketIndex = _buckets.indexWhere((b) => b.id == newBucketId);
|
final newBucketIndex = _buckets.indexWhere((b) => b.id == newBucketId);
|
||||||
if (sameBucket && index > _buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task?.id)) index--;
|
if (sameBucket &&
|
||||||
|
index >
|
||||||
|
_buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task?.id))
|
||||||
|
index--;
|
||||||
|
|
||||||
_buckets[_buckets.indexWhere((b) => b.id == task?.bucketId)].tasks.remove(task);
|
_buckets[_buckets.indexWhere((b) => b.id == task?.bucketId)]
|
||||||
|
.tasks
|
||||||
|
.remove(task);
|
||||||
if (index >= _buckets[newBucketIndex].tasks.length)
|
if (index >= _buckets[newBucketIndex].tasks.length)
|
||||||
_buckets[newBucketIndex].tasks.add(task);
|
_buckets[newBucketIndex].tasks.add(task);
|
||||||
else
|
else
|
||||||
_buckets[newBucketIndex].tasks.insert(index, task);
|
_buckets[newBucketIndex].tasks.insert(index, task);
|
||||||
|
|
||||||
task = await VikunjaGlobal.of(context).taskService.update(task.copyWith(
|
task = await VikunjaGlobal.of(context).taskService.update(task.copyWith(
|
||||||
bucketId: newBucketId,
|
bucketId: newBucketId,
|
||||||
kanbanPosition: calculateItemPosition(
|
kanbanPosition: calculateItemPosition(
|
||||||
positionBefore: index != 0
|
positionBefore: index != 0
|
||||||
? _buckets[newBucketIndex].tasks[index - 1].kanbanPosition : null,
|
? _buckets[newBucketIndex].tasks[index - 1].kanbanPosition
|
||||||
positionAfter: index < _buckets[newBucketIndex].tasks.length - 1
|
: null,
|
||||||
? _buckets[newBucketIndex].tasks[index + 1].kanbanPosition : null,
|
positionAfter: index < _buckets[newBucketIndex].tasks.length - 1
|
||||||
),
|
? _buckets[newBucketIndex].tasks[index + 1].kanbanPosition
|
||||||
));
|
: null,
|
||||||
if(task == null)
|
),
|
||||||
return;
|
));
|
||||||
|
if (task == null) return;
|
||||||
_buckets[newBucketIndex].tasks[index] = task;
|
_buckets[newBucketIndex].tasks[index] = task;
|
||||||
|
|
||||||
// make sure the first 2 tasks don't have 0 kanbanPosition
|
// make sure the first 2 tasks don't have 0 kanbanPosition
|
||||||
Task? secondTask;
|
Task? secondTask;
|
||||||
if (index == 0 && _buckets[newBucketIndex].tasks.length > 1
|
if (index == 0 &&
|
||||||
&& _buckets[newBucketIndex].tasks[1].kanbanPosition == 0) {
|
_buckets[newBucketIndex].tasks.length > 1 &&
|
||||||
secondTask = await VikunjaGlobal.of(context).taskService.update(
|
_buckets[newBucketIndex].tasks[1].kanbanPosition == 0) {
|
||||||
_buckets[newBucketIndex].tasks[1].copyWith(
|
secondTask = await VikunjaGlobal.of(context)
|
||||||
kanbanPosition: calculateItemPosition(
|
.taskService
|
||||||
positionBefore: task.kanbanPosition,
|
.update(_buckets[newBucketIndex].tasks[1].copyWith(
|
||||||
positionAfter: 1 < _buckets[newBucketIndex].tasks.length - 1
|
kanbanPosition: calculateItemPosition(
|
||||||
? _buckets[newBucketIndex].tasks[2].kanbanPosition : null,
|
positionBefore: task.kanbanPosition,
|
||||||
),
|
positionAfter: 1 < _buckets[newBucketIndex].tasks.length - 1
|
||||||
));
|
? _buckets[newBucketIndex].tasks[2].kanbanPosition
|
||||||
if(secondTask != null)
|
: null,
|
||||||
_buckets[newBucketIndex].tasks[1] = secondTask;
|
),
|
||||||
|
));
|
||||||
|
if (secondTask != null) _buckets[newBucketIndex].tasks[1] = secondTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_tasks.isNotEmpty) {
|
if (_tasks.isNotEmpty) {
|
||||||
|
@ -243,8 +278,12 @@ class ProjectProvider with ChangeNotifier {
|
||||||
_tasks[_tasks.indexWhere((t) => t.id == secondTask!.id)] = secondTask;
|
_tasks[_tasks.indexWhere((t) => t.id == secondTask!.id)] = secondTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buckets[newBucketIndex].tasks[_buckets[newBucketIndex].tasks.indexWhere((t) => t.id == task?.id)] = task;
|
_buckets[newBucketIndex].tasks[_buckets[newBucketIndex]
|
||||||
_buckets[newBucketIndex].tasks.sort((a, b) => a.kanbanPosition!.compareTo(b.kanbanPosition!));
|
.tasks
|
||||||
|
.indexWhere((t) => t.id == task?.id)] = task;
|
||||||
|
_buckets[newBucketIndex]
|
||||||
|
.tasks
|
||||||
|
.sort((a, b) => a.kanbanPosition!.compareTo(b.kanbanPosition!));
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@ class FancyButton extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ElevatedButton(onPressed: onPressed,
|
return ElevatedButton(
|
||||||
|
onPressed: onPressed,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Center(child: child),
|
child: Center(child: child),
|
||||||
),);
|
),
|
||||||
|
);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: vStandardVerticalPadding,
|
padding: vStandardVerticalPadding,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
|
@ -13,7 +13,9 @@ class VikunjaButtonText extends StatelessWidget {
|
||||||
return Text(text);
|
return Text(text);
|
||||||
return Text(
|
return Text(
|
||||||
text,
|
text,
|
||||||
style: TextStyle(color: Theme.of(context).primaryTextTheme.labelMedium?.color, fontWeight: FontWeight.w600),
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryTextTheme.labelMedium?.color,
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,4 @@ 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");
|
var vDateFormatShort = DateFormat("d MMM yyyy, H:mm");
|
||||||
|
|
|
@ -4,17 +4,15 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:vikunja_app/theme/constants.dart';
|
import 'package:vikunja_app/theme/constants.dart';
|
||||||
|
|
||||||
ThemeData buildVikunjaTheme() => _buildVikunjaTheme(ThemeData.light());
|
ThemeData buildVikunjaTheme() => _buildVikunjaTheme(ThemeData.light());
|
||||||
ThemeData buildVikunjaDarkTheme() => _buildVikunjaTheme(ThemeData.dark(), isDark: true);
|
ThemeData buildVikunjaDarkTheme() =>
|
||||||
|
_buildVikunjaTheme(ThemeData.dark(), isDark: true);
|
||||||
|
|
||||||
ThemeData buildVikunjaMaterialLightTheme() {
|
ThemeData buildVikunjaMaterialLightTheme() {
|
||||||
return ThemeData.light().copyWith(
|
return ThemeData.light().copyWith();
|
||||||
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData buildVikunjaMaterialDarkTheme() {
|
ThemeData buildVikunjaMaterialDarkTheme() {
|
||||||
return ThemeData.dark().copyWith(
|
return ThemeData.dark().copyWith();
|
||||||
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData _buildVikunjaTheme(ThemeData base, {bool isDark = false}) {
|
ThemeData _buildVikunjaTheme(ThemeData base, {bool isDark = false}) {
|
||||||
|
@ -39,11 +37,8 @@ ThemeData _buildVikunjaTheme(ThemeData base, {bool isDark = false}) {
|
||||||
),
|
),
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
enabledBorder: UnderlineInputBorder(
|
enabledBorder: UnderlineInputBorder(
|
||||||
borderSide: const BorderSide(color: Colors.grey, width: 1)
|
borderSide: const BorderSide(color: Colors.grey, width: 1)),
|
||||||
),
|
|
||||||
|
|
||||||
),
|
),
|
||||||
|
|
||||||
dividerTheme: DividerThemeData(
|
dividerTheme: DividerThemeData(
|
||||||
color: () {
|
color: () {
|
||||||
return isDark ? Colors.white10 : Colors.black12;
|
return isDark ? Colors.white10 : Colors.black12;
|
||||||
|
@ -54,14 +49,18 @@ ThemeData _buildVikunjaTheme(ThemeData base, {bool isDark = false}) {
|
||||||
// Make bottomNavigationBar backgroundColor darker to provide more separation
|
// Make bottomNavigationBar backgroundColor darker to provide more separation
|
||||||
backgroundColor: () {
|
backgroundColor: () {
|
||||||
final _hslColor = HSLColor.fromColor(
|
final _hslColor = HSLColor.fromColor(
|
||||||
base.bottomNavigationBarTheme.backgroundColor
|
base.bottomNavigationBarTheme.backgroundColor ??
|
||||||
?? base.scaffoldBackgroundColor
|
base.scaffoldBackgroundColor);
|
||||||
);
|
return _hslColor
|
||||||
return _hslColor.withLightness(max(_hslColor.lightness - 0.03, 0)).toColor();
|
.withLightness(max(_hslColor.lightness - 0.03, 0))
|
||||||
|
.toColor();
|
||||||
}(),
|
}(),
|
||||||
), colorScheme: base.colorScheme.copyWith(
|
),
|
||||||
primary: vPrimaryDark,
|
colorScheme: base.colorScheme
|
||||||
secondary: vPrimary,
|
.copyWith(
|
||||||
).copyWith(error: vRed),
|
primary: vPrimaryDark,
|
||||||
|
secondary: vPrimary,
|
||||||
|
)
|
||||||
|
.copyWith(error: vRed),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,4 +18,4 @@ double calculateItemPosition({double? positionBefore, double? positionAfter}) {
|
||||||
|
|
||||||
// in the middle (positionBefore != null && positionAfter != null)
|
// in the middle (positionBefore != null && positionAfter != null)
|
||||||
return (positionBefore! + positionAfter!) / 2;
|
return (positionBefore! + positionAfter!) / 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,4 +46,3 @@ CheckboxStatistics getCheckboxStatistics(String text) {
|
||||||
checked: checkboxes.checked.length,
|
checked: checkboxes.checked.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
String durationToHumanReadable(Duration dur) {
|
String durationToHumanReadable(Duration dur) {
|
||||||
var durString = '';
|
var durString = '';
|
||||||
if(dur.inDays.abs() > 1)
|
if (dur.inDays.abs() > 1)
|
||||||
durString = dur.inDays.abs().toString() + " days";
|
durString = dur.inDays.abs().toString() + " days";
|
||||||
else if(dur.inDays.abs() == 1)
|
else if (dur.inDays.abs() == 1)
|
||||||
durString = dur.inDays.abs().toString() + " day";
|
durString = dur.inDays.abs().toString() + " day";
|
||||||
|
else if (dur.inHours.abs() > 1)
|
||||||
else if(dur.inHours.abs() > 1)
|
|
||||||
durString = dur.inHours.abs().toString() + " hours";
|
durString = dur.inHours.abs().toString() + " hours";
|
||||||
else if(dur.inHours.abs() == 1)
|
else if (dur.inHours.abs() == 1)
|
||||||
durString = dur.inHours.abs().toString() + " hour";
|
durString = dur.inHours.abs().toString() + " hour";
|
||||||
|
else if (dur.inMinutes.abs() > 1)
|
||||||
else if(dur.inMinutes.abs() > 1)
|
|
||||||
durString = dur.inMinutes.abs().toString() + " minutes";
|
durString = dur.inMinutes.abs().toString() + " minutes";
|
||||||
else if(dur.inMinutes.abs() == 1)
|
else if (dur.inMinutes.abs() == 1)
|
||||||
durString = dur.inMinutes.abs().toString() + " minute";
|
durString = dur.inMinutes.abs().toString() + " minute";
|
||||||
else durString = "less than a minute";
|
else
|
||||||
|
durString = "less than a minute";
|
||||||
|
|
||||||
if (dur.isNegative) return durString + " ago";
|
if (dur.isNegative) return durString + " ago";
|
||||||
return "in " + durString;
|
return "in " + durString;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,7 @@ priorityFromString(String? priority) {
|
||||||
case 'DO NOW':
|
case 'DO NOW':
|
||||||
return 5;
|
return 5;
|
||||||
default:
|
default:
|
||||||
// unset
|
// unset
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,6 @@ Duration? getDurationFromType(String? value, String? type) {
|
||||||
case 'Years':
|
case 'Years':
|
||||||
return Duration(days: val * 365);
|
return Duration(days: val * 365);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ import 'package:vikunja_app/models/user.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('label color from json', () {
|
test('label color from json', () {
|
||||||
final String json = '{"TaskID": 123,"id": 1,"title": "this","description": "","hex_color": "e8e8e8","created_by":{"id": 1,"username": "user","email": "test@example.com","created": 1537855131,"updated": 1545233325},"created": 1552903790,"updated": 1552903790}';
|
final String json =
|
||||||
|
'{"TaskID": 123,"id": 1,"title": "this","description": "","hex_color": "e8e8e8","created_by":{"id": 1,"username": "user","email": "test@example.com","created": 1537855131,"updated": 1545233325},"created": 1552903790,"updated": 1552903790}';
|
||||||
final JsonDecoder _decoder = new JsonDecoder();
|
final JsonDecoder _decoder = new JsonDecoder();
|
||||||
Label label = Label.fromJson(_decoder.convert(json));
|
Label label = Label.fromJson(_decoder.convert(json));
|
||||||
|
|
||||||
|
@ -15,9 +16,14 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hex color string from object', () {
|
test('hex color string from object', () {
|
||||||
Label label = Label(id: 1, title: '', color: Color(0xFFe8e8e8), createdBy: User(id: 0, username: ''));
|
Label label = Label(
|
||||||
|
id: 1,
|
||||||
|
title: '',
|
||||||
|
color: Color(0xFFe8e8e8),
|
||||||
|
createdBy: User(id: 0, username: ''));
|
||||||
var json = label.toJSON();
|
var json = label.toJSON();
|
||||||
|
|
||||||
expect(json.toString(), '{id: 1, title: , description: null, hex_color: e8e8e8, created_by: {id: 0, username: ,}, updated: null, created: null}');
|
expect(json.toString(),
|
||||||
|
'{id: 1, title: , description: null, hex_color: e8e8e8, created_by: {id: 0, username: ,}, updated: null, created: null}');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Check encoding with all values set', () {
|
test('Check encoding with all values set', () {
|
||||||
final String json = '{"id": 1,"text": "test","description": "Lorem Ipsum","done": true,"dueDate": 1543834800,"reminderDates": [1543834800,1544612400],"repeatAfter": 3600,"parentTaskID": 0,"priority": 100,"startDate": 1543834800,"endDate": 1543835000,"assignees": null,"labels": null,"subtasks": null,"created": 1542465818,"updated": 1552771527,"createdBy": {"id": 1,"username": "user","email": "test@example.com","created": 1537855131,"updated": 1545233325}}';
|
final String json =
|
||||||
|
'{"id": 1,"text": "test","description": "Lorem Ipsum","done": true,"dueDate": 1543834800,"reminderDates": [1543834800,1544612400],"repeatAfter": 3600,"parentTaskID": 0,"priority": 100,"startDate": 1543834800,"endDate": 1543835000,"assignees": null,"labels": null,"subtasks": null,"created": 1542465818,"updated": 1552771527,"createdBy": {"id": 1,"username": "user","email": "test@example.com","created": 1537855131,"updated": 1545233325}}';
|
||||||
final JsonDecoder _decoder = new JsonDecoder();
|
final JsonDecoder _decoder = new JsonDecoder();
|
||||||
final task = Task.fromJson(_decoder.convert(json));
|
final task = Task.fromJson(_decoder.convert(json));
|
||||||
|
|
||||||
|
@ -17,19 +18,25 @@ void main() {
|
||||||
DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000),
|
DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000),
|
||||||
DateTime.fromMillisecondsSinceEpoch(1544612400 * 1000),
|
DateTime.fromMillisecondsSinceEpoch(1544612400 * 1000),
|
||||||
]);
|
]);
|
||||||
expect(task.dueDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
expect(
|
||||||
|
task.dueDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
||||||
expect(task.repeatAfter, Duration(seconds: 3600));
|
expect(task.repeatAfter, Duration(seconds: 3600));
|
||||||
expect(task.parentTaskId, 0);
|
expect(task.parentTaskId, 0);
|
||||||
expect(task.priority, 100);
|
expect(task.priority, 100);
|
||||||
expect(task.startDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
expect(
|
||||||
expect(task.endDate, DateTime.fromMillisecondsSinceEpoch(1543835000 * 1000));
|
task.startDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
||||||
|
expect(
|
||||||
|
task.endDate, DateTime.fromMillisecondsSinceEpoch(1543835000 * 1000));
|
||||||
expect(task.labels, null);
|
expect(task.labels, null);
|
||||||
expect(task.subtasks, null);
|
expect(task.subtasks, null);
|
||||||
expect(task.created, DateTime.fromMillisecondsSinceEpoch(1542465818 * 1000));
|
expect(
|
||||||
expect(task.updated, DateTime.fromMillisecondsSinceEpoch(1552771527 * 1000));
|
task.created, DateTime.fromMillisecondsSinceEpoch(1542465818 * 1000));
|
||||||
|
expect(
|
||||||
|
task.updated, DateTime.fromMillisecondsSinceEpoch(1552771527 * 1000));
|
||||||
});
|
});
|
||||||
test('Check encoding with reminder dates as null', () {
|
test('Check encoding with reminder dates as null', () {
|
||||||
final String json = '{"id": 1,"text": "test","description": "Lorem Ipsum","done": true,"dueDate": 1543834800,"reminderDates": null,"repeatAfter": 3600,"parentTaskID": 0,"priority": 100,"startDate": 1543834800,"endDate": 1543835000,"assignees": null,"labels": null,"subtasks": null,"created": 1542465818,"updated": 1552771527,"createdBy": {"id": 1,"username": "user","email": "test@example.com","created": 1537855131,"updated": 1545233325}}';
|
final String json =
|
||||||
|
'{"id": 1,"text": "test","description": "Lorem Ipsum","done": true,"dueDate": 1543834800,"reminderDates": null,"repeatAfter": 3600,"parentTaskID": 0,"priority": 100,"startDate": 1543834800,"endDate": 1543835000,"assignees": null,"labels": null,"subtasks": null,"created": 1542465818,"updated": 1552771527,"createdBy": {"id": 1,"username": "user","email": "test@example.com","created": 1537855131,"updated": 1545233325}}';
|
||||||
final JsonDecoder _decoder = new JsonDecoder();
|
final JsonDecoder _decoder = new JsonDecoder();
|
||||||
final task = Task.fromJson(_decoder.convert(json));
|
final task = Task.fromJson(_decoder.convert(json));
|
||||||
|
|
||||||
|
@ -38,15 +45,20 @@ void main() {
|
||||||
expect(task.description, 'Lorem Ipsum');
|
expect(task.description, 'Lorem Ipsum');
|
||||||
expect(task.done, true);
|
expect(task.done, true);
|
||||||
expect(task.reminderDates, null);
|
expect(task.reminderDates, null);
|
||||||
expect(task.dueDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
expect(
|
||||||
|
task.dueDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
||||||
expect(task.repeatAfter, Duration(seconds: 3600));
|
expect(task.repeatAfter, Duration(seconds: 3600));
|
||||||
expect(task.parentTaskId, 0);
|
expect(task.parentTaskId, 0);
|
||||||
expect(task.priority, 100);
|
expect(task.priority, 100);
|
||||||
expect(task.startDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
expect(
|
||||||
expect(task.endDate, DateTime.fromMillisecondsSinceEpoch(1543835000 * 1000));
|
task.startDate, DateTime.fromMillisecondsSinceEpoch(1543834800 * 1000));
|
||||||
|
expect(
|
||||||
|
task.endDate, DateTime.fromMillisecondsSinceEpoch(1543835000 * 1000));
|
||||||
expect(task.labels, null);
|
expect(task.labels, null);
|
||||||
expect(task.subtasks, null);
|
expect(task.subtasks, null);
|
||||||
expect(task.created, DateTime.fromMillisecondsSinceEpoch(1542465818 * 1000));
|
expect(
|
||||||
expect(task.updated, DateTime.fromMillisecondsSinceEpoch(1552771527 * 1000));
|
task.created, DateTime.fromMillisecondsSinceEpoch(1542465818 * 1000));
|
||||||
|
expect(
|
||||||
|
task.updated, DateTime.fromMillisecondsSinceEpoch(1552771527 * 1000));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user