first commit

This commit is contained in:
Vyn 2024-04-10 16:53:18 +02:00
commit 3e7d8b4b70
43 changed files with 2681 additions and 0 deletions

142
src/Backend.cpp Normal file
View file

@ -0,0 +1,142 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Backend.h"
Backend::Backend() {
std::cout << "Backend created" << std::endl;
QDir().mkdir(QDir::homePath() + "/.config/mirai");
QFile loadFile(QDir::homePath() + "/.config/mirai/config.json");
if (!loadFile.open(QIODevice::ReadOnly)) {
qWarning() << "Couldn't find existing config file";
exit(1);
}
QByteArray loadData = loadFile.readAll();
QJsonDocument json = QJsonDocument::fromJson(loadData);
loadFile.close();
if (!json.isObject()) {
qWarning() << "config.json is not a valid config file";
exit(1);
}
QJsonObject jsonRootObject = json.object();
auto jsonFilesPath = json["files"];
if (!jsonFilesPath.isArray()) {
qWarning() << "config.json should contains a 'files' string array";
exit(1);
}
QString filePath = jsonFilesPath.toArray()[0].toString();
mirai.loadFile(filePath.toStdString());
view = mirai.getTasks();
rebuildQMLTasksList();
}
void Backend::addTodo(QString newTodo, QString date) {
mirai.addTask(newTodo.toStdString(), date.toStdString());
mirai.save();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::addTagFilter(QString tag) {
view.lock()->addTagFilter(tag.toStdString());
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeTagFilter(QString tag) {
view.lock()->removeTagFilter(tag.toStdString());
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeFilters() {
view.lock()->removeFilters();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::updateTodo(int todoIndex, QString state, QString text, QString date) {
QMLTaskItem& taskItem = QMLTasks[todoIndex];
taskItem.taskItem->state = state == "TODO" ? mirai::TODO : mirai::DONE;
if (state == "DONE") {
taskItem.taskItem->markAsDone();
} else if (state == "TODO") {
taskItem.taskItem->markAsUndone();
}
taskItem.taskItem->setText(text.toStdString());
taskItem.taskItem->setDate(date.toStdString());
mirai.save();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::removeTodo(int todoIndex) {
QMLTaskItem& taskItem = QMLTasks[todoIndex];
mirai.removeTask(taskItem.taskItem);
mirai.save();
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::hideCompletedTasks(bool shouldHide) {
shouldHideCompletedTasks_ = shouldHide;
rebuildQMLTasksList();
emit tasksChanged();
}
void Backend::rebuildQMLTasksList() {
QMLTasks.clear();
std::string lastDate = "";
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
std::stringstream currentDate;
currentDate << std::put_time(&tm, "%Y-%m-%d");
for (int i = 0; i < view.lock()->count(); ++i) {
mirai::TaskItem& task = (*view.lock())[i];
if (shouldHideCompletedTasks_ && task.getState() == mirai::DONE && task.hasDate() && task.getDate() < currentDate.str()) {
continue;
}
bool shouldShowDate = false;
if (lastDate != task.date) {
lastDate = task.date;
shouldShowDate = true;
}
QMLTasks.push_back({
.taskItem = &task,
.shouldShowDate = shouldShowDate
});
}
QMLTags.clear();
for (auto& tag : mirai.getTags()) {
QMLTags.push_back(QString::fromStdString(tag));
}
QMLActiveTagsFilter.clear();
for (auto& activeTagFilter: view.lock()->getActiveTagsFilter()) {
QMLActiveTagsFilter.push_back(QString::fromStdString(activeTagFilter));
}
}
QVariant Backend::getTasks() {
return QVariant::fromValue(QMLTasks);
}
QVariant Backend::getTags() {
return QVariant::fromValue(QMLTags);
}
QVariant Backend::getActiveTagsFilter() {
return QVariant::fromValue(QMLActiveTagsFilter);
}
bool Backend::shouldHideCompletedTasks() {
return shouldHideCompletedTasks_;
}

75
src/Backend.h Normal file
View file

@ -0,0 +1,75 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef BACKEND_H
#define BACKEND_H
#include "QtCore/qcontainerfwd.h"
#include "QtCore/qtmetamacros.h"
#include "QtCore/qvariant.h"
#include <QtQml/qqmlregistration.h>
#include <QtCore/QObject>
#include <QtCore/QDateTime>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QDir>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <iostream>
#include <memory>
#include "core/TaskItem.h"
#include "core/TasksView.h"
#include "core/TodoMd.h"
#include "core/Mirai.h"
#include "TaskItem.h"
class Backend : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QVariant tasks READ getTasks NOTIFY tasksChanged)
Q_PROPERTY(QVariant tags READ getTags NOTIFY tasksChanged)
Q_PROPERTY(QVariant activeTagsFilter READ getActiveTagsFilter NOTIFY tasksChanged)
Q_PROPERTY(bool shouldHideCompletedTasks READ shouldHideCompletedTasks WRITE hideCompletedTasks NOTIFY tasksChanged)
public:
Backend();
Q_INVOKABLE void addTodo(QString newTodo, QString date);
Q_INVOKABLE void addTagFilter(QString tag);
Q_INVOKABLE void removeTagFilter(QString tag);
Q_INVOKABLE void removeFilters();
Q_INVOKABLE void updateTodo(int todoIndex, QString state, QString text, QString date);
Q_INVOKABLE void removeTodo(int todoIndex);
Q_INVOKABLE void hideCompletedTasks(bool shouldHide);
private:
void rebuildQMLTasksList();
QVariant getTasks();
QVariant getTags();
QVariant getActiveTagsFilter();
bool shouldHideCompletedTasks();
mirai::Mirai mirai;
std::weak_ptr<mirai::TasksView> view;
QList<QMLTaskItem> QMLTasks;
QList<QString> QMLTags;
QList<QString> QMLActiveTagsFilter;
bool shouldHideCompletedTasks_ = true;
signals:
void tasksChanged();
};
#endif

23
src/TaskItem.cpp Normal file
View file

@ -0,0 +1,23 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TaskItem.h"
QString QMLTaskItem::getText() {
return QString::fromStdString(taskItem->getText());
}
QString QMLTaskItem::getState() {
return QString::fromStdString(taskItem->getState() == mirai::TODO ? "TODO" : "DONE");
}
QString QMLTaskItem::getDate() {
return QString::fromStdString(taskItem->getDate());
}
bool QMLTaskItem::getShouldShowDate() {
return shouldShowDate;
}

37
src/TaskItem.h Normal file
View file

@ -0,0 +1,37 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef QML_TASKITEM_H
#define QML_TASKITEM_H
#include "QtCore/qvariant.h"
#include <QtQml/qqmlregistration.h>
#include <QtCore/QString>
#include <memory>
#include "core/TaskItem.h"
struct QMLTaskItem {
Q_GADGET
Q_PROPERTY(QString text READ getText)
Q_PROPERTY(QString state READ getState)
Q_PROPERTY(QString date READ getDate)
Q_PROPERTY(bool shouldShowDate READ getShouldShowDate)
QML_VALUE_TYPE(taskItem)
public:
QString getText();
QString getState();
QString getDate();
bool getShouldShowDate();
mirai::TaskItem* taskItem;
bool shouldShowDate = false;
};
#endif

55
src/core/Mirai.cpp Normal file
View file

@ -0,0 +1,55 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "Mirai.h"
namespace mirai {
void Mirai::loadFile(const std::string& path) {
auto tasksFile = TodoMdFormat::readFile(path);
files->push_back(std::move(tasksFile));
tags.clear();
for (auto& file : *files) {
for (auto& tag : file.getTags()) {
tags.push_back(tag);
}
}
}
void Mirai::save() {
for (auto& file : *files) {
TodoMdFormat::writeFile(file);
}
}
void Mirai::addTask(std::string text, std::string date) {
/*std::time_t t = std::time(nullptr);*/
/*std::tm tm = *std::localtime(&t);*/
/*std::stringstream ssCreationDate;*/
/*ssCreationDate << std::put_time(&tm, "%Y-%m-%d");*/
(*files)[0].addTask(text, date);
for (auto& view : views) {
view->update();
}
}
void Mirai::removeTask(const TaskItem* taskItem) {
(*files)[0].removeTask(taskItem);
for (auto& view : views) {
view->update();
}
}
std::weak_ptr<TasksView> Mirai::getTasks() {
auto view = std::make_shared<TasksView>(files);
views.push_back(view);
return view;
}
const std::vector<std::string>& Mirai::getTags() {
return tags;
}
}

45
src/core/Mirai.h Normal file
View file

@ -0,0 +1,45 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_MIRAI_H
#define MIRAI_MIRAI_H
#include "TaskItem.h"
#include "TasksFile.h"
#include "TasksView.h"
#include "TodoMd.h"
#include <algorithm>
#include <memory>
namespace mirai {
class Mirai {
public:
void loadFile(const std::string& path);
void save();
void addTask(std::string text, std::string date);
void removeTask(const TaskItem* taskItem);
std::weak_ptr<TasksView> getTasks();
const std::vector<std::string>& getTags();
private:
// The `TasksFile`s are shared to the views, their lifetime can outlive
// this (Mirai) object
// because we can't control if the caller will keep the main object alive.
std::shared_ptr<std::vector<TasksFile>> files = std::make_shared<std::vector<TasksFile>>();
// We keep a vector of shared_ptr because we need the ref counting mechanism to know
// if we have to remove the view (not used) or tell the view to update() on
// some changes.
std::vector<std::shared_ptr<TasksView>> views;
std::vector<std::string> tags;
};
}
#endif

32
src/core/TaskItem.cpp Normal file
View file

@ -0,0 +1,32 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TaskItem.h"
namespace mirai {
void TaskItem::markAsDone() {
state = DONE;
}
void TaskItem::markAsUndone() {
state = TODO;
}
void TaskItem::setDate(const std::string& date) {
this->date = date;
}
void TaskItem::setText(const std::string& text) {
this->text = text;
}
const std::string& TaskItem::getText() const { return text; }
const TaskItemState& TaskItem::getState() const { return state; }
const std::string& TaskItem::getDate() const { return date; }
bool TaskItem::hasDate() const { return isDate(date); }
}

49
src/core/TaskItem.h Normal file
View file

@ -0,0 +1,49 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TASKITEM_H
#define MIRAI_TASKITEM_H
#include "utils.h"
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace mirai {
enum TaskItemState {
TODO,
DONE
};
struct TaskItem {
public:
void markAsDone();
void markAsUndone();
void setDate(const std::string& date);
void setText(const std::string& text);
const std::string& getText() const;
const TaskItemState& getState() const;
const std::string& getDate() const;
bool hasDate() const;
std::string text;
TaskItemState state;
std::string date;
};
}
#endif

71
src/core/TasksFile.cpp Normal file
View file

@ -0,0 +1,71 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TasksFile.h"
#include "using.h"
namespace mirai {
TasksFile::TasksFile(TasksFileConstructor params) : name(params.name), path(params.path) {
for (const auto& task : params.tasks) {
processTaskMetaData(task);
tasks.push_back(std::make_unique<TaskItem>(task));
}
sortByDate();
}
const std::string& TasksFile::getName() const { return name; }
const std::string& TasksFile::getPath() const { return path; }
TasksPtrs& TasksFile::getTasks() { return tasks; }
Tags& TasksFile::getTags() { return tags; }
void TasksFile::addTask(std::string text, std::string date) {
auto newTask = std::make_unique<TaskItem>(TaskItem{
.text = text,
.state = TODO,
.date = date == "" ? "No date" : date
});
tasks.push_back(std::move(newTask));
sortByDate();
}
void TasksFile::removeTask(const TaskItem* taskToRemove) {
tasks.erase(std::remove_if(tasks.begin(), tasks.end(), [&] (const std::unique_ptr<TaskItem>& taskInFilter){
return taskInFilter.get() == taskToRemove;
}));
}
void TasksFile::processTaskMetaData(const TaskItem& task) {
const std::string metaDataSeparator = " -- ";
auto separatorIndex = task.text.find(metaDataSeparator);
if (separatorIndex == std::string::npos) {
return;
}
auto metaData = task.text.substr(separatorIndex + metaDataSeparator.length());
std::regex regex("(#[a-zA-Z0-1]+)");
std::smatch matches;
while (std::regex_search(metaData, matches, regex))
{
if (std::find(tags.begin(), tags.end(), matches[0]) == tags.end()) {
tags.push_back(matches[0]);
}
metaData = matches.suffix();
}
}
void TasksFile::sortByDate() {
std::sort(tasks.begin(), tasks.end(), [] (const std::unique_ptr<TaskItem>& t1, const std::unique_ptr<TaskItem>& t2) {
if (t1->hasDate() && !t2->hasDate()) {
return true;
} else if (!t1->hasDate() && t2->hasDate()) {
return false;
}
return t1->date < t2->date;
});
}
}

58
src/core/TasksFile.h Normal file
View file

@ -0,0 +1,58 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TASKSFILE_H
#define MIRAI_TASKSFILE_H
#include <algorithm>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <ostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "TaskItem.h"
#include "using.h"
#include "utils.h"
namespace mirai {
struct TasksFileConstructor {
const std::string& name;
const std::string& path;
std::vector<TaskItem> tasks;
};
class TasksFile {
public:
TasksFile(TasksFileConstructor params);
void addTask(std::string text, std::string date);
void removeTask(const TaskItem* taskToRemove);
void processTaskMetaData(const TaskItem& task);
void sortByDate();
const std::string& getName() const;
const std::string& getPath() const;
TasksPtrs& getTasks();
Tags& getTags();
private:
std::string name;
std::string path;
TasksPtrs tasks;
Tags tags;
};
}
#endif

56
src/core/TasksView.cpp Normal file
View file

@ -0,0 +1,56 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TasksView.h"
namespace mirai {
TasksView::TasksView(std::shared_ptr<std::vector<TasksFile>> files) : files(files) {
update();
}
TaskItem& TasksView::operator[](int index) {
return *(tasksToShow.at(index));
}
int TasksView::count() const { return tasksToShow.size(); }
void TasksView::update() {
tasksToShow.clear();
for (auto& file : *files) {
for (auto& task : file.getTasks()) {
if (tagsFilter.size() != 0
&& std::find_if(tagsFilter.begin(), tagsFilter.end(), [&](const std::string& tag) {
return task->getText().find(tag) != std::string::npos;
}) == tagsFilter.end())
continue;
tasksToShow.push_back(task.get());
}
}
}
void TasksView::addTagFilter(const std::string& tag) {
tagsFilter.push_back(tag);
update();
}
void TasksView::removeTagFilter(const std::string& tag) {
tagsFilter.erase(std::remove_if(tagsFilter.begin(), tagsFilter.end(), [&] (const auto& tagInFilter){
return tag == tagInFilter;
}));
update();
}
void TasksView::removeFilters() {
tagsFilter.clear();
update();
}
const std::vector<std::string>& TasksView::getActiveTagsFilter() {
return tagsFilter;
}
}

41
src/core/TasksView.h Normal file
View file

@ -0,0 +1,41 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TASKSVIEW_H
#define MIRAI_TASKSVIEW_H
#include "TasksFile.h"
#include "using.h"
#include <algorithm>
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
namespace mirai {
class TasksView {
public:
TasksView(std::shared_ptr<std::vector<TasksFile>> files);
TaskItem& operator[](int index);
int count() const;
void update();
void addTagFilter(const std::string& tag);
void removeTagFilter(const std::string& tag);
void removeFilters();
const Tags& getActiveTagsFilter();
private:
std::shared_ptr<std::vector<TasksFile>> files;
std::vector<TaskItem*> tasksToShow;
Tags tagsFilter;
};
}
#endif

78
src/core/TodoMd.cpp Normal file
View file

@ -0,0 +1,78 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "TodoMd.h"
namespace mirai {
TasksFile TodoMdFormat::readFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
throw std::runtime_error("todo.txt file not found");
}
std::vector<TaskItem> taskItems;
std::string line;
if (!std::getline(file, line) || line.substr(0, 2) != "# ") {
std::cerr << "Couldn't find the task list name" << std::endl;
}
std::string name = line.substr(2);
std::string currentDate = "";
while (std::getline(file, line)) {
if (line.substr(0, 3) == "## ") {
currentDate = line.substr(3);
} else if (line.substr(0, 5) == "- [ ]" || line.substr(0, 5) == "- [X]") {
std::string text = line.substr(5);
trim(text);
TaskItem taskItem = {
.text = text,
.state = line.substr(0, 5) == "- [X]" ? DONE : TODO,
.date = currentDate
};
taskItems.push_back(taskItem);
}
}
file.close();
TasksFile tasks({
.name = name,
.path = path,
.tasks = taskItems
});
return tasks;
}
void TodoMdFormat::writeFile(TasksFile& tasks) {
std::ofstream file(tasks.getPath());
if (!file.is_open()) {
throw std::runtime_error("can't create todo.txt");
}
std::string currentDate = "";
file << "# " << tasks.getName() << "\n";
for (const auto& task : tasks.getTasks()) {
if (currentDate != task->date) {
currentDate = task->date;
file << "\n## " << (task->date != "" ? task->date : "No date") << "\n\n";
}
if (task->getState() == TODO) {
file << "- [ ] ";
} else if (task->getState() == DONE) {
file << "- [X] ";
}
file << task->getText() << '\n';
}
file.close();
}
std::string TodoMdFormat::fieldWithSpace(const std::string& field) {
if (field.length() == 0)
return "";
return (field + " ");
}
}

38
src/core/TodoMd.h Normal file
View file

@ -0,0 +1,38 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TODOMD_H
#define MIRAI_TODOMD_H
#include "TaskItem.h"
#include "TasksFile.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <ostream>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
namespace mirai {
class TodoMdFormat {
public:
static TasksFile readFile(const std::string& path);
static void writeFile(TasksFile& tasks);
private:
static std::string fieldWithSpace(const std::string& field);
static TaskItem parseLine(const std::string& line);
};
}
#endif

95
src/core/TodoTxt.h Normal file
View file

@ -0,0 +1,95 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_TODOTXT_H
#define MIRAI_TODOTXT_H
#include "TaskItem.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
namespace mirai {
class TodoTxtFormat {
public:
static TaskList readFile() {
std::ifstream file("../newqml/todo.txt");
if (!file.is_open()) {
throw std::runtime_error("todo.txt file not found");
}
std::vector<TaskItem> taskItems;
std::string line;
while (std::getline(file, line)) {
auto taskItem = parseLine(line);
taskItems.push_back(taskItem);
}
file.close();
TaskList tasks({
.tasks = taskItems
});
return tasks;
}
static void writeFile(TaskList& tasks) {
std::ofstream file("../newqml/todo.txt");
if (!file.is_open()) {
throw std::runtime_error("can't create todo.txt");
}
for (const auto& task : tasks.getTasks()) {
file << fieldWithSpace(task->state == DONE ? "x" : task->priority);
file << fieldWithSpace(task->state == DONE ? task->getDate() : "");
file << fieldWithSpace(task->getCreationDate());
file << task->getText() << '\n';
}
file.close();
}
private:
static std::string fieldWithSpace(const std::string& field) {
if (field.length() == 0)
return "";
return (field + " ");
}
static TaskItem parseLine(const std::string& line) {
std::smatch matches;
std::regex regex("(^x )?(\\([A-Z]\\) )?([0-9]{4}-[0-9]{2}-[0-9]{2} )?([0-9]{4}-[0-9]{2}-[0-9]{2} )?(.*)");
std::regex_match(line, matches, regex);
for (size_t i = 0; i < matches.size(); ++i) {
std::ssub_match sub_match = matches[i];
std::string piece = sub_match.str();
}
const TaskItemState taskState = trim_copy(matches[1].str()) == "x" ? DONE : TODO;
const std::string priority = trim_copy(matches[2].str());
const std::string firstDate = trim_copy(matches[3].str());
const std::string secondDate = trim_copy(matches[4].str());
const std::string text = trim_copy(matches[5].str());
const std::string date = taskState == DONE ? firstDate : "";
return {
.text = text,
.state = taskState,
.date = date,
};
}
};
}
#endif

20
src/core/using.h Normal file
View file

@ -0,0 +1,20 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_USING_H
#define MIRAI_USING_H
#include "TaskItem.h"
#include <memory>
#include <string>
#include <vector>
namespace mirai {
using Tags = std::vector<std::string>;
using TasksPtrs = std::vector<std::unique_ptr<TaskItem>>;
}
#endif

44
src/core/utils.cpp Normal file
View file

@ -0,0 +1,44 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include "utils.h"
void ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
void rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
void trim(std::string& s) {
rtrim(s);
ltrim(s);
}
std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
std::string trim_copy(std::string s) {
trim(s);
return s;
}
bool isDate(const std::string& dateStr) {
std::regex regex("[0-9]{4}-[0-9]{2}-[0-9]{2}");
return std::regex_match(dateStr, regex);
}

25
src/core/utils.h Normal file
View file

@ -0,0 +1,25 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#ifndef MIRAI_UTILS_H
#define MIRAI_UTILS_H
#include <algorithm>
#include <cctype>
#include <locale>
#include <regex>
void ltrim(std::string& s);
void rtrim(std::string& s);
void trim(std::string& s);
std::string ltrim_copy(std::string s);
std::string rtrim_copy(std::string s);
std::string trim_copy(std::string s);
bool isDate(const std::string& dateStr);
#endif

BIN
src/images/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
src/images/calendar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
src/images/task.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

45
src/main.cpp Normal file
View file

@ -0,0 +1,45 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
#include <QtGui/QGuiApplication>
#include <QtGui/QFont>
#include <QtCore/QRect>
#include <QtGui/QScreen>
#include <QtQml/QQmlApplicationEngine>
#include <exception>
#include <iostream>
#include <ostream>
int main(int argc, char *argv[])
{
try {
QGuiApplication app(argc, argv);
qreal refDpi = 54.;
qreal refHeight = 1440.;
qreal refWidth = 2560.;
QRect rect = QGuiApplication::primaryScreen()->geometry();
qreal height = qMax(rect.width(), rect.height());
qreal width = qMin(rect.width(), rect.height());
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
//auto m_ratio = qMin(height/refHeight, width/refWidth);
auto m_ratioFont = qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth));
QFont font("Helvetica", m_ratioFont);
app.setFont(font);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/qt/qml/Mirai/src/qml/Main.qml"_qs);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(url);
return app.exec();
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
}

51
src/qml/AppButton.qml Normal file
View file

@ -0,0 +1,51 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Button {
id: control
icon.color: MiraiColorPalette.buttonIcon
property bool noBackgroundColor: false
contentItem: RowLayout {
spacing: control.icon.source != "" ? 4 : 0
Component {
id: buttonIcon
AppIcon {
icon.source: control.icon.source
icon.color: control?.icon?.color
}
}
Loader {
sourceComponent: control.icon.source != "" ? buttonIcon : undefined
}
Text {
text: control.text
color: MiraiColorPalette.text
leftPadding: 8
rightPadding: 8
}
}
background: Rectangle {
color: control.noBackgroundColor ? "transparent"
: mouse.hovered ? colorPalette.selected.buttonHovered
: colorPalette.selected.buttonBackground
radius: 4
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}

41
src/qml/AppCheckbox.qml Normal file
View file

@ -0,0 +1,41 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
CheckBox {
id: control
property alias textComponent: text
contentItem: Text {
id: text
text: control.text
color: MiraiColorPalette.text
verticalAlignment: Text.AlignVCenter
leftPadding: control.indicator.width + control.spacing
}
indicator: Rectangle {
x: control.leftPadding
y: control.topPadding + (control.availableHeight - height) / 2
implicitWidth: 12
implicitHeight: 12
radius: 999
color: control.checked ? colorPalette.selected.palette.green : colorPalette.selected.text
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 10
implicitHeight: 10
radius: 999
color: control.checked ? colorPalette.selected.palette.green : colorPalette.selected.fieldBackground
}
}
}

15
src/qml/AppIcon.qml Normal file
View file

@ -0,0 +1,15 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
Button {
icon.color: MiraiColorPalette.buttonIcon
background: Rectangle { color: "transparent" }
}

22
src/qml/AppLineEdit.qml Normal file
View file

@ -0,0 +1,22 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import Mirai
TextField {
color: MiraiColorPalette.text
placeholderTextColor: MiraiColorPalette.textPlaceholder
leftPadding: 10
implicitHeight: 32
background: Rectangle {
color: MiraiColorPalette.fieldBackground
radius: 4
}
}

13
src/qml/AppText.qml Normal file
View file

@ -0,0 +1,13 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Controls
import Mirai
Text {
color: MiraiColorPalette.text
}

77
src/qml/DateField.qml Normal file
View file

@ -0,0 +1,77 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Rectangle {
id: datePickerRoot
property alias text: newTodoDate.text
property alias textFieldComponent: newTodoDate
color: MiraiColorPalette.fieldBackground
radius: 4
implicitHeight: 32
RowLayout {
anchors.fill: parent
AppIcon {
icon.source: "qrc:/qt/qml/Mirai/src/images/calendar.png"
Layout.preferredWidth: 32
Layout.preferredHeight: 32
}
AppLineEdit {
id: newTodoDate
Layout.fillWidth: true
Layout.fillHeight: true
readOnly: true
background: Rectangle {
color: "transparent"
}
MouseArea {
anchors.fill: parent
onClicked: {
datePicker.open()
}
}
}
Popup {
id: datePicker
implicitWidth: 300
implicitHeight: 300
x: datePickerRoot.x
y: datePickerRoot.y - 300 - 10
background: Rectangle {
color: colorPalette.selected.pane
border.color: colorPalette.selected.modalBorder
border.width: 2
}
DatePicker {
anchors.fill: parent
Component.onCompleted: set(new Date()) // today
onClicked: {
const formattedDate = Qt.formatDate(date, 'yyyy-MM-dd')
print('onClicked', formattedDate)
newTodoDate.text = formattedDate
datePicker.close()
}
onReset: {
newTodoDate.text = ""
datePicker.close()
}
}
}
}
}

124
src/qml/DatePicker.qml Normal file
View file

@ -0,0 +1,124 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import Mirai
import QtQuick
ListView {
id: root
function set(date) {
selectedDate = new Date(date)
positionViewAtIndex((selectedDate.getFullYear()) * 12 + selectedDate.getMonth(), ListView.Center) // index from month year
}
signal clicked(date date);
signal reset();
property date selectedDate: new Date()
width: 500
height: 100
snapMode: ListView.SnapOneItem
orientation: Qt.Horizontal
clip: true
model: 3000 * 12 // index == months since January of the year 0
delegate: Item {
property int year: Math.floor(index / 12)
property int month: index % 12 // 0 January
property int firstDay: new Date(year, month, 1).getDay() // 0 Sunday to 6 Saturday
width: root.width
height: root.height
Column {
Item { // month year header
width: root.width
height: root.height - grid.height
AppText { // month year
anchors.centerIn: parent
text: ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'][month] + ' ' + year
font {pixelSize: 0.5 * grid.cellHeight}
}
AppButton {
text: "Remove"
onClicked: {
root.reset()
}
}
}
Grid { // 1 month calender
id: grid
width: root.width
height: 0.875 * root.height
property real cellWidth: width / columns;
property real cellHeight: height / rows // width and height of each cell in the grid.
columns: 7 // days
rows: 7
Repeater {
model: grid.columns * grid.rows // 49 cells per month
delegate: Rectangle { // index is 0 to 48
property int day: index - 7 // 0 = top left below Sunday (-7 to 41)
property int date: day - firstDay + 2 // 1-31
color: internal.selected ? colorPalette.selected.filterSelected
: mouseArea.containsMouse ? colorPalette.selected.filterHovered
: "transparent"
width: grid.cellWidth; height: grid.cellHeight
radius: 0.02 * root.height
opacity: !mouseArea.pressed? 1: 0.3
QtObject {
id: internal
property bool selected: new Date(year, month, date).toDateString() == selectedDate.toDateString() && text.text && day >= 0
}
AppText {
id: text
anchors.centerIn: parent
font.pixelSize: 0.5 * parent.height
font.bold: new Date(year, month, date).toDateString() == new Date().toDateString() // today
text: {
if(day < 0)
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][index] // Su-Sa
else if(new Date(year, month, date).getMonth() == month)
date // 1-31
else
''
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
enabled: text.text && day >= 0
hoverEnabled: true
onClicked: {
selectedDate = new Date(year, month, date)
root.clicked(selectedDate)
}
}
}
}
}
}
}
// Component.onCompleted: set(new Date()) // today (otherwise Jan 0000)
}

145
src/qml/Main.qml Normal file
View file

@ -0,0 +1,145 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property string selectedView: "list"
Backend {
id: backend
}
Item {
id: colorPalette
property QtObject selected: MiraiColorPalette
}
function newTask() {
taskForm.taskToEdit = undefined
taskForm.taskToEditIndex = -1
taskFormPopup.open()
}
function editTask(task, taskIndex) {
taskForm.taskToEdit = task
taskForm.taskToEditIndex = taskIndex
taskFormPopup.open()
}
RowLayout {
anchors.fill: parent
spacing: 0
Rectangle {
color: MiraiColorPalette.pane
Layout.preferredWidth: childrenRect.width + 20
Layout.fillHeight: true
SideMenu {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
}
}
Rectangle {
color: colorPalette.selected.background
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 16
TabSelector {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
tabs: [
{
label: "List",
onClick: () => { root.selectedView = "list" },
selected: root.selectedView === "list"
},
{
label: "Calendar",
onClick: () => { root.selectedView = "calendar" },
selected: root.selectedView === "calendar"
}
]
}
AppButton {
icon.source: "qrc:/qt/qml/Mirai/src/images/add.png"
icon.color: colorPalette.selected.palette.green
text: "Add task"
onClicked: {
root.newTask()
}
}
Component {
id: listViewComponent
ListView {
}
}
Component {
id: calendarViewComponent
CalendarView {
}
}
Loader {
sourceComponent: selectedView === "list" ? listViewComponent
: selectedView === "calendar" ? calendarViewComponent
: undefined
Layout.fillWidth: true
Layout.fillHeight: true
}
Popup {
id: taskFormPopup
width: parent.width * 0.75
implicitHeight: taskForm.height + padding * 2
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height * 0.4) / 2)
padding: 8
background: Rectangle {
border.color: colorPalette.selected.modalBorder
border.width: 2
color: colorPalette.selected.pane
radius: 4
}
TaskForm {
id: taskForm
width: parent.width
onConfirmed: {
taskFormPopup.close()
}
}
}
}
}
}
}

