/* * 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 "UiState.h" #include "Utils.h" #include "appwindow.h" #include "mirai-core/Mirai.h" #include "mirai-core/TaskItem.h" #include "mirai-core/TodoMd.h" #include "slint.h" #include "slint_sharedvector.h" #include "slint_string.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include UiState::UiState(mirai::Mirai *miraiInstance) : miraiInstance_(miraiInstance), view_(miraiInstance) { sources_ = std::make_shared>(); days_ = std::make_shared>(); unscheduledTasks_ = std::make_shared>(); tags_ = std::make_shared>(); taskWindow_->global().set_sources(sources_); taskWindow_->global().set_tags(tags_); eventWindow_->global().set_sources(sources_); eventWindow_->global().set_tags(tags_); view_.update(); reloadSources(); reloadTags(); reloadTasks(); setupCallbacks(); } std::optional stringToDate(const std::string &dateStr) { using std::operator""sv; auto dateSplitView = dateStr | std::views::split("-"sv) | std::views::transform([](auto v) -> int { int i = 0; std::from_chars(v.data(), v.data() + v.size(), i); return i; }); std::vector dateSplit{dateSplitView.begin(), dateSplitView.end()}; if (dateSplit.size() != 3) { return std::nullopt; } auto year = dateSplit[0]; auto month = dateSplit[1]; auto day = dateSplit[2]; return Date{.year = year, .month = month, .day = day}; } void UiState::setupTaskWindowCallbacks() { taskWindow_->on_save([&](SaveTaskData newTaskData) { auto source = miraiInstance_->getSourceById(newTaskData.sourceId); assert(source); auto task = source->getTaskById(newTaskData.id); assert(task); const Date &date = newTaskData.date; const std::string dateStr = SlintDateToStdString(date); auto taskData = mirai::TodoMdFormat::stringToTask("- [ ] " + std::string(newTaskData.title), dateStr); task->setText(taskData.text); if (task->day() && task->day()->getDate() != SlintDateToMiraiDate(date)) { auto newDate = source->day(SlintDateToMiraiDate(date)); newDate->createTask({ .text = taskData.text, .state = task->getState(), .tags = taskData.tags, }); auto oldDate = task->day(); source->deleteTask(*task); } miraiInstance_->save(); view_.update(); reloadTasks(); taskWindow_->hide(); }); taskWindow_->on_create([&](NewTaskData newTaskData) { const Date &date = newTaskData.date; const std::string dateStr = SlintDateToStdString(date); auto source = miraiInstance_->getSourceById(newTaskData.sourceId); auto task = mirai::TodoMdFormat::stringToTask("- [ ] " + std::string(newTaskData.title), dateStr); if (!newTaskData.scheduled) { source->addUnscheduledTask(task); } else if (newTaskData.eventId == -1) { auto day = source->day(SlintDateToMiraiDate(date)); assert(day); day->createTask(task); } else { auto event = source->getEventById(newTaskData.eventId); assert(event); event->createTask(task); } miraiInstance_->save(); view_.update(); reloadTasks(); taskWindow_->hide(); }); } void UiState::setupEventWindowCallbacks() { mainWindow_->global().on_open_new_event_form([&]() { auto todayDate = std::chrono::year_month_day{ std::chrono::floor(std::chrono::system_clock::now()) }; eventWindow_->set_eventId(-1); eventWindow_->set_taskTitle(""); eventWindow_->set_taskDate({ .year = static_cast(todayDate.year()), // Try to directly use `unsigned` .month = static_cast(static_cast(todayDate.month())), .day = static_cast(static_cast(todayDate.day())), }); eventWindow_->show(); }); mainWindow_->global().on_open_edit_event_form([&](int sourceId, int eventId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto event = source->getEventById(eventId); assert(event); eventWindow_->global().set_sources(sources_); eventWindow_->global().set_tags(tags_); eventWindow_->set_sourceId(sourceId); eventWindow_->set_eventId(eventId); eventWindow_->set_taskTitle(slint::SharedString(event->getText())); eventWindow_->set_taskDate(MiraiDateToSlintDate(event->getDate())); eventWindow_->set_startsAt(MiraiTimeToSlintTime(event->getStartTime())); eventWindow_->set_endsAt(MiraiTimeToSlintTime(event->getEndTime())); eventWindow_->show(); }); mainWindow_->global().on_delete_event_clicked([&](int sourceId, int eventId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto event = source->getEventById(eventId); assert(event); auto day = event->day(); assert(day); day->deleteEvent(*event); miraiInstance_->save(); view_.update(); reloadTasks(); }); eventWindow_->on_create([&](NewEventParams newEventParams) { const Date &date = newEventParams.date; const std::string dateStr = SlintDateToStdString(date); auto source = miraiInstance_->getSourceById(newEventParams.sourceId); auto day = source->day(SlintDateToMiraiDate(date)); day->createEvent({ .description = std::string(newEventParams.title), .date = SlintDateToMiraiDate(newEventParams.date), .startTime = SlintTimeToMiraiTime(newEventParams.startsAt), .endTime = SlintTimeToMiraiTime(newEventParams.endsAt), }); miraiInstance_->save(); view_.update(); reloadTasks(); eventWindow_->hide(); }); eventWindow_->on_save([&](SaveEventParams newEventParams) { const Date &date = newEventParams.date; const std::string dateStr = SlintDateToStdString(date); auto source = miraiInstance_->getSourceById(newEventParams.sourceId); assert(source); auto event = source->getEventById(newEventParams.id); assert(event); event->setText(std::string(newEventParams.title)); event->setStartTime(SlintTimeToMiraiTime(newEventParams.startsAt)); event->setEndTime(SlintTimeToMiraiTime(newEventParams.endsAt)); // TODO we can't change the date of the event for now. miraiInstance_->save(); view_.update(); reloadTasks(); eventWindow_->hide(); }); } void UiState::setupUtilsCallbacks() { mainWindow_->global().on_formatDate([&](const Date &date) { std::chrono::year_month_day chronoDate{ std::chrono::year(date.year), std::chrono::month(date.month), std::chrono::day(date.day), }; return std::format("{:%d %B}", chronoDate); }); } void UiState::setupCallbacks() { mainWindow_->global().on_task_clicked([&](int sourceId, int taskId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto task = source->getTaskById(taskId); assert(task); task->getState() == mirai::DONE ? task->markAsUndone() : task->markAsDone(); miraiInstance_->save(); }); mainWindow_->global().on_source_clicked([&](int index) { const auto &source = miraiInstance_->getSourceById(index); const auto &sourceName = source->getName(); if (std::ranges::find(view_.getActiveFilesFilter(), sourceName) == view_.getActiveFilesFilter().end()) { view_.addSourceFilter(sourceName); } else { view_.removeSourceFilter(sourceName); } view_.update(); reloadTasks(); }); mainWindow_->global().on_tag_clicked([&](int index) { const std::string &tag = miraiInstance_->getTags().at(index); if (std::ranges::find(view_.getActiveTagsFilter(), tag) == view_.getActiveTagsFilter().end()) { view_.addTagFilter(tag); } else { view_.removeTagFilter(tag); } view_.update(); reloadTasks(); }); mainWindow_->global().on_delete_task_clicked([&](int sourceId, int taskId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto task = source->getTaskById(taskId); assert(task); source->deleteTask(*task); miraiInstance_->save(); view_.update(); reloadTasks(); }); mainWindow_->global().on_open_edit_task_form([&](int sourceId, int taskId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto task = source->getTaskById(taskId); assert(task); taskWindow_->set_taskSourceIndex(sourceId); taskWindow_->set_taskId(task->id()); taskWindow_->set_taskTitle(slint::SharedString(task->getText())); if (task->day() != nullptr) { taskWindow_->set_scheduled(true); taskWindow_->set_taskDate(MiraiDateToSlintDate(task->day()->getDate())); } else { taskWindow_->set_scheduled(false); } taskWindow_->show(); }); mainWindow_->global().on_open_new_task_form([&](OpenNewTaskFormParams params) { taskWindow_->global().set_sources(sources_); taskWindow_->global().set_tags(tags_); auto todayDate = std::chrono::year_month_day{ std::chrono::floor(std::chrono::system_clock::now()) }; taskWindow_->set_taskId(-1); taskWindow_->set_eventId(params.eventId); taskWindow_->set_taskSourceIndex(params.eventSourceId == -1 ? 0 : params.eventSourceId); taskWindow_->set_taskTitle(""); if (params.eventId == -1) { taskWindow_->set_taskDate({ .year = static_cast(todayDate.year()), // Try to directly use `unsigned` .month = static_cast(static_cast(todayDate.month())), .day = static_cast(static_cast(todayDate.day())), }); } else { auto source = miraiInstance_->getSourceById(params.eventSourceId); assert(source); auto event = source->getEventById(params.eventId); assert(event); taskWindow_->set_taskDate(MiraiDateToSlintDate(event->getDate())); } taskWindow_->show(); }); mainWindow_->global().on_toggle_show_completed_tasks([&] { view_.hideCompletedTasks(!view_.shouldHideCompletedTasks()); view_.update(); reloadTasks(); }); mainWindow_->global().set_sources(sources_); mainWindow_->global().set_tags(tags_); mainWindow_->global().set_visible_tasks(days_); setupTaskWindowCallbacks(); setupEventWindowCallbacks(); setupUtilsCallbacks(); } std::shared_ptr> stdToSlintStringVector(const std::vector &stdVector) { auto slintVector = std::make_shared>(); for (const auto &item : stdVector) { slintVector->push_back(slint::SharedString(item)); } return slintVector; } void UiState::reloadTasks() { days_->clear(); if (miraiInstance_->getSources().size() == 0) { return; } auto todayDate = mirai::Date(std::chrono::system_clock::now()); auto &days = view_; auto slintDays = std::make_shared>(); for (int dayIndex = 0; dayIndex < days.count(); ++dayIndex) { auto ¤tDay = view_[dayIndex]; auto slintEvents = std::make_shared>(); auto slintDayTasks = std::make_shared>(); slintDays->push_back(Day{ .sourceId = currentDay.day->source()->id(), .id = dayIndex, .date = MiraiDateToSlintDate(currentDay.day->getDate()), .events = slintEvents, .tasks = slintDayTasks, .isLate = currentDay.day->getDate() < todayDate, .isToday = currentDay.day->getDate() == todayDate, }); for (int taskIndex = 0; taskIndex < currentDay.filteredTasks.size(); ++taskIndex) { auto &task = currentDay.filteredTasks.at(taskIndex); std::vector tags; std::transform( task->getTags().begin(), task->getTags().end(), std::back_inserter(tags), [&](const std::string &tag) { return slint::SharedString(tag); } ); slintDayTasks->push_back({ .sourceId = task->source()->id(), .id = task->id(), .title = slint::SharedString(task->getText()), .checked = task->getState() == mirai::DONE, .tags = std::make_shared>(tags), }); } for (int eventIndex = 0; eventIndex < currentDay.filteredEvents.size(); ++eventIndex) { auto ¤tEvent = currentDay.filteredEvents.at(eventIndex); auto slintTasks = std::make_shared>(); slintEvents->push_back(Event{ .sourceId = currentEvent.event->day()->source()->id(), .id = currentEvent.event->id(), .title = slint::SharedString(currentEvent.event->getText()), .startsAt = MiraiTimeToSlintTime(currentEvent.event->getStartTime()), .endsAt = MiraiTimeToSlintTime(currentEvent.event->getEndTime()), .tasks = slintTasks, }); for (int taskIndex = 0; taskIndex < currentEvent.filteredTasks.size(); ++taskIndex) { auto &task = currentEvent.filteredTasks.at(taskIndex); std::vector tags; std::transform( task->getTags().begin(), task->getTags().end(), std::back_inserter(tags), [&](const std::string &tag) { return slint::SharedString(tag); } ); slintTasks->push_back({ .sourceId = task->source()->id(), .id = task->id(), .title = slint::SharedString(task->getText()), .checked = task->getState() == mirai::DONE, .tags = std::make_shared>(tags), }); } } } days_ = slintDays; mainWindow_->global().set_visible_tasks(days_); unscheduledTasks_->clear(); for (int taskIndex = 0; taskIndex < view_.filteredUnscheduledTasks().size(); ++taskIndex) { auto &task = view_.filteredUnscheduledTasks().at(taskIndex); std::vector tags; std::transform( task->getTags().begin(), task->getTags().end(), std::back_inserter(tags), [&](const std::string &tag) { return slint::SharedString(tag); } ); unscheduledTasks_->push_back({ .sourceId = task->source()->id(), .id = task->id(), .title = slint::SharedString(task->getText()), .checked = task->getState() == mirai::DONE, .tags = std::make_shared>(tags), }); } mainWindow_->global().set_unscheduled_tasks(unscheduledTasks_); } void UiState::reloadSources() { sources_->clear(); for (const auto &source : miraiInstance_->getSources()) { sources_->push_back(slint::SharedString(source->getName())); } } void UiState::reloadTags() { tags_->clear(); for (const auto &tag : miraiInstance_->getTags()) { tags_->push_back(slint::SharedString(tag)); } } void UiState::run() { mainWindow_->run(); }