Compare commits

...

34 Commits

Author SHA1 Message Date
kolaente 873bcef161
format 2020-06-25 10:10:00 +02:00
kolaente 11e5711aab
Move all maxPages related stuff to provider 2020-06-25 10:07:37 +02:00
kolaente d4dbc5b4ae
Use provider to add a new task 2020-06-24 11:12:14 +02:00
kolaente a89068bbdf
Merge branch 'master' into feature/task-list
# Conflicts:
#	pubspec.lock
#	pubspec.yaml
2020-06-24 10:57:14 +02:00
kolaente ac2d9722e9
Make _loadList async to make it satisfy the refresh listener 2020-06-16 16:15:20 +02:00
kolaente 7bd340f45c
Start using providers 2020-06-16 16:04:18 +02:00
kolaente bc8188f44c
Format 2020-06-16 00:34:25 +02:00
kolaente 972b194f56
Enable multiple order parameters 2020-06-16 00:33:09 +02:00
kolaente 9f37496497
Merge branch 'master' into feature/task-list 2020-06-16 00:18:37 +02:00
kolaente db9be17857
Merge branch 'master' into feature/task-list
# Conflicts:
#	lib/models/task.dart
2020-06-15 23:48:50 +02:00
kolaente 38c7348aa8
Format 2020-06-15 23:48:01 +02:00
kolaente 876ac88a1d
Merge branch 'master' into feature/task-list
# Conflicts:
#	lib/models/task.dart
2020-06-15 23:47:26 +02:00
kolaente 9348e6acc5
Cleanup 2020-04-27 19:39:21 +02:00
kolaente bb6139cef7
Fix loading state not being set 2020-04-27 19:38:04 +02:00
kolaente 9fd47cde57
Add workaround for pagination 2020-04-27 18:39:28 +02:00
kolaente a34daacf23
Fix next page condition not working 2020-04-27 18:27:26 +02:00
kolaente 25d64f14a5
Merge with master 2020-04-27 17:24:00 +02:00
kolaente 413c2703e6
Fix crash when no due date was present 2020-04-27 17:18:50 +02:00
kolaente e110a4b9eb
Format 2020-04-27 17:09:10 +02:00
kolaente c146add9bf
Merge with master 2020-04-27 17:08:20 +02:00
kolaente d08c7c3e70
format 2020-01-16 22:10:51 +01:00
kolaente 5f7d59c7ea
Don't try to show a task tile if there is none for the current index 2020-01-16 22:08:26 +01:00
kolaente 78ddbecd19
Merge branch 'master' into feature/task-list 2020-01-15 23:38:17 +01:00
kolaente 6fc336223b
Format 2020-01-15 23:33:33 +01:00
kolaente b5363cb6ac
Add fixme 2020-01-15 23:32:43 +01:00
kolaente 96dbddb10c
Fix scrolling to last element 2020-01-15 23:29:31 +01:00
kolaente 53dfe7327b
Add basic infinite scrolling (still has bugs) 2020-01-15 22:59:07 +01:00
kolaente ea8ba3cfd9
Format 2020-01-13 23:16:26 +01:00
kolaente ac67ccbd4c
Note 2020-01-13 23:15:50 +01:00
kolaente b36f0215d8
Add sorting for tasks 2020-01-13 23:14:51 +01:00
kolaente 2345a131b7
Add optional query params to api get function 2020-01-13 23:09:32 +01:00
kolaente 187337c580
Format 2020-01-13 22:47:40 +01:00
kolaente cf9c9b4cb4
Fix crash after save 2020-01-13 22:47:10 +01:00
kolaente 9833ef4885
Re-implemented getting tasks with the new seperate endpoint 2020-01-13 22:31:15 +01:00
15 changed files with 210 additions and 106 deletions

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:vikunja_app/api/response.dart';
class Client { class Client {
final JsonDecoder _decoder = new JsonDecoder(); final JsonDecoder _decoder = new JsonDecoder();
@ -25,33 +26,45 @@ class Client {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}; };
Future<dynamic> get(String url) { Future<Response> get(String url,
return http [Map<String, List<String>> queryParameters]) {
.get('${this.base}$url', headers: _headers) // TODO: This could be moved to a seperate function
.then(_handleResponse); 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,
// Because dart takes a Map<String, String> 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);
} }
Future<dynamic> delete(String url) { Future<Response> delete(String url) {
return http return http
.delete('${this.base}$url', headers: _headers) .delete('${this.base}$url', headers: _headers)
.then(_handleResponse); .then(_handleResponse);
} }
Future<dynamic> post(String url, {dynamic body}) { Future<Response> post(String url, {dynamic body}) {
return http return http
.post('${this.base}$url', .post('${this.base}$url',
headers: _headers, body: _encoder.convert(body)) headers: _headers, body: _encoder.convert(body))
.then(_handleResponse); .then(_handleResponse);
} }
Future<dynamic> put(String url, {dynamic body}) { Future<Response> put(String url, {dynamic body}) {
return http return http
.put('${this.base}$url', .put('${this.base}$url',
headers: _headers, body: _encoder.convert(body)) headers: _headers, body: _encoder.convert(body))
.then(_handleResponse); .then(_handleResponse);
} }
dynamic _handleResponse(http.Response response) { Response _handleResponse(http.Response response) {
if (response.statusCode < 200 || if (response.statusCode < 200 ||
response.statusCode >= 400 || response.statusCode >= 400 ||
json == null) { json == null) {
@ -65,12 +78,14 @@ class Client {
throw new ApiException( throw new ApiException(
response.statusCode, response.request.url.toString()); response.statusCode, response.request.url.toString());
} }
return _decoder.convert(response.body); return new Response(
_decoder.convert(response.body), response.statusCode, response.headers);
} }
} }
class InvalidRequestApiException extends ApiException { class InvalidRequestApiException extends ApiException {
final String message; final String message;
InvalidRequestApiException(int errorCode, String path, this.message) InvalidRequestApiException(int errorCode, String path, this.message)
: super(errorCode, path); : super(errorCode, path);
@ -83,6 +98,7 @@ class InvalidRequestApiException extends ApiException {
class ApiException implements Exception { class ApiException implements Exception {
final int errorCode; final int errorCode;
final String path; final String path;
ApiException(this.errorCode, this.path); ApiException(this.errorCode, this.path);
@override @override

View File

@ -12,7 +12,7 @@ class ListAPIService extends APIService implements ListService {
Future<TaskList> create(namespaceId, TaskList tl) { Future<TaskList> create(namespaceId, TaskList tl) {
return client return client
.put('/namespaces/$namespaceId/lists', body: tl.toJSON()) .put('/namespaces/$namespaceId/lists', body: tl.toJSON())
.then((map) => TaskList.fromJson(map)); .then((response) => TaskList.fromJson(response.body));
} }
@override @override
@ -22,25 +22,27 @@ class ListAPIService extends APIService implements ListService {
@override @override
Future<TaskList> get(int listId) { Future<TaskList> 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 @override
Future<List<TaskList>> getAll() { Future<List<TaskList>> getAll() {
return client.get('/lists').then( return client.get('/lists').then((response) =>
(list) => convertList(list, (result) => TaskList.fromJson(result))); convertList(response.body, (result) => TaskList.fromJson(result)));
} }
@override @override
Future<List<TaskList>> getByNamespace(int namespaceId) { Future<List<TaskList>> getByNamespace(int namespaceId) {
return client.get('/namespaces/$namespaceId/lists').then( return client.get('/namespaces/$namespaceId/lists').then((response) =>
(list) => convertList(list, (result) => TaskList.fromJson(result))); convertList(response.body, (result) => TaskList.fromJson(result)));
} }
@override @override
Future<TaskList> update(TaskList tl) { Future<TaskList> update(TaskList tl) {
return client return client
.post('/lists/${tl.id}', body: tl.toJSON()) .post('/lists/${tl.id}', body: tl.toJSON())
.then((map) => TaskList.fromJson(map)); .then((response) => TaskList.fromJson(response.body));
} }
} }

View File

@ -12,7 +12,7 @@ class NamespaceAPIService extends APIService implements NamespaceService {
Future<Namespace> create(Namespace ns) { Future<Namespace> create(Namespace ns) {
return client return client
.put('/namespaces', body: ns.toJSON()) .put('/namespaces', body: ns.toJSON())
.then((map) => Namespace.fromJson(map)); .then((response) => Namespace.fromJson(response.body));
} }
@override @override
@ -24,19 +24,19 @@ class NamespaceAPIService extends APIService implements NamespaceService {
Future<Namespace> get(int namespaceId) { Future<Namespace> get(int namespaceId) {
return client return client
.get('/namespaces/$namespaceId') .get('/namespaces/$namespaceId')
.then((map) => Namespace.fromJson(map)); .then((response) => Namespace.fromJson(response.body));
} }
@override @override
Future<List<Namespace>> getAll() { Future<List<Namespace>> getAll() {
return client.get('/namespaces').then( return client.get('/namespaces').then((response) =>
(list) => convertList(list, (result) => Namespace.fromJson(result))); convertList(response.body, (result) => Namespace.fromJson(result)));
} }
@override @override
Future<Namespace> update(Namespace ns) { Future<Namespace> update(Namespace ns) {
return client return client
.post('/namespaces/${ns.id}', body: ns.toJSON()) .post('/namespaces/${ns.id}', body: ns.toJSON())
.then((map) => Namespace.fromJson(map)); .then((response) => Namespace.fromJson(response.body));
} }
} }

9
lib/api/response.dart Normal file
View File

@ -0,0 +1,9 @@
// 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<String, String> headers;
}

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:vikunja_app/api/client.dart'; 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/api/service.dart';
import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/service/services.dart'; import 'package:vikunja_app/service/services.dart';
@ -12,7 +13,7 @@ class TaskAPIService extends APIService implements TaskService {
Future<Task> add(int listId, Task task) { Future<Task> add(int listId, Task task) {
return client return client
.put('/lists/$listId', body: task.toJSON()) .put('/lists/$listId', body: task.toJSON())
.then((map) => Task.fromJson(map)); .then((response) => Task.fromJson(response.body));
} }
@override @override
@ -24,6 +25,16 @@ class TaskAPIService extends APIService implements TaskService {
Future<Task> update(Task task) { Future<Task> update(Task task) {
return client return client
.post('/tasks/${task.id}', body: task.toJSON()) .post('/tasks/${task.id}', body: task.toJSON())
.then((map) => Task.fromJson(map)); .then((response) => Task.fromJson(response.body));
}
@override
Future<Response> getAll(int listId,
[Map<String, List<String>> queryParameters]) {
return client.get('/lists/$listId/tasks', queryParameters).then(
(response) => new Response(
convertList(response.body, (result) => Task.fromJson(result)),
response.statusCode,
response.headers));
} }
} }

View File

@ -13,7 +13,7 @@ class UserAPIService extends APIService implements UserService {
var token = await client.post('/login', body: { var token = await client.post('/login', body: {
'username': username, 'username': username,
'password': password 'password': password
}).then((map) => map['token']); }).then((response) => response.body['token']);
return UserAPIService(Client(token, client.base)) return UserAPIService(Client(token, client.base))
.getCurrentUser() .getCurrentUser()
.then((user) => UserTokenPair(user, token)); .then((user) => UserTokenPair(user, token));
@ -25,12 +25,12 @@ class UserAPIService extends APIService implements UserService {
'username': username, 'username': username,
'email': email, 'email': email,
'password': password 'password': password
}).then((resp) => resp['username']); }).then((response) => response.body['username']);
return login(newUser, password); return login(newUser, password);
} }
@override @override
Future<User> getCurrentUser() { Future<User> getCurrentUser() {
return client.get('/user').then((map) => User.fromJson(map)); return client.get('/user').then((response) => User.fromJson(response.body));
} }
} }

