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(); });