diff --git a/README.md b/README.md index 917bf9a..04aa6e6 100644 --- a/README.md +++ b/README.md @@ -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 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 + + + +### "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). + diff --git a/docs/detail.png b/docs/detail.png new file mode 100644 index 0000000..f3b72a1 Binary files /dev/null and b/docs/detail.png differ diff --git a/docs/main.png b/docs/main.png new file mode 100644 index 0000000..c49bb3f Binary files /dev/null and b/docs/main.png differ diff --git a/oh_the_things_you_ll_do.pro b/oh_the_things_you_ll_do.pro index fbc3f6d..bded058 100644 --- a/oh_the_things_you_ll_do.pro +++ b/oh_the_things_you_ll_do.pro @@ -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 diff --git a/qml/oh_the_things_you_ll_do.qml b/qml/oh_the_things_you_ll_do.qml index 0ab89be..bd44eb0 100644 --- a/qml/oh_the_things_you_ll_do.qml +++ b/qml/oh_the_things_you_ll_do.qml @@ -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 } + diff --git a/qml/pages/ProjectView.qml b/qml/pages/ProjectView.qml new file mode 100644 index 0000000..4140fd5 --- /dev/null +++ b/qml/pages/ProjectView.qml @@ -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); + } + } + } + } +} diff --git a/qml/pages/TaskView.qml b/qml/pages/TaskView.qml index 6bc4a2f..1db2727 100644 --- a/qml/pages/TaskView.qml +++ b/qml/pages/TaskView.qml @@ -5,7 +5,7 @@ import fr.uca.iut 1.0 Page { id: taskView - property Task taskModel: Task {} + property var taskModel SilicaFlickable { anchors.fill: parent diff --git a/src/ObservableTaskList.cpp b/src/ObservableTaskList.cpp new file mode 100644 index 0000000..7c421e9 --- /dev/null +++ b/src/ObservableTaskList.cpp @@ -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() << 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 ObservableTaskList::roleNames() const +{ + QHash roles; + roles[TitleRole] = "title"; + roles[DescriptionRole] = "description"; + roles[PriorityRole] = "priority"; + return roles; +} diff --git a/src/ObservableTaskList.h b/src/ObservableTaskList.h new file mode 100644 index 0000000..f76c597 --- /dev/null +++ b/src/ObservableTaskList.h @@ -0,0 +1,39 @@ +#ifndef OBSERVABLETASKLIST_H +#define OBSERVABLETASKLIST_H + +#include +#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 roleNames() const override; + +private: + QList m_tasks; +}; + +#endif // OBSERVABLETASKLIST_H diff --git a/src/Project.cpp b/src/Project.cpp index 371c203..090900b 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -4,7 +4,7 @@ Project::Project(QObject *parent) : QObject(parent) { } -Project::Project(const QString &title, const QList &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& 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(); -} diff --git a/src/Project.h b/src/Project.h index 7a9f582..09cd697 100644 --- a/src/Project.h +++ b/src/Project.h @@ -2,35 +2,29 @@ #define PROJECT_H #include -#include -#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 tasks READ tasks NOTIFY tasksChanged) + Q_PROPERTY(ObservableTaskList* tasks READ tasks CONSTANT) public: explicit Project(QObject *parent = nullptr); - Project(const QString &title, const QList &tasks, QObject *parent = nullptr); + Project(const QString &title, ObservableTaskList* tasks, QObject *parent = nullptr); QString title() const; void setTitle(const QString &title); - const QList& 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 m_tasks; + ObservableTaskList* m_tasks; }; #endif // PROJECT_H diff --git a/src/Task.cpp b/src/Task.cpp index dca150d..889e466 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -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(); } diff --git a/src/Task.h b/src/Task.h index 23470fe..612eb3d 100644 --- a/src/Task.h +++ b/src/Task.h @@ -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(); diff --git a/src/oh_the_things_you_ll_do.cpp b/src/oh_the_things_you_ll_do.cpp index eddd199..25eccfc 100644 --- a/src/oh_the_things_you_ll_do.cpp +++ b/src/oh_the_things_you_ll_do.cpp @@ -6,10 +6,23 @@ #include #include #include "Task.h" +#include "Project.h" +#include "ObservableTaskList.h" int main(int argc, char *argv[]) { qmlRegisterType("fr.uca.iut", 1, 0, "Task"); + qmlRegisterType("fr.uca.iut", 1, 0, "Project"); + qmlRegisterType("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(); } diff --git a/translations/oh_the_things_you_ll_do-de.ts b/translations/oh_the_things_you_ll_do-de.ts index e1c490e..9673915 100644 --- a/translations/oh_the_things_you_ll_do-de.ts +++ b/translations/oh_the_things_you_ll_do-de.ts @@ -8,6 +8,13 @@ Mein Cover + + ProjectView + + Project Title + + + TaskView diff --git a/translations/oh_the_things_you_ll_do.ts b/translations/oh_the_things_you_ll_do.ts index fc4fc51..c6ddc8f 100644 --- a/translations/oh_the_things_you_ll_do.ts +++ b/translations/oh_the_things_you_ll_do.ts @@ -8,6 +8,13 @@ + + ProjectView + + Project Title + + + TaskView