61
src/qml/SideMenu.qml Normal file
View file

@ -0,0 +1,61 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
ColumnLayout {
AppText {
text: "Tags"
font.pixelSize: 32
}
Item { Layout.preferredHeight: 16 }
Repeater {
model: backend.tags
Rectangle {
Layout.preferredHeight: childrenRect.height
Layout.fillWidth: true
color: backend.activeTagsFilter.includes(modelData) ? MiraiColorPalette.filterSelected : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent"
radius: 4
AppText {
text: modelData
padding: 4
}
MouseArea {
anchors.fill: parent
onClicked: {
if (backend.activeTagsFilter.includes(modelData)) {
backend.removeTagFilter(modelData)
} else {
backend.addTagFilter(modelData)
}
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
}
Item {
Layout.fillHeight: true
}
AppButton {
text: `Hide completed tasks: ${backend.shouldHideCompletedTasks ? "ON" : "OFF"}`
onClicked: {
backend.hideCompletedTasks(!backend.shouldHideCompletedTasks)
}
}
}

27
src/qml/TaskItem.qml Normal file
View file

@ -0,0 +1,27 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Mirai
RowLayout {
property taskItem task
AppCheckbox {
id: checkbox
text: task.text
checked: task.state === 'DONE'
textComponent.color: task.date < internal.todayDate ? colorPalette.selected.palette.pink
// : task.date === internal.todayDate ? colorPalette.selected.palette.sapphire
: colorPalette.selected.text
onClicked: {
backend.updateTodo(index, modelData.state === 'DONE' ? "TODO" : "DONE", modelData.text, modelData.date)
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
RowLayout {
id: control
property var tabs: []
Repeater {
model: tabs
Item {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
AppText {
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
id: tabText
text: modelData.label
font.pointSize: 24
}
Rectangle {
Layout.fillWidth: true
color: modelData.selected ? "white" : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent"
implicitHeight: 2
}
}
MouseArea {
anchors.fill: parent
onClicked: {
modelData?.onClick()
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
}
}

View file

@ -0,0 +1,55 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
ColumnLayout {
id: form
property var taskToEdit
property int taskToEditIndex
spacing: 6
signal confirmed
onTaskToEditChanged: {
newTodoContent.text = taskToEdit?.text ?? ""
newTodoDate.text = taskToEdit?.date ?? ""
}
AppText {
text: "New task"
}
AppLineEdit {
id: newTodoContent
Layout.fillWidth: true
placeholderText: "Enter your new task..."
text: taskToEdit?.text ?? ""
Keys.onReturnPressed: {
if (newTodoContent.text == "") {
return
}
if (taskToEdit && taskToEditIndex !== undefined) {
backend.updateTodo(taskToEditIndex, taskToEdit.state, newTodoContent.text, newTodoDate.text)
} else {
backend.addTodo(newTodoContent.text, newTodoDate.text)
}
form.confirmed()
}
}
DateField {
id: newTodoDate
text: taskToEdit?.date ?? ""
textFieldComponent.placeholderText: "No date"
Layout.fillWidth: true
}
}

View file

@ -0,0 +1,38 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
pragma Singleton
import QtQuick 2.5
// Catppuccin Frappe theme (https://github.com/catppuccin/catppuccin)
QtObject {
property string rosewater: "#f2d5cf"
property string flamingo: "#eebebe"
property string pink: "#f4b8e4"
property string mauve: "#ca9ee6"
property string red: "#e78284"
property string maroon: "#ea999c"
property string peach: "#ef9f76"
property string yellow: "#e5c890"
property string green: "#a6d189"
property string teal: "#81c8be"
property string sky: "#99d1db"
property string sapphire: "#85c1dc"
property string blue: "#8caaee"
property string lavender: "#babbf1"
property string text: "#c6d0f5"
property string subtext1: "#b5bfe2"
property string subtext0: "#a5adce"
property string overlay2: "#949cbb"
property string overlay1: "#838ba7"
property string overlay0: "#737994"
property string surface2: "#626880"
property string surface1: "#51576d"
property string surface0: "#414559"
property string base: "#303446"
property string mantle: "#292c3c"
property string crust: "#232634"
}

View file

@ -0,0 +1,24 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
pragma Singleton
import QtQuick 2.5
import Mirai
QtObject {
property QtObject palette: CatppuccinFrappe
property string background: CatppuccinFrappe.base
property string pane: CatppuccinFrappe.mantle
property string text: CatppuccinFrappe.text
property string textPlaceholder: CatppuccinFrappe.overlay1
property string fieldBackground: CatppuccinFrappe.surface0
property string buttonIcon: CatppuccinFrappe.sapphire
property string buttonBackground: CatppuccinFrappe.surface0
property string buttonHovered: CatppuccinFrappe.surface1
property string filterHovered: CatppuccinFrappe.surface0
property string filterSelected: CatppuccinFrappe.surface1
property string modalBorder: CatppuccinFrappe.lavender
}

View file

@ -0,0 +1,17 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import Mirai
ColumnLayout {
AppText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
text: "Not implemented yet :)"
}
}

View file

@ -0,0 +1,98 @@
/*
* Mirai. Copyright (C) 2024 Vyn
* This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
* The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts
import Mirai
ColumnLayout {
QtObject {
id: internal
property string todayDate: Qt.formatDate(new Date(), 'yyyy-MM-dd')
property string tomorrowDate: Qt.formatDate(new Date(new Date().setDate(new Date().getDate() + 1)), 'yyyy-MM-dd')
}
Repeater {
model: backend.tasks
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
id: task
required property var modelData
required property int index
color: "transparent"
HoverHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
Component {
id: dateForTasks
AppText {
topPadding: 32
bottomPadding: 16
text: task.modelData.date === internal.todayDate ? "Today"
: task.modelData.date === internal.tomorrowDate ? "Tomorrow"
: task.modelData.date
color: task.modelData.date < internal.todayDate ? colorPalette.selected.palette.pink
// : task.modelData.date === internal.todayDate ? colorPalette.selected.palette.sapphire
: colorPalette.selected.text
font.pointSize: 24
}
}
Loader {
sourceComponent: task.modelData.shouldShowDate ? dateForTasks : undefined
}
TaskItem {
id: taskItem
task: task.modelData
}
}
Menu {
id: contextMenu
MenuItem {
text: "Edit"
onClicked: {
root.editTask(task.modelData, task.index)
}
}
MenuItem {
text: "Delete"
onClicked: {
backend.removeTodo(task.index)
}
}
}
MouseArea {
id: mouse
anchors.fill: parent
acceptedButtons: Qt.RightButton
propagateComposedEvents: true
onClicked: {
contextMenu.popup()
}
}
}
}
Item {
Layout.fillHeight: true
}
}