249 lines
7.5 KiB
Dart
249 lines
7.5 KiB
Dart
import 'package:boardview/board_item.dart';
|
|
import 'package:boardview/board_list.dart';
|
|
import 'package:boardview/boardview.dart';
|
|
import 'package:boardview/boardview_controller.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:invoiceninja_flutter/data/models/entities.dart';
|
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/history_drawer_vm.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/menu_drawer_vm.dart';
|
|
import 'package:invoiceninja_flutter/ui/task/kanban_view_vm.dart';
|
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
|
import 'package:invoiceninja_flutter/utils/platforms.dart';
|
|
import 'package:timeago/timeago.dart' as timeago;
|
|
|
|
class KanbanView extends StatefulWidget {
|
|
const KanbanView({
|
|
Key key,
|
|
@required this.viewModel,
|
|
}) : super(key: key);
|
|
|
|
final KanbanVM viewModel;
|
|
|
|
@override
|
|
_KanbanViewState createState() => _KanbanViewState();
|
|
}
|
|
|
|
class _KanbanViewState extends State<KanbanView> {
|
|
final _boardViewController = new BoardViewController();
|
|
|
|
List<TaskStatusEntity> _statuses = [];
|
|
Map<String, List<TaskEntity>> _tasks = {};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
print('## initState: ${_statuses.length}');
|
|
|
|
final state = widget.viewModel.state;
|
|
_statuses = state.taskStatusState.list
|
|
.map((statusId) => state.taskStatusState.get(statusId))
|
|
.where((status) => status.isActive)
|
|
.toList();
|
|
_statuses.sort((statusA, statusB) {
|
|
if (statusA.statusOrder == statusB.statusOrder) {
|
|
return statusB.updatedAt.compareTo(statusA.updatedAt);
|
|
} else {
|
|
return (statusA.statusOrder ?? 99999)
|
|
.compareTo(statusB.statusOrder ?? 99999);
|
|
}
|
|
});
|
|
|
|
state.taskState.list.forEach((taskId) {
|
|
final task = state.taskState.map[taskId];
|
|
if (task.isActive && task.statusId.isNotEmpty) {
|
|
final status = state.taskStatusState.get(task.statusId);
|
|
if (!_tasks.containsKey(status.id)) {
|
|
_tasks[status.id] = [];
|
|
}
|
|
_tasks[status.id].add(task);
|
|
}
|
|
});
|
|
|
|
_tasks.forEach((key, value) {
|
|
_tasks[key].sort((taskA, taskB) {
|
|
if (taskA.statusOrder == taskB.statusOrder) {
|
|
return taskB.updatedAt.compareTo(taskA.updatedAt);
|
|
} else {
|
|
return (taskA.statusOrder ?? 99999)
|
|
.compareTo(taskB.statusOrder ?? 99999);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
print('## BUILD: ${_statuses.length}');
|
|
final state = widget.viewModel.state;
|
|
|
|
final boardList = _statuses.map((status) {
|
|
return BoardList(
|
|
backgroundColor: Theme.of(context).cardColor,
|
|
headerBackgroundColor: Theme.of(context).cardColor,
|
|
onDropList: (endIndex, startIndex) {
|
|
if (endIndex == startIndex) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
final status = _statuses[startIndex];
|
|
_statuses.removeAt(startIndex);
|
|
_statuses = [
|
|
..._statuses.sublist(0, endIndex),
|
|
status,
|
|
..._statuses.sublist(endIndex),
|
|
];
|
|
});
|
|
|
|
widget.viewModel.onStatusOrderChanged(context, status.id, endIndex);
|
|
},
|
|
header: [
|
|
Expanded(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(8),
|
|
child: Text(
|
|
'${status.statusOrder} - ${status.name} - ${timeago.format(DateTime.fromMillisecondsSinceEpoch(status.updatedAt * 1000))}'),
|
|
),
|
|
),
|
|
],
|
|
footer: Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 8),
|
|
child: TextButton(
|
|
child: Text(AppLocalization.of(context).newTask),
|
|
onPressed: () {
|
|
//
|
|
},
|
|
),
|
|
),
|
|
),
|
|
items: (_tasks[status.id] ?? [])
|
|
.map(
|
|
(task) => BoardItem(
|
|
item: _TaskCard(task: task),
|
|
onDropItem: (
|
|
int listIndex,
|
|
int itemIndex,
|
|
int oldListIndex,
|
|
int oldItemIndex,
|
|
BoardItemState state,
|
|
) {
|
|
if (listIndex == oldListIndex && itemIndex == oldItemIndex) {
|
|
return;
|
|
}
|
|
|
|
final oldStatus = _statuses[oldListIndex];
|
|
final newStatus = _statuses[listIndex];
|
|
final task = _tasks[status.id][oldItemIndex];
|
|
|
|
setState(() {
|
|
if (_tasks.containsKey(oldStatus.id) &&
|
|
_tasks[oldStatus.id].contains(task)) {
|
|
_tasks[oldStatus.id].remove(task);
|
|
}
|
|
|
|
if (!_tasks.containsKey(newStatus.id)) {
|
|
_tasks[newStatus.id] = [];
|
|
}
|
|
|
|
_tasks[newStatus.id] = [
|
|
..._tasks[newStatus.id].sublist(0, itemIndex),
|
|
task,
|
|
..._tasks[newStatus.id].sublist(itemIndex),
|
|
];
|
|
});
|
|
|
|
widget.viewModel.onTaskOrderChanged(
|
|
context, task.id, newStatus.id, itemIndex);
|
|
},
|
|
),
|
|
)
|
|
.toList(),
|
|
);
|
|
}).toList();
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: BoardView(
|
|
boardViewController: _boardViewController,
|
|
lists: boardList,
|
|
dragDelay: 1,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _TaskCard extends StatefulWidget {
|
|
const _TaskCard({this.task});
|
|
final TaskEntity task;
|
|
|
|
@override
|
|
__TaskCardState createState() => __TaskCardState();
|
|
}
|
|
|
|
class __TaskCardState extends State<_TaskCard> {
|
|
bool _isEditing = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final localization = AppLocalization.of(context);
|
|
|
|
if (_isEditing) {
|
|
return Card(
|
|
color: Theme.of(context).backgroundColor,
|
|
child: Column(
|
|
children: [
|
|
DecoratedFormField(
|
|
autofocus: true,
|
|
initialValue: widget.task.description,
|
|
minLines: 4,
|
|
maxLines: 4,
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () => setState(() => _isEditing = false),
|
|
child: Text(localization.cancel),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
//
|
|
},
|
|
child: Text(localization.view),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
//
|
|
},
|
|
child: Text(localization.save),
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return InkWell(
|
|
child: Card(
|
|
color: Theme.of(context).backgroundColor,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Text(
|
|
'${widget.task.statusOrder} - ${widget.task.id} - ${timeago.format(DateTime.fromMillisecondsSinceEpoch(widget.task.updatedAt * 1000))}'),
|
|
),
|
|
),
|
|
onTap: () {
|
|
setState(() {
|
|
_isEditing = true;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|