Add API implementations of List, Namespace, Task, User
Use Services in order to retrieve data
This commit is contained in:
parent
1994892b63
commit
f7db5324aa
|
@ -31,6 +31,12 @@ class Client {
|
||||||
.then(_handleResponse);
|
.then(_handleResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> delete(String url) {
|
||||||
|
return http
|
||||||
|
.delete('${this.base}$url', headers: _headers)
|
||||||
|
.then(_handleResponse);
|
||||||
|
}
|
||||||
|
|
||||||
Future<dynamic> post(String url, {dynamic body}) {
|
Future<dynamic> post(String url, {dynamic body}) {
|
||||||
return http
|
return http
|
||||||
.post('${this.base}$url',
|
.post('${this.base}$url',
|
||||||
|
@ -38,6 +44,13 @@ class Client {
|
||||||
.then(_handleResponse);
|
.then(_handleResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> put(String url, {dynamic body}) {
|
||||||
|
return http
|
||||||
|
.put('${this.base}$url',
|
||||||
|
headers: _headers, body: _encoder.convert(body))
|
||||||
|
.then(_handleResponse);
|
||||||
|
}
|
||||||
|
|
||||||
dynamic _handleResponse(http.Response response) {
|
dynamic _handleResponse(http.Response response) {
|
||||||
if (response.statusCode < 200 ||
|
if (response.statusCode < 200 ||
|
||||||
response.statusCode > 400 ||
|
response.statusCode > 400 ||
|
||||||
|
|
|
@ -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<TaskList> 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<TaskList> get(int listId) {
|
||||||
|
return client.get('/lists/$listId').then((map) => TaskList.fromJson(map));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TaskList>> getAll() {
|
||||||
|
return client.get('/lists').then(
|
||||||
|
(list) => convertList(list, (result) => TaskList.fromJson(result)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TaskList>> getByNamespace(int namespaceId) {
|
||||||
|
return client.get('/namespaces/$namespaceId/lists').then(
|
||||||
|
(list) => convertList(list, (result) => TaskList.fromJson(result)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TaskList> update(TaskList tl) {
|
||||||
|
return client
|
||||||
|
.put('/lists/${tl.id}', body: tl.toJSON())
|
||||||
|
.then((map) => TaskList.fromJson(map));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Namespace> 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<Namespace> get(int namespaceId) {
|
||||||
|
return client
|
||||||
|
.get('/namespaces/$namespaceId')
|
||||||
|
.then((map) => Namespace.fromJson(map));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Namespace>> getAll() {
|
||||||
|
return client.get('/namespaces').then(
|
||||||
|
(list) => convertList(list, (result) => Namespace.fromJson(result)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Namespace> update(Namespace ns) {
|
||||||
|
return client
|
||||||
|
.post('/namespaces/${ns.id}', body: ns.toJSON())
|
||||||
|
.then((map) => Namespace.fromJson(map));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T> convertList<T>(dynamic value, Mapper<T> mapper) {
|
||||||
|
if (value == null) return [];
|
||||||
|
return (value as List<dynamic>).map((map) => mapper(map)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef T Mapper<T>(Map<String, dynamic> json);
|
|
@ -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<Task> 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<Task> update(Task task) {
|
||||||
|
return client
|
||||||
|
.post('/tasks/${task.id}', body: task.toJSON())
|
||||||
|
.then((map) => Task.fromJson(map));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,26 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fluttering_vikunja/api/client.dart';
|
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/models/user.dart';
|
||||||
import 'package:fluttering_vikunja/service/services.dart';
|
import 'package:fluttering_vikunja/service/services.dart';
|
||||||
|
|
||||||
class UserAPIService implements UserService {
|
class UserAPIService extends APIService implements UserService {
|
||||||
final Client _client;
|
UserAPIService(Client client) : super(client);
|
||||||
|
|
||||||
UserAPIService(this._client);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<UserTokenPair> login(String username, password) async {
|
Future<UserTokenPair> login(String username, password) async {
|
||||||
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((map) => map['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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<User> getCurrentUser() {
|
Future<User> getCurrentUser() {
|
||||||
return _client.get('/user').then((map) => User.fromJson(map));
|
return client.get('/user').then((map) => User.fromJson(map));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,121 @@
|
||||||
|
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: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';
|
import 'package:fluttering_vikunja/pages/list_page.dart';
|
||||||
|
|
||||||
class NamespaceFragment extends StatefulWidget {
|
class NamespaceFragment extends StatefulWidget {
|
||||||
final String namespace;
|
final Namespace namespace;
|
||||||
NamespaceFragment({this.namespace}) : super(key: Key(namespace));
|
NamespaceFragment({this.namespace})
|
||||||
|
: super(key: Key(namespace.id.toString()));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_NamespaceFragmentState createState() => new _NamespaceFragmentState();
|
_NamespaceFragmentState createState() => new _NamespaceFragmentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NamespaceFragmentState extends State<NamespaceFragment> {
|
class _NamespaceFragmentState extends State<NamespaceFragment> {
|
||||||
Set<String> _lists = Set.from(
|
List<TaskList> _lists = [];
|
||||||
["Cupertino List", "Material List", "Shopping List", "NAS List"]);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ListView(
|
return Scaffold(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
body: new ListView(
|
||||||
children: ListTile.divideTiles(
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
context: context,
|
children: ListTile.divideTiles(
|
||||||
tiles: _lists.map((name) => Dismissible(
|
context: context,
|
||||||
key: Key(name),
|
tiles: _lists.map((ls) => Dismissible(
|
||||||
direction: DismissDirection.startToEnd,
|
key: Key(ls.id.toString()),
|
||||||
child: ListTile(
|
direction: DismissDirection.startToEnd,
|
||||||
title: new Text(name),
|
child: ListTile(
|
||||||
onTap: () => _openList(context, name),
|
title: new Text(ls.title),
|
||||||
trailing: Icon(Icons.arrow_right),
|
onTap: () => _openList(context, ls),
|
||||||
),
|
trailing: Icon(Icons.arrow_right),
|
||||||
background: Container(
|
),
|
||||||
color: Colors.red,
|
background: Container(
|
||||||
child: const ListTile(
|
color: Colors.red,
|
||||||
leading:
|
child: const ListTile(
|
||||||
Icon(Icons.delete, color: Colors.white, size: 36.0)),
|
leading: Icon(Icons.delete,
|
||||||
),
|
color: Colors.white, size: 36.0)),
|
||||||
onDismissed: (direction) {
|
),
|
||||||
setState(() => _lists.remove(name));
|
onDismissed: (direction) {
|
||||||
Scaffold.of(context)
|
_removeList(ls).then((_) => Scaffold.of(context)
|
||||||
.showSnackBar(SnackBar(content: Text("$name removed")));
|
.showSnackBar(
|
||||||
},
|
SnackBar(content: Text("${ls.title} removed"))));
|
||||||
))).toList(),
|
},
|
||||||
|
))).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(
|
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: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new TextField(
|
||||||
|
autofocus: true,
|
||||||
|
decoration: new InputDecoration(
|
||||||
|
labelText: 'List Name', hintText: 'eg. Shopping List'),
|
||||||
|
controller: textController,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
actions: <Widget>[
|
||||||
|
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(() {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:fluttering_vikunja/api/client.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/api/user_implementation.dart';
|
||||||
import 'package:fluttering_vikunja/managers/user.dart';
|
import 'package:fluttering_vikunja/managers/user.dart';
|
||||||
import 'package:fluttering_vikunja/models/user.dart';
|
import 'package:fluttering_vikunja/models/user.dart';
|
||||||
|
@ -36,6 +39,9 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||||
UserManager get userManager => new UserManager(_storage);
|
UserManager get userManager => new UserManager(_storage);
|
||||||
UserService get userService => new UserAPIService(_client);
|
UserService get userService => new UserAPIService(_client);
|
||||||
UserService newLoginService(base) => new UserAPIService(Client(null, base));
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -84,12 +90,27 @@ class VikunjaGlobalState extends State<VikunjaGlobal> {
|
||||||
});
|
});
|
||||||
return;
|
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(() {
|
setState(() {
|
||||||
_client = Client(token, base);
|
_client = client;
|
||||||
});
|
|
||||||
var loadedCurrentUser = await userService.getCurrentUser();
|
|
||||||
setState(() {
|
|
||||||
_currentUser = loadedCurrentUser;
|
_currentUser = loadedCurrentUser;
|
||||||
|
_loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,21 @@ import 'package:fluttering_vikunja/pages/home_page.dart';
|
||||||
import 'package:fluttering_vikunja/pages/login_page.dart';
|
import 'package:fluttering_vikunja/pages/login_page.dart';
|
||||||
import 'package:fluttering_vikunja/style.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 {
|
class VikunjaApp extends StatelessWidget {
|
||||||
|
final Widget home;
|
||||||
|
|
||||||
|
const VikunjaApp({Key key, this.home}) : super(key: key);
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new MaterialApp(
|
return new MaterialApp(
|
||||||
title: 'Vikunja',
|
title: 'Vikunja',
|
||||||
theme: buildVikunjaTheme(),
|
theme: buildVikunjaTheme(),
|
||||||
home: VikunjaGlobal(child: new HomePage(), login: new LoginPage()),
|
home: this.home,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,12 @@ class Namespace {
|
||||||
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
|
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
|
||||||
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
|
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
|
||||||
owner = User.fromJson(json['owner']);
|
owner = User.fromJson(json['owner']);
|
||||||
|
|
||||||
|
toJSON() => {
|
||||||
|
"created": created?.millisecondsSinceEpoch,
|
||||||
|
"updated": updated?.millisecondsSinceEpoch,
|
||||||
|
"name": name,
|
||||||
|
"owner": owner?.toJSON(),
|
||||||
|
"description": description
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Task {
|
||||||
this.due,
|
this.due,
|
||||||
@required this.text,
|
@required this.text,
|
||||||
this.description,
|
this.description,
|
||||||
this.done,
|
@required this.done,
|
||||||
@required this.owner});
|
@required this.owner});
|
||||||
|
|
||||||
Task.fromJson(Map<String, dynamic> json)
|
Task.fromJson(Map<String, dynamic> json)
|
||||||
|
@ -29,6 +29,18 @@ class Task {
|
||||||
text = json['text'],
|
text = json['text'],
|
||||||
done = json['done'],
|
done = json['done'],
|
||||||
owner = User.fromJson(json['createdBy']);
|
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 {
|
class TaskList {
|
||||||
|
@ -54,5 +66,17 @@ class TaskList {
|
||||||
title = json['title'],
|
title = json['title'],
|
||||||
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
|
updated = DateTime.fromMillisecondsSinceEpoch(json['updated']),
|
||||||
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
|
created = DateTime.fromMillisecondsSinceEpoch(json['created']),
|
||||||
tasks = json['tasks'].map((taskJson) => Task.fromJson(taskJson));
|
tasks = (json['tasks'] as List<dynamic>)
|
||||||
|
?.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()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ class User {
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
email = json['email'],
|
email = json['email'],
|
||||||
username = json['username'];
|
username = json['username'];
|
||||||
|
|
||||||
|
toJSON() => {"id": this.id, "email": this.email, "username": this.username};
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserTokenPair {
|
class UserTokenPair {
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'package:fluttering_vikunja/components/GravatarImage.dart';
|
||||||
import 'package:fluttering_vikunja/fragments/namespace.dart';
|
import 'package:fluttering_vikunja/fragments/namespace.dart';
|
||||||
import 'package:fluttering_vikunja/fragments/placeholder.dart';
|
import 'package:fluttering_vikunja/fragments/placeholder.dart';
|
||||||
import 'package:fluttering_vikunja/global.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';
|
import 'package:fluttering_vikunja/models/user.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
|
@ -11,14 +13,18 @@ class HomePage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomePageState extends State<HomePage> {
|
class HomePageState extends State<HomePage> {
|
||||||
List<String> namespaces = ["Jonas's namespace", 'Another namespace'];
|
List<Namespace> _namespaces = [];
|
||||||
|
Namespace get _currentNamespace =>
|
||||||
|
_selectedDrawerIndex >= 0 && _selectedDrawerIndex < _namespaces.length
|
||||||
|
? _namespaces[_selectedDrawerIndex]
|
||||||
|
: null;
|
||||||
int _selectedDrawerIndex = -1;
|
int _selectedDrawerIndex = -1;
|
||||||
|
|
||||||
_getDrawerItemWidget(int pos) {
|
_getDrawerItemWidget(int pos) {
|
||||||
if (pos == -1) {
|
if (pos == -1) {
|
||||||
return new PlaceholderFragment();
|
return new PlaceholderFragment();
|
||||||
}
|
}
|
||||||
return new NamespaceFragment(namespace: namespaces[pos]);
|
return new NamespaceFragment(namespace: _namespaces[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSelectItem(int index) {
|
_onSelectItem(int index) {
|
||||||
|
@ -26,7 +32,7 @@ class HomePageState extends State<HomePage> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
_addNamespace() {
|
_addNamespaceDialog() {
|
||||||
var textController = new TextEditingController();
|
var textController = new TextEditingController();
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -50,8 +56,9 @@ class HomePageState extends State<HomePage> {
|
||||||
new FlatButton(
|
new FlatButton(
|
||||||
child: const Text('ADD'),
|
child: const Text('ADD'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (textController.text.isNotEmpty)
|
if (textController.text.isNotEmpty) {
|
||||||
setState(() => namespaces.add(textController.text));
|
_addNamespace(textController.text);
|
||||||
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -60,28 +67,42 @@ class HomePageState extends State<HomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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
|
@override
|
||||||
void initState() {
|
void didChangeDependencies() {
|
||||||
super.initState();
|
super.didChangeDependencies();
|
||||||
|
_updateNamespaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var currentUser = VikunjaGlobal.of(context).currentUser;
|
var currentUser = VikunjaGlobal.of(context).currentUser;
|
||||||
List<Widget> drawerOptions = <Widget>[];
|
List<Widget> drawerOptions = <Widget>[];
|
||||||
namespaces.asMap().forEach((i, namespace) => drawerOptions.add(new ListTile(
|
_namespaces
|
||||||
leading: const Icon(Icons.folder),
|
.asMap()
|
||||||
title: new Text(namespace),
|
.forEach((i, namespace) => drawerOptions.add(new ListTile(
|
||||||
selected: i == _selectedDrawerIndex,
|
leading: const Icon(Icons.folder),
|
||||||
onTap: () => _onSelectItem(i),
|
title: new Text(namespace.name),
|
||||||
)));
|
selected: i == _selectedDrawerIndex,
|
||||||
|
onTap: () => _onSelectItem(i),
|
||||||
|
)));
|
||||||
|
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: new Text(_currentNamespace?.name ?? 'Vakunja')),
|
||||||
title: new Text(_selectedDrawerIndex == -1
|
|
||||||
? 'Vakunja'
|
|
||||||
: namespaces[_selectedDrawerIndex]),
|
|
||||||
),
|
|
||||||
drawer: new Drawer(
|
drawer: new Drawer(
|
||||||
child: new Column(children: <Widget>[
|
child: new Column(children: <Widget>[
|
||||||
new UserAccountsDrawerHeader(
|
new UserAccountsDrawerHeader(
|
||||||
|
@ -110,7 +131,7 @@ class HomePageState extends State<HomePage> {
|
||||||
child: new ListTile(
|
child: new ListTile(
|
||||||
leading: const Icon(Icons.add),
|
leading: const Icon(Icons.add),
|
||||||
title: const Text('Add namespace...'),
|
title: const Text('Add namespace...'),
|
||||||
onTap: () => _addNamespace(),
|
onTap: () => _addNamespaceDialog(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
|
|
|
@ -1,46 +1,82 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttering_vikunja/global.dart';
|
||||||
|
import 'package:fluttering_vikunja/models/task.dart';
|
||||||
|
|
||||||
class ListPage extends StatefulWidget {
|
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
|
@override
|
||||||
_ListPageState createState() => _ListPageState();
|
_ListPageState createState() => _ListPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ListPageState extends State<ListPage> {
|
class _ListPageState extends State<ListPage> {
|
||||||
Map<String, bool> items = {"Butter": true, "Milch": false};
|
TaskList items;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
items = TaskList(
|
||||||
|
id: widget.taskList.id, title: widget.taskList.title, tasks: []);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: new Text(widget.listName),
|
title: new Text(items.title),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
children: ListTile.divideTiles(
|
children: ListTile.divideTiles(
|
||||||
context: context,
|
context: context,
|
||||||
tiles: items
|
tiles: items?.tasks?.map((task) => CheckboxListTile(
|
||||||
.map((item, checked) => MapEntry(
|
title: Text(task.text),
|
||||||
item,
|
|
||||||
CheckboxListTile(
|
|
||||||
title: Text(item),
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: checked,
|
value: task.done ?? false,
|
||||||
onChanged: (bool value) =>
|
subtitle: task.description == null
|
||||||
setState(() => items[item] = value),
|
? null
|
||||||
)))
|
: Text(task.description),
|
||||||
.values)
|
onChanged: (bool value) => _updateTask(task, value),
|
||||||
|
)) ??
|
||||||
|
[])
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
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();
|
var textController = new TextEditingController();
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -64,8 +100,7 @@ class _ListPageState extends State<ListPage> {
|
||||||
new FlatButton(
|
new FlatButton(
|
||||||
child: const Text('ADD'),
|
child: const Text('ADD'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (textController.text.isNotEmpty)
|
if (textController.text.isNotEmpty) _addItem(textController.text);
|
||||||
setState(() => items[textController.text] = false);
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -73,4 +108,21 @@ class _ListPageState extends State<ListPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ var _lists = {
|
||||||
1: TaskList(
|
1: TaskList(
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'List 1',
|
title: 'List 1',
|
||||||
tasks: _tasks.values,
|
tasks: _tasks.values.toList(),
|
||||||
owner: _users[1],
|
owner: _users[1],
|
||||||
description: 'A nice list',
|
description: 'A nice list',
|
||||||
created: DateTime.now(),
|
created: DateTime.now(),
|
||||||
|
@ -81,7 +81,8 @@ class MockedNamespaceService implements NamespaceService {
|
||||||
|
|
||||||
class MockedListService implements ListService {
|
class MockedListService implements ListService {
|
||||||
@override
|
@override
|
||||||
Future<TaskList> create(TaskList tl) {
|
Future<TaskList> create(namespaceId, TaskList tl) {
|
||||||
|
_nsLists[namespaceId].add(tl.id);
|
||||||
return Future.value(_lists[tl.id] = tl);
|
return Future.value(_lists[tl.id] = tl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ class MockedListService implements ListService {
|
||||||
Future<TaskList> update(TaskList tl) {
|
Future<TaskList> update(TaskList tl) {
|
||||||
if (!_lists.containsKey(tl))
|
if (!_lists.containsKey(tl))
|
||||||
throw Exception('TaskList ${tl.id} does not exists');
|
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
|
@override
|
||||||
Future<Task> update(Task task) {
|
Future<Task> 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);
|
return Future.value(_tasks[task.id] = task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Task> 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 {
|
class MockedUserService implements UserService {
|
||||||
|
|
|
@ -16,7 +16,7 @@ abstract class ListService {
|
||||||
Future<List<TaskList>> getAll();
|
Future<List<TaskList>> getAll();
|
||||||
Future<TaskList> get(int listId);
|
Future<TaskList> get(int listId);
|
||||||
Future<List<TaskList>> getByNamespace(int namespaceId);
|
Future<List<TaskList>> getByNamespace(int namespaceId);
|
||||||
Future<TaskList> create(TaskList tl);
|
Future<TaskList> create(int namespaceId, TaskList tl);
|
||||||
Future<TaskList> update(TaskList tl);
|
Future<TaskList> update(TaskList tl);
|
||||||
Future delete(int listId);
|
Future delete(int listId);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ abstract class ListService {
|
||||||
abstract class TaskService {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class UserService {
|
abstract class UserService {
|
||||||
|
|
Reference in New Issue