💄 ⚗️ 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="">

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@ -15,11 +15,13 @@ TARGET = oh_the_things_you_ll_do
CONFIG += sailfishapp
SOURCES += src/oh_the_things_you_ll_do.cpp \
src/ObservableTaskList.cpp \
src/Project.cpp \
src/Task.cpp
DISTFILES += qml/oh_the_things_you_ll_do.qml \
qml/cover/CoverPage.qml \
qml/pages/ProjectView.qml \
qml/pages/TaskView.qml \
rpm/oh_the_things_you_ll_do.changes.in \
rpm/oh_the_things_you_ll_do.changes.run.in \
@ -40,5 +42,6 @@ CONFIG += sailfishapp_i18n
TRANSLATIONS += translations/oh_the_things_you_ll_do-de.ts
HEADERS += \
src/ObservableTaskList.h \
src/Project.h \
src/Task.h

@ -4,15 +4,13 @@ import "pages"
import fr.uca.iut 1.0
ApplicationWindow {
id: window
initialPage: Component {
TaskView {
taskModel: Task {
title: "Example Task"
description: "This is an example task."
priority: 2
}
ProjectView {
id: projectView
}
}
cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: defaultAllowedOrientations
}

@ -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);
}
}
}
}
}

@ -5,7 +5,7 @@ import fr.uca.iut 1.0
Page {
id: taskView
property Task taskModel: Task {}
property var taskModel
SilicaFlickable {
anchors.fill: parent

@ -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

@ -4,7 +4,7 @@ Project::Project(QObject *parent) : QObject(parent)
{
}
Project::Project(const QString &title, const QList<Task*> &tasks, QObject *parent)
Project::Project(const QString &title, ObservableTaskList* tasks, QObject *parent)
: QObject(parent), m_title(title), m_tasks(tasks)
{
}
@ -22,28 +22,7 @@ void Project::setTitle(const QString &title)
}
}
const QList<Task*>& Project::tasks() const
ObservableTaskList* Project::tasks() const
{
return m_tasks;
}
void Project::addTask(Task* task)
{
m_tasks.append(task);
emit tasksChanged();
}
void Project::updateTask(Task* task)
{
int index = m_tasks.indexOf(task);
if (index != -1) {
m_tasks[index] = task;
emit tasksChanged();
}
}
void Project::removeTask(Task* task)
{
m_tasks.removeOne(task);
emit tasksChanged();
}

@ -2,35 +2,29 @@
#define PROJECT_H
#include <QObject>
#include <QList>
#include "Task.h"
#include "ObservableTaskList.h"
class Project : public QObject
{
Q_OBJECT
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
Q_PROPERTY(QList<Task*> tasks READ tasks NOTIFY tasksChanged)
Q_PROPERTY(ObservableTaskList* tasks READ tasks CONSTANT)
public:
explicit Project(QObject *parent = nullptr);
Project(const QString &title, const QList<Task*> &tasks, QObject *parent = nullptr);
Project(const QString &title, ObservableTaskList* tasks, QObject *parent = nullptr);
QString title() const;
void setTitle(const QString &title);
const QList<Task*>& tasks() const;
void addTask(Task* task);
void updateTask(Task* task);
void removeTask(Task* task);
ObservableTaskList* tasks() const;
signals:
void titleChanged();
void tasksChanged();
private:
QString m_title;
QList<Task*> m_tasks;
ObservableTaskList* m_tasks;
};
#endif // PROJECT_H

@ -42,7 +42,7 @@ int Task::priority() const
void Task::setPriority(int priority)
{
if (priority >= 0 && priority <= MAX_PRIORITY && m_priority != priority) {
if (priority >= 0 && priority <= LEAST_CRITICAL_PRIORITY && m_priority != priority) {
m_priority = priority;
emit priorityChanged();
}

@ -23,7 +23,7 @@ public:
int priority() const;
void setPriority(int priority);
static const int MAX_PRIORITY = 3;
static const int LEAST_CRITICAL_PRIORITY = 3;
signals:
void titleChanged();

@ -6,10 +6,23 @@
#include <QQmlContext>
#include <QQmlEngine>
#include "Task.h"
#include "Project.h"
#include "ObservableTaskList.h"
int main(int argc, char *argv[])
{
qmlRegisterType<Task>("fr.uca.iut", 1, 0, "Task");
qmlRegisterType<Project>("fr.uca.iut", 1, 0, "Project");
qmlRegisterType<ObservableTaskList>("fr.uca.iut", 1, 0, "TaskModel");
return SailfishApp::main(argc, argv);
ObservableTaskList model;
model.addTask(new Task("Example Task 1", "This is an example task.", 2));
model.addTask(new Task("Example Task 2", "This is another example task.", 1));
QGuiApplication *app = SailfishApp::application(argc,argv);
QQuickView *view = SailfishApp::createView();
Project* project = new Project("Example Project", &model);
view->rootContext()->setContextProperty("projectModel", project);
view->setSource(SailfishApp::pathTo("qml/oh_the_things_you_ll_do.qml"));
view->show();
return app->exec();
}

@ -8,6 +8,13 @@
<translation>Mein Cover</translation>
</message>
</context>
<context>
<name>ProjectView</name>
<message>
<source>Project Title</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TaskView</name>
<message>

@ -8,6 +8,13 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProjectView</name>
<message>
<source>Project Title</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TaskView</name>
<message>

Loading…
Cancel
Save