File indexing completed on 2024-03-24 17:28:04

0001 // SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 #include "tasksmodel.h"
0005 #include "tasks_debug.h"
0006 
0007 #include <QStandardPaths>
0008 #include <QFile>
0009 #include <QDir>
0010 
0011 #include <QJsonArray>
0012 #include <QJsonDocument>
0013 #include <QJsonObject>
0014 
0015 TasksModel::TasksModel(QObject *parent)
0016     : QAbstractListModel(parent)
0017 {
0018     loadTasks();
0019 }
0020 
0021 QHash<int, QByteArray> TasksModel::roleNames() const
0022 {
0023     return {
0024         {Roles::TitleRole, QByteArrayLiteral("title")},
0025         {Roles::CheckedRole, QByteArrayLiteral("checked")}
0026     };
0027 }
0028 
0029 QVariant TasksModel::data(const QModelIndex &index, int role) const
0030 {
0031     if (!index.isValid() || index.row() < 0 || index.row() >= m_tasks.count()) {
0032         return {};
0033     }
0034 
0035     auto task = m_tasks.at(index.row());
0036 
0037     switch (role) {
0038     case Roles::TitleRole:
0039         return task.title();
0040     case Roles::CheckedRole:
0041         return task.checked();
0042     }
0043 
0044     return {};
0045 }
0046 
0047 int TasksModel::rowCount(const QModelIndex &parent) const
0048 {
0049     Q_UNUSED(parent)
0050     return m_tasks.size();
0051 }
0052 
0053 bool TasksModel::setData(const QModelIndex &index, const QVariant &value, int role)
0054 {
0055     if (index.row() < 0 || index.row() >= m_tasks.count()) {
0056         return false;
0057     }
0058 
0059     auto& task = m_tasks[index.row()];
0060 
0061     switch (role) {
0062         case Roles::CheckedRole:
0063             task.setChecked(value.toBool());
0064 
0065             if (task.checked()) {
0066                 m_completedTasks++;
0067                 Q_EMIT completedTasksChanged();
0068             }
0069 
0070             if (!task.checked()) {
0071                 if (m_completedTasks > 0) {
0072                     m_completedTasks--;
0073                     Q_EMIT completedTasksChanged();
0074                 }
0075             }
0076 
0077             break;
0078         case Roles::TitleRole:
0079             task.setTitle(value.toString());
0080             break;
0081     }
0082 
0083     Q_EMIT dataChanged(index, index, { role });
0084 
0085     saveTasks();
0086 
0087     return true;
0088 }
0089 
0090 void TasksModel::add(const QString &title)
0091 {
0092     Task t(title, false);
0093 
0094     beginInsertRows(QModelIndex(), m_tasks.count(), m_tasks.count());
0095     m_tasks.append(t);
0096     endInsertRows();
0097 
0098     saveTasks();
0099 }
0100 
0101 void TasksModel::remove(const QModelIndex &index)
0102 {
0103     const int row = index.row();
0104     if (row < 0 || row > m_tasks.count()) {
0105         return;
0106     }
0107 
0108     if (m_tasks[row].checked()) {
0109         if (m_completedTasks > 0) {
0110             m_completedTasks--;
0111             Q_EMIT completedTasksChanged();
0112         }
0113     }
0114 
0115     beginRemoveRows(QModelIndex(), row, row);
0116     m_tasks.removeAt(row);
0117     endRemoveRows();
0118 
0119     saveTasks();
0120 }
0121 
0122 void TasksModel::clear()
0123 {
0124     beginResetModel();
0125     m_tasks.clear();
0126     endResetModel();
0127 
0128     saveTasks();
0129 }
0130 
0131 bool TasksModel::saveTasks() const
0132 {
0133     const QString outputDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0134 
0135     QFile outputFile(outputDir + QStringLiteral("/tasks.json"));
0136     if (!QDir(outputDir).mkpath(QStringLiteral("."))) {
0137         qCWarning(TASKS_LOG) << "Destdir doesn't exist and I can't create it: " << outputDir;
0138         return false;
0139     }
0140     if (!outputFile.open(QIODevice::WriteOnly)) {
0141         qCWarning(TASKS_LOG) << "Failed to write tabs to disk";
0142     }
0143 
0144     QJsonArray tasksArray;
0145     std::transform(m_tasks.cbegin(), m_tasks.cend(), std::back_inserter(tasksArray), [](const Task &task) {
0146         return task.toJson();
0147     });
0148 
0149     qCDebug(TASKS_LOG) << "Wrote to file" << outputFile.fileName() << "(" << tasksArray.count() << "tasks" << ")";
0150 
0151     const QJsonDocument document({
0152         {QLatin1String("tasks"), tasksArray},
0153         {QLatin1String("completedTasks"), m_completedTasks}
0154     });
0155 
0156     outputFile.write(document.toJson());
0157 
0158     return true;
0159 }
0160 
0161 bool TasksModel::loadTasks()
0162 {
0163     beginResetModel();
0164 
0165     const QString input = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/tasks.json");
0166 
0167     QFile inputFile(input);
0168     if (!inputFile.exists()) {
0169         return false;
0170     }
0171 
0172     if (!inputFile.open(QIODevice::ReadOnly)) {
0173         qCWarning(TASKS_LOG) << "Failed to load tabs from disk";
0174     }
0175 
0176     const auto tasksStorage = QJsonDocument::fromJson(inputFile.readAll()).object();
0177     m_tasks.clear();
0178 
0179     m_completedTasks = tasksStorage.value(QLatin1String("completedTasks")).toInt();
0180 
0181     const auto tasks = tasksStorage.value(QLatin1String("tasks")).toArray();
0182 
0183     std::transform(tasks.cbegin(), tasks.cend(), std::back_inserter(m_tasks), [](const QJsonValue &task) {
0184         return Task::fromJson(task.toObject());
0185     });
0186 
0187     qCDebug(TASKS_LOG) << "loaded from file:" << m_tasks.count() << input;
0188 
0189     endResetModel();
0190 
0191     return true;
0192 }