From 9833ef4885a5faa793d69aa30d13708b133c7efc Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 22:31:15 +0100 Subject: [PATCH 01/27] Re-implemented getting tasks with the new seperate endpoint --- lib/api/task_implementation.dart | 6 ++++++ lib/models/list.dart | 10 ++-------- lib/pages/list/list.dart | 18 ++++++++++++++---- lib/pages/namespace/namespace.dart | 2 +- lib/service/mocked_services.dart | 16 ++++++---------- lib/service/services.dart | 1 + 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index 1d85b1d..ead2d65 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -26,4 +26,10 @@ class TaskAPIService extends APIService implements TaskService { .post('/tasks/${task.id}', body: task.toJSON()) .then((map) => Task.fromJson(map)); } + + @override + Future> getAll(int listId) { + return client.get('/lists/$listId/tasks').then( + (list) => convertList(list, (result) => Task.fromJson(result))); + } } diff --git a/lib/models/list.dart b/lib/models/list.dart index cf9a5ad..accccd8 100644 --- a/lib/models/list.dart +++ b/lib/models/list.dart @@ -1,5 +1,4 @@ import 'package:meta/meta.dart'; -import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/models/user.dart'; class TaskList { @@ -7,7 +6,6 @@ class TaskList { final String title, description; final User owner; final DateTime created, updated; - final List tasks; TaskList( {@required this.id, @@ -15,8 +13,7 @@ class TaskList { this.description, this.owner, this.created, - this.updated, - this.tasks}); + this.updated}); TaskList.fromJson(Map json) : id = json['id'], @@ -24,10 +21,7 @@ class TaskList { description = json['description'], title = json['title'], updated = DateTime.fromMillisecondsSinceEpoch(json['updated']), - created = DateTime.fromMillisecondsSinceEpoch(json['created']), - tasks = (json['tasks'] == null ? [] : json['tasks'] as List) - ?.map((taskJson) => Task.fromJson(taskJson)) - ?.toList(); + created = DateTime.fromMillisecondsSinceEpoch(json['created']); toJSON() { return { diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index f963a2a..3fce729 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -19,13 +19,14 @@ class ListPage extends StatefulWidget { class _ListPageState extends State { TaskList _list; + List _tasks = []; List _loadingTasks = []; bool _loading = true; @override void initState() { _list = TaskList( - id: widget.taskList.id, title: widget.taskList.title, tasks: []); + id: widget.taskList.id, title: widget.taskList.title); super.initState(); } @@ -53,7 +54,7 @@ class _ListPageState extends State { ), body: !this._loading ? RefreshIndicator( - child: _list.tasks.length > 0 + child: _tasks.length > 0 ? ListView( padding: EdgeInsets.symmetric(vertical: 8.0), children: ListTile.divideTiles( @@ -71,7 +72,7 @@ class _ListPageState extends State { } List _listTasks() { - var tasks = (_list?.tasks?.map(_buildTile) ?? []).toList(); + var tasks = (_tasks?.map(_buildTile) ?? []).toList(); tasks.addAll(_loadingTasks.map(_buildLoadingTile)); return tasks; } @@ -95,6 +96,15 @@ class _ListPageState extends State { setState(() { _loading = false; _list = list; + // Load tasks + VikunjaGlobal.of(context) + .taskService + .getAll(list.id) + .then((tasks) { + setState(() { + _tasks = tasks; + }); + }); }); }); } @@ -115,7 +125,7 @@ class _ListPageState extends State { setState(() => _loadingTasks.add(newTask)); globalState.taskService.add(_list.id, newTask).then((task) { setState(() { - _list.tasks.add(task); + _tasks.add(task); }); }).then((_) { _loadList(); diff --git a/lib/pages/namespace/namespace.dart b/lib/pages/namespace/namespace.dart index 07f6f87..612c074 100644 --- a/lib/pages/namespace/namespace.dart +++ b/lib/pages/namespace/namespace.dart @@ -116,7 +116,7 @@ class _NamespacePageState extends State _addList(String name, BuildContext context) { VikunjaGlobal.of(context) .listService - .create(widget.namespace.id, TaskList(id: null, title: name, tasks: [])) + .create(widget.namespace.id, TaskList(id: null, title: name)) .then((_) { setState(() {}); _loadLists(); diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index 98913e1..dda39f3 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -28,7 +28,6 @@ var _lists = { 1: TaskList( id: 1, title: 'List 1', - tasks: _tasks.values.toList(), owner: _users[1], description: 'A nice list', created: DateTime.now(), @@ -120,20 +119,13 @@ class MockedListService implements ListService { class MockedTaskService implements TaskService { @override Future delete(int taskId) { - _lists.forEach( - (_, list) => list.tasks.removeWhere((task) => task.id == taskId)); _tasks.remove(taskId); return Future.value(); } @override Future update(Task task) { - _lists.forEach((_, list) { - if (list.tasks.where((t) => t.id == task.id).length > 0) { - list.tasks.removeWhere((t) => t.id == task.id); - list.tasks.add(task); - } - }); + _tasks[task.id] = task; return Future.value(_tasks[task.id] = task); } @@ -141,9 +133,13 @@ class MockedTaskService implements TaskService { Future add(int listId, Task task) { var id = _tasks.keys.last + 1; _tasks[id] = task; - _lists[listId].tasks.add(task); return Future.value(task); } + + @override + Future> getAll(int listId) { + return Future.value(_tasks.values.toList()); + } } class MockedUserService implements UserService { diff --git a/lib/service/services.dart b/lib/service/services.dart index 54f8c51..750b2ed 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -26,6 +26,7 @@ abstract class TaskService { Future update(Task task); Future delete(int taskId); Future add(int listId, Task task); + Future> getAll(int listId); } abstract class UserService { -- 2.40.1 From cf9c9b4cb4dd6d5a8e1d74e65d3b8c31bfcced40 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 22:47:10 +0100 Subject: [PATCH 02/27] Fix crash after save --- lib/models/task.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/task.dart b/lib/models/task.dart index 897f63e..6f9b6a1 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -31,7 +31,7 @@ class Task { description = json['description'], text = json['text'], done = json['done'], - owner = User.fromJson(json['createdBy']); + owner = json['createdBy'].toString() == "null" ? null : User.fromJson(json['createdBy']); // There has to be a better way of doing this... toJSON() => { 'id': id, -- 2.40.1 From 187337c580b836b443641792c814d8d5911c92f0 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 22:47:40 +0100 Subject: [PATCH 03/27] Format --- lib/api/task_implementation.dart | 5 +++-- lib/models/task.dart | 5 ++++- lib/pages/list/list.dart | 8 ++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index ead2d65..8c570ff 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -29,7 +29,8 @@ class TaskAPIService extends APIService implements TaskService { @override Future> getAll(int listId) { - return client.get('/lists/$listId/tasks').then( - (list) => convertList(list, (result) => Task.fromJson(result))); + return client + .get('/lists/$listId/tasks') + .then((list) => convertList(list, (result) => Task.fromJson(result))); } } diff --git a/lib/models/task.dart b/lib/models/task.dart index 6f9b6a1..0cb49ef 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -31,7 +31,10 @@ class Task { description = json['description'], text = json['text'], done = json['done'], - owner = json['createdBy'].toString() == "null" ? null : User.fromJson(json['createdBy']); // There has to be a better way of doing this... + owner = json['createdBy'].toString() == "null" + ? null + : User.fromJson(json[ + 'createdBy']); // There has to be a better way of doing this... toJSON() => { 'id': id, diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 3fce729..fb2ba57 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -25,8 +25,7 @@ class _ListPageState extends State { @override void initState() { - _list = TaskList( - id: widget.taskList.id, title: widget.taskList.title); + _list = TaskList(id: widget.taskList.id, title: widget.taskList.title); super.initState(); } @@ -97,10 +96,7 @@ class _ListPageState extends State { _loading = false; _list = list; // Load tasks - VikunjaGlobal.of(context) - .taskService - .getAll(list.id) - .then((tasks) { + VikunjaGlobal.of(context).taskService.getAll(list.id).then((tasks) { setState(() { _tasks = tasks; }); -- 2.40.1 From 2345a131b7701fd09feab737a948a2d5a679b578 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 23:09:32 +0100 Subject: [PATCH 04/27] Add optional query params to api get function --- lib/api/client.dart | 18 ++++++++++++++---- lib/api/task_implementation.dart | 4 ++-- lib/service/mocked_services.dart | 2 +- lib/service/services.dart | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index f347d38..16b37cc 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -25,10 +25,20 @@ class Client { 'Content-Type': 'application/json' }; - Future get(String url) { - return http - .get('${this.base}$url', headers: _headers) - .then(_handleResponse); + Future get(String url, [Map queryParameters]) { + // TODO: This could be moved to a seperate function + var uri = Uri.parse('${this.base}$url'); + // Because these are all final values, we can't just add the queryParameters and must instead build a new Uri Object every time this method is called. + var newUri = Uri( + scheme: uri.scheme, + userInfo: uri.userInfo, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + queryParameters: queryParameters, + fragment: uri.fragment); + return http.get(newUri, headers: _headers).then(_handleResponse); } Future delete(String url) { diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index 8c570ff..241b1e7 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -28,9 +28,9 @@ class TaskAPIService extends APIService implements TaskService { } @override - Future> getAll(int listId) { + Future> getAll(int listId, [Map queryParameters]) { return client - .get('/lists/$listId/tasks') + .get('/lists/$listId/tasks', queryParameters) .then((list) => convertList(list, (result) => Task.fromJson(result))); } } diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index dda39f3..297b518 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -137,7 +137,7 @@ class MockedTaskService implements TaskService { } @override - Future> getAll(int listId) { + Future> getAll(int listId, [Map queryParameters]) { return Future.value(_tasks.values.toList()); } } diff --git a/lib/service/services.dart b/lib/service/services.dart index 750b2ed..263e4e1 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -26,7 +26,7 @@ abstract class TaskService { Future update(Task task); Future delete(int taskId); Future add(int listId, Task task); - Future> getAll(int listId); + Future> getAll(int listId, [Map queryParameters]); } abstract class UserService { -- 2.40.1 From b36f0215d88d5e1d33ee9b625b986fcc855abdb2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 23:14:51 +0100 Subject: [PATCH 05/27] Add sorting for tasks --- lib/pages/list/list.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index fb2ba57..ff54d28 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -96,7 +96,8 @@ class _ListPageState extends State { _loading = false; _list = list; // Load tasks - VikunjaGlobal.of(context).taskService.getAll(list.id).then((tasks) { + VikunjaGlobal.of(context).taskService.getAll( + list.id, {"sort_by": "done", "order_by": "asc"}).then((tasks) { setState(() { _tasks = tasks; }); -- 2.40.1 From ac67ccbd4c68eaa674fec6cc6b2807f29648614e Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 23:15:50 +0100 Subject: [PATCH 06/27] Note --- lib/api/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index 16b37cc..8e4df0e 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -36,7 +36,7 @@ class Client { port: uri.port, path: uri.path, query: uri.query, - queryParameters: queryParameters, + queryParameters: queryParameters, // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. fragment: uri.fragment); return http.get(newUri, headers: _headers).then(_handleResponse); } -- 2.40.1 From ea8ba3cfd9be27c2d44887dc055b24477fc7261e Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 13 Jan 2020 23:16:26 +0100 Subject: [PATCH 07/27] Format --- lib/api/client.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index 8e4df0e..b6bc0ef 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -36,7 +36,8 @@ class Client { port: uri.port, path: uri.path, query: uri.query, - queryParameters: queryParameters, // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. + queryParameters: + queryParameters, // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. fragment: uri.fragment); return http.get(newUri, headers: _headers).then(_handleResponse); } -- 2.40.1 From 53dfe7327b9ebd5128736cceb0c74707476e2eb7 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 15 Jan 2020 22:59:07 +0100 Subject: [PATCH 08/27] Add basic infinite scrolling (still has bugs) --- lib/api/client.dart | 3 +++ lib/api/service.dart | 2 ++ lib/pages/list/list.dart | 39 +++++++++++++++++++++----------- lib/service/mocked_services.dart | 3 +++ lib/service/services.dart | 2 ++ 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index b6bc0ef..ed44640 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -7,6 +7,8 @@ class Client { final JsonEncoder _encoder = new JsonEncoder(); final String _token; final String _base; + int _maxPages; + int get maxPages => _maxPages; String get base => _base; @@ -69,6 +71,7 @@ class Client { throw new ApiException( response.statusCode, response.request.url.toString()); } + _maxPages = response.headers["x-pagination-total-pages"] != null ? int.parse(response.headers["x-pagination-total-pages"]) : 0; return _decoder.convert(response.body); } } diff --git a/lib/api/service.dart b/lib/api/service.dart index c7d837e..3ca5a26 100644 --- a/lib/api/service.dart +++ b/lib/api/service.dart @@ -4,6 +4,8 @@ import 'package:meta/meta.dart'; class APIService { final Client _client; + int get maxPages => _client.maxPages; + @protected Client get client => _client; diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index ff54d28..5602739 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -22,6 +22,7 @@ class _ListPageState extends State { List _tasks = []; List _loadingTasks = []; bool _loading = true; + int _currentPage = 1; @override void initState() { @@ -54,12 +55,20 @@ class _ListPageState extends State { body: !this._loading ? RefreshIndicator( child: _tasks.length > 0 - ? ListView( - padding: EdgeInsets.symmetric(vertical: 8.0), - children: ListTile.divideTiles( - context: context, tiles: _listTasks()) - .toList(), - ) + ? ListView.builder( + padding: EdgeInsets.symmetric(vertical: 8.0), + itemBuilder: (context, i) { + if (i.isOdd) return Divider(); + + final index = i ~/ 2; + if (index >= _tasks.length && VikunjaGlobal.of(context).taskService.maxPages < _currentPage) { + _currentPage++; + _loadTasksForPage(_currentPage); + } + return TaskTile( + task: _tasks[index], + ); + }) : Center(child: Text('This list is empty.')), onRefresh: _loadList, ) @@ -95,13 +104,17 @@ class _ListPageState extends State { setState(() { _loading = false; _list = list; - // Load tasks - VikunjaGlobal.of(context).taskService.getAll( - list.id, {"sort_by": "done", "order_by": "asc"}).then((tasks) { - setState(() { - _tasks = tasks; - }); - }); + }); + _loadTasksForPage(_currentPage); + }); + } + + Future _loadTasksForPage(int page) { + return VikunjaGlobal.of(context).taskService.getAll( + _list.id, {"sort_by": "done", "order_by": "asc", "page": page.toString()}).then((tasks) { + setState(() { + _loading = false; + _tasks.addAll(tasks); }); }); } diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index 297b518..003238c 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -140,6 +140,9 @@ class MockedTaskService implements TaskService { Future> getAll(int listId, [Map queryParameters]) { return Future.value(_tasks.values.toList()); } + + @override + int get maxPages => 1; } class MockedUserService implements UserService { diff --git a/lib/service/services.dart b/lib/service/services.dart index 263e4e1..354bed4 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -27,6 +27,8 @@ abstract class TaskService { Future delete(int taskId); Future add(int listId, Task task); Future> getAll(int listId, [Map queryParameters]); + // TODO: Avoid having to add this to each abstract class + int get maxPages; } abstract class UserService { -- 2.40.1 From 96dbddb10ce1e83a3cef7ceb2b6f8d276a5e4518 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 15 Jan 2020 23:29:31 +0100 Subject: [PATCH 09/27] Fix scrolling to last element --- lib/pages/list/list.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 5602739..a683890 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -61,8 +61,16 @@ class _ListPageState extends State { if (i.isOdd) return Divider(); final index = i ~/ 2; + + // This handles the case if there are no more elements in the list left which can be provided by the api + if(VikunjaGlobal.of(context).taskService.maxPages == _currentPage && index == _tasks.length - 1) return null; + if (index >= _tasks.length && VikunjaGlobal.of(context).taskService.maxPages < _currentPage) { _currentPage++; + // FIXME: This does not get loaded until some time after the next (_tasks.length + 1) entry should have been rendered + // This leads to errors because the render method tries to access an entry of _tasks with an index which does not exist + // The load function gets actually called after the return below. I assume this is because of the setState() invocation + // in _loadTasksForPage()? _loadTasksForPage(_currentPage); } return TaskTile( -- 2.40.1 From b5363cb6acbf50706e813b7f627d7793d0c8f7b2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 15 Jan 2020 23:32:43 +0100 Subject: [PATCH 10/27] Add fixme --- lib/pages/namespace/namespace.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/namespace/namespace.dart b/lib/pages/namespace/namespace.dart index 612c074..dc0b1f8 100644 --- a/lib/pages/namespace/namespace.dart +++ b/lib/pages/namespace/namespace.dart @@ -89,6 +89,7 @@ class _NamespacePageState extends State } Future _loadLists() { + // FIXME: This is called even when the tasks on a list are loaded - which is not needed at all return VikunjaGlobal.of(context) .listService .getByNamespace(widget.namespace.id) -- 2.40.1 From 6fc336223b84f476becf4c9ab499b4b3a4f7941c Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 15 Jan 2020 23:33:33 +0100 Subject: [PATCH 11/27] Format --- lib/api/client.dart | 4 +++- lib/pages/list/list.dart | 47 +++++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index ed44640..670640c 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -71,7 +71,9 @@ class Client { throw new ApiException( response.statusCode, response.request.url.toString()); } - _maxPages = response.headers["x-pagination-total-pages"] != null ? int.parse(response.headers["x-pagination-total-pages"]) : 0; + _maxPages = response.headers["x-pagination-total-pages"] != null + ? int.parse(response.headers["x-pagination-total-pages"]) + : 0; return _decoder.convert(response.body); } } diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index a683890..8b9cf29 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -56,27 +56,31 @@ class _ListPageState extends State { ? RefreshIndicator( child: _tasks.length > 0 ? ListView.builder( - padding: EdgeInsets.symmetric(vertical: 8.0), - itemBuilder: (context, i) { - if (i.isOdd) return Divider(); + padding: EdgeInsets.symmetric(vertical: 8.0), + itemBuilder: (context, i) { + if (i.isOdd) return Divider(); - final index = i ~/ 2; + final index = i ~/ 2; - // This handles the case if there are no more elements in the list left which can be provided by the api - if(VikunjaGlobal.of(context).taskService.maxPages == _currentPage && index == _tasks.length - 1) return null; + // This handles the case if there are no more elements in the list left which can be provided by the api + if (VikunjaGlobal.of(context).taskService.maxPages == + _currentPage && + index == _tasks.length - 1) return null; - if (index >= _tasks.length && VikunjaGlobal.of(context).taskService.maxPages < _currentPage) { - _currentPage++; - // FIXME: This does not get loaded until some time after the next (_tasks.length + 1) entry should have been rendered - // This leads to errors because the render method tries to access an entry of _tasks with an index which does not exist - // The load function gets actually called after the return below. I assume this is because of the setState() invocation - // in _loadTasksForPage()? - _loadTasksForPage(_currentPage); - } - return TaskTile( - task: _tasks[index], - ); - }) + if (index >= _tasks.length && + VikunjaGlobal.of(context).taskService.maxPages < + _currentPage) { + _currentPage++; + // FIXME: This does not get loaded until some time after the next (_tasks.length + 1) entry should have been rendered + // This leads to errors because the render method tries to access an entry of _tasks with an index which does not exist + // The load function gets actually called after the return below. I assume this is because of the setState() invocation + // in _loadTasksForPage()? + _loadTasksForPage(_currentPage); + } + return TaskTile( + task: _tasks[index], + ); + }) : Center(child: Text('This list is empty.')), onRefresh: _loadList, ) @@ -118,8 +122,11 @@ class _ListPageState extends State { } Future _loadTasksForPage(int page) { - return VikunjaGlobal.of(context).taskService.getAll( - _list.id, {"sort_by": "done", "order_by": "asc", "page": page.toString()}).then((tasks) { + return VikunjaGlobal.of(context).taskService.getAll(_list.id, { + "sort_by": "done", + "order_by": "asc", + "page": page.toString() + }).then((tasks) { setState(() { _loading = false; _tasks.addAll(tasks); -- 2.40.1 From 5f7d59c7eae2fc343d5877e916b82f4572240e90 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 16 Jan 2020 22:08:26 +0100 Subject: [PATCH 12/27] Don't try to show a task tile if there is none for the current index --- lib/pages/list/list.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 8b9cf29..58215f2 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -77,9 +77,9 @@ class _ListPageState extends State { // in _loadTasksForPage()? _loadTasksForPage(_currentPage); } - return TaskTile( + return index < _tasks.length ? TaskTile( task: _tasks[index], - ); + ): null; }) : Center(child: Text('This list is empty.')), onRefresh: _loadList, -- 2.40.1 From d08c7c3e701d5b83e7c04b3a5df9ecae11926ef2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 16 Jan 2020 22:10:51 +0100 Subject: [PATCH 13/27] format --- lib/pages/list/list.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 58215f2..058027c 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -77,9 +77,11 @@ class _ListPageState extends State { // in _loadTasksForPage()? _loadTasksForPage(_currentPage); } - return index < _tasks.length ? TaskTile( - task: _tasks[index], - ): null; + return index < _tasks.length + ? TaskTile( + task: _tasks[index], + ) + : null; }) : Center(child: Text('This list is empty.')), onRefresh: _loadList, -- 2.40.1 From e110a4b9ebba2c08d210bc71266b7067846da301 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 27 Apr 2020 17:09:10 +0200 Subject: [PATCH 14/27] Format --- lib/models/user.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index 08b0280..56e21fa 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -14,7 +14,10 @@ class User { toJSON() => {"id": this.id, "email": this.email, "username": this.username}; String avatarUrl(BuildContext context) { - return VikunjaGlobal.of(context).client.base + "/" + this.username + "/avatar"; + return VikunjaGlobal.of(context).client.base + + "/" + + this.username + + "/avatar"; } } -- 2.40.1 From 413c2703e667222edc913385198f03c2c93c3ea3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 27 Apr 2020 17:18:50 +0200 Subject: [PATCH 15/27] Fix crash when no due date was present --- lib/models/task.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/models/task.dart b/lib/models/task.dart index 8bdbc95..d0401bc 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -27,7 +27,9 @@ class Task { reminders = (json['reminderDates'] as List) ?.map((r) => DateTime.parse(r)) ?.toList(), - due = DateTime.parse(json['dueDate']), + due = json['dueDate'].toString() == 'null' + ? null + : DateTime.parse(json['dueDate']), description = json['description'], text = json['text'], done = json['done'], -- 2.40.1 From a34daacf23d228d37c8b59df8dfdcfe60f851be8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 27 Apr 2020 18:27:26 +0200 Subject: [PATCH 16/27] Fix next page condition not working --- lib/pages/list/list.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 058027c..5a11586 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -68,8 +68,10 @@ class _ListPageState extends State { index == _tasks.length - 1) return null; if (index >= _tasks.length && - VikunjaGlobal.of(context).taskService.maxPages < - _currentPage) { + _currentPage < + VikunjaGlobal.of(context) + .taskService + .maxPages) { _currentPage++; // FIXME: This does not get loaded until some time after the next (_tasks.length + 1) entry should have been rendered // This leads to errors because the render method tries to access an entry of _tasks with an index which does not exist -- 2.40.1 From 9fd47cde57efc31476455f00b257a64297afed4d Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 27 Apr 2020 18:39:28 +0200 Subject: [PATCH 17/27] Add workaround for pagination --- lib/api/client.dart | 19 ++++++++++++++----- lib/pages/list/list.dart | 4 ---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index 670640c..c4ff250 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -8,6 +8,7 @@ class Client { final String _token; final String _base; int _maxPages; + int get maxPages => _maxPages; String get base => _base; @@ -38,8 +39,8 @@ class Client { port: uri.port, path: uri.path, query: uri.query, - queryParameters: - queryParameters, // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. + queryParameters: queryParameters, + // Because dart takes a Map here, it is only possible to sort by one parameter while the api supports n parameters. fragment: uri.fragment); return http.get(newUri, headers: _headers).then(_handleResponse); } @@ -71,9 +72,16 @@ class Client { throw new ApiException( response.statusCode, response.request.url.toString()); } - _maxPages = response.headers["x-pagination-total-pages"] != null - ? int.parse(response.headers["x-pagination-total-pages"]) - : 0; + // FIXME: This is a workaround for when the client makes another + // unrelated api request in between requesting multiple pages of the same thing. + // To properly fix this, we need a way to pass the max number of pages somewhere else + // and not save them in the global client which everyone uses. + // This workaround only works when the other api requests in between two pages + // are not using pagination. + if (response.headers["x-pagination-total-pages"] != null) { + _maxPages = int.parse(response.headers["x-pagination-total-pages"]); + } + return _decoder.convert(response.body); } } @@ -81,6 +89,7 @@ class Client { class ApiException implements Exception { final int errorCode; final String path; + ApiException(this.errorCode, this.path); @override diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 5a11586..9273edc 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -73,10 +73,6 @@ class _ListPageState extends State { .taskService .maxPages) { _currentPage++; - // FIXME: This does not get loaded until some time after the next (_tasks.length + 1) entry should have been rendered - // This leads to errors because the render method tries to access an entry of _tasks with an index which does not exist - // The load function gets actually called after the return below. I assume this is because of the setState() invocation - // in _loadTasksForPage()? _loadTasksForPage(_currentPage); } return index < _tasks.length -- 2.40.1 From bb6139cef7c83e9360ba57dbbcf711591665743e Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 27 Apr 2020 19:38:04 +0200 Subject: [PATCH 18/27] Fix loading state not being set --- lib/pages/list/list.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 9273edc..d4a2a4b 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -114,7 +114,7 @@ class _ListPageState extends State { .get(widget.taskList.id) .then((list) { setState(() { - _loading = false; + _loading = true; _list = list; }); _loadTasksForPage(_currentPage); -- 2.40.1 From 9348e6acc5c635751c5878ef598babe725cbb88d Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 27 Apr 2020 19:39:21 +0200 Subject: [PATCH 19/27] Cleanup --- lib/pages/list/list.dart | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index d4a2a4b..eb973f2 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -91,23 +91,6 @@ class _ListPageState extends State { )); } - List _listTasks() { - var tasks = (_tasks?.map(_buildTile) ?? []).toList(); - tasks.addAll(_loadingTasks.map(_buildLoadingTile)); - return tasks; - } - - TaskTile _buildTile(Task task) { - return TaskTile(task: task, loading: false); - } - - TaskTile _buildLoadingTile(Task task) { - return TaskTile( - task: task, - loading: true, - ); - } - Future _loadList() { return VikunjaGlobal.of(context) .listService -- 2.40.1 From 38c7348aa89a370ce28d250d86784866ff47f1e1 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 15 Jun 2020 23:48:01 +0200 Subject: [PATCH 20/27] Format --- lib/pages/list/list.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 7bfded0..e88cc1c 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -128,8 +128,8 @@ class _ListPageState extends State { _addItem(String name, BuildContext context) { var globalState = VikunjaGlobal.of(context); - var newTask = - Task(id: null, title: name, owner: globalState.currentUser, done: false); + var newTask = Task( + id: null, title: name, owner: globalState.currentUser, done: false); setState(() => _loadingTasks.add(newTask)); globalState.taskService.add(_list.id, newTask).then((task) { setState(() { -- 2.40.1 From 972b194f56bdac5485e02a93f658f5f613809039 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 16 Jun 2020 00:33:09 +0200 Subject: [PATCH 21/27] Enable multiple order parameters --- lib/api/client.dart | 2 +- lib/api/task_implementation.dart | 2 +- lib/pages/list/list.dart | 6 +++--- lib/service/mocked_services.dart | 2 +- lib/service/services.dart | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/api/client.dart b/lib/api/client.dart index c4ff250..7ba7e4f 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -28,7 +28,7 @@ class Client { 'Content-Type': 'application/json' }; - Future get(String url, [Map queryParameters]) { + Future get(String url, [Map> queryParameters]) { // TODO: This could be moved to a seperate function var uri = Uri.parse('${this.base}$url'); // Because these are all final values, we can't just add the queryParameters and must instead build a new Uri Object every time this method is called. diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index 241b1e7..4870831 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -28,7 +28,7 @@ class TaskAPIService extends APIService implements TaskService { } @override - Future> getAll(int listId, [Map queryParameters]) { + Future> getAll(int listId, [Map> queryParameters]) { return client .get('/lists/$listId/tasks', queryParameters) .then((list) => convertList(list, (result) => Task.fromJson(result))); diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index e88cc1c..ed2b818 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -106,9 +106,9 @@ class _ListPageState extends State { Future _loadTasksForPage(int page) { return VikunjaGlobal.of(context).taskService.getAll(_list.id, { - "sort_by": "done", - "order_by": "asc", - "page": page.toString() + "sort_by": ["done", "id"], + "order_by": ["asc", "desc"], + "page": [page.toString()] }).then((tasks) { setState(() { _loading = false; diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index fe4050b..959aed7 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -137,7 +137,7 @@ class MockedTaskService implements TaskService { } @override - Future> getAll(int listId, [Map queryParameters]) { + Future> getAll(int listId, [Map> queryParameters]) { return Future.value(_tasks.values.toList()); } diff --git a/lib/service/services.dart b/lib/service/services.dart index 354bed4..e7fe1aa 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -26,7 +26,7 @@ abstract class TaskService { Future update(Task task); Future delete(int taskId); Future add(int listId, Task task); - Future> getAll(int listId, [Map queryParameters]); + Future> getAll(int listId, [Map> queryParameters]); // TODO: Avoid having to add this to each abstract class int get maxPages; } -- 2.40.1 From bc8188f44cda1c99a845bbc992d8a6f59e7abab7 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 16 Jun 2020 00:34:25 +0200 Subject: [PATCH 22/27] Format --- lib/api/task_implementation.dart | 3 ++- lib/service/mocked_services.dart | 3 ++- lib/service/services.dart | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index 4870831..e74ba77 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -28,7 +28,8 @@ class TaskAPIService extends APIService implements TaskService { } @override - Future> getAll(int listId, [Map> queryParameters]) { + Future> getAll(int listId, + [Map> queryParameters]) { return client .get('/lists/$listId/tasks', queryParameters) .then((list) => convertList(list, (result) => Task.fromJson(result))); diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index 959aed7..d9f3ea3 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -137,7 +137,8 @@ class MockedTaskService implements TaskService { } @override - Future> getAll(int listId, [Map> queryParameters]) { + Future> getAll(int listId, + [Map> queryParameters]) { return Future.value(_tasks.values.toList()); } diff --git a/lib/service/services.dart b/lib/service/services.dart index e7fe1aa..bb266f7 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -26,7 +26,8 @@ abstract class TaskService { Future update(Task task); Future delete(int taskId); Future add(int listId, Task task); - Future> getAll(int listId, [Map> queryParameters]); + Future> getAll(int listId, + [Map> queryParameters]); // TODO: Avoid having to add this to each abstract class int get maxPages; } -- 2.40.1 From 7bd340f45cbf59a8e10bf34ed50bc626c4482ff0 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 16 Jun 2020 16:04:18 +0200 Subject: [PATCH 23/27] Start using providers --- lib/pages/list/list.dart | 52 +++++++++++------------------- lib/pages/namespace/namespace.dart | 12 +++++-- lib/stores/list_store.dart | 33 +++++++++++++++++++ pubspec.lock | 15 +++++++++ pubspec.yaml | 1 + 5 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 lib/stores/list_store.dart diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index ed2b818..fb6348a 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -1,12 +1,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/components/TaskTile.dart'; import 'package:vikunja_app/global.dart'; import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/pages/list/list_edit.dart'; +import 'package:vikunja_app/stores/list_store.dart'; class ListPage extends StatefulWidget { final TaskList taskList; @@ -21,23 +23,18 @@ class _ListPageState extends State { TaskList _list; List _tasks = []; List _loadingTasks = []; - bool _loading = true; int _currentPage = 1; @override void initState() { _list = TaskList(id: widget.taskList.id, title: widget.taskList.title); + Future.microtask(() => _loadList()); super.initState(); } - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _loadList(); - } - @override Widget build(BuildContext context) { + final taskState = Provider.of(context); return Scaffold( appBar: AppBar( title: new Text(_list.title), @@ -52,9 +49,9 @@ class _ListPageState extends State { )))) ], ), - body: !this._loading + body: !taskState.isLoading ? RefreshIndicator( - child: _tasks.length > 0 + child: taskState.tasks.length > 0 ? ListView.builder( padding: EdgeInsets.symmetric(vertical: 8.0), itemBuilder: (context, i) { @@ -65,9 +62,9 @@ class _ListPageState extends State { // This handles the case if there are no more elements in the list left which can be provided by the api if (VikunjaGlobal.of(context).taskService.maxPages == _currentPage && - index == _tasks.length - 1) return null; + index == taskState.tasks.length - 1) return null; - if (index >= _tasks.length && + if (index >= taskState.tasks.length && _currentPage < VikunjaGlobal.of(context) .taskService @@ -75,9 +72,9 @@ class _ListPageState extends State { _currentPage++; _loadTasksForPage(_currentPage); } - return index < _tasks.length + return index < taskState.tasks.length ? TaskTile( - task: _tasks[index], + task: taskState.tasks[index], ) : null; }) @@ -92,29 +89,15 @@ class _ListPageState extends State { } Future _loadList() { - return VikunjaGlobal.of(context) - .listService - .get(widget.taskList.id) - .then((list) { - setState(() { - _loading = true; - _list = list; - }); - _loadTasksForPage(_currentPage); - }); + _loadTasksForPage(1); } - Future _loadTasksForPage(int page) { - return VikunjaGlobal.of(context).taskService.getAll(_list.id, { - "sort_by": ["done", "id"], - "order_by": ["asc", "desc"], - "page": [page.toString()] - }).then((tasks) { - setState(() { - _loading = false; - _tasks.addAll(tasks); - }); - }); + void _loadTasksForPage(int page) { + Provider.of(context, listen: false).loadTasks( + context: context, + listId: _list.id, + page: page, + ); } _addItemDialog(BuildContext context) { @@ -127,6 +110,7 @@ class _ListPageState extends State { } _addItem(String name, BuildContext context) { + // FIXME: Use provider var globalState = VikunjaGlobal.of(context); var newTask = Task( id: null, title: name, owner: globalState.currentUser, done: false); diff --git a/lib/pages/namespace/namespace.dart b/lib/pages/namespace/namespace.dart index dc0b1f8..4b2f18c 100644 --- a/lib/pages/namespace/namespace.dart +++ b/lib/pages/namespace/namespace.dart @@ -3,12 +3,14 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:after_layout/after_layout.dart'; +import 'package:provider/provider.dart'; import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/global.dart'; import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/namespace.dart'; import 'package:vikunja_app/pages/list/list.dart'; +import 'package:vikunja_app/stores/list_store.dart'; class NamespacePage extends StatefulWidget { final Namespace namespace; @@ -100,8 +102,14 @@ class _NamespacePageState extends State } _openList(BuildContext context, TaskList list) { - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => ListPage(taskList: list))); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => ChangeNotifierProvider( + create: (_) => new ListProvider(), + child: ListPage( + taskList: list, + ), + ), + )); } _addListDialog(BuildContext context) { diff --git a/lib/stores/list_store.dart b/lib/stores/list_store.dart new file mode 100644 index 0000000..9918488 --- /dev/null +++ b/lib/stores/list_store.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:vikunja_app/models/task.dart'; +import 'package:vikunja_app/global.dart'; + +class ListProvider with ChangeNotifier { + bool _isLoading = false; + // TODO: Streams + List _tasks = []; + + bool get isLoading => _isLoading; + + set tasks(List tasks) { + _tasks = tasks; + notifyListeners(); + } + + List get tasks => _tasks; + + void loadTasks({BuildContext context, int listId, int page = 1}) { + _isLoading = true; + notifyListeners(); + + VikunjaGlobal.of(context).taskService.getAll(listId, { + "sort_by": ["done", "id"], + "order_by": ["asc", "desc"], + "page": [page.toString()] + }).then((tasks) { + _tasks.addAll(tasks); + _isLoading = false; + notifyListeners(); + }); + } +} diff --git a/pubspec.lock b/pubspec.lock index a781b71..92ad7a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -139,6 +139,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.8" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" path: dependency: transitive description: @@ -160,6 +167,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.4.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5+1" quiver: dependency: transitive description: @@ -244,3 +258,4 @@ packages: version: "2.2.0" sdks: dart: ">=2.4.0 <3.0.0" + flutter: ">=1.12.1" diff --git a/pubspec.yaml b/pubspec.yaml index 067ec71..3d6e45d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: flutter_secure_storage: 3.3.1+1 http: 0.12.0+3 after_layout: ^1.0.7 + provider: ^4.0.5 dev_dependencies: flutter_test: -- 2.40.1 From ac2d9722e9aac90b3567cd3420201acbd73ebebb Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 16 Jun 2020 16:15:20 +0200 Subject: [PATCH 24/27] Make _loadList async to make it satisfy the refresh listener --- lib/pages/list/list.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index fb6348a..4003cf5 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -88,7 +88,7 @@ class _ListPageState extends State { )); } - Future _loadList() { + Future _loadList() async { _loadTasksForPage(1); } -- 2.40.1 From d4dbc5b4ae693f585d905fa85b4dedcedbca8a36 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 24 Jun 2020 11:12:14 +0200 Subject: [PATCH 25/27] Use provider to add a new task --- lib/pages/list/list.dart | 22 +++++----------------- lib/stores/list_store.dart | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 4003cf5..d9447b4 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -6,7 +6,6 @@ import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/components/TaskTile.dart'; import 'package:vikunja_app/global.dart'; import 'package:vikunja_app/models/list.dart'; -import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/pages/list/list_edit.dart'; import 'package:vikunja_app/stores/list_store.dart'; @@ -21,8 +20,6 @@ class ListPage extends StatefulWidget { class _ListPageState extends State { TaskList _list; - List _tasks = []; - List _loadingTasks = []; int _currentPage = 1; @override @@ -104,24 +101,15 @@ class _ListPageState extends State { showDialog( context: context, builder: (_) => AddDialog( - onAdd: (name) => _addItem(name, context), + onAdd: (title) => _addItem(title, context), decoration: new InputDecoration( labelText: 'Task Name', hintText: 'eg. Milk'))); } - _addItem(String name, BuildContext context) { - // FIXME: Use provider - var globalState = VikunjaGlobal.of(context); - var newTask = Task( - id: null, title: name, owner: globalState.currentUser, done: false); - setState(() => _loadingTasks.add(newTask)); - globalState.taskService.add(_list.id, newTask).then((task) { - setState(() { - _tasks.add(task); - }); - }).then((_) { - _loadList(); - setState(() => _loadingTasks.remove(newTask)); + _addItem(String title, BuildContext context) { + Provider.of(context, listen: false) + .addTask(context: context, title: title, listId: _list.id) + .then((_) { Scaffold.of(context).showSnackBar(SnackBar( content: Text('The task was added successfully!'), )); diff --git a/lib/stores/list_store.dart b/lib/stores/list_store.dart index 9918488..cb2643b 100644 --- a/lib/stores/list_store.dart +++ b/lib/stores/list_store.dart @@ -4,6 +4,7 @@ import 'package:vikunja_app/global.dart'; class ListProvider with ChangeNotifier { bool _isLoading = false; + // TODO: Streams List _tasks = []; @@ -30,4 +31,18 @@ class ListProvider with ChangeNotifier { notifyListeners(); }); } + + Future addTask({BuildContext context, String title, int listId}) { + var globalState = VikunjaGlobal.of(context); + var newTask = Task( + id: null, title: title, owner: globalState.currentUser, done: false); + _isLoading = true; + notifyListeners(); + + return globalState.taskService.add(listId, newTask).then((task) { + _tasks.insert(0, task); + _isLoading = false; + notifyListeners(); + }); + } } -- 2.40.1 From 11e5711aab1ab59bc9cb49d6447a3f6c89012419 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 25 Jun 2020 10:07:37 +0200 Subject: [PATCH 26/27] Move all maxPages related stuff to provider --- lib/api/client.dart | 29 +++++++++------------------ lib/api/list_implementation.dart | 10 ++++----- lib/api/namespace_implementation.dart | 8 ++++---- lib/api/response.dart | 11 ++++++++++ lib/api/service.dart | 2 -- lib/api/task_implementation.dart | 13 ++++++------ lib/api/user_implementation.dart | 6 +++--- lib/pages/list/list.dart | 9 ++------- lib/service/mocked_services.dart | 5 +++-- lib/service/services.dart | 5 ++--- lib/stores/list_store.dart | 10 +++++++-- 11 files changed, 55 insertions(+), 53 deletions(-) create mode 100644 lib/api/response.dart diff --git a/lib/api/client.dart b/lib/api/client.dart index b38e7d8..29d616c 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -1,15 +1,13 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:vikunja_app/api/response.dart'; class Client { final JsonDecoder _decoder = new JsonDecoder(); final JsonEncoder _encoder = new JsonEncoder(); final String _token; final String _base; - int _maxPages; - - int get maxPages => _maxPages; String get base => _base; @@ -28,7 +26,8 @@ class Client { 'Content-Type': 'application/json' }; - Future get(String url, [Map> queryParameters]) { + Future get(String url, + [Map> queryParameters]) { // TODO: This could be moved to a seperate function var uri = Uri.parse('${this.base}$url'); // Because these are all final values, we can't just add the queryParameters and must instead build a new Uri Object every time this method is called. @@ -45,27 +44,27 @@ class Client { return http.get(newUri, headers: _headers).then(_handleResponse); } - Future delete(String url) { + Future delete(String url) { return http .delete('${this.base}$url', headers: _headers) .then(_handleResponse); } - Future post(String url, {dynamic body}) { + Future post(String url, {dynamic body}) { return http .post('${this.base}$url', headers: _headers, body: _encoder.convert(body)) .then(_handleResponse); } - Future put(String url, {dynamic body}) { + Future put(String url, {dynamic body}) { return http .put('${this.base}$url', headers: _headers, body: _encoder.convert(body)) .then(_handleResponse); } - dynamic _handleResponse(http.Response response) { + Response _handleResponse(http.Response response) { if (response.statusCode < 200 || response.statusCode >= 400 || json == null) { @@ -79,22 +78,14 @@ class Client { throw new ApiException( response.statusCode, response.request.url.toString()); } - // FIXME: This is a workaround for when the client makes another - // unrelated api request in between requesting multiple pages of the same thing. - // To properly fix this, we need a way to pass the max number of pages somewhere else - // and not save them in the global client which everyone uses. - // This workaround only works when the other api requests in between two pages - // are not using pagination. - if (response.headers["x-pagination-total-pages"] != null) { - _maxPages = int.parse(response.headers["x-pagination-total-pages"]); - } - - return _decoder.convert(response.body); + return new Response( + _decoder.convert(response.body), response.statusCode, response.headers); } } class InvalidRequestApiException extends ApiException { final String message; + InvalidRequestApiException(int errorCode, String path, this.message) : super(errorCode, path); diff --git a/lib/api/list_implementation.dart b/lib/api/list_implementation.dart index 38228d7..aad7f9f 100644 --- a/lib/api/list_implementation.dart +++ b/lib/api/list_implementation.dart @@ -12,7 +12,7 @@ class ListAPIService extends APIService implements ListService { Future create(namespaceId, TaskList tl) { return client .put('/namespaces/$namespaceId/lists', body: tl.toJSON()) - .then((map) => TaskList.fromJson(map)); + .then((response) => TaskList.fromJson(response.body)); } @override @@ -22,25 +22,25 @@ class ListAPIService extends APIService implements ListService { @override Future get(int listId) { - return client.get('/lists/$listId').then((map) => TaskList.fromJson(map)); + return client.get('/lists/$listId').then((response) => TaskList.fromJson(response.body)); } @override Future> getAll() { return client.get('/lists').then( - (list) => convertList(list, (result) => TaskList.fromJson(result))); + (response) => convertList(response.body, (result) => TaskList.fromJson(result))); } @override Future> getByNamespace(int namespaceId) { return client.get('/namespaces/$namespaceId/lists').then( - (list) => convertList(list, (result) => TaskList.fromJson(result))); + (response) => convertList(response.body, (result) => TaskList.fromJson(result))); } @override Future update(TaskList tl) { return client .post('/lists/${tl.id}', body: tl.toJSON()) - .then((map) => TaskList.fromJson(map)); + .then((response) => TaskList.fromJson(response.body)); } } diff --git a/lib/api/namespace_implementation.dart b/lib/api/namespace_implementation.dart index b5d5508..4286a92 100644 --- a/lib/api/namespace_implementation.dart +++ b/lib/api/namespace_implementation.dart @@ -12,7 +12,7 @@ class NamespaceAPIService extends APIService implements NamespaceService { Future create(Namespace ns) { return client .put('/namespaces', body: ns.toJSON()) - .then((map) => Namespace.fromJson(map)); + .then((response) => Namespace.fromJson(response.body)); } @override @@ -24,19 +24,19 @@ class NamespaceAPIService extends APIService implements NamespaceService { Future get(int namespaceId) { return client .get('/namespaces/$namespaceId') - .then((map) => Namespace.fromJson(map)); + .then((response) => Namespace.fromJson(response.body)); } @override Future> getAll() { return client.get('/namespaces').then( - (list) => convertList(list, (result) => Namespace.fromJson(result))); + (response) => convertList(response.body, (result) => Namespace.fromJson(result))); } @override Future update(Namespace ns) { return client .post('/namespaces/${ns.id}', body: ns.toJSON()) - .then((map) => Namespace.fromJson(map)); + .then((response) => Namespace.fromJson(response.body)); } } diff --git a/lib/api/response.dart b/lib/api/response.dart new file mode 100644 index 0000000..000b514 --- /dev/null +++ b/lib/api/response.dart @@ -0,0 +1,11 @@ + + +// This is a wrapper class to be able to return the headers up to the provider +// to properly handle things like pagination with it. +class Response { + Response(this.body, this.statusCode, this.headers); + + final dynamic body; + final int statusCode; + final Map headers; +} \ No newline at end of file diff --git a/lib/api/service.dart b/lib/api/service.dart index 3ca5a26..c7d837e 100644 --- a/lib/api/service.dart +++ b/lib/api/service.dart @@ -4,8 +4,6 @@ import 'package:meta/meta.dart'; class APIService { final Client _client; - int get maxPages => _client.maxPages; - @protected Client get client => _client; diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index e74ba77..fa0f3c6 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:vikunja_app/api/client.dart'; +import 'package:vikunja_app/api/response.dart'; import 'package:vikunja_app/api/service.dart'; import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/service/services.dart'; @@ -12,7 +13,7 @@ class TaskAPIService extends APIService implements TaskService { Future add(int listId, Task task) { return client .put('/lists/$listId', body: task.toJSON()) - .then((map) => Task.fromJson(map)); + .then((response) => Task.fromJson(response.body)); } @override @@ -24,14 +25,14 @@ class TaskAPIService extends APIService implements TaskService { Future update(Task task) { return client .post('/tasks/${task.id}', body: task.toJSON()) - .then((map) => Task.fromJson(map)); + .then((response) => Task.fromJson(response.body)); } @override - Future> getAll(int listId, + Future getAll(int listId, [Map> queryParameters]) { - return client - .get('/lists/$listId/tasks', queryParameters) - .then((list) => convertList(list, (result) => Task.fromJson(result))); + return client.get('/lists/$listId/tasks', queryParameters).then( + (response) => + new Response(convertList(response.body, (result) => Task.fromJson(result)), response.statusCode, response.headers)); } } diff --git a/lib/api/user_implementation.dart b/lib/api/user_implementation.dart index a07bb2e..5064fb8 100644 --- a/lib/api/user_implementation.dart +++ b/lib/api/user_implementation.dart @@ -13,7 +13,7 @@ class UserAPIService extends APIService implements UserService { var token = await client.post('/login', body: { 'username': username, 'password': password - }).then((map) => map['token']); + }).then((response) => response.body['token']); return UserAPIService(Client(token, client.base)) .getCurrentUser() .then((user) => UserTokenPair(user, token)); @@ -25,12 +25,12 @@ class UserAPIService extends APIService implements UserService { 'username': username, 'email': email, 'password': password - }).then((resp) => resp['username']); + }).then((response) => response.body['username']); return login(newUser, password); } @override Future getCurrentUser() { - return client.get('/user').then((map) => User.fromJson(map)); + return client.get('/user').then((response) => User.fromJson(response.body)); } } diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index d9447b4..8759692 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/components/TaskTile.dart'; -import 'package:vikunja_app/global.dart'; import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/pages/list/list_edit.dart'; import 'package:vikunja_app/stores/list_store.dart'; @@ -57,15 +56,11 @@ class _ListPageState extends State { final index = i ~/ 2; // This handles the case if there are no more elements in the list left which can be provided by the api - if (VikunjaGlobal.of(context).taskService.maxPages == - _currentPage && + if (taskState.maxPages == _currentPage && index == taskState.tasks.length - 1) return null; if (index >= taskState.tasks.length && - _currentPage < - VikunjaGlobal.of(context) - .taskService - .maxPages) { + _currentPage < taskState.maxPages) { _currentPage++; _loadTasksForPage(_currentPage); } diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index d9f3ea3..e40afe2 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:vikunja_app/api/response.dart'; import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/namespace.dart'; import 'package:vikunja_app/models/task.dart'; @@ -137,9 +138,9 @@ class MockedTaskService implements TaskService { } @override - Future> getAll(int listId, + Future getAll(int listId, [Map> queryParameters]) { - return Future.value(_tasks.values.toList()); + return Future.value(new Response(_tasks.values.toList(), 200, {})); } @override diff --git a/lib/service/services.dart b/lib/service/services.dart index bb266f7..1825629 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:vikunja_app/api/response.dart'; import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/namespace.dart'; import 'package:vikunja_app/models/task.dart'; @@ -26,10 +27,8 @@ abstract class TaskService { Future update(Task task); Future delete(int taskId); Future add(int listId, Task task); - Future> getAll(int listId, + Future getAll(int listId, [Map> queryParameters]); - // TODO: Avoid having to add this to each abstract class - int get maxPages; } abstract class UserService { diff --git a/lib/stores/list_store.dart b/lib/stores/list_store.dart index cb2643b..1c7891d 100644 --- a/lib/stores/list_store.dart +++ b/lib/stores/list_store.dart @@ -4,12 +4,15 @@ import 'package:vikunja_app/global.dart'; class ListProvider with ChangeNotifier { bool _isLoading = false; + int _maxPages = 0; // TODO: Streams List _tasks = []; bool get isLoading => _isLoading; + int get maxPages => _maxPages; + set tasks(List tasks) { _tasks = tasks; notifyListeners(); @@ -25,8 +28,11 @@ class ListProvider with ChangeNotifier { "sort_by": ["done", "id"], "order_by": ["asc", "desc"], "page": [page.toString()] - }).then((tasks) { - _tasks.addAll(tasks); + }).then((response) { + if (response.headers["x-pagination-total-pages"] != null) { + _maxPages = int.parse(response.headers["x-pagination-total-pages"]); + } + _tasks.addAll(response.body); _isLoading = false; notifyListeners(); }); -- 2.40.1 From 873bcef16155c5a8cc58f37eb645f71b14f34b18 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 25 Jun 2020 10:10:00 +0200 Subject: [PATCH 27/27] format --- lib/api/list_implementation.dart | 12 +++++++----- lib/api/namespace_implementation.dart | 4 ++-- lib/api/response.dart | 4 +--- lib/api/task_implementation.dart | 6 ++++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/api/list_implementation.dart b/lib/api/list_implementation.dart index aad7f9f..78bc9bd 100644 --- a/lib/api/list_implementation.dart +++ b/lib/api/list_implementation.dart @@ -22,19 +22,21 @@ class ListAPIService extends APIService implements ListService { @override Future get(int listId) { - return client.get('/lists/$listId').then((response) => TaskList.fromJson(response.body)); + return client + .get('/lists/$listId') + .then((response) => TaskList.fromJson(response.body)); } @override Future> getAll() { - return client.get('/lists').then( - (response) => convertList(response.body, (result) => TaskList.fromJson(result))); + return client.get('/lists').then((response) => + convertList(response.body, (result) => TaskList.fromJson(result))); } @override Future> getByNamespace(int namespaceId) { - return client.get('/namespaces/$namespaceId/lists').then( - (response) => convertList(response.body, (result) => TaskList.fromJson(result))); + return client.get('/namespaces/$namespaceId/lists').then((response) => + convertList(response.body, (result) => TaskList.fromJson(result))); } @override diff --git a/lib/api/namespace_implementation.dart b/lib/api/namespace_implementation.dart index 4286a92..dda454d 100644 --- a/lib/api/namespace_implementation.dart +++ b/lib/api/namespace_implementation.dart @@ -29,8 +29,8 @@ class NamespaceAPIService extends APIService implements NamespaceService { @override Future> getAll() { - return client.get('/namespaces').then( - (response) => convertList(response.body, (result) => Namespace.fromJson(result))); + return client.get('/namespaces').then((response) => + convertList(response.body, (result) => Namespace.fromJson(result))); } @override diff --git a/lib/api/response.dart b/lib/api/response.dart index 000b514..cfbf4b2 100644 --- a/lib/api/response.dart +++ b/lib/api/response.dart @@ -1,5 +1,3 @@ - - // This is a wrapper class to be able to return the headers up to the provider // to properly handle things like pagination with it. class Response { @@ -8,4 +6,4 @@ class Response { final dynamic body; final int statusCode; final Map headers; -} \ No newline at end of file +} diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart index fa0f3c6..61f5d92 100644 --- a/lib/api/task_implementation.dart +++ b/lib/api/task_implementation.dart @@ -32,7 +32,9 @@ class TaskAPIService extends APIService implements TaskService { Future getAll(int listId, [Map> queryParameters]) { return client.get('/lists/$listId/tasks', queryParameters).then( - (response) => - new Response(convertList(response.body, (result) => Task.fromJson(result)), response.statusCode, response.headers)); + (response) => new Response( + convertList(response.body, (result) => Task.fromJson(result)), + response.statusCode, + response.headers)); } } -- 2.40.1