// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md #include "ApplicationMessages.hpp" #include "MessagesStyle.hpp" #include "SMSOutputWidget.hpp" #include "SMSThreadModel.hpp" #include #include #include #include namespace { constexpr auto threadQueryTimeoutMs = 1000; } SMSThreadModel::SMSThreadModel(app::ApplicationCommon *app) : DatabaseModel(app), app::AsyncCallbackReceiver{app} { smsInput = new gui::SMSInputWidget(application); } SMSThreadModel::~SMSThreadModel() { delete smsInput; } unsigned int SMSThreadModel::getMinimalItemSpaceRequired() const { return style::messages::smsOutput::default_h; } gui::ListItem *SMSThreadModel::getItem(gui::Order order) { const auto sms = getRecord(order); if (sms == nullptr) { return nullptr; } // Small hack to trick current model logic -> adding empty row into query result for Input Widget if (sms->type == SMSType::INPUT) { addReturnNumber(); return smsInput; } return new gui::SMSOutputWidget(application, sms); } unsigned int SMSThreadModel::requestRecordsCount() { return recordsCount; } void SMSThreadModel::requestRecords(std::uint32_t offset, std::uint32_t limit) { auto query = std::make_unique(smsThreadID, offset, limit, numberID); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS); task->setCallback([this](auto response) { return handleQueryResponse(response); }); task->execute(application, this); } bool SMSThreadModel::updateRecords(std::vector records) { DatabaseModel::updateRecords(std::move(records)); list->onProviderDataUpdate(); return true; } auto SMSThreadModel::handleQueryResponse(db::QueryResult *queryResult) -> bool { auto msgResponse = dynamic_cast(queryResult); assert(msgResponse != nullptr); auto records_data = msgResponse->getResults(); // If list record count has changed we need to rebuild list. if (recordsCount != (msgResponse->getCount() + 1)) { // Additional one element for SMSInputWidget. recordsCount = msgResponse->getCount() + 1; list->reSendLastRebuildRequest(); return false; } resetInputWidget(); if (msgResponse->getDraft().isValid() && smsInput->inputText->isEmpty()) { smsInput->draft = msgResponse->getDraft().type == SMSType::DRAFT ? std::optional{msgResponse->getDraft()} : std::nullopt; smsInput->displayDraftMessage(); } number = std::make_unique(msgResponse->getNumber()); return this->updateRecords(std::move(records_data)); } void SMSThreadModel::addReturnNumber() { if (number != nullptr) { smsInput->number = std::move(number); } smsInput->activatedCallback = [this]([[maybe_unused]] gui::Item &item) { auto app = dynamic_cast(application); assert(app != nullptr); assert(smsInput->number != nullptr); if (!app->handleSendSmsFromThread(*smsInput->number, smsInput->inputText->getText())) { LOG_ERROR("Send SMS from thread failed"); } smsInput->clearDraftMessage(); return true; }; } void SMSThreadModel::handleDraftMessage() { smsInput->handleDraftMessage(); } void SMSThreadModel::resetInputWidget() { smsInput->setFocus(false); smsInput->setVisible(true); smsInput->clearNavigationItem(gui::NavigationDirection::UP); smsInput->clearNavigationItem(gui::NavigationDirection::DOWN); } void SMSThreadModel::markCurrentThreadAsRead() { const auto [code, msg] = DBServiceAPI::GetQueryWithReply(application, db::Interface::Name::SMSThread, std::make_unique(smsThreadID), threadQueryTimeoutMs); if (code == sys::ReturnCodes::Success && msg != nullptr) { const auto queryResponse = dynamic_cast(msg.get()); assert(queryResponse != nullptr); const auto resultResponse = queryResponse->getResult(); const auto result = dynamic_cast(resultResponse.get()); assert(result != nullptr); if (const auto unreadMsgCount = result->getRecord()->unreadMsgCount; unreadMsgCount > 0) { DBServiceAPI::GetQuery( application, db::Interface::Name::SMSThread, std::make_unique(smsThreadID, db::query::MarkAsRead::Read::True)); DBServiceAPI::GetQuery( application, db::Interface::Name::Notifications, std::make_unique(NotificationsRecord::Key::Sms, unreadMsgCount)); } } }