💄 ⚗️ Fix #4 Create an observable list and implement project view #9
Merged
alexis.drai
merged 4 commits from genesis/implement-master
into main
2 years ago
@ -1,39 +1,78 @@
|
||||
|
||||
# Oh the things you'll do
|
||||
|
||||
- [Model](#model)
|
||||
- [View](#view)
|
||||
- [Auteur](#auteur)
|
||||
- [Descriptif](#descriptif)
|
||||
- [Usage](#usage)
|
||||
- [Techniques de programmation utilisées](#techniques-de-programmation-utilisées)
|
||||
- ["master" : SilicaListView](#master--silicalistview)
|
||||
- ["detail": champs modifiables avec validation](#detail--champs-modifiables-avec-validation)
|
||||
|
||||
## Auteur
|
||||
|
||||
- [Alexis Drai](https://codefirst.iut.uca.fr/git/alexis.drai)
|
||||
|
||||
## Descriptif
|
||||
|
||||
## Model
|
||||
*Oh the things you'll do* est une app the choses à faire. Elle affiche une liste de `tasks` qui appartiennent à un `project`.
|
||||
|
||||
The model will include Projects and Tasks. A Project owns a collection of Tasks (but Tasks can exist outside of projects too),
|
||||
and a Project has a title. A Task has a title, an optional description, and an integer priority level between 0 and 3, zero being critical.
|
||||
On peut faire des opérations CRUD sur les `tasks`.
|
||||
|
||||
Un projet a un titre et des `tasks`.
|
||||
|
||||
Une `task` a un titre, une description, et un niveau de priorité entre `0` et `3` (`0` étant le plus critique).
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Project {
|
||||
+String title
|
||||
+List<Task> tasks
|
||||
}
|
||||
|
||||
class Task {
|
||||
+String title
|
||||
+String description
|
||||
+int priority
|
||||
}
|
||||
|
||||
class ObservableTaskList {
|
||||
+addTask(Task*)
|
||||
+removeTask(int)
|
||||
+updateTask(int, Task*)
|
||||
+rowCount(QModelIndex) const
|
||||
+data(QModelIndex, int) const
|
||||
+setData(QModelIndex, QVariant, int)
|
||||
+flags(QModelIndex) const
|
||||
+removeRows(int, int, QModelIndex)
|
||||
+insertRows(int, int, QModelIndex)
|
||||
+roleNames() const
|
||||
}
|
||||
|
||||
Project --> "*" Task
|
||||
Project --> ObservableTaskList : tasks
|
||||
ObservableTaskList --> "*" Task : m_tasks
|
||||
QAbstractListModel <|-- ObservableTaskList
|
||||
|
||||
```
|
||||
|
||||
## View
|
||||
## Usage
|
||||
|
||||
There will be a collection of projects on the home page. Clicking on a project will navigate the user to the project detail page,
|
||||
where they will find the project title on top and a collection of tasks. Clicking on a task will navigate the user to the task detail page,
|
||||
where they will find the task title, the task description if any, and the task priority represented with a label of a certain color next to
|
||||
the words 'priority X' where X is the priority level.
|
||||
Il suffit de lancer l'application, et vous pouvez tester les opérations CRUD sur les `tasks`.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
A[Home Page] --> B[Project Detail Page]
|
||||
B --> C[Task Detail Page]
|
||||
```
|
||||
Les boutons disponibles sont décrits [plus bas](#techniques-de-programmation-utilisées).
|
||||
|
||||
## Techniques de programmation utilisées
|
||||
|
||||
### "master" : SilicaListView
|
||||
La liste présente sur la page principale est une `ObservableTaskList`, une redéfinition maison de
|
||||
`QAbstractListModel`. Ainsi, l'app peut observer la liste, et afficher tous les changements qu'on lui inflige.
|
||||
|
||||
On y voit:
|
||||
* un bouton `Create` en haut de l'écran pour créer des `tasks`
|
||||
* un bouton `Delete` en dessous de chaque `task` pour l'effacer
|
||||
* un bouton `GoTo` en dessous de chaque `task` pour accéder à ses détails
|
||||
|
||||
<img src="./docs/main.png" width="410" style="margin:20px" alt="">
|
||||
|
||||
### "detail" : champs modifiables avec validation
|
||||
Le titre et la description de la `task` sont éditables. C'est aussi le cas pour la priorité -- et l'utilisateur ne peut entrer qu'un entier entre `0` (le plus critique) et `3` (le moins critique).
|
||||
|
||||
<img src="./docs/detail.png" width="410" style="margin:20px" alt="">
|
||||
|
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 78 KiB |
@ -0,0 +1,84 @@
|
||||
import QtQuick 2.0
|
||||
import Sailfish.Silica 1.0
|
||||
import fr.uca.iut 1.0
|
||||
|
||||
Page {
|
||||
id: projectView
|
||||
|
||||
SilicaFlickable {
|
||||
anchors.fill: parent
|
||||
|
||||
TextField {
|
||||
id: titleField
|
||||
text: projectModel.title
|
||||
label: qsTr("Project Title")
|
||||
font.pixelSize: Theme.fontSizeExtraLarge
|
||||
onTextChanged: projectModel.title = text
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: addButton
|
||||
text: "Create"
|
||||
onClicked: {
|
||||
projectModel.tasks.insertRows(0, 1);
|
||||
// FIXME this does insert a new task in the list, but the task title doesn't get displayed
|
||||
}
|
||||
anchors {
|
||||
top: titleField.bottom
|
||||
left: parent.left
|
||||
}
|
||||
}
|
||||
|
||||
SilicaListView {
|
||||
id: tasksList
|
||||
model: projectModel.tasks
|
||||
anchors {
|
||||
top: addButton.bottom
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
delegate: ListItem {
|
||||
width: ListView.view.width
|
||||
height: Theme.itemSizeLarge
|
||||
|
||||
Label {
|
||||
id: label
|
||||
text: model.title
|
||||
color: model.priority === 0 ? "red" :
|
||||
model.priority === 1 ? "orange" :
|
||||
model.priority === 2 ? "blue" : Theme.primaryColor
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
}
|
||||
|
||||
Button {
|
||||
id: navButton
|
||||
anchors {
|
||||
top: label.bottom
|
||||
left: parent.left
|
||||
}
|
||||
height: Theme.itemSizeSmall
|
||||
text: "GoTo"
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("TaskView.qml"), {taskModel: model})
|
||||
}
|
||||
|
||||
Button {
|
||||
id: deleteButton
|
||||
anchors {
|
||||
top: label.bottom
|
||||
left: navButton.right
|
||||
}
|
||||
height: Theme.itemSizeSmall
|
||||
text: "Delete"
|
||||
onClicked: projectModel.tasks.removeRows(index, 1)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
console.log("New ListItem created. Task title: " + model.title + " -- Priority: " + model.priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
#include "ObservableTaskList.h"
|
||||
|
||||
ObservableTaskList::ObservableTaskList(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ObservableTaskList::addTask(Task* task)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_tasks << task;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ObservableTaskList::removeTask(int index)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_tasks.removeAt(index);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ObservableTaskList::updateTask(int index, Task* task)
|
||||
{
|
||||
m_tasks[index] = task;
|
||||
emit dataChanged(this->index(index), this->index(index), {TitleRole, DescriptionRole, PriorityRole});
|
||||
}
|
||||
|
||||
int ObservableTaskList::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return m_tasks.count();
|
||||
}
|
||||
|
||||
QVariant ObservableTaskList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= m_tasks.count())
|
||||
return QVariant();
|
||||
|
||||
Task* task = m_tasks[index.row()];
|
||||
if (role == TitleRole)
|
||||
return task->title();
|
||||
else if (role == DescriptionRole)
|
||||
return task->description();
|
||||
else if (role == PriorityRole)
|
||||
return task->priority();
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool ObservableTaskList::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= m_tasks.count())
|
||||
return false;
|
||||
|
||||
if ( data(index, role) == value)
|
||||
return true;
|
||||
|
||||
Task* task = m_tasks[index.row()];
|
||||
if (role == TitleRole)
|
||||
task->setTitle(value.toString());
|
||||
else if (role == DescriptionRole)
|
||||
task->setDescription(value.toString());
|
||||
else if (role == PriorityRole)
|
||||
task->setPriority(value.toInt());
|
||||
|
||||
emit dataChanged(index, index, QVector<int>() << role);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags ObservableTaskList::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
return Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
bool ObservableTaskList::insertRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
beginInsertRows(parent, row, row + count - 1);
|
||||
for (int nb = 0; nb < count; ++nb) {
|
||||
Task* newTask = new Task("no title", "no description", 3);
|
||||
m_tasks.insert(row, newTask);
|
||||
}
|
||||
endInsertRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ObservableTaskList::removeRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
if (row < 0 || row + count > m_tasks.count())
|
||||
return false;
|
||||
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
for (int nb = 0; nb < count; ++nb) {
|
||||
m_tasks.removeAt(row);
|
||||
}
|
||||
endRemoveRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ObservableTaskList::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[TitleRole] = "title";
|
||||
roles[DescriptionRole] = "description";
|
||||
roles[PriorityRole] = "priority";
|
||||
return roles;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
#ifndef OBSERVABLETASKLIST_H
|
||||
#define OBSERVABLETASKLIST_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include "Task.h"
|
||||
|
||||
class ObservableTaskList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum TaskRoles {
|
||||
TitleRole,
|
||||
DescriptionRole,
|
||||
PriorityRole
|
||||
};
|
||||
|
||||
explicit ObservableTaskList(QObject *parent = nullptr);
|
||||
|
||||
void addTask(Task* task);
|
||||
void removeTask(int index);
|
||||
void updateTask(int index, Task* task);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent= QModelIndex()) override;
|
||||
Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex & parent = QModelIndex()) override;
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QList<Task*> m_tasks;
|
||||
};
|
||||
|
||||
#endif // OBSERVABLETASKLIST_H
|
Loading…
Reference in new issue