330 lines
10 KiB
Dart
330 lines
10 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/ui/task/kanban/kanban_card.dart';
|
|
import 'package:invoiceninja_flutter/ui/task/kanban/kanban_status.dart';
|
|
import 'package:invoiceninja_flutter/ui/task/kanban/kanban_view_vm.dart';
|
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
|
import 'package:invoiceninja_flutter/utils/completers.dart';
|
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
|
|
|
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<String> _statuses;
|
|
TaskEntity _newTask;
|
|
Map<String, List<String>> _tasks;
|
|
bool isDragging = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_initBoard();
|
|
|
|
/*
|
|
WidgetsBinding.instance.addPostFrameCallback((duration) {
|
|
_checkBoard();
|
|
});
|
|
*/
|
|
}
|
|
|
|
void _initBoard() {
|
|
final viewModel = widget.viewModel;
|
|
final state = viewModel.state;
|
|
|
|
_statuses = state.taskStatusState.list
|
|
.where((statusId) => state.taskStatusState.get(statusId).isActive)
|
|
.toList();
|
|
|
|
_statuses.sort((statusIdA, statusIdB) {
|
|
final statusA = state.taskStatusState.get(statusIdA);
|
|
final statusB = state.taskStatusState.get(statusIdB);
|
|
if (statusA.statusOrder == statusB.statusOrder) {
|
|
return statusB.updatedAt.compareTo(statusA.updatedAt);
|
|
} else {
|
|
return (statusA.statusOrder ?? 99999)
|
|
.compareTo(statusB.statusOrder ?? 99999);
|
|
}
|
|
});
|
|
|
|
_tasks = {};
|
|
|
|
viewModel.taskList.forEach((taskId) {
|
|
final task = state.taskState.map[taskId];
|
|
final status = state.taskStatusState.get(task.statusId);
|
|
final statusId = status.isNew ? '' : status.id;
|
|
if (!_tasks.containsKey(statusId)) {
|
|
_tasks[statusId] = [];
|
|
}
|
|
_tasks[statusId].add(task.id);
|
|
});
|
|
|
|
_tasks.forEach((key, value) {
|
|
_tasks[key].sort((taskIdA, taskIdB) {
|
|
final taskA = state.taskState.get(taskIdA);
|
|
final taskB = state.taskState.get(taskIdB);
|
|
if (taskA.statusOrder == taskB.statusOrder) {
|
|
return taskB.updatedAt.compareTo(taskA.updatedAt);
|
|
} else {
|
|
return (taskA.statusOrder ?? 99999)
|
|
.compareTo(taskB.statusOrder ?? 99999);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/*
|
|
void _checkBoard() {
|
|
final viewModel = widget.viewModel;
|
|
final state = viewModel.state;
|
|
|
|
final filteredStatusIds = _statuses.where((statusId) {
|
|
return statusId.isNotEmpty || _tasks.containsKey(statusId);
|
|
}).toList();
|
|
|
|
bool isCorrect = true;
|
|
|
|
filteredStatusIds.forEach((statusId) {
|
|
final status = state.taskStatusState.get(statusId);
|
|
|
|
if (status.statusOrder != filteredStatusIds.indexOf(status.id)) {
|
|
isCorrect = false;
|
|
}
|
|
|
|
(_tasks[status.id] ?? []).forEach((taskId) {
|
|
final task = state.taskState.get(taskId);
|
|
|
|
if (task.statusOrder != _tasks[status.id].indexOf(task.id) ||
|
|
task.statusId != statusId) {
|
|
isCorrect = false;
|
|
}
|
|
});
|
|
});
|
|
|
|
if (!isCorrect) {
|
|
_onBoardChanged();
|
|
}
|
|
}
|
|
*/
|
|
|
|
void _onBoardChanged() {
|
|
final localization = AppLocalization.of(context);
|
|
final completer =
|
|
snackBarCompleter<Null>(context, localization.updatedTaskStatus);
|
|
completer.future.catchError((Object error) {
|
|
_initBoard();
|
|
});
|
|
|
|
widget.viewModel.onBoardChanged(completer, _statuses, _tasks);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final state = widget.viewModel.state;
|
|
final color = state.prefState.enableDarkMode
|
|
? Theme.of(context).cardColor
|
|
: Colors.grey.shade300;
|
|
|
|
final filteredStatusIds = _statuses.where((statusId) {
|
|
return statusId.isNotEmpty || _tasks.containsKey(statusId);
|
|
}).toList();
|
|
|
|
final boardList = filteredStatusIds.map((statusId) {
|
|
final status = state.taskStatusState.get(statusId);
|
|
final hasCorectOrder = statusId.isEmpty ||
|
|
status.statusOrder == filteredStatusIds.indexOf(status.id);
|
|
|
|
return BoardList(
|
|
draggable: status.isOld,
|
|
backgroundColor: color,
|
|
headerBackgroundColor: color,
|
|
onDropList: (endIndex, startIndex) {
|
|
if (endIndex == startIndex) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
final status = _statuses[startIndex];
|
|
_statuses.removeAt(startIndex);
|
|
_statuses = [
|
|
..._statuses.sublist(0, endIndex),
|
|
status,
|
|
..._statuses.sublist(endIndex),
|
|
];
|
|
});
|
|
|
|
_onBoardChanged();
|
|
},
|
|
header: [
|
|
Expanded(
|
|
child: KanbanStatusCard(
|
|
status: status,
|
|
isSaving: state.isSaving,
|
|
isCorrectOrder: hasCorectOrder,
|
|
onSavePressed: (completer, name) {
|
|
final statusOrder = _statuses.indexOf(statusId);
|
|
widget.viewModel.onSaveStatusPressed(
|
|
completer, statusId, name, statusOrder);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
footer: Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: _newTask?.statusId == status.id
|
|
? KanbanTaskCard(
|
|
task: _newTask,
|
|
isSaving: state.isSaving,
|
|
isDragging: isDragging,
|
|
onSavePressed: (completer, description) {
|
|
final statusOrder = _tasks[status.id]?.length ?? 0;
|
|
widget.viewModel.onSaveTaskPressed(
|
|
completer,
|
|
_newTask.id,
|
|
status.id,
|
|
description,
|
|
statusOrder,
|
|
);
|
|
},
|
|
onCancelPressed: () {
|
|
setState(() {
|
|
_newTask = null;
|
|
});
|
|
},
|
|
)
|
|
: Padding(
|
|
padding: const EdgeInsets.only(left: 8, top: 2, bottom: 4),
|
|
child: TextButton(
|
|
child: Text(AppLocalization.of(context).newTask),
|
|
onPressed: () {
|
|
setState(() {
|
|
_newTask = TaskEntity(state: widget.viewModel.state)
|
|
.rebuild((b) => b..statusId = status.id);
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
items: (_tasks[status.id] ?? [])
|
|
.map((taskId) => widget.viewModel.state.taskState.get(taskId))
|
|
.map(
|
|
(task) {
|
|
final isVisible =
|
|
widget.viewModel.filteredTaskList.contains(task.id) ||
|
|
task.isNew;
|
|
return BoardItem(
|
|
draggable: task.isOld,
|
|
item: !isVisible
|
|
? SizedBox()
|
|
: KanbanTaskCard(
|
|
task: task,
|
|
isSaving: state.isSaving,
|
|
isDragging: isDragging,
|
|
isSelected: (state.uiState.isEditing
|
|
? state.taskUIState.editingId
|
|
: state.taskUIState.selectedId) ==
|
|
task.id,
|
|
isCorrectOrder: (task.statusOrder ==
|
|
_tasks[status.id].indexOf(task.id)) &&
|
|
task.statusId == statusId,
|
|
onSavePressed: (completer, description) {
|
|
final statusOrder = _tasks[status.id].indexOf(task.id);
|
|
widget.viewModel.onSaveTaskPressed(
|
|
completer,
|
|
task.id,
|
|
status.id,
|
|
description,
|
|
statusOrder,
|
|
);
|
|
},
|
|
onCancelPressed: () {
|
|
if (task.isNew) {
|
|
setState(() {
|
|
_newTask = null;
|
|
});
|
|
}
|
|
},
|
|
),
|
|
onStartDragItem: (listIndex, itemIndex, state) {
|
|
setState(() => isDragging = true);
|
|
},
|
|
/*
|
|
onDragItem: (oldListIndex, oldItemIndex, newListIndex,
|
|
newItemIndex, state) {
|
|
setState(() => _isDragging = true);
|
|
},
|
|
*/
|
|
onDropItem: (
|
|
int listIndex,
|
|
int itemIndex,
|
|
int oldListIndex,
|
|
int oldItemIndex,
|
|
BoardItemState state,
|
|
) {
|
|
setState(() => isDragging = false);
|
|
|
|
if (listIndex == oldListIndex && itemIndex == oldItemIndex) {
|
|
return;
|
|
}
|
|
|
|
final oldStatusId = _statuses[oldListIndex];
|
|
final newStatusId = _statuses[listIndex];
|
|
final taskId = _tasks[status.id][oldItemIndex];
|
|
|
|
setState(() {
|
|
if (_tasks.containsKey(oldStatusId) &&
|
|
_tasks[oldStatusId].contains(taskId)) {
|
|
_tasks[oldStatusId].remove(taskId);
|
|
}
|
|
|
|
if (!_tasks.containsKey(newStatusId)) {
|
|
_tasks[newStatusId] = [];
|
|
}
|
|
|
|
_tasks[newStatusId] = [
|
|
..._tasks[newStatusId].sublist(0, itemIndex),
|
|
taskId,
|
|
..._tasks[newStatusId].sublist(itemIndex),
|
|
];
|
|
});
|
|
|
|
_onBoardChanged();
|
|
},
|
|
);
|
|
},
|
|
).toList(),
|
|
);
|
|
}).toList();
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Stack(
|
|
alignment: Alignment.topCenter,
|
|
children: [
|
|
BoardView(
|
|
boardViewController: _boardViewController,
|
|
lists: boardList,
|
|
dragDelay: 1,
|
|
),
|
|
if (state.isLoading || state.isSaving) LinearProgressIndicator(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|