View File

@ -1,5 +1,4 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/models/user.dart'; import 'package:vikunja_app/models/user.dart';
class TaskList { class TaskList {
@ -7,7 +6,6 @@ class TaskList {
final String title, description; final String title, description;
final User owner; final User owner;
final DateTime created, updated; final DateTime created, updated;
final List<Task> tasks;
TaskList( TaskList(
{@required this.id, {@required this.id,
@ -15,8 +13,7 @@ class TaskList {
this.description, this.description,
this.owner, this.owner,
this.created, this.created,
this.updated, this.updated});
this.tasks});
TaskList.fromJson(Map<String, dynamic> json) TaskList.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
@ -24,10 +21,7 @@ class TaskList {
description = json['description'], description = json['description'],
title = json['title'], title = json['title'],
updated = DateTime.parse(json['updated']), updated = DateTime.parse(json['updated']),
created = DateTime.parse(json['created']), created = DateTime.parse(json['created']);
tasks = (json['tasks'] == null ? [] : json['tasks'] as List<dynamic>)
?.map((taskJson) => Task.fromJson(taskJson))
?.toList();
toJSON() { toJSON() {
return { return {

View File

@ -27,12 +27,16 @@ class Task {
reminders = (json['reminder_dates'] as List<dynamic>) reminders = (json['reminder_dates'] as List<dynamic>)
?.map((r) => DateTime.parse(r)) ?.map((r) => DateTime.parse(r))
?.toList(), ?.toList(),
due = due = json['due_date'].toString() == 'null'
json['due_date'] != null ? DateTime.parse(json['due_date']) : null, ? null
: DateTime.parse(json['due_date']),
description = json['description'], description = json['description'],
title = json['title'], title = json['title'],
done = json['done'], done = json['done'],
owner = User.fromJson(json['created_by']); owner = json['created_by'].toString() == "null"
? null
: User.fromJson(json[
'created_by']); // There has to be a better way of doing this...
toJSON() => { toJSON() => {
'id': id, 'id': id,

View File

@ -1,12 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/TaskTile.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/list.dart';
import 'package:vikunja_app/models/task.dart';
import 'package:vikunja_app/pages/list/list_edit.dart'; import 'package:vikunja_app/pages/list/list_edit.dart';
import 'package:vikunja_app/stores/list_store.dart';
class ListPage extends StatefulWidget { class ListPage extends StatefulWidget {
final TaskList taskList; final TaskList taskList;
@ -19,24 +19,18 @@ class ListPage extends StatefulWidget {
class _ListPageState extends State<ListPage> { class _ListPageState extends State<ListPage> {
TaskList _list; TaskList _list;
List<Task> _loadingTasks = []; int _currentPage = 1;
bool _loading = true;
@override @override
void initState() { void initState() {
_list = TaskList( _list = TaskList(id: widget.taskList.id, title: widget.taskList.title);
id: widget.taskList.id, title: widget.taskList.title, tasks: []); Future.microtask(() => _loadList());
super.initState(); super.initState();
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
_loadList();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final taskState = Provider.of<ListProvider>(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: new Text(_list.title), title: new Text(_list.title),
@ -51,15 +45,31 @@ class _ListPageState extends State<ListPage> {
)))) ))))
], ],
), ),
body: !this._loading body: !taskState.isLoading
? RefreshIndicator( ? RefreshIndicator(
child: _list.tasks.length > 0 child: taskState.tasks.length > 0
? ListView( ? ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0), padding: EdgeInsets.symmetric(vertical: 8.0),
children: ListTile.divideTiles( itemBuilder: (context, i) {
context: context, tiles: _listTasks()) if (i.isOdd) return Divider();
.toList(),
) 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 (taskState.maxPages == _currentPage &&
index == taskState.tasks.length - 1) return null;
if (index >= taskState.tasks.length &&
_currentPage < taskState.maxPages) {
_currentPage++;
_loadTasksForPage(_currentPage);
}
return index < taskState.tasks.length
? TaskTile(
task: taskState.tasks[index],
)
: null;
})
: Center(child: Text('This list is empty.')), : Center(child: Text('This list is empty.')),
onRefresh: _loadList, onRefresh: _loadList,
) )
@ -70,56 +80,31 @@ class _ListPageState extends State<ListPage> {
)); ));
} }
List<Widget> _listTasks() { Future<void> _loadList() async {
var tasks = (_list?.tasks?.map(_buildTile) ?? []).toList(); _loadTasksForPage(1);
tasks.addAll(_loadingTasks.map(_buildLoadingTile));
return tasks;
} }
TaskTile _buildTile(Task task) { void _loadTasksForPage(int page) {
return TaskTile(task: task, loading: false); Provider.of<ListProvider>(context, listen: false).loadTasks(
} context: context,
listId: _list.id,
TaskTile _buildLoadingTile(Task task) { page: page,
return TaskTile(
task: task,
loading: true,
); );
} }
Future<void> _loadList() {
return VikunjaGlobal.of(context)
.listService
.get(widget.taskList.id)
.then((list) {
setState(() {
_loading = false;
_list = list;
});
});
}
_addItemDialog(BuildContext context) { _addItemDialog(BuildContext context) {
showDialog( showDialog(
context: context, context: context,
builder: (_) => AddDialog( builder: (_) => AddDialog(
onAdd: (name) => _addItem(name, context), onAdd: (title) => _addItem(title, context),
decoration: new InputDecoration( decoration: new InputDecoration(
labelText: 'Task Name', hintText: 'eg. Milk'))); labelText: 'Task Name', hintText: 'eg. Milk')));
} }
_addItem(String name, BuildContext context) { _addItem(String title, BuildContext context) {
var globalState = VikunjaGlobal.of(context); Provider.of<ListProvider>(context, listen: false)
var newTask = Task( .addTask(context: context, title: title, listId: _list.id)
id: null, title: name, owner: globalState.currentUser, done: false); .then((_) {
setState(() => _loadingTasks.add(newTask));
globalState.taskService.add(_list.id, newTask).then((task) {
setState(() {
_list.tasks.add(task);
});
}).then((_) {
_loadList();
setState(() => _loadingTasks.remove(newTask));
Scaffold.of(context).showSnackBar(SnackBar( Scaffold.of(context).showSnackBar(SnackBar(
content: Text('The task was added successfully!'), content: Text('The task was added successfully!'),
)); ));

View File

@ -3,12 +3,14 @@ import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:after_layout/after_layout.dart'; import 'package:after_layout/after_layout.dart';
import 'package:provider/provider.dart';
import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/global.dart'; import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/namespace.dart'; import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/pages/list/list.dart'; import 'package:vikunja_app/pages/list/list.dart';
import 'package:vikunja_app/stores/list_store.dart';
class NamespacePage extends StatefulWidget { class NamespacePage extends StatefulWidget {
final Namespace namespace; final Namespace namespace;
@ -89,6 +91,7 @@ class _NamespacePageState extends State<NamespacePage>
} }
Future<void> _loadLists() { Future<void> _loadLists() {
// FIXME: This is called even when the tasks on a list are loaded - which is not needed at all
return VikunjaGlobal.of(context) return VikunjaGlobal.of(context)
.listService .listService
.getByNamespace(widget.namespace.id) .getByNamespace(widget.namespace.id)
@ -99,8 +102,14 @@ class _NamespacePageState extends State<NamespacePage>
} }
_openList(BuildContext context, TaskList list) { _openList(BuildContext context, TaskList list) {
Navigator.of(context).push( Navigator.of(context).push(MaterialPageRoute(
MaterialPageRoute(builder: (context) => ListPage(taskList: list))); builder: (context) => ChangeNotifierProvider<ListProvider>(
create: (_) => new ListProvider(),
child: ListPage(
taskList: list,
),
),
));
} }
_addListDialog(BuildContext context) { _addListDialog(BuildContext context) {
@ -116,7 +125,7 @@ class _NamespacePageState extends State<NamespacePage>
_addList(String name, BuildContext context) { _addList(String name, BuildContext context) {
VikunjaGlobal.of(context) VikunjaGlobal.of(context)
.listService .listService
.create(widget.namespace.id, TaskList(id: null, title: name, tasks: [])) .create(widget.namespace.id, TaskList(id: null, title: name))
.then((_) { .then((_) {
setState(() {}); setState(() {});
_loadLists(); _loadLists();

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:vikunja_app/api/response.dart';
import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/namespace.dart'; import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/models/task.dart';
@ -28,7 +29,6 @@ var _lists = {
1: TaskList( 1: TaskList(
id: 1, id: 1,
title: 'List 1', title: 'List 1',
tasks: _tasks.values.toList(),
owner: _users[1], owner: _users[1],
description: 'A nice list', description: 'A nice list',
created: DateTime.now(), created: DateTime.now(),
@ -120,20 +120,13 @@ class MockedListService implements ListService {
class MockedTaskService implements TaskService { class MockedTaskService implements TaskService {
@override @override
Future delete(int taskId) { Future delete(int taskId) {
_lists.forEach(
(_, list) => list.tasks.removeWhere((task) => task.id == taskId));
_tasks.remove(taskId); _tasks.remove(taskId);
return Future.value(); return Future.value();
} }
@override @override
Future<Task> update(Task task) { Future<Task> update(Task task) {
_lists.forEach((_, list) { _tasks[task.id] = task;
if (list.tasks.where((t) => t.id == task.id).length > 0) {
list.tasks.removeWhere((t) => t.id == task.id);
list.tasks.add(task);
}
});
return Future.value(_tasks[task.id] = task); return Future.value(_tasks[task.id] = task);
} }
@ -141,9 +134,17 @@ class MockedTaskService implements TaskService {
Future<Task> add(int listId, Task task) { Future<Task> add(int listId, Task task) {
var id = _tasks.keys.last + 1; var id = _tasks.keys.last + 1;
_tasks[id] = task; _tasks[id] = task;
_lists[listId].tasks.add(task);
return Future.value(task); return Future.value(task);
} }
@override
Future<Response> getAll(int listId,
[Map<String, List<String>> queryParameters]) {
return Future.value(new Response(_tasks.values.toList(), 200, {}));
}
@override
int get maxPages => 1;
} }
class MockedUserService implements UserService { class MockedUserService implements UserService {

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:vikunja_app/api/response.dart';
import 'package:vikunja_app/models/list.dart'; import 'package:vikunja_app/models/list.dart';
import 'package:vikunja_app/models/namespace.dart'; import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart'; import 'package:vikunja_app/models/task.dart';
@ -26,6 +27,8 @@ abstract class TaskService {
Future<Task> update(Task task); Future<Task> update(Task task);
Future delete(int taskId); Future delete(int taskId);
Future<Task> add(int listId, Task task); Future<Task> add(int listId, Task task);
Future<Response> getAll(int listId,
[Map<String, List<String>> queryParameters]);
} }
abstract class UserService { abstract class UserService {

View File

@ -0,0 +1,54 @@
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;
int _maxPages = 0;
// TODO: Streams
List<Task> _tasks = [];
bool get isLoading => _isLoading;
int get maxPages => _maxPages;
set tasks(List<Task> tasks) {
_tasks = tasks;
notifyListeners();
}
List<Task> 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((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();
});
}
Future<void> 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();
});
}
}

View File

@ -139,6 +139,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.8" version: "1.1.8"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -160,6 +167,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.0" version: "2.4.0"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.5+1"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
@ -258,3 +272,4 @@ packages:
version: "2.2.0" version: "2.2.0"
sdks: sdks:
dart: ">=2.6.0 <3.0.0" dart: ">=2.6.0 <3.0.0"
flutter: ">=1.12.1"

View File

@ -14,6 +14,7 @@ dependencies:
http: ^0.12.1 http: ^0.12.1
after_layout: ^1.0.7 after_layout: ^1.0.7
sentry: ^3.0.1 sentry: ^3.0.1
provider: ^4.0.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: