~aleteoryx/muditaos

f2ce6e946b1488b10442b9e93242c5c61522a13a — Michał Kamoń 4 years ago 2e6899a
[EGD-6707] Add single number message notification

This PR provides implementation of home screen notification for
`notSeen` messages received from a single number. The "single-number:
`notSeen` message notification distinct from "multiple-number"
notification with the following features:
 - displaying formatted contact name
 - `onActivated` it switches to respective thread window (instead of
 all thread window) in `ApplicationMessages`

 The PR also introduced some `ActiveNotificationsModel` code refactor
 to align the implementation with `Single Responsibility Principle`
M module-apps/application-desktop/ApplicationDesktop.cpp => module-apps/application-desktop/ApplicationDesktop.cpp +6 -3
@@ 43,8 43,8 @@ namespace app
                                           std::string parent,
                                           sys::phone_modes::PhoneMode mode,
                                           StartInBackground startInBackground)
        : Application(std::move(name), std::move(parent), mode, startInBackground), lockHandler(this),
          dbNotificationHandler(this)
        : Application(std::move(name), std::move(parent), mode, startInBackground), AsyncCallbackReceiver(this),
          lockHandler(this), dbNotificationHandler(this)
    {
        using namespace gui::top_bar;
        topBarManager->enableIndicators({Indicator::Signal,


@@ 171,7 171,10 @@ namespace app

        // handle database response
        if (resp != nullptr) {
            if (auto msg = dynamic_cast<db::QueryResponse *>(resp)) {
            if (auto command = callbackStorage->getCallback(resp); command->execute()) {
                handled = true;
            }
            else if (auto msg = dynamic_cast<db::QueryResponse *>(resp)) {
                auto result = msg->getResult();
                if (dbNotificationHandler.handle(result.get())) {
                    handled = true;

M module-apps/application-desktop/ApplicationDesktop.hpp => module-apps/application-desktop/ApplicationDesktop.hpp +1 -1
@@ 23,7 23,7 @@ namespace gui

namespace app
{
    class ApplicationDesktop : public Application
    class ApplicationDesktop : public Application, public AsyncCallbackReceiver
    {
      public:
        bool need_sim_select = false;

M module-apps/application-desktop/models/ActiveNotificationsModel.cpp => module-apps/application-desktop/models/ActiveNotificationsModel.cpp +166 -80
@@ 2,14 2,169 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ActiveNotificationsModel.hpp"
#include <application-desktop/ApplicationDesktop.hpp>
#include <module-db/queries/notifications/QueryNotificationsClear.hpp>
#include <service-appmgr/Controller.hpp>
#include <application-messages/ApplicationMessages.hpp>
#include <application-call/data/CallSwitchData.hpp>
#include <SystemManager/messages/TetheringStateRequest.hpp>
#include <queries/messages/threads/QueryThreadGetByNumber.hpp>
#include <application-messages/data/SMSdata.hpp>
#include <application-messages/Constants.hpp>
#include <service-appmgr/messages/SwitchRequest.hpp>

using namespace gui;

namespace
{
    using Notification = const notifications::NotificationWithContact *;
    void setSMSFocusChangedCallback(NotificationListItem *item, ActiveNotificationsModel *model)
    {
        item->focusChangedCallback = [model](gui::Item &_item) {
            if (_item.focus) {
                model->setParentBottomBar(
                    {}, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear"));
                return true;
            }
            return false;
        };
    }

    auto createSMSActivatedCallback(app::Application *app)
    {
        return [app]([[maybe_unused]] gui::Item &_item) {
            return app::manager::Controller::sendAction(
                app, app::manager::actions::Launch, std::make_unique<app::ApplicationLaunchData>(app::name_messages));
        };
    }

    auto createSMSActivatedCallback(app::Application *app, const ContactRecord &record)
    {
        Expects(not record.numbers.empty());
        return [app, number = record.numbers[0].number]([[maybe_unused]] gui::Item &_item) {
            auto query = std::make_unique<db::query::ThreadGetByNumber>(number);
            auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);

            auto queryCallback = [app](db::QueryResult *msg) -> bool {
                Expects(typeid(*msg) == typeid(db::query::ThreadGetByNumberResult));
                auto result  = static_cast<db::query::ThreadGetByNumberResult *>(msg);
                auto data    = std::make_unique<SMSThreadData>(std::make_shared<ThreadRecord>(result->getThread()));
                auto request = std::make_shared<app::manager::SwitchRequest>(
                    app->GetName(), app::name_messages, gui::name::window::thread_view, std::move(data));
                return app->bus.sendUnicast(std::move(request), app::manager::ApplicationManager::ServiceName);
            };
            task->setCallback(std::move(queryCallback));
            task->execute(app, static_cast<app::ApplicationDesktop *>(app));
            return true;
        };
    }

    void setSMSActivatedCallback(NotificationListItem *item, Notification provider, app::Application *app)
    {
        if (provider->hasRecord() && not provider->getRecord().numbers.empty()) {
            item->activatedCallback = createSMSActivatedCallback(app, provider->getRecord());
        }
        else {
            item->activatedCallback = createSMSActivatedCallback(app);
        }
    }

    void setSMSOnInputCallback(NotificationListItem *item, app::Application *app)
    {
        item->inputCallback = [app]([[maybe_unused]] Item &item, const InputEvent &inputEvent) {
            if (inputEvent.isShortRelease(KeyCode::KEY_RF)) {
                DBServiceAPI::GetQuery(
                    app,
                    db::Interface::Name::Notifications,
                    std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Sms));
                return true;
            }
            return false;
        };
    }

    void setCallFocusChangedCallback(NotificationListItem *item, Notification provider, ActiveNotificationsModel *model)
    {
        item->focusChangedCallback = [model, canCall = provider->hasRecord()](gui::Item &_item) {
            if (_item.focus) {
                UTF8 bottomBarLeftText = canCall ? UTF8{utils::translate("common_call")} : UTF8{};
                model->setParentBottomBar(
                    bottomBarLeftText, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear"));
            }
            return true;
        };
    }

    void setCallActivatedCallback(NotificationListItem *item, app::Application *app)
    {
        item->activatedCallback = [app]([[maybe_unused]] gui::Item &_item) {
            return app::manager::Controller::sendAction(app, app::manager::actions::ShowCallLog);
        };
    }

    auto createCallOnRightFunctionalCallback(app::Application *app) -> std::function<void()>
    {
        return [app]() {
            DBServiceAPI::GetQuery(app,
                                   db::Interface::Name::Notifications,
                                   std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Calls));
        };
    }
    auto createCallOnLeftFunctionalCallback(app::Application *app, Notification provider) -> std::function<void()>
    {
        if (!provider->hasRecord()) {
            return nullptr;
        }
        if (const auto &record = provider->getRecord(); !record.numbers.empty()) {
            return [app, number = record.numbers[0].number]() {
                app::manager::Controller::sendAction(
                    app, app::manager::actions::Call, std::make_unique<app::ExecuteCallData>(number));
            };
        }
        return nullptr;
    }

    void setCallOnInputCallback(NotificationListItem *item, Notification provider, app::Application *app)
    {
        auto onRightFunctionalKeyCallback = createCallOnRightFunctionalCallback(app);
        auto onLeftFunctionalKeyCallback  = createCallOnLeftFunctionalCallback(app, provider);

        item->inputCallback = [keyRightFunctionalCb = std::move(onRightFunctionalKeyCallback),
                               keyLeftFunctionalCb  = std::move(onLeftFunctionalKeyCallback)](
                                  [[maybe_unused]] Item &item, const InputEvent &inputEvent) {
            if (inputEvent.isShortRelease()) {
                if (inputEvent.is(KeyCode::KEY_RF) && keyRightFunctionalCb != nullptr) {
                    keyRightFunctionalCb();
                    return true;
                }
                else if (inputEvent.is(KeyCode::KEY_LF) && keyLeftFunctionalCb != nullptr) {
                    keyLeftFunctionalCb();
                    return true;
                }
            }
            return false;
        };
    }

    void setTetheringActivatedCallback(NotificationListItem *item, app::Application *app)
    {
        item->activatedCallback = [app]([[maybe_unused]] gui::Item &_item) {
            return app->bus.sendUnicast(std::make_shared<sys::TetheringStateRequest>(sys::phone_modes::Tethering::Off),
                                        service::name::system_manager);
        };
    }

    void setTetheringFocusChangedCallback(NotificationListItem *item, ActiveNotificationsModel *model)
    {
        item->focusChangedCallback = [model](gui::Item &_item) {
            if (_item.focus) {
                model->setParentBottomBar({}, utils::translate("common_disconnect"), {});
                return true;
            }
            return false;
        };
    }
} // namespace

ActiveNotificationsModel::ActiveNotificationsModel(AppWindow *parent) : parent(parent)
{}



@@ 19,83 174,24 @@ void ActiveNotificationsModel::setParentBottomBar(const UTF8 &left, const UTF8 &
    parent->setBottomBarText(center, BottomBar::Side::CENTER);
    parent->setBottomBarText(right, BottomBar::Side::RIGHT);
}

auto ActiveNotificationsModel::create(const notifications::NotSeenSMSNotification *notification)
    -> NotificationListItem *
{
    auto item                  = NotificationsModel::create(notification);
    item->focusChangedCallback = [this](gui::Item &_item) {
        if (_item.focus) {
            setParentBottomBar({}, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear"));
            return true;
        }
        return false;
    };
    item->activatedCallback = [this]([[maybe_unused]] gui::Item &_item) {
        return app::manager::Controller::sendAction(parent->getApplication(),
                                                    app::manager::actions::Launch,
                                                    std::make_unique<app::ApplicationLaunchData>(app::name_messages));
    };
    item->inputCallback = [this]([[maybe_unused]] Item &item, const InputEvent &inputEvent) {
        if (inputEvent.isShortRelease(KeyCode::KEY_RF)) {
            DBServiceAPI::GetQuery(parent->getApplication(),
                                   db::Interface::Name::Notifications,
                                   std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Sms));
            return true;
        }
        return false;
    };
    setSMSFocusChangedCallback(item, this);
    setSMSActivatedCallback(item, notification, parent->getApplication());
    setSMSOnInputCallback(item, parent->getApplication());
    item->setDismissible(true);
    return item;
}
auto ActiveNotificationsModel::create(const notifications::NotSeenCallNotification *notification)
    -> NotificationListItem *
{
    auto item               = NotificationsModel::create(notification);
    item->activatedCallback = [this]([[maybe_unused]] gui::Item &_item) {
        return app::manager::Controller::sendAction(parent->getApplication(), app::manager::actions::ShowCallLog);
    };

    std::function<void()> onRightFunctionalCallback = [this]() {
        DBServiceAPI::GetQuery(parent->getApplication(),
                               db::Interface::Name::Notifications,
                               std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Calls));
    };
    std::function<void()> onKeyLeftFunctionalCallback = nullptr;

    if (notification->hasRecord()) {
        if (const auto &record = notification->getRecord(); !record.numbers.empty()) {
            onKeyLeftFunctionalCallback = [this, number = record.numbers[0].number]() {
                app::manager::Controller::sendAction(parent->getApplication(),
                                                     app::manager::actions::Dial,
                                                     std::make_unique<app::ExecuteCallData>(number));
            };
        }
    }
    item->inputCallback = [keyRightFunctionalCb = std::move(onRightFunctionalCallback),
                           keyLeftFunctionalCb  = std::move(onKeyLeftFunctionalCallback)]([[maybe_unused]] Item &item,
                                                                                         const InputEvent &inputEvent) {
        if (inputEvent.isShortRelease()) {
            if (inputEvent.is(KeyCode::KEY_RF)) {
                keyRightFunctionalCb();
                return true;
            }
            else if (inputEvent.is(KeyCode::KEY_LF) && keyLeftFunctionalCb != nullptr) {
                keyLeftFunctionalCb();
                return true;
            }
        }
        return false;
    };

    item->focusChangedCallback = [this, canCall = notification->hasRecord()](gui::Item &_item) {
        if (_item.focus) {
            UTF8 bottomBarLeftText = canCall ? UTF8{utils::translate("common_call")} : UTF8{};
            setParentBottomBar(
                bottomBarLeftText, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear"));
        }
        return true;
    };

    auto item = NotificationsModel::create(notification);
    setCallFocusChangedCallback(item, notification, this);
    setCallActivatedCallback(item, parent->getApplication());
    setCallOnInputCallback(item, notification, parent->getApplication());
    item->setDismissible(true);
    return item;
}


@@ 104,17 200,7 @@ auto ActiveNotificationsModel::create(const notifications::TetheringNotification
    -> NotificationListItem *
{
    auto item               = NotificationsModel::create(notification);
    item->activatedCallback = [this]([[maybe_unused]] gui::Item &_item) {
        return parent->getApplication()->bus.sendUnicast(
            std::make_shared<sys::TetheringStateRequest>(sys::phone_modes::Tethering::Off),
            service::name::system_manager);
    };
    item->focusChangedCallback = [this](gui::Item &_item) {
        if (_item.focus) {
            setParentBottomBar({}, utils::translate("common_disconnect"), {});
            return true;
        }
        return false;
    };
    setTetheringActivatedCallback(item, parent->getApplication());
    setTetheringFocusChangedCallback(item, this);
    return item;
}

M module-apps/application-desktop/models/ActiveNotificationsModel.hpp => module-apps/application-desktop/models/ActiveNotificationsModel.hpp +0 -1
@@ 11,7 11,6 @@ namespace gui
    {
      private:
        AppWindow *parent = nullptr;

      public:
        explicit ActiveNotificationsModel(AppWindow *parent);
        void setParentBottomBar(const UTF8 &left, const UTF8 &center, const UTF8 &right);

M module-apps/application-desktop/windows/DesktopMainWindow.cpp => module-apps/application-desktop/windows/DesktopMainWindow.cpp +6 -9
@@ 53,14 53,9 @@ namespace gui
                                         listview::ScrollBarType::Fixed);
        notificationsList->emptyListCallback = [this]() {
            setFocusItem(nullptr);
            setVisibleState();
        };
        notificationsList->notEmptyListCallback = [this]() {
            if (focusItem == nullptr) {
                setVisibleState();
            }
            setActiveState();
        };

        notificationsList->notEmptyListCallback = [this]() { setActiveState(); };
        setVisibleState();
    }



@@ 203,8 198,11 @@ namespace gui
        buildInterface();
    }

    auto DesktopMainWindow::setActiveState() -> bool
    void DesktopMainWindow::setActiveState()
    {
        if (focusItem != nullptr) {
            return;
        }
        bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_desktop_menu"));
        bottomBar->setText(BottomBar::Side::LEFT, utils::translate("app_desktop_calls"));
        const auto hasDismissibleNotification = notificationsModel->hasDismissibleNotification();


@@ 227,7 225,6 @@ namespace gui
            }
            return false;
        };
        return true;
    }

    app::ApplicationDesktop *DesktopMainWindow::getAppDesktop() const

M module-apps/application-desktop/windows/DesktopMainWindow.hpp => module-apps/application-desktop/windows/DesktopMainWindow.hpp +1 -1
@@ 30,7 30,7 @@ namespace gui

        // method hides or show widgets and sets bars according to provided state
        void setVisibleState();
        auto setActiveState() -> bool;
        void setActiveState();
        bool processLongReleaseEvent(const InputEvent &inputEvent);
        bool processShortReleaseEvent(const InputEvent &inputEvent);
        app::ApplicationDesktop *getAppDesktop() const;

M module-apps/application-messages/ApplicationMessages.hpp => module-apps/application-messages/ApplicationMessages.hpp +6 -21
@@ 8,40 8,25 @@
#include <Interface/SMSTemplateRecord.hpp>
#include <Interface/SMSRecord.hpp>
#include <PhoneNumber.hpp>
#include "Constants.hpp"

namespace gui
{
    // fw declarations
    class OptionWindow;
    class Text;
    namespace name
    {
        namespace window
        {
            inline constexpr auto dialog_yes_no     = "DialogYesNo";
            inline constexpr auto dialog_confirm    = "DialogConfirm";
            inline constexpr auto dialog            = "Dialog";
            inline constexpr auto new_sms           = "NewSMS";
            inline constexpr auto thread_sms_search = "SMSSearch";
            inline constexpr auto sms_templates     = "SMSTemplates";
            inline constexpr auto thread_view       = "ThreadViewWindow";

        }; // namespace window
    };     // namespace name
};         // namespace gui
} // namespace gui

namespace app
{

    inline constexpr auto name_messages = "ApplicationMessages";

    class ApplicationMessages : public app::Application, public app::AsyncCallbackReceiver
    {
      public:
        ApplicationMessages(std::string name                    = name_messages,
                            std::string parent                  = {},
                            sys::phone_modes::PhoneMode mode    = sys::phone_modes::PhoneMode::Connected,
                            StartInBackground startInBackground = {false});
        explicit ApplicationMessages(std::string name                    = name_messages,
                                     std::string parent                  = {},
                                     sys::phone_modes::PhoneMode mode    = sys::phone_modes::PhoneMode::Connected,
                                     StartInBackground startInBackground = {false});

        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
        sys::ReturnCodes InitHandler() override;

M module-apps/application-messages/CMakeLists.txt => module-apps/application-messages/CMakeLists.txt +1 -0
@@ 40,6 40,7 @@ target_sources( ${PROJECT_NAME}

    PUBLIC
        "ApplicationMessages.hpp"
        "Constants.hpp"
        "data/MessagesStyle.hpp"
        "models/ThreadsModel.hpp"
        "models/SMSThreadModel.hpp"

A module-apps/application-messages/Constants.hpp => module-apps/application-messages/Constants.hpp +21 -0
@@ 0,0 1,21 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

namespace app
{
    inline constexpr auto name_messages = "ApplicationMessages";
}

namespace gui::name::window
{
    inline constexpr auto dialog_yes_no     = "DialogYesNo";
    inline constexpr auto dialog_confirm    = "DialogConfirm";
    inline constexpr auto dialog            = "Dialog";
    inline constexpr auto new_sms           = "NewSMS";
    inline constexpr auto thread_sms_search = "SMSSearch";
    inline constexpr auto sms_templates     = "SMSTemplates";
    inline constexpr auto thread_view       = "ThreadViewWindow";

} // namespace gui::name::window

M module-apps/notifications/NotificationsModel.cpp => module-apps/notifications/NotificationsModel.cpp +18 -8
@@ 6,6 6,22 @@

using namespace gui;

namespace
{
    void setNotificationText(NotificationListItem *item,
                             const notifications::NotificationWithContact *notification,
                             const std::string &text)
    {
        if (notification->hasRecord()) {
            const auto &record = notification->getRecord();
            item->setName(record.getFormattedName());
        }
        else {
            item->setName(utils::translate(text), true);
        }
    }
} // namespace

unsigned int NotificationsModel::requestRecordsCount()
{
    return internalData.size();


@@ 55,7 71,7 @@ auto NotificationsModel::create(const notifications::NotSeenSMSNotification *not
{
    auto item = new NotificationWithEventCounter(notifications::NotificationType::NotSeenSms,
                                                 utils::to_string(notification->getValue()));
    item->setName(utils::translate("app_desktop_unread_messages"), true);
    setNotificationText(item, notification, "app_desktop_unread_messages");
    item->deleteByList = false;
    return item;
}


@@ 63,13 79,7 @@ auto NotificationsModel::create(const notifications::NotSeenCallNotification *no
{
    auto item = new NotificationWithEventCounter(notifications::NotificationType::NotSeenCall,
                                                 utils::to_string(notification->getValue()));
    if (notification->hasRecord()) {
        const auto &record = notification->getRecord();
        item->setName(record.getFormattedName());
    }
    else {
        item->setName(utils::translate("app_desktop_missed_calls"), true);
    }
    setNotificationText(item, notification, "app_desktop_missed_calls");
    item->deleteByList = false;
    return item;
}

M module-db/queries/messages/threads/QueryThreadGetByNumber.cpp => module-db/queries/messages/threads/QueryThreadGetByNumber.cpp +3 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryThreadGetByNumber.hpp"


@@ 10,8 10,8 @@

namespace db::query
{
    ThreadGetByNumber::ThreadGetByNumber(utils::PhoneNumber::View number)
        : Query(Query::Type::Read), number(std::move(number))
    ThreadGetByNumber::ThreadGetByNumber(const utils::PhoneNumber::View &number)
        : Query(Query::Type::Read), number(number)
    {}

    auto ThreadGetByNumber::getNumber() const -> const utils::PhoneNumber::View &

M module-db/queries/messages/threads/QueryThreadGetByNumber.hpp => module-db/queries/messages/threads/QueryThreadGetByNumber.hpp +4 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 17,8 17,8 @@ namespace db::query
        utils::PhoneNumber::View number;

      public:
        ThreadGetByNumber(utils::PhoneNumber::View number);
        auto getNumber() const -> const utils::PhoneNumber::View &;
        explicit ThreadGetByNumber(const utils::PhoneNumber::View &number);
        [[nodiscard]] auto getNumber() const -> const utils::PhoneNumber::View &;

        [[nodiscard]] auto debugInfo() const -> std::string override;
    };


@@ 28,7 28,7 @@ namespace db::query
        ThreadRecord thread;

      public:
        ThreadGetByNumberResult(ThreadRecord record);
        explicit ThreadGetByNumberResult(ThreadRecord record);
        [[nodiscard]] auto getThread() -> ThreadRecord;
        [[nodiscard]] auto debugInfo() const -> std::string override;
    };