mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-01-02 02:48:51 -05:00
* Adding document describing how to use logger * Adjusting logs to follow a new guide * Change order in log header: line number is now before function name
487 lines
22 KiB
C++
487 lines
22 KiB
C++
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "ApplicationMessages.hpp"
|
|
#include "MessagesMainWindow.hpp"
|
|
#include "NewMessage.hpp"
|
|
#include "OptionsMessages.hpp"
|
|
#include "SearchResults.hpp"
|
|
#include "SearchStart.hpp"
|
|
#include "SMSTemplatesWindow.hpp"
|
|
#include "SMSTextToSearch.hpp"
|
|
#include "SMSThreadViewWindow.hpp"
|
|
#include "ThreadWindowOptions.hpp"
|
|
|
|
#include <Dialog.hpp>
|
|
#include <DialogMetadataMessage.hpp>
|
|
#include <messages/OptionsWindow.hpp>
|
|
#include <module-db/queries/messages/sms/QuerySMSRemove.hpp>
|
|
#include <module-db/queries/messages/sms/QuerySMSUpdate.hpp>
|
|
#include <module-db/queries/messages/threads/QueryThreadGetByID.hpp>
|
|
#include <module-db/queries/messages/threads/QueryThreadGetByNumber.hpp>
|
|
#include <module-db/queries/messages/threads/QueryThreadRemove.hpp>
|
|
#include <module-db/queries/phonebook/QueryContactGetByNumberID.hpp>
|
|
#include <module-db/queries/notifications/QueryNotificationsDecrement.hpp>
|
|
#include <service-cellular/CellularMessage.hpp>
|
|
#include <service-db/DBNotificationMessage.hpp>
|
|
#include <service-appmgr/Controller.hpp>
|
|
#include <apps-common/locks/data/PhoneLockMessages.hpp>
|
|
namespace app
|
|
{
|
|
static constexpr auto messagesStackDepth = 1024 * 6; // 6Kb stack size
|
|
|
|
ApplicationMessages::ApplicationMessages(std::string name,
|
|
std::string parent,
|
|
StatusIndicators statusIndicators,
|
|
StartInBackground startInBackground)
|
|
: Application(std::move(name), std::move(parent), statusIndicators, startInBackground, messagesStackDepth),
|
|
AsyncCallbackReceiver{this}
|
|
{
|
|
bus.channels.push_back(sys::BusChannel::ServiceDBNotifications);
|
|
bus.channels.push_back(sys::BusChannel::PhoneLockChanges);
|
|
|
|
addActionReceiver(manager::actions::CreateSms,
|
|
[this](auto &&data) { return handleCreateSmsAction(std::move(data)); });
|
|
addActionReceiver(manager::actions::ShowSmsTemplates, [this](auto &&data) {
|
|
switchWindow(gui::name::window::call_sms_templates, std::move(data));
|
|
return actionHandled();
|
|
});
|
|
addActionReceiver(manager::actions::SmsRejectNoSim, [this](auto &&data) {
|
|
auto action = [this]() {
|
|
returnToPreviousWindow();
|
|
return true;
|
|
};
|
|
const auto notification = utils::translate("app_messages_no_sim");
|
|
showNotification(action, notification);
|
|
return actionHandled();
|
|
});
|
|
addActionReceiver(manager::actions::SMSRejectedByOfflineNotification, [this](auto &&data) {
|
|
auto action = [=]() -> bool {
|
|
returnToPreviousWindow();
|
|
return true;
|
|
};
|
|
const auto notification = utils::translate("app_sms_offline");
|
|
showNotification(action, notification);
|
|
|
|
return actionHandled();
|
|
});
|
|
|
|
connect(typeid(cellular::CallMissedNotification), [&](sys::Message *request) {
|
|
if (getCurrentWindow()->getName() == gui::name::window::call_sms_templates) {
|
|
app::manager::Controller::switchBack(this);
|
|
}
|
|
return sys::MessageNone{};
|
|
});
|
|
|
|
connect(typeid(locks::UnlockedPhone), [&](sys::Message *msg) -> sys::MessagePointer {
|
|
phoneLockState = PhoneLockState::Unlocked;
|
|
return sys::MessageNone{};
|
|
});
|
|
|
|
connect(typeid(locks::LockedPhone), [&](sys::Message *msg) -> sys::MessagePointer {
|
|
phoneLockState = PhoneLockState::Locked;
|
|
return sys::MessageNone{};
|
|
});
|
|
}
|
|
|
|
// Invoked upon receiving data message
|
|
sys::MessagePointer ApplicationMessages::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
|
|
{
|
|
auto retMsg = Application::DataReceivedHandler(msgl);
|
|
// if message was handled by application's template there is no need to process further.
|
|
if (reinterpret_cast<sys::ResponseMessage *>(retMsg.get())->retCode == sys::ReturnCodes::Success) {
|
|
return retMsg;
|
|
}
|
|
if (msgl->messageType == MessageType::DBServiceNotification) {
|
|
auto msg = dynamic_cast<db::NotificationMessage *>(msgl);
|
|
if (msg != nullptr) {
|
|
userInterfaceDBNotification(msgl,
|
|
[&]([[maybe_unused]] sys::Message *, [[maybe_unused]] const std::string &) {
|
|
if (phoneLockState == PhoneLockState::Locked) {
|
|
return false;
|
|
}
|
|
return msg->interface == db::Interface::Name::SMSThread ||
|
|
msg->interface == db::Interface::Name::SMS ||
|
|
msg->interface == db::Interface::Name::SMSTemplate;
|
|
});
|
|
return std::make_shared<sys::ResponseMessage>();
|
|
}
|
|
}
|
|
|
|
return handleAsyncResponse(resp);
|
|
}
|
|
|
|
// Invoked during initialization
|
|
sys::ReturnCodes ApplicationMessages::InitHandler()
|
|
{
|
|
auto ret = Application::InitHandler();
|
|
if (ret != sys::ReturnCodes::Success) {
|
|
return ret;
|
|
}
|
|
|
|
createUserInterface();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ApplicationMessages::createUserInterface()
|
|
{
|
|
windowsFactory.attach(gui::name::window::main_window, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::MessagesMainWindow>(app);
|
|
});
|
|
windowsFactory.attach(gui::name::window::thread_view, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::SMSThreadViewWindow>(app);
|
|
});
|
|
windowsFactory.attach(gui::name::window::new_sms, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::NewMessageWindow>(app);
|
|
});
|
|
windowsFactory.attach(window::name::option_window, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::OptionWindow>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::thread_options, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::ThreadWindowOptions>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::sms_options, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::SmsWindowOptions>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::dialog, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::Dialog>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::dialog_confirm, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::DialogConfirm>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::dialog_yes_no, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::DialogYesNo>(app, name);
|
|
});
|
|
windowsFactory.attach(
|
|
gui::name::window::thread_sms_search,
|
|
[](ApplicationCommon *app, const std::string &name) { return std::make_unique<gui::SMSSearch>(app); });
|
|
windowsFactory.attach(gui::name::window::sms_templates, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::SMSTemplatesWindow>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::call_sms_templates,
|
|
[](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::SMSTemplatesWindow>(app, name);
|
|
});
|
|
windowsFactory.attach(gui::name::window::search_results, [](ApplicationCommon *app, const std::string &name) {
|
|
return std::make_unique<gui::SearchResults>(app);
|
|
});
|
|
|
|
attachPopups({gui::popup::ID::Volume,
|
|
gui::popup::ID::Tethering,
|
|
gui::popup::ID::BluetoothAuthenticate,
|
|
gui::popup::ID::PhoneModes,
|
|
gui::popup::ID::PhoneLock,
|
|
gui::popup::ID::SimLock,
|
|
gui::popup::ID::Alarm});
|
|
}
|
|
|
|
void ApplicationMessages::destroyUserInterface()
|
|
{}
|
|
|
|
bool ApplicationMessages::markSmsThreadAsRead(const ThreadRecord *record)
|
|
{
|
|
using namespace db::query;
|
|
if (record == nullptr) {
|
|
LOG_ERROR("Trying to mark as read a null SMS thread!");
|
|
return false;
|
|
}
|
|
DBServiceAPI::GetQuery(
|
|
this, db::Interface::Name::SMSThread, std::make_unique<MarkAsRead>(record->ID, MarkAsRead::Read::True));
|
|
|
|
if (record->unreadMsgCount) {
|
|
DBServiceAPI::GetQuery(
|
|
this,
|
|
db::Interface::Name::Notifications,
|
|
std::make_unique<notifications::Decrement>(NotificationsRecord::Key::Sms, record->unreadMsgCount));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::markSmsThreadAsUnread(const uint32_t id)
|
|
{
|
|
using namespace db::query;
|
|
DBServiceAPI::GetQuery(
|
|
this, db::Interface::Name::SMSThread, std::make_unique<MarkAsRead>(id, MarkAsRead::Read::False));
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::removeSmsThread(const ThreadRecord *record)
|
|
{
|
|
using db::query::ContactGetByNumberID;
|
|
using db::query::ContactGetByNumberIDResult;
|
|
|
|
if (record == nullptr) {
|
|
LOG_ERROR("Trying to remove a null SMS thread!");
|
|
return false;
|
|
}
|
|
LOG_DEBUG("Removing thread: %" PRIu32, record->ID);
|
|
|
|
auto query = std::make_unique<ContactGetByNumberID>(record->numberID);
|
|
auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
|
|
task->setCallback([this, record](auto response) {
|
|
auto result = dynamic_cast<ContactGetByNumberIDResult *>(response);
|
|
if (result != nullptr) {
|
|
const auto &contact = result->getResult();
|
|
auto metaData = std::make_unique<gui::DialogMetadataMessage>(
|
|
gui::DialogMetadata{contact.getFormattedName(),
|
|
"delete_128px_W_G",
|
|
utils::translate("app_messages_thread_delete_confirmation"),
|
|
"",
|
|
[this, record]() { return onRemoveSmsThreadConfirmed(*record); }});
|
|
switchWindow(gui::name::window::dialog_yes_no, gui::ShowMode::GUI_SHOW_INIT, std::move(metaData));
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
task->execute(this, this);
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::onRemoveSmsThreadConfirmed(const ThreadRecord &record)
|
|
{
|
|
using db::query::ThreadRemove;
|
|
using db::query::ThreadRemoveResult;
|
|
|
|
auto query = std::make_unique<ThreadRemove>(record.ID);
|
|
auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
|
|
task->setCallback([this, threadId = record.ID, unreadMsgCount = record.unreadMsgCount](auto response) {
|
|
const auto result = dynamic_cast<ThreadRemoveResult *>(response);
|
|
if ((result != nullptr) && result->success()) {
|
|
if (unreadMsgCount) {
|
|
DBServiceAPI::GetQuery(this,
|
|
db::Interface::Name::Notifications,
|
|
std::make_unique<db::query::notifications::Decrement>(
|
|
NotificationsRecord::Key::Sms, unreadMsgCount));
|
|
}
|
|
switchWindow(gui::name::window::main_window);
|
|
return true;
|
|
}
|
|
LOG_ERROR("Thread remove id=%" PRIu32 " failed", threadId);
|
|
return false;
|
|
});
|
|
task->execute(this, this);
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::removeSms(const SMSRecord &record)
|
|
{
|
|
LOG_DEBUG("Removing sms: %" PRIu32, record.ID);
|
|
auto metaData = std::make_unique<gui::DialogMetadataMessage>(
|
|
gui::DialogMetadata{record.body,
|
|
"delete_128px_W_G",
|
|
utils::translate("app_messages_message_delete_confirmation"),
|
|
"",
|
|
[this, record] { return onRemoveSmsConfirmed(record); }});
|
|
switchWindow(gui::name::window::dialog_yes_no, gui::ShowMode::GUI_SHOW_INIT, std::move(metaData));
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::onRemoveSmsConfirmed(const SMSRecord &record)
|
|
{
|
|
using db::query::SMSRemove;
|
|
using db::query::SMSRemoveResult;
|
|
using db::query::ThreadGetByID;
|
|
using db::query::ThreadGetByIDResult;
|
|
|
|
auto query = std::make_unique<SMSRemove>(record.ID);
|
|
auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS);
|
|
task->setCallback([this, record](auto response) {
|
|
auto result = dynamic_cast<SMSRemoveResult *>(response);
|
|
if (result != nullptr && result->getResults()) {
|
|
auto query = std::make_unique<ThreadGetByID>(record.threadID);
|
|
auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
|
|
task->setCallback([this](auto response) {
|
|
const auto result = dynamic_cast<ThreadGetByIDResult *>(response);
|
|
if (result != nullptr) {
|
|
const auto thread = result->getRecord();
|
|
if (thread.has_value()) {
|
|
this->switchWindow(gui::name::window::thread_view);
|
|
}
|
|
else {
|
|
this->switchWindow(gui::name::window::main_window);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
task->execute(this, this);
|
|
return true;
|
|
}
|
|
LOG_ERROR("SMS remove id=%" PRIu32 " failed", record.ID);
|
|
return false;
|
|
});
|
|
task->execute(this, this);
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::searchEmpty(const std::string &query)
|
|
{
|
|
gui::DialogMetadata meta;
|
|
meta.icon = "search_128px_W_G";
|
|
meta.text = utils::translate("app_messages_thread_no_result");
|
|
meta.title = utils::translate("common_results_prefix") + query;
|
|
auto data = std::make_unique<gui::DialogMetadataMessage>(meta);
|
|
data->ignoreCurrentWindowOnStack = true;
|
|
switchWindow(gui::name::window::dialog, std::make_unique<gui::DialogMetadataMessage>(meta));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::showSearchResults(const UTF8 &title, const UTF8 &search_text)
|
|
{
|
|
switchWindow(gui::name::window::search_results, std::make_unique<SMSTextToSearch>(search_text, title));
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::showNotification(std::function<bool()> action,
|
|
const std::string ¬ification,
|
|
bool ignoreCurrentWindowOnStack)
|
|
{
|
|
gui::DialogMetadata meta;
|
|
meta.icon = "info_128px_W_G";
|
|
meta.text = notification;
|
|
meta.action = std::move(action);
|
|
auto switchData = std::make_unique<gui::DialogMetadataMessage>(meta);
|
|
switchData->ignoreCurrentWindowOnStack = ignoreCurrentWindowOnStack;
|
|
switchWindow(gui::name::window::dialog_confirm, std::move(switchData));
|
|
return true;
|
|
}
|
|
|
|
bool ApplicationMessages::updateDraft(SMSRecord &record, const UTF8 &body)
|
|
{
|
|
assert(!body.empty()); // precondition check.
|
|
|
|
record.body = body;
|
|
record.date = std::time(nullptr);
|
|
|
|
using db::query::SMSUpdate;
|
|
const auto [succeed, _] =
|
|
DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSUpdate>(record));
|
|
return succeed;
|
|
}
|
|
|
|
void ApplicationMessages::createDraft(const utils::PhoneNumber::View &number,
|
|
const UTF8 &body,
|
|
std::function<void(const SMSRecord &)> onSuccess)
|
|
{
|
|
assert(!body.empty()); // precondition check.
|
|
|
|
SMSRecord record;
|
|
record.number = number;
|
|
record.body = body;
|
|
record.type = SMSType::DRAFT;
|
|
record.date = std::time(nullptr);
|
|
|
|
using db::query::SMSAdd;
|
|
auto query = std::make_unique<db::query::SMSAdd>(record);
|
|
auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS);
|
|
task->setCallback([onSuccess](auto response) {
|
|
using db::query::SMSAddResult;
|
|
if (auto result = static_cast<SMSAddResult *>(response); result->succeed()) {
|
|
if (onSuccess) {
|
|
onSuccess(result->record);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
LOG_ERROR("Failed to add an SMS record to the DB.");
|
|
return false;
|
|
});
|
|
task->execute(this, this);
|
|
}
|
|
|
|
bool ApplicationMessages::removeDraft(const SMSRecord &record)
|
|
{
|
|
using db::query::SMSRemove;
|
|
if (!record.isValid()) {
|
|
LOG_ERROR("Draft SMS is invalid.");
|
|
return false;
|
|
}
|
|
const auto [succeed, _] =
|
|
DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSRemove>(record.ID));
|
|
return succeed;
|
|
}
|
|
|
|
bool ApplicationMessages::sendSms(const utils::PhoneNumber::View &number, const UTF8 &body)
|
|
{
|
|
if (number.getEntered().empty() || body.length() == 0) {
|
|
LOG_WARN("Number or sms body is empty");
|
|
return false;
|
|
}
|
|
SMSRecord record;
|
|
record.number = number;
|
|
record.body = body;
|
|
record.type = SMSType::QUEUED;
|
|
record.date = std::time(nullptr);
|
|
|
|
using db::query::SMSAdd;
|
|
const auto [succeed, _] =
|
|
DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSAdd>(record));
|
|
return succeed;
|
|
}
|
|
|
|
bool ApplicationMessages::resendSms(const SMSRecord &record)
|
|
{
|
|
auto resendRecord = record;
|
|
resendRecord.type = SMSType::QUEUED;
|
|
resendRecord.date = std::time(nullptr); // update date sent - it will display an old, failed sms at
|
|
// the the bottom, but this is correct
|
|
|
|
using db::query::SMSUpdate;
|
|
const auto [succeed, _] =
|
|
DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSUpdate>(resendRecord));
|
|
return succeed;
|
|
}
|
|
|
|
bool ApplicationMessages::handleSendSmsFromThread(const utils::PhoneNumber::View &number, const UTF8 &body)
|
|
{
|
|
return sendSms(number, body);
|
|
}
|
|
|
|
bool ApplicationMessages::newMessageOptions(const std::string &requestingWindow, gui::Text *text)
|
|
{
|
|
LOG_INFO("New message options for %s", requestingWindow.c_str());
|
|
auto opts = std::make_unique<gui::OptionsWindowOptions>(newMessageWindowOptions(this, requestingWindow, text));
|
|
switchWindow(window::name::option_window, std::move(opts));
|
|
return true;
|
|
}
|
|
|
|
ActionResult ApplicationMessages::handleCreateSmsAction(std::unique_ptr<gui::SwitchData> data)
|
|
{
|
|
if (auto sendRequest = dynamic_cast<SMSSendRequest *>(data.get()); sendRequest != nullptr) {
|
|
const auto phoneNumber = sendRequest->getPhoneNumber();
|
|
auto query = std::make_unique<db::query::ThreadGetByNumber>(phoneNumber);
|
|
auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
|
|
|
|
auto queryCallback = [this, capturedData = std::move(data)](auto response) mutable {
|
|
const auto result = dynamic_cast<db::query::ThreadGetByNumberResult *>(response);
|
|
if (result == nullptr) {
|
|
switchWindow(gui::name::window::new_sms, std::move(capturedData));
|
|
return false;
|
|
}
|
|
|
|
const auto &thread = result->getThread();
|
|
if (!thread.isValid()) {
|
|
switchWindow(gui::name::window::new_sms, std::move(capturedData));
|
|
}
|
|
else {
|
|
auto switchData = std::make_unique<SMSThreadData>(std::make_unique<ThreadRecord>(thread));
|
|
switchData->ignoreCurrentWindowOnStack = true;
|
|
switchData->nameOfSenderApplication = capturedData->nameOfSenderApplication;
|
|
switchWindow(gui::name::window::thread_view, std::move(switchData));
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
task->setCallback([callback = std::make_shared<decltype(queryCallback)>(std::move(queryCallback))](
|
|
auto response) { return (*callback)(response); });
|
|
task->execute(this, this);
|
|
return actionHandled();
|
|
}
|
|
return actionNotHandled();
|
|
}
|
|
|
|
} /* namespace app */
|