From 9231772185ccc1937abce303b9a27bfde27b9e45 Mon Sep 17 00:00:00 2001 From: Aleksandr Borisenko Date: Thu, 10 Jun 2021 10:58:25 +0300 Subject: [PATCH] ListProvider from PR39 with fixes --- lib/components/TaskTile.dart | 14 +-- lib/components/datetimePicker.dart | 2 +- lib/models/task.dart | 20 ++-- lib/pages/list/list.dart | 172 +++++++++++++++-------------- lib/stores/list_store.dart | 38 ++++++- 5 files changed, 141 insertions(+), 105 deletions(-) diff --git a/lib/components/TaskTile.dart b/lib/components/TaskTile.dart index 156ec67..4c6a372 100644 --- a/lib/components/TaskTile.dart +++ b/lib/components/TaskTile.dart @@ -47,10 +47,9 @@ class TaskTileState extends State { )), ), title: Text(widget.task.title), - subtitle: - widget.task.description == null || widget.task.description.isEmpty - ? null - : Text(widget.task.description), + subtitle: widget.task.description == null || widget.task.description.isEmpty + ? null + : Text(widget.task.description), trailing: IconButton( icon: Icon(Icons.settings), onPressed: () => widget.onEdit, @@ -61,10 +60,9 @@ class TaskTileState extends State { title: Text(widget.task.title), controlAffinity: ListTileControlAffinity.leading, value: widget.task.done ?? false, - subtitle: - widget.task.description == null || widget.task.description.isEmpty - ? null - : Text(widget.task.description), + subtitle: widget.task.description == null || widget.task.description.isEmpty + ? null + : Text(widget.task.description), secondary: IconButton( icon: Icon(Icons.settings), onPressed: widget.onEdit, diff --git a/lib/components/datetimePicker.dart b/lib/components/datetimePicker.dart index a1b816b..9a72782 100644 --- a/lib/components/datetimePicker.dart +++ b/lib/components/datetimePicker.dart @@ -43,7 +43,7 @@ class VikunjaDateTimePicker extends StatelessWidget { return showDatePicker( context: context, firstDate: DateTime(1900), - initialDate: currentValue ?? DateTime.now(), + initialDate: currentValue.millisecondsSinceEpoch > 0 ? currentValue : DateTime.now(), lastDate: DateTime(2100)); }, ); diff --git a/lib/models/task.dart b/lib/models/task.dart index 5d00613..3dcca6e 100644 --- a/lib/models/task.dart +++ b/lib/models/task.dart @@ -60,25 +60,25 @@ class Task { ?.toList(), updated = DateTime.parse(json['updated']), created = DateTime.parse(json['created']), - createdBy = User.fromJson(json['created_by']); + createdBy = json['created_by'] == null ? null : User.fromJson(json['created_by']); toJSON() => { 'id': id, 'title': title, 'description': description, 'done': done ?? false, - 'reminderDates': reminderDates - ?.map((date) => datetimeToUnixTimestamp(date)) + 'reminder_dates': reminderDates + ?.map((date) => date?.toIso8601String()) ?.toList(), - 'dueDate': datetimeToUnixTimestamp(dueDate), - 'startDate': datetimeToUnixTimestamp(startDate), - 'endDate': datetimeToUnixTimestamp(endDate), + 'due_date': dueDate?.toIso8601String(), + 'start_date': startDate?.toIso8601String(), + 'end_date': endDate?.toIso8601String(), 'priority': priority, - 'repeatAfter': repeatAfter?.inSeconds, + 'repeat_after': repeatAfter?.inSeconds, 'labels': labels?.map((label) => label.toJSON())?.toList(), 'subtasks': subtasks?.map((subtask) => subtask.toJSON())?.toList(), - 'createdBy': createdBy?.toJSON(), - 'updated': datetimeToUnixTimestamp(updated), - 'created': datetimeToUnixTimestamp(created), + 'created_by': createdBy?.toJSON(), + 'updated': updated?.toIso8601String(), + 'created': created?.toIso8601String(), }; } diff --git a/lib/pages/list/list.dart b/lib/pages/list/list.dart index 8c07341..c84fe7f 100644 --- a/lib/pages/list/list.dart +++ b/lib/pages/list/list.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:vikunja_app/components/AddDialog.dart'; import 'package:vikunja_app/components/TaskTile.dart'; import 'package:vikunja_app/global.dart'; @@ -8,6 +9,8 @@ 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/task_edit.dart'; +import 'package:vikunja_app/stores/list_store.dart'; + class ListPage extends StatefulWidget { final TaskList taskList; @@ -21,7 +24,7 @@ class ListPage extends StatefulWidget { class _ListPageState extends State { TaskList _list; List _loadingTasks = []; - bool _loading = true; + int _currentPage = 1; @override void initState() { @@ -30,57 +33,71 @@ class _ListPageState extends State { title: widget.taskList.title, tasks: [], ); + Future.microtask(() => _loadList()); super.initState(); } - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _loadList(); - } - @override Widget build(BuildContext context) { - var tasks = (_list?.tasks?.map(_buildTile) ?? []).toList(); - tasks.addAll(_loadingTasks.map(_buildLoadingTile)); - + final taskState = Provider.of(context); return Scaffold( - appBar: AppBar( - title: Text(_list.title), - actions: [ - IconButton( - icon: Icon(Icons.edit), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ListEditPage( - list: _list, + appBar: AppBar( + title: Text(_list.title), + actions: [ + IconButton( + icon: Icon(Icons.edit), + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ListEditPage( + list: _list, + ), + ) ), - ) ), - ), - ], - ), - body: !this._loading - ? RefreshIndicator( - onRefresh: _loadList, - child: _list.tasks.length > 0 - ? ListView( - padding: EdgeInsets.symmetric(vertical: 8.0), - children: ListTile.divideTiles( - context: context, - tiles: tasks, - ).toList(), - ) - : Center(child: Text('This list is empty.')), - ) - : Center(child: CircularProgressIndicator()), - floatingActionButton: Builder( - builder: (context) => FloatingActionButton( - onPressed: () => _addItemDialog(context), - child: Icon(Icons.add), + ], ), - ), + // TODO: it brakes the flow with _loadingTasks and conflicts with the provider + body: !taskState.isLoading + ? RefreshIndicator( + child: taskState.tasks.length > 0 + ? ListenableProvider.value( + value: taskState, + child: ListView.builder( + padding: EdgeInsets.symmetric(vertical: 8.0), + itemBuilder: (context, i) { + if (i.isOdd) return Divider(); + + if (_loadingTasks.isNotEmpty) { + final loadingTask = _loadingTasks.removeLast(); + return _buildLoadingTile(loadingTask); + } + + 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 + ? _buildTile(taskState.tasks[index]) + : null; + } + ), + ) + : Center(child: Text('This list is empty.')), + onRefresh: _loadList, + ) + : Center(child: CircularProgressIndicator()), + floatingActionButton: Builder( + builder: (context) => FloatingActionButton( + onPressed: () => _addItemDialog(context), child: Icon(Icons.add)), + ), ); } @@ -97,21 +114,10 @@ class _ListPageState extends State { ), ), onMarkedAsDone: (done) { - VikunjaGlobal.of(context) - .taskService - .update(Task( - id: task.id, - done: done, - ) - ).then((newTask) => setState(() { - // FIXME: This is ugly. We should use a redux to not have to do these kind of things. - // This is enough for now (it works™) but we should definitly fix it later. - _list.tasks.asMap().forEach((i, t) { - if (newTask.id == t.id) { - _list.tasks[i] = newTask; - } - }); - }) + Provider.of(context, listen: false).updateTask( + context: context, + id: task.id, + done: done, ); }, ); @@ -132,33 +138,32 @@ class _ListPageState extends State { ); } - Future _loadList() { - return VikunjaGlobal.of(context) - .listService - .get(widget.taskList.id) - .then((list) { - setState(() { - _loading = false; - _list = list; - }); - }); + Future _loadList() async { + _loadTasksForPage(1); + } + + void _loadTasksForPage(int page) { + Provider.of(context, listen: false).loadTasks( + context: context, + listId: _list.id, + page: page, + ); } _addItemDialog(BuildContext context) { showDialog( - context: context, - builder: (_) => AddDialog( - onAdd: (name) => _addItem(name, context), - decoration: new InputDecoration( - labelText: 'Task Name', - hintText: 'eg. Milk', - ) - ) + context: context, + builder: (_) => AddDialog( + onAdd: (title) => _addItem(title, context), + decoration: InputDecoration( + labelText: 'Task Name', + hintText: 'eg. Milk', + ), + ), ); } _addItem(String title, BuildContext context) { - // FIXME: Use provider var globalState = VikunjaGlobal.of(context); var newTask = Task( id: null, @@ -167,13 +172,12 @@ class _ListPageState extends State { done: false, ); setState(() => _loadingTasks.add(newTask)); - globalState.taskService.add(_list.id, newTask).then((task) { - setState(() { - _list.tasks.add(task); - }); - }).then((_) { - _loadList(); - setState(() => _loadingTasks.remove(newTask)); + Provider.of(context, listen: false) + .addTask( + context: context, + newTask: newTask, + listId: _list.id, + ).then((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('The task was added successfully!'), )); diff --git a/lib/stores/list_store.dart b/lib/stores/list_store.dart index dcb2a36..70f00a0 100644 --- a/lib/stores/list_store.dart +++ b/lib/stores/list_store.dart @@ -21,6 +21,7 @@ class ListProvider with ChangeNotifier { List get tasks => _tasks; void loadTasks({BuildContext context, int listId, int page = 1}) { + _tasks = []; _isLoading = true; notifyListeners(); @@ -38,10 +39,14 @@ class ListProvider with ChangeNotifier { }); } - Future addTask({BuildContext context, String title, int listId}) { + Future addTaskByTitle({BuildContext context, String title, int listId}) { var globalState = VikunjaGlobal.of(context); var newTask = Task( - id: null, title: title, createdBy: globalState.currentUser, done: false); + id: null, + title: title, + createdBy: globalState.currentUser, + done: false, + ); _isLoading = true; notifyListeners(); @@ -51,4 +56,33 @@ class ListProvider with ChangeNotifier { notifyListeners(); }); } + + Future addTask({BuildContext context, Task newTask, int listId}) { + var globalState = VikunjaGlobal.of(context); + _isLoading = true; + notifyListeners(); + + return globalState.taskService.add(listId, newTask).then((task) { + _tasks.insert(0, task); + _isLoading = false; + notifyListeners(); + }); + } + + Future updateTask({BuildContext context, int id, bool done}) { + var globalState = VikunjaGlobal.of(context); + globalState.taskService.update(Task( + id: id, + done: done, + )).then((task) { + // FIXME: This is ugly. We should use a redux to not have to do these kind of things. + // This is enough for now (it works™) but we should definitly fix it later. + _tasks.asMap().forEach((i, t) { + if (task.id == t.id) { + _tasks[i] = task; + } + }); + notifyListeners(); + }); + } }