From f7db5324aa27535e4249680df6f9fdb21fcf11e8 Mon Sep 17 00:00:00 2001 From: Jonas Franz Date: Mon, 17 Sep 2018 18:16:50 +0200 Subject: [PATCH] Add API implementations of List, Namespace, Task, User Use Services in order to retrieve data --- lib/api/client.dart | 13 +++ lib/api/list_implementation.dart | 46 +++++++++ lib/api/namespace_implementation.dart | 42 +++++++++ lib/api/service.dart | 19 ++++ lib/api/task_implementation.dart | 29 ++++++ lib/api/user_implementation.dart | 13 ++- lib/fragments/namespace.dart | 131 ++++++++++++++++++++------ lib/global.dart | 29 +++++- lib/main.dart | 9 +- lib/models/namespace.dart | 8 ++ lib/models/task.dart | 28 +++++- lib/models/user.dart | 2 + lib/pages/home_page.dart | 59 ++++++++---- lib/pages/list_page.dart | 88 +++++++++++++---- lib/service/mocked_services.dart | 21 ++++- lib/service/services.dart | 3 +- 16 files changed, 454 insertions(+), 86 deletions(-) create mode 100644 lib/api/list_implementation.dart create mode 100644 lib/api/namespace_implementation.dart create mode 100644 lib/api/service.dart create mode 100644 lib/api/task_implementation.dart diff --git a/lib/api/client.dart b/lib/api/client.dart index 01aab8e..f347d38 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -31,6 +31,12 @@ class Client { .then(_handleResponse); } + Future delete(String url) { + return http + .delete('${this.base}$url', headers: _headers) + .then(_handleResponse); + } + Future post(String url, {dynamic body}) { return http .post('${this.base}$url', @@ -38,6 +44,13 @@ class Client { .then(_handleResponse); } + 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) { if (response.statusCode < 200 || response.statusCode > 400 || diff --git a/lib/api/list_implementation.dart b/lib/api/list_implementation.dart new file mode 100644 index 0000000..9742687 --- /dev/null +++ b/lib/api/list_implementation.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:fluttering_vikunja/api/client.dart'; +import 'package:fluttering_vikunja/api/service.dart'; +import 'package:fluttering_vikunja/models/task.dart'; +import 'package:fluttering_vikunja/service/services.dart'; + +class ListAPIService extends APIService implements ListService { + ListAPIService(Client client) : super(client); + + @override + Future create(namespaceId, TaskList tl) { + return client + .put('/namespaces/$namespaceId/lists', body: tl.toJSON()) + .then((map) => TaskList.fromJson(map)); + } + + @override + Future delete(int listId) { + return client.delete('/lists/$listId').then((_) {}); + } + + @override + Future get(int listId) { + return client.get('/lists/$listId').then((map) => TaskList.fromJson(map)); + } + + @override + Future> getAll() { + return client.get('/lists').then( + (list) => convertList(list, (result) => TaskList.fromJson(result))); + } + + @override + Future> getByNamespace(int namespaceId) { + return client.get('/namespaces/$namespaceId/lists').then( + (list) => convertList(list, (result) => TaskList.fromJson(result))); + } + + @override + Future update(TaskList tl) { + return client + .put('/lists/${tl.id}', body: tl.toJSON()) + .then((map) => TaskList.fromJson(map)); + } +} diff --git a/lib/api/namespace_implementation.dart b/lib/api/namespace_implementation.dart new file mode 100644 index 0000000..72e7899 --- /dev/null +++ b/lib/api/namespace_implementation.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:fluttering_vikunja/api/client.dart'; +import 'package:fluttering_vikunja/api/service.dart'; +import 'package:fluttering_vikunja/models/namespace.dart'; +import 'package:fluttering_vikunja/service/services.dart'; + +class NamespaceAPIService extends APIService implements NamespaceService { + NamespaceAPIService(Client client) : super(client); + + @override + Future create(Namespace ns) { + return client + .put('/namespaces', body: ns.toJSON()) + .then((map) => Namespace.fromJson(map)); + } + + @override + Future delete(int namespaceId) { + return client.delete('/namespaces/$namespaceId'); + } + + @override + Future get(int namespaceId) { + return client + .get('/namespaces/$namespaceId') + .then((map) => Namespace.fromJson(map)); + } + + @override + Future> getAll() { + return client.get('/namespaces').then( + (list) => convertList(list, (result) => Namespace.fromJson(result))); + } + + @override + Future update(Namespace ns) { + return client + .post('/namespaces/${ns.id}', body: ns.toJSON()) + .then((map) => Namespace.fromJson(map)); + } +} diff --git a/lib/api/service.dart b/lib/api/service.dart new file mode 100644 index 0000000..c65c863 --- /dev/null +++ b/lib/api/service.dart @@ -0,0 +1,19 @@ +import 'package:fluttering_vikunja/api/client.dart'; +import 'package:meta/meta.dart'; + +class APIService { + final Client _client; + + @protected + Client get client => _client; + + APIService(this._client); + + @protected + List convertList(dynamic value, Mapper mapper) { + if (value == null) return []; + return (value as List).map((map) => mapper(map)).toList(); + } +} + +typedef T Mapper(Map json); diff --git a/lib/api/task_implementation.dart b/lib/api/task_implementation.dart new file mode 100644 index 0000000..d0861c5 --- /dev/null +++ b/lib/api/task_implementation.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:fluttering_vikunja/api/client.dart'; +import 'package:fluttering_vikunja/api/service.dart'; +import 'package:fluttering_vikunja/models/task.dart'; +import 'package:fluttering_vikunja/service/services.dart'; + +class TaskAPIService extends APIService implements TaskService { + TaskAPIService(Client client) : super(client); + + @override + Future add(int listId, Task task) { + return client + .put('/lists/$listId', body: task.toJSON()) + .then((map) => Task.fromJson(map)); + } + + @override + Future delete(int taskId) { + return client.delete('/tasks/$taskId'); + } + + @override + Future update(Task task) { + return client + .post('/tasks/${task.id}', body: task.toJSON()) + .then((map) => Task.fromJson(map)); + } +} diff --git a/lib/api/user_implementation.dart b/lib/api/user_implementation.dart index 35cc438..a8789c3 100644 --- a/lib/api/user_implementation.dart +++ b/lib/api/user_implementation.dart @@ -1,27 +1,26 @@ import 'dart:async'; import 'package:fluttering_vikunja/api/client.dart'; +import 'package:fluttering_vikunja/api/service.dart'; import 'package:fluttering_vikunja/models/user.dart'; import 'package:fluttering_vikunja/service/services.dart'; -class UserAPIService implements UserService { - final Client _client; - - UserAPIService(this._client); +class UserAPIService extends APIService implements UserService { + UserAPIService(Client client) : super(client); @override Future login(String username, password) async { - var token = await _client.post('/login', body: { + var token = await client.post('/login', body: { 'username': username, 'password': password }).then((map) => map['token']); - return UserAPIService(Client(token, _client.base)) + return UserAPIService(Client(token, client.base)) .getCurrentUser() .then((user) => UserTokenPair(user, token)); } @override Future getCurrentUser() { - return _client.get('/user').then((map) => User.fromJson(map)); + return client.get('/user').then((map) => User.fromJson(map)); } } diff --git a/lib/fragments/namespace.dart b/lib/fragments/namespace.dart index ad46bcb..78ff810 100644 --- a/lib/fragments/namespace.dart +++ b/lib/fragments/namespace.dart @@ -1,50 +1,121 @@ +import 'dart:async'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:fluttering_vikunja/global.dart'; +import 'package:fluttering_vikunja/models/namespace.dart'; +import 'package:fluttering_vikunja/models/task.dart'; import 'package:fluttering_vikunja/pages/list_page.dart'; class NamespaceFragment extends StatefulWidget { - final String namespace; - NamespaceFragment({this.namespace}) : super(key: Key(namespace)); + final Namespace namespace; + NamespaceFragment({this.namespace}) + : super(key: Key(namespace.id.toString())); @override _NamespaceFragmentState createState() => new _NamespaceFragmentState(); } class _NamespaceFragmentState extends State { - Set _lists = Set.from( - ["Cupertino List", "Material List", "Shopping List", "NAS List"]); + List _lists = []; @override Widget build(BuildContext context) { - return new ListView( - padding: EdgeInsets.symmetric(vertical: 8.0), - children: ListTile.divideTiles( - context: context, - tiles: _lists.map((name) => Dismissible( - key: Key(name), - direction: DismissDirection.startToEnd, - child: ListTile( - title: new Text(name), - onTap: () => _openList(context, name), - trailing: Icon(Icons.arrow_right), - ), - background: Container( - color: Colors.red, - child: const ListTile( - leading: - Icon(Icons.delete, color: Colors.white, size: 36.0)), - ), - onDismissed: (direction) { - setState(() => _lists.remove(name)); - Scaffold.of(context) - .showSnackBar(SnackBar(content: Text("$name removed"))); - }, - ))).toList(), + return Scaffold( + body: new ListView( + padding: EdgeInsets.symmetric(vertical: 8.0), + children: ListTile.divideTiles( + context: context, + tiles: _lists.map((ls) => Dismissible( + key: Key(ls.id.toString()), + direction: DismissDirection.startToEnd, + child: ListTile( + title: new Text(ls.title), + onTap: () => _openList(context, ls), + trailing: Icon(Icons.arrow_right), + ), + background: Container( + color: Colors.red, + child: const ListTile( + leading: Icon(Icons.delete, + color: Colors.white, size: 36.0)), + ), + onDismissed: (direction) { + _removeList(ls).then((_) => Scaffold.of(context) + .showSnackBar( + SnackBar(content: Text("${ls.title} removed")))); + }, + ))).toList(), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _addListDialog(), child: const Icon(Icons.add)), ); } - _openList(BuildContext context, String name) { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateLists(); + } + + Future _removeList(TaskList list) { + return VikunjaGlobal.of(context) + .listService + .delete(list.id) + .then((_) => _updateLists()); + } + + _updateLists() { + VikunjaGlobal.of(context) + .listService + .getByNamespace(widget.namespace.id) + .then((lists) => setState(() => this._lists = lists)); + } + + _openList(BuildContext context, TaskList list) { Navigator.of(context).push( - MaterialPageRoute(builder: (context) => ListPage(listName: name))); + MaterialPageRoute(builder: (context) => ListPage(taskList: list))); + } + + _addListDialog() { + var textController = new TextEditingController(); + showDialog( + context: context, + child: new AlertDialog( + contentPadding: const EdgeInsets.all(16.0), + content: new Row(children: [ + Expanded( + child: new TextField( + autofocus: true, + decoration: new InputDecoration( + labelText: 'List Name', hintText: 'eg. Shopping List'), + controller: textController, + ), + ) + ]), + actions: [ + new FlatButton( + child: const Text('CANCEL'), + onPressed: () => Navigator.pop(context), + ), + new FlatButton( + child: const Text('ADD'), + onPressed: () { + if (textController.text.isNotEmpty) { + _addList(textController.text); + } + Navigator.pop(context); + }, + ) + ], + ), + ); + } + + _addList(String name) { + VikunjaGlobal.of(context) + .listService + .create(widget.namespace.id, TaskList(id: null, title: name, tasks: [])) + .then((_) => setState(() {})); } } diff --git a/lib/global.dart b/lib/global.dart index 8328dfe..8cf2a90 100644 --- a/lib/global.dart +++ b/lib/global.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:fluttering_vikunja/api/client.dart'; +import 'package:fluttering_vikunja/api/list_implementation.dart'; +import 'package:fluttering_vikunja/api/namespace_implementation.dart'; +import 'package:fluttering_vikunja/api/task_implementation.dart'; import 'package:fluttering_vikunja/api/user_implementation.dart'; import 'package:fluttering_vikunja/managers/user.dart'; import 'package:fluttering_vikunja/models/user.dart'; @@ -36,6 +39,9 @@ class VikunjaGlobalState extends State { UserManager get userManager => new UserManager(_storage); UserService get userService => new UserAPIService(_client); UserService newLoginService(base) => new UserAPIService(Client(null, base)); + NamespaceService get namespaceService => new NamespaceAPIService(client); + TaskService get taskService => new TaskAPIService(client); + ListService get listService => new ListAPIService(client); @override void initState() { @@ -84,12 +90,27 @@ class VikunjaGlobalState extends State { }); return; } + var client = Client(token, base); + var loadedCurrentUser; + try { + loadedCurrentUser = await UserAPIService(client).getCurrentUser(); + } on ApiException catch (e) { + if (e.errorCode ~/ 100 == 4) { + setState(() { + _client = null; + _currentUser = null; + _loading = false; + }); + return; + } + loadedCurrentUser = User(int.tryParse(currentUser), "", ""); + } catch (otherExceptions) { + loadedCurrentUser = User(int.tryParse(currentUser), "", ""); + } setState(() { - _client = Client(token, base); - }); - var loadedCurrentUser = await userService.getCurrentUser(); - setState(() { + _client = client; _currentUser = loadedCurrentUser; + _loading = false; }); } diff --git a/lib/main.dart b/lib/main.dart index 86eeff1..3b79801 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,16 +6,21 @@ import 'package:fluttering_vikunja/pages/home_page.dart'; import 'package:fluttering_vikunja/pages/login_page.dart'; import 'package:fluttering_vikunja/style.dart'; -void main() => runApp(new VikunjaApp()); +void main() => runApp(VikunjaGlobal( + child: new VikunjaApp(home: HomePage()), + login: new VikunjaApp(home: LoginPage()))); class VikunjaApp extends StatelessWidget { + final Widget home; + + const VikunjaApp({Key key, this.home}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Vikunja', theme: buildVikunjaTheme(), - home: VikunjaGlobal(child: new HomePage(), login: new LoginPage()), + home: this.home, ); } } diff --git a/lib/models/namespace.dart b/lib/models/namespace.dart index e62c5be..34bafdd 100644 --- a/lib/models/namespace.dart +++ b/lib/models/namespace.dart @@ -22,4 +22,12 @@ class Namespace { created = DateTime.fromMillisecondsSinceEpoch(json['created']), updated = DateTime.fromMillisecondsSinceEpoch(json['updated']), owner = User.fromJson(json['owner']); + + toJSON() => { + "created": created?.millisecondsSinceEpoch, + "updated": updated?.millisecondsSinceEpoch, + "name": name, + "owner": owner?.toJSON(), + "description": description + }; } diff --git a/lib/models/task.dart b/lib/models/task.dart index 0b0b795..63c2fc9 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -16,7 +16,7 @@ class Task { this.due, @required this.text, this.description, - this.done, + @required this.done, @required this.owner}); Task.fromJson(Map json) @@ -29,6 +29,18 @@ class Task { text = json['text'], done = json['done'], owner = User.fromJson(json['createdBy']); + + toJSON() => { + 'id': id, + 'updated': updated?.millisecondsSinceEpoch, + 'created': created?.millisecondsSinceEpoch, + 'reminderDate': reminder?.millisecondsSinceEpoch, + 'dueDate': due?.millisecondsSinceEpoch, + 'description': description, + 'text': text, + 'done': done ?? false, + 'createdBy': owner?.toJSON() + }; } class TaskList { @@ -54,5 +66,17 @@ class TaskList { title = json['title'], updated = DateTime.fromMillisecondsSinceEpoch(json['updated']), created = DateTime.fromMillisecondsSinceEpoch(json['created']), - tasks = json['tasks'].map((taskJson) => Task.fromJson(taskJson)); + tasks = (json['tasks'] as List) + ?.map((taskJson) => Task.fromJson(taskJson)) + ?.toList(); + + toJSON() { + return { + "created": this.created?.millisecondsSinceEpoch, + "updated": this.updated?.millisecondsSinceEpoch, + "id": this.id, + "title": this.title, + "owner": this.owner?.toJSON() + }; + } } diff --git a/lib/models/user.dart b/lib/models/user.dart index d136ebc..ce8fdeb 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -9,6 +9,8 @@ class User { : id = json['id'], email = json['email'], username = json['username']; + + toJSON() => {"id": this.id, "email": this.email, "username": this.username}; } class UserTokenPair { diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index c0196bc..61efb38 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -3,6 +3,8 @@ import 'package:fluttering_vikunja/components/GravatarImage.dart'; import 'package:fluttering_vikunja/fragments/namespace.dart'; import 'package:fluttering_vikunja/fragments/placeholder.dart'; import 'package:fluttering_vikunja/global.dart'; +import 'package:fluttering_vikunja/models/namespace.dart'; +import 'package:fluttering_vikunja/models/task.dart'; import 'package:fluttering_vikunja/models/user.dart'; class HomePage extends StatefulWidget { @@ -11,14 +13,18 @@ class HomePage extends StatefulWidget { } class HomePageState extends State { - List namespaces = ["Jonas's namespace", 'Another namespace']; + List _namespaces = []; + Namespace get _currentNamespace => + _selectedDrawerIndex >= 0 && _selectedDrawerIndex < _namespaces.length + ? _namespaces[_selectedDrawerIndex] + : null; int _selectedDrawerIndex = -1; _getDrawerItemWidget(int pos) { if (pos == -1) { return new PlaceholderFragment(); } - return new NamespaceFragment(namespace: namespaces[pos]); + return new NamespaceFragment(namespace: _namespaces[pos]); } _onSelectItem(int index) { @@ -26,7 +32,7 @@ class HomePageState extends State { Navigator.of(context).pop(); } - _addNamespace() { + _addNamespaceDialog() { var textController = new TextEditingController(); showDialog( context: context, @@ -50,8 +56,9 @@ class HomePageState extends State { new FlatButton( child: const Text('ADD'), onPressed: () { - if (textController.text.isNotEmpty) - setState(() => namespaces.add(textController.text)); + if (textController.text.isNotEmpty) { + _addNamespace(textController.text); + } Navigator.pop(context); }, ) @@ -60,28 +67,42 @@ class HomePageState extends State { ); } + _addNamespace(String name) { + VikunjaGlobal.of(context) + .namespaceService + .create(Namespace(id: null, name: name)) + .then((_) => _updateNamespaces()); + } + + _updateNamespaces() { + VikunjaGlobal.of(context).namespaceService.getAll().then((result) { + setState(() { + _namespaces = result; + }); + }); + } + @override - void initState() { - super.initState(); + void didChangeDependencies() { + super.didChangeDependencies(); + _updateNamespaces(); } @override Widget build(BuildContext context) { var currentUser = VikunjaGlobal.of(context).currentUser; List drawerOptions = []; - namespaces.asMap().forEach((i, namespace) => drawerOptions.add(new ListTile( - leading: const Icon(Icons.folder), - title: new Text(namespace), - selected: i == _selectedDrawerIndex, - onTap: () => _onSelectItem(i), - ))); + _namespaces + .asMap() + .forEach((i, namespace) => drawerOptions.add(new ListTile( + leading: const Icon(Icons.folder), + title: new Text(namespace.name), + selected: i == _selectedDrawerIndex, + onTap: () => _onSelectItem(i), + ))); return new Scaffold( - appBar: AppBar( - title: new Text(_selectedDrawerIndex == -1 - ? 'Vakunja' - : namespaces[_selectedDrawerIndex]), - ), + appBar: AppBar(title: new Text(_currentNamespace?.name ?? 'Vakunja')), drawer: new Drawer( child: new Column(children: [ new UserAccountsDrawerHeader( @@ -110,7 +131,7 @@ class HomePageState extends State { child: new ListTile( leading: const Icon(Icons.add), title: const Text('Add namespace...'), - onTap: () => _addNamespace(), + onTap: () => _addNamespaceDialog(), ), ), ])), diff --git a/lib/pages/list_page.dart b/lib/pages/list_page.dart index fc29755..3aa3e88 100644 --- a/lib/pages/list_page.dart +++ b/lib/pages/list_page.dart @@ -1,46 +1,82 @@ import 'package:flutter/material.dart'; +import 'package:fluttering_vikunja/global.dart'; +import 'package:fluttering_vikunja/models/task.dart'; class ListPage extends StatefulWidget { - final String listName; + final TaskList taskList; - ListPage({this.listName}) : super(key: Key(listName)); + ListPage({this.taskList}) : super(key: Key(taskList.id.toString())); @override _ListPageState createState() => _ListPageState(); } class _ListPageState extends State { - Map items = {"Butter": true, "Milch": false}; + TaskList items; + + @override + void initState() { + items = TaskList( + id: widget.taskList.id, title: widget.taskList.title, tasks: []); + super.initState(); + } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: new Text(widget.listName), + title: new Text(items.title), ), body: ListView( padding: EdgeInsets.symmetric(vertical: 8.0), children: ListTile.divideTiles( context: context, - tiles: items - .map((item, checked) => MapEntry( - item, - CheckboxListTile( - title: Text(item), + tiles: items?.tasks?.map((task) => CheckboxListTile( + title: Text(task.text), controlAffinity: ListTileControlAffinity.leading, - value: checked, - onChanged: (bool value) => - setState(() => items[item] = value), - ))) - .values) + value: task.done ?? false, + subtitle: task.description == null + ? null + : Text(task.description), + onChanged: (bool value) => _updateTask(task, value), + )) ?? + []) .toList(), ), floatingActionButton: FloatingActionButton( - onPressed: () => _addItem(), child: Icon(Icons.add)), + onPressed: () => _addItemDialog(), child: Icon(Icons.add)), ); } - _addItem() { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateList(); + } + + _updateTask(Task task, bool checked) { + // TODO use copyFrom + VikunjaGlobal.of(context) + .taskService + .update(Task( + id: task.id, + done: checked, + text: task.text, + description: task.description, + owner: null, + )) + .then((_) => _updateList()); + } + + _updateList() { + VikunjaGlobal.of(context).listService.get(widget.taskList.id).then((tasks) { + setState(() { + items = tasks; + }); + }); + } + + _addItemDialog() { var textController = new TextEditingController(); showDialog( context: context, @@ -64,8 +100,7 @@ class _ListPageState extends State { new FlatButton( child: const Text('ADD'), onPressed: () { - if (textController.text.isNotEmpty) - setState(() => items[textController.text] = false); + if (textController.text.isNotEmpty) _addItem(textController.text); Navigator.pop(context); }, ) @@ -73,4 +108,21 @@ class _ListPageState extends State { ), ); } + + _addItem(String name) { + var globalState = VikunjaGlobal.of(context); + globalState.taskService + .add( + items.id, + Task( + id: null, + text: name, + owner: globalState.currentUser, + done: false)) + .then((task) { + setState(() { + items.tasks.add(task); + }); + }).then((_) => _updateList()); + } } diff --git a/lib/service/mocked_services.dart b/lib/service/mocked_services.dart index 77d5d5c..fb6dc5d 100644 --- a/lib/service/mocked_services.dart +++ b/lib/service/mocked_services.dart @@ -27,7 +27,7 @@ var _lists = { 1: TaskList( id: 1, title: 'List 1', - tasks: _tasks.values, + tasks: _tasks.values.toList(), owner: _users[1], description: 'A nice list', created: DateTime.now(), @@ -81,7 +81,8 @@ class MockedNamespaceService implements NamespaceService { class MockedListService implements ListService { @override - Future create(TaskList tl) { + Future create(namespaceId, TaskList tl) { + _nsLists[namespaceId].add(tl.id); return Future.value(_lists[tl.id] = tl); } @@ -111,7 +112,7 @@ class MockedListService implements ListService { Future update(TaskList tl) { if (!_lists.containsKey(tl)) throw Exception('TaskList ${tl.id} does not exists'); - return create(tl); + return Future.value(_lists[tl.id] = tl); } } @@ -126,8 +127,22 @@ class MockedTaskService implements TaskService { @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); + } + }); return Future.value(_tasks[task.id] = task); } + + @override + Future add(int listId, Task task) { + var id = _tasks.keys.last + 1; + _tasks[id] = task; + _lists[listId].tasks.add(task); + return Future.value(task); + } } class MockedUserService implements UserService { diff --git a/lib/service/services.dart b/lib/service/services.dart index 910bc69..9eb5f0c 100644 --- a/lib/service/services.dart +++ b/lib/service/services.dart @@ -16,7 +16,7 @@ abstract class ListService { Future> getAll(); Future get(int listId); Future> getByNamespace(int namespaceId); - Future create(TaskList tl); + Future create(int namespaceId, TaskList tl); Future update(TaskList tl); Future delete(int listId); } @@ -24,6 +24,7 @@ abstract class ListService { abstract class TaskService { Future update(Task task); Future delete(int taskId); + Future add(int listId, Task task); } abstract class UserService {