Add improved loading indicators (#9)
the build failed Details

This commit is contained in:
JonasFranz 2018-09-27 15:55:56 +00:00 committed by Gitea
parent 3c52931538
commit 2e04969689
6 changed files with 262 additions and 179 deletions

View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class AddDialog extends StatelessWidget {
final ValueChanged<String> onAdd;
final InputDecoration decoration;
const AddDialog({Key key, this.onAdd, this.decoration}) : super(key: key);
@override
Widget build(BuildContext context) {
var textController = TextEditingController();
return new AlertDialog(
contentPadding: const EdgeInsets.all(16.0),
content: new Row(children: <Widget>[
Expanded(
child: new TextField(
autofocus: true,
decoration: this.decoration,
controller: textController,
),
)
]),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () => Navigator.pop(context),
),
new FlatButton(
child: const Text('ADD'),
onPressed: () {
if (this.onAdd != null && textController.text.isNotEmpty)
this.onAdd(textController.text);
Navigator.pop(context);
},
)
],
);
}
}

View File

@ -0,0 +1,89 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/task.dart';
class TaskTile extends StatefulWidget {
final Task task;
final VoidCallback onEdit;
final bool loading;
const TaskTile(
{Key key, @required this.task, this.onEdit, this.loading = false})
: assert(task != null),
super(key: key);
@override
TaskTileState createState() {
return new TaskTileState(this.task, this.loading);
}
}
class TaskTileState extends State<TaskTile> {
bool _loading;
Task _currentTask;
TaskTileState(this._currentTask, this._loading)
: assert(_currentTask != null),
assert(_loading != null);
@override
Widget build(BuildContext context) {
if (_loading) {
return ListTile(
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
height: Checkbox.width,
width: Checkbox.width,
child: CircularProgressIndicator(
strokeWidth: 2.0,
)),
),
title: Text(_currentTask.text),
subtitle:
_currentTask.description == null || _currentTask.description.isEmpty
? null
: Text(_currentTask.description),
trailing: IconButton(icon: Icon(Icons.settings), onPressed: null),
);
}
return CheckboxListTile(
title: Text(_currentTask.text),
controlAffinity: ListTileControlAffinity.leading,
value: _currentTask.done ?? false,
subtitle:
_currentTask.description == null || _currentTask.description.isEmpty
? null
: Text(_currentTask.description),
secondary:
IconButton(icon: Icon(Icons.settings), onPressed: widget.onEdit),
onChanged: _change,
);
}
void _change(bool value) async {
setState(() {
this._loading = true;
});
Task newTask = await _updateTask(_currentTask, value);
setState(() {
this._currentTask = newTask;
this._loading = false;
});
}
Future<Task> _updateTask(Task task, bool checked) {
// TODO use copyFrom
return VikunjaGlobal.of(context).taskService.update(Task(
id: task.id,
done: checked,
text: task.text,
description: task.description,
owner: null,
));
}
}
typedef Future<void> TaskChanged(Task task, bool newValue);

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/global.dart';
import 'package:vikunja_app/models/namespace.dart';
import 'package:vikunja_app/models/task.dart';
@ -18,35 +19,41 @@ class NamespaceFragment extends StatefulWidget {
class _NamespaceFragmentState extends State<NamespaceFragment> {
List<TaskList> _lists = [];
bool _loading = true;
@override
Widget build(BuildContext context) {
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(),
),
body: !this._loading
? RefreshIndicator(
child: 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(),
),
onRefresh: _updateLists,
)
: Center(child: CircularProgressIndicator()),
floatingActionButton: FloatingActionButton(
onPressed: () => _addListDialog(), child: const Icon(Icons.add)),
);
@ -65,11 +72,14 @@ class _NamespaceFragmentState extends State<NamespaceFragment> {
.then((_) => _updateLists());
}
_updateLists() {
VikunjaGlobal.of(context)
Future<void> _updateLists() {
return VikunjaGlobal.of(context)
.listService
.getByNamespace(widget.namespace.id)
.then((lists) => setState(() => this._lists = lists));
.then((lists) => setState(() {
this._lists = lists;
this._loading = false;
}));
}
_openList(BuildContext context, TaskList list) {
@ -78,37 +88,12 @@ class _NamespaceFragmentState extends State<NamespaceFragment> {
}
_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);
},
)
],
),
builder: (_) => AddDialog(
onAdd: _addList,
decoration: new InputDecoration(
labelText: 'List Name', hintText: 'eg. Shopping List')),
);
}

View File

@ -1,4 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vikunja_app/components/AddDialog.dart';
import 'package:vikunja_app/components/GravatarImage.dart';
import 'package:vikunja_app/fragments/namespace.dart';
import 'package:vikunja_app/fragments/placeholder.dart';
@ -19,6 +22,7 @@ class HomePageState extends State<HomePage> {
? _namespaces[_selectedDrawerIndex]
: null;
int _selectedDrawerIndex = -1;
bool _loading = true;
_getDrawerItemWidget(int pos) {
if (pos == -1) {
@ -33,38 +37,13 @@ class HomePageState extends State<HomePage> {
}
_addNamespaceDialog() {
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,
context: context,
builder: (_) => AddDialog(
onAdd: _addNamespace,
decoration: new InputDecoration(
labelText: 'Namespace', hintText: 'eg. Family Namespace'),
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) {
_addNamespace(textController.text);
}
Navigator.pop(context);
},
)
],
),
);
labelText: 'Namespace', hintText: 'eg. Personal Namespace'),
));
}
_addNamespace(String name) {
@ -74,9 +53,10 @@ class HomePageState extends State<HomePage> {
.then((_) => _updateNamespaces());
}
_updateNamespaces() {
VikunjaGlobal.of(context).namespaceService.getAll().then((result) {
Future<void> _updateNamespaces() {
return VikunjaGlobal.of(context).namespaceService.getAll().then((result) {
setState(() {
_loading = false;
_namespaces = result;
});
});
@ -121,11 +101,16 @@ class HomePageState extends State<HomePage> {
),
),
new Expanded(
child: ListView(
padding: EdgeInsets.zero,
children:
ListTile.divideTiles(context: context, tiles: drawerOptions)
.toList())),
child: this._loading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
child: ListView(
padding: EdgeInsets.zero,
children: ListTile.divideTiles(
context: context, tiles: drawerOptions)
.toList()),
onRefresh: _updateNamespaces,
)),
new Align(
alignment: FractionalOffset.bottomCenter,
child: new ListTile(

View File

@ -1,4 +1,8 @@
import 'dart:async';
import 'package:flutter/material.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/task.dart';
@ -12,117 +16,99 @@ class ListPage extends StatefulWidget {
}
class _ListPageState extends State<ListPage> {
TaskList items;
TaskList _items;
List<Task> _loadingTasks = [];
bool _loading = true;
@override
void initState() {
items = TaskList(
_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(items.title),
),
body: ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children: ListTile.divideTiles(
context: context,
tiles: items?.tasks?.map((task) => CheckboxListTile(
title: Text(task.text),
controlAffinity: ListTileControlAffinity.leading,
value: task.done ?? false,
subtitle: task.description == null
? null
: Text(task.description),
onChanged: (bool value) => _updateTask(task, value),
)) ??
[])
.toList(),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _addItemDialog(), child: Icon(Icons.add)),
);
}
@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());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text(_items.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => {/* TODO add edit list functionality */},
)
],
),
body: !this._loading
? RefreshIndicator(
child: ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children:
ListTile.divideTiles(context: context, tiles: _listTasks())
.toList(),
),
onRefresh: _updateList,
)
: Center(child: CircularProgressIndicator()),
floatingActionButton: FloatingActionButton(
onPressed: () => _addItemDialog(), child: Icon(Icons.add)),
);
}
_updateList() {
VikunjaGlobal.of(context).listService.get(widget.taskList.id).then((tasks) {
List<Widget> _listTasks() {
var tasks = (_items?.tasks?.map(_buildTile) ?? []).toList();
tasks.addAll(_loadingTasks.map(_buildLoadingTile));
return tasks;
}
TaskTile _buildTile(Task task) {
return TaskTile(task: task, loading: false);
}
TaskTile _buildLoadingTile(Task task) {
return TaskTile(
task: task,
loading: true,
);
}
Future<void> _updateList() {
return VikunjaGlobal.of(context)
.listService
.get(widget.taskList.id)
.then((tasks) {
setState(() {
items = tasks;
_loading = false;
_items = tasks;
});
});
}
_addItemDialog() {
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 Item', hintText: 'eg. Milk'),
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) _addItem(textController.text);
Navigator.pop(context);
},
)
],
),
);
context: context,
builder: (_) => AddDialog(
onAdd: _addItem,
decoration: new InputDecoration(
labelText: 'List Item', hintText: 'eg. Milk')));
}
_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) {
var newTask =
Task(id: null, text: name, owner: globalState.currentUser, done: false);
setState(() => _loadingTasks.add(newTask));
globalState.taskService.add(_items.id, newTask).then((task) {
setState(() {
items.tasks.add(task);
_items.tasks.add(task);
});
}).then((_) => _updateList());
}).then((_) => _updateList()
.then((_) => setState(() => _loadingTasks.remove(newTask))));
}
}

View File

@ -99,7 +99,6 @@ class _LoginPageState extends State<LoginPage> {
await vGlobal.newLoginService(_server).login(_username, _password);
vGlobal.changeUser(newUser.user, token: newUser.token, base: _server);
} catch (ex) {
print(ex);
showDialog(
context: context,
builder: (context) => new AlertDialog(