~aleteoryx/muditaos

d0f5f743665dcb4c5a3f815c497ccfbd3867b6f1 — Michał Kamoń 5 years ago 49c132c
[EGD-6456] ListView based desktop notification

This PR provides following changes in notifications:
- notifications were extracted as widget for pop-up-based
locked-screen implementation
- notifications are ListView based implemented
- the most recent notification is displayed on the top of the list
33 files changed, 1000 insertions(+), 452 deletions(-)

M module-apps/Application.cpp
M module-apps/Application.hpp
M module-apps/CMakeLists.txt
M module-apps/application-desktop/ApplicationDesktop.cpp
M module-apps/application-desktop/ApplicationDesktop.hpp
M module-apps/application-desktop/CMakeLists.txt
M module-apps/application-desktop/data/Style.hpp
A module-apps/application-desktop/models/ActiveNotificationsModel.cpp
A module-apps/application-desktop/models/ActiveNotificationsModel.hpp
D module-apps/application-desktop/widgets/NotificationsBox.cpp
D module-apps/application-desktop/widgets/NotificationsBox.hpp
M module-apps/application-desktop/windows/DesktopMainWindow.cpp
M module-apps/application-desktop/windows/DesktopMainWindow.hpp
A module-apps/notifications/NotificationData.cpp
A module-apps/notifications/NotificationData.hpp
A module-apps/notifications/NotificationListItem.cpp
A module-apps/notifications/NotificationListItem.hpp
A module-apps/notifications/NotificationProvider.cpp
A module-apps/notifications/NotificationProvider.hpp
A module-apps/notifications/NotificationsModel.cpp
A module-apps/notifications/NotificationsModel.hpp
M module-gui/gui/widgets/ListView.cpp
M module-gui/gui/widgets/Style.hpp
M module-gui/test/mock/TestListViewProvider.hpp
M module-services/service-appmgr/CMakeLists.txt
M module-services/service-appmgr/Controller.cpp
A module-services/service-appmgr/data/NotificationsChangedActionsParams.cpp
M module-services/service-appmgr/model/ApplicationManager.cpp
M module-services/service-appmgr/service-appmgr/Actions.hpp
M module-services/service-appmgr/service-appmgr/Controller.hpp
A module-services/service-appmgr/service-appmgr/data/NotificationsChangedActionsParams.hpp
A module-services/service-appmgr/service-appmgr/messages/GetAllNotificationsRequest.hpp
M module-services/service-appmgr/service-appmgr/model/ApplicationManager.hpp
M module-apps/Application.cpp => module-apps/Application.cpp +11 -0
@@ 25,6 25,7 @@
#include <service-evtmgr/EVMessages.hpp>
#include <service-appmgr/service-appmgr/messages/DOMRequest.hpp>
#include <service-appmgr/messages/UserPowerDownRequest.hpp>
#include <service-appmgr/data/NotificationsChangedActionsParams.hpp>
#include "service-gui/messages/DrawMessage.hpp" // for DrawMessage
#include "task.h"                               // for xTaskGetTic...
#include "windows/AppWindow.hpp"                // for AppWindow


@@ 132,6 133,11 @@ namespace app
            abortPopup(popupId);
            return actionHandled();
        });
        addActionReceiver(app::manager::actions::NotificationsChanged, [this](auto &&params) {
            auto notificationParams = static_cast<manager::actions::NotificationsChangedParams *>(params.get());
            handle(notificationParams);
            return actionHandled();
        });
    }

    Application::~Application() noexcept


@@ 864,6 870,11 @@ namespace app
        receivers.insert_or_assign(actionId, std::move(callback));
    }

    void Application::handle(manager::actions::NotificationsChangedParams *params)
    {
        LOG_DEBUG("To be implemented by Pop-up based Locked-Screen [EGD-5884]");
    }

    void Application::cancelCallbacks(AsyncCallbackReceiver::Ptr receiver)
    {
        callbackStorage->removeAll(receiver);

M module-apps/Application.hpp => module-apps/Application.hpp +7 -10
@@ 36,24 36,20 @@
namespace app
{
    class WindowsStack;

    namespace manager::actions
    {
        class NotificationsChangedParams;
    }
} // namespace app
namespace gui
{
    class AppWindow;
} // namespace gui
namespace gui
{
    class InputEvent;
}
namespace gui
{
    class Item;
    class PopupRequestParams;
}
namespace gui
{
    class KeyInputSimpleTranslation;
}
} // namespace gui
namespace settings
{
    class Settings;


@@ 394,6 390,7 @@ namespace app
                                                 const gui::InputEvent &event);

        void addActionReceiver(manager::actions::ActionId actionId, OnActionReceived &&callback);
        virtual void handle(manager::actions::NotificationsChangedParams *params);

        std::unique_ptr<TopBarManager> topBarManager;


M module-apps/CMakeLists.txt => module-apps/CMakeLists.txt +4 -1
@@ 46,6 46,9 @@ set( SOURCES
    "widgets/DateWidget.cpp"
    "widgets/TimeWidget.cpp"
    "widgets/WidgetsUtils.cpp"
    "notifications/NotificationListItem.cpp"
    "notifications/NotificationsModel.cpp"
    "notifications/NotificationProvider.cpp"
    "options/OptionsModel.cpp"
    "options/type/OptionSimple.cpp"
    "options/type/OptionCall.cpp"


@@ 53,7 56,7 @@ set( SOURCES
    "options/type/OptionSetting.cpp"
    "options/type/OptionChangePin.cpp"
    "options/type/OptionWithActiveIcons.cpp"
    "application-desktop/windows/UpdateProgress.cpp")
    "notifications/NotificationData.cpp")

add_library(${PROJECT_NAME} STATIC ${SOURCES} ${BOARD_SOURCES})


M module-apps/application-desktop/ApplicationDesktop.cpp => module-apps/application-desktop/ApplicationDesktop.cpp +12 -74
@@ 26,6 26,7 @@

#include "AppWindow.hpp"
#include "data/LockPhoneData.hpp"
#include "models/ActiveNotificationsModel.hpp"

#include <service-db/DBServiceAPI.hpp>
#include <application-settings-new/ApplicationSettings.hpp>


@@ 50,12 51,6 @@ namespace app

    namespace
    {
        bool requestNotSeenNotifications(app::Application *app)
        {
            const auto [succeed, _] = DBServiceAPI::GetQuery(
                app, db::Interface::Name::Notifications, std::make_unique<db::query::notifications::GetAll>());
            return succeed;
        }

        bool requestUnreadThreadsCount(app::Application *app)
        {


@@ 76,7 71,8 @@ namespace app
                                           std::string parent,
                                           sys::phone_modes::PhoneMode mode,
                                           StartInBackground startInBackground)
        : Application(name, parent, mode, startInBackground), lockHandler(this)
        : Application(std::move(name), std::move(parent), mode, startInBackground),
          lockHandler(this), notificationsModel{std::make_shared<gui::ActiveNotificationsModel>()}
    {
        using namespace gui::top_bar;
        topBarManager->enableIndicators({Indicator::Signal,


@@ 185,9 181,6 @@ namespace app
            return actionHandled();
        });
    }
    ApplicationDesktop::~ApplicationDesktop()
    {
    }

    // Invoked upon receiving data message
    sys::MessagePointer ApplicationDesktop::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)


@@ 219,9 212,6 @@ namespace app
        if (resp != nullptr) {
            if (auto msg = dynamic_cast<db::QueryResponse *>(resp)) {
                auto result = msg->getResult();
                if (auto response = dynamic_cast<db::query::notifications::GetAllResult *>(result.get())) {
                    handled = handle(response);
                }
                if (auto response = dynamic_cast<db::query::ThreadGetCountResult *>(result.get())) {
                    handled = handle(response);
                }


@@ 262,48 252,17 @@ namespace app
        return true;
    }

    auto ApplicationDesktop::handle(db::query::notifications::GetAllResult *msg) -> bool
    void ApplicationDesktop::handle(manager::actions::NotificationsChangedParams *params)
    {
        assert(msg);
        auto records = *msg->getResult();

        bool rebuildMainWindow = false;

        for (auto record : records) {
            switch (record.key) {
            case NotificationsRecord::Key::Calls:
                rebuildMainWindow |= record.value != notifications.notSeen.Calls;
                notifications.notSeen.Calls = record.value;
                break;

            case NotificationsRecord::Key::Sms:
                rebuildMainWindow |= record.value != notifications.notSeen.SMS;
                notifications.notSeen.SMS = record.value;
                break;

            case NotificationsRecord::Key::NotValidKey:
            case NotificationsRecord::Key::NumberOfKeys:
                LOG_ERROR("Not a valid key");
                return false;
            }
        if (getCurrentWindow()->getName() == app::window::name::desktop_main_window) {
            notificationsModel->updateData(params);
            refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
        }

        auto ptr = getCurrentWindow();
        if (rebuildMainWindow && ptr->getName() == app::window::name::desktop_main_window) {
            ptr->rebuild();
        }

        return true;
    }

    auto ApplicationDesktop::handle(db::NotificationMessage *msg) -> bool
    {
        assert(msg);

        if (msg->interface == db::Interface::Name::Notifications && msg->type == db::Query::Type::Update) {
            return requestNotSeenNotifications(this);
        }

        if (msg->interface == db::Interface::Name::Calllog && msg->type != db::Query::Type::Read) {
            return requestUnreadCallsCount(this);
        }


@@ 321,7 280,7 @@ namespace app
        if (msg->getState() == EntryState::UNREAD) {
            notifications.notRead.SMS = msg->getCount();
        }
        return refreshMainWindow();
        return refreshMenuWindow();
    }

    auto ApplicationDesktop::handle(db::query::CalllogGetCountResult *msg) -> bool


@@ 329,7 288,7 @@ namespace app
        if (msg->getState() == EntryState::UNREAD) {
            notifications.notRead.Calls = msg->getCount();
        }
        return refreshMainWindow();
        return refreshMenuWindow();
    }

    auto ApplicationDesktop::handle(cellular::StateChange *msg) -> bool


@@ 356,26 315,6 @@ namespace app
        return manager::Controller::sendAction(this, manager::actions::ShowCallLog);
    }

    bool ApplicationDesktop::clearCallsNotification()
    {
        LOG_DEBUG("Clear Call notifications");
        DBServiceAPI::GetQuery(this,
                               db::Interface::Name::Notifications,
                               std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Calls));
        notifications.notSeen.Calls = 0;
        return true;
    }

    bool ApplicationDesktop::clearMessagesNotification()
    {
        LOG_DEBUG("Clear Sms notifications");
        DBServiceAPI::GetQuery(this,
                               db::Interface::Name::Notifications,
                               std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Sms));
        notifications.notSeen.SMS = 0;
        return true;
    }

    // Invoked during initialization
    sys::ReturnCodes ApplicationDesktop::InitHandler()
    {


@@ 388,7 327,6 @@ namespace app
        lockPassHashChanged(
            settings->getValue(settings::SystemProperties::lockPassHash, settings::SettingsScope::Global));

        requestNotSeenNotifications(this);
        requestUnreadThreadsCount(this);
        requestUnreadCallsCount(this);
        createUserInterface();


@@ 519,8 457,8 @@ namespace app
    void ApplicationDesktop::createUserInterface()
    {
        using namespace app::window::name;
        windowsFactory.attach(desktop_main_window, [](Application *app, const std::string &name) {
            return std::make_unique<gui::DesktopMainWindow>(app);
        windowsFactory.attach(desktop_main_window, [this](Application *app, const std::string &name) {
            return std::make_unique<gui::DesktopMainWindow>(app, notificationsModel);
        });
        windowsFactory.attach(desktop_pin_lock, [&](Application *app, const std::string newname) {
            return std::make_unique<gui::PinLockWindow>(app, desktop_pin_lock);


@@ 579,7 517,7 @@ namespace app
    void ApplicationDesktop::destroyUserInterface()
    {}

    bool ApplicationDesktop::refreshMainWindow()
    bool ApplicationDesktop::refreshMenuWindow()
    {
        if (auto menuWindow = dynamic_cast<gui::MenuWindow *>(getWindow(app::window::name::desktop_menu));
            menuWindow != nullptr) {

M module-apps/application-desktop/ApplicationDesktop.hpp => module-apps/application-desktop/ApplicationDesktop.hpp +17 -12
@@ 14,6 14,7 @@
#include <endpoints/update/UpdateMuditaOS.hpp>
#include <service-desktop/ServiceDesktop.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <notifications/NotificationListItem.hpp>

namespace cellular
{


@@ 24,7 25,12 @@ namespace db::query
{
    class ThreadGetCountResult;
    class CalllogGetCountResult;
}; // namespace db::query
} // namespace db::query

namespace gui
{
    class NotificationsModel;
}

namespace app
{


@@ 47,18 53,17 @@ namespace app
                }
            };

            Counters notSeen;
            Counters notRead;

        } notifications;

        gui::PinLockHandler lockHandler;

        ApplicationDesktop(std::string name                    = name_desktop,
                           std::string parent                  = {},
                           sys::phone_modes::PhoneMode mode    = sys::phone_modes::PhoneMode::Connected,
                           StartInBackground startInBackground = {false});
        virtual ~ApplicationDesktop();
        explicit ApplicationDesktop(std::string name                    = name_desktop,
                                    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;
        sys::ReturnCodes DeinitHandler() override;


@@ 73,19 78,17 @@ namespace app
        // done
        bool handle(db::NotificationMessage *msg);
        bool handle(cellular::StateChange *msg);
        auto handle(db::query::notifications::GetAllResult *msg) -> bool;
        auto handle(db::query::ThreadGetCountResult *msg) -> bool;
        auto handle(db::query::CalllogGetCountResult *msg) -> bool;
        auto handle(sdesktop::UpdateOsMessage *msg) -> bool;
        auto handle(sdesktop::developerMode::ScreenlockCheckEvent *event) -> bool;
        void handle(manager::actions::NotificationsChangedParams *params) override;
        /**
         * This static method will be used to lock the phone
         */
        //	static bool messageLockPhone( sys::Service* sender, std::string application , const gui::InputEvent& event
        //);
        bool showCalls();
        bool clearCallsNotification();
        bool clearMessagesNotification();
        unsigned int getLockPassHash() const noexcept
        {
            return lockPassHash;


@@ 101,7 104,7 @@ namespace app
        void setOsUpdateVersion(const std::string &value);

      private:
        bool refreshMainWindow();
        bool refreshMenuWindow();
        void activeSimChanged(std::string value);
        void lockPassHashChanged(std::string value);
        void handleLowBatteryNotification(manager::actions::ActionParamsPtr &&data);


@@ 110,6 113,7 @@ namespace app
        void osCurrentVersionChanged(const std::string &value);
        std::string osUpdateVersion{updateos::initSysVer};
        std::string osCurrentVersion{updateos::initSysVer};
        std::shared_ptr<gui::NotificationsModel> notificationsModel;
    };

    template <> struct ManifestTraits<ApplicationDesktop>


@@ 133,7 137,8 @@ namespace app
                     manager::actions::DisplayLowBatteryScreen,
                     manager::actions::SystemBrownout,
                     manager::actions::DisplayLogoAtExit,
                     manager::actions::PhoneModeChanged}};
                     manager::actions::PhoneModeChanged,
                     manager::actions::NotificationsChanged}};
        }
    };


M module-apps/application-desktop/CMakeLists.txt => module-apps/application-desktop/CMakeLists.txt +3 -2
@@ 17,7 17,6 @@ target_sources( ${PROJECT_NAME}
		"${CMAKE_CURRENT_LIST_DIR}/ApplicationDesktop.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/PinLock.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/PinLockHandler.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/NotificationsBox.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/DesktopInputWidget.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/DesktopMainWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/PinLockBaseWindow.cpp"


@@ 40,7 39,9 @@ target_sources( ${PROJECT_NAME}
		"${CMAKE_CURRENT_LIST_DIR}/windows/MmiInternalMsgWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/ScreenLockBaseBox.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/LockWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/UpdateProgress.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/PowerOffPresenter.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/models/ActiveNotificationsModel.cpp"
	PUBLIC
		"${CMAKE_CURRENT_LIST_DIR}/ApplicationDesktop.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/data/LockPhoneData.hpp"


@@ 50,7 51,6 @@ target_sources( ${PROJECT_NAME}
		"${CMAKE_CURRENT_LIST_DIR}/widgets/PinLock.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/DesktopInputWidget.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/PinLockHandler.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/NotificationsBox.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/DesktopMainWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/PinLockBaseWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/PinLockBox.hpp"


@@ 71,6 71,7 @@ target_sources( ${PROJECT_NAME}
		"${CMAKE_CURRENT_LIST_DIR}/windows/MmiInternalMsgWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/ScreenLockBaseBox.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/LockWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/models/ActiveNotificationsModel.hpp"
)

target_link_libraries(${PROJECT_NAME}

M module-apps/application-desktop/data/Style.hpp => module-apps/application-desktop/data/Style.hpp +0 -14
@@ 5,20 5,6 @@

namespace style::desktop
{

    namespace notifications
    {
        inline constexpr auto SpanSize     = 8;
        inline constexpr auto DigitSize    = 16;
        inline constexpr auto IconWidth    = 35;
        inline constexpr auto TextMaxWidth = 250;

        inline constexpr auto X     = 0;
        inline constexpr auto Y     = 284;
        inline constexpr auto Width = style::window_width;

    }; // namespace notifications

    namespace timeLabel
    {
        inline constexpr auto X      = 0;

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

#include "ActiveNotificationsModel.hpp"
#include <module-db/queries/notifications/QueryNotificationsClear.hpp>
#include <service-appmgr/Controller.hpp>
#include <application-messages/ApplicationMessages.hpp>
#include <application-call/data/CallSwitchData.hpp>

using namespace gui;

void ActiveNotificationsModel::setParentBottomBar(const UTF8 &left, const UTF8 &center, const UTF8 &right)
{
    parent->setBottomBarText(left, BottomBar::Side::LEFT);
    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.isShortPress()) {
            if (inputEvent.is(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;
    };
    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.isShortPress()) {
            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;
    };

    item->setDismissible(true);
    return item;
}

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

#pragma once

#include <notifications/NotificationsModel.hpp>

namespace gui
{
    class ActiveNotificationsModel : public gui::NotificationsModel
    {
        void setParentBottomBar(const UTF8 &left, const UTF8 &center, const UTF8 &right);
        auto create(const notifications::NotSeenSMSNotification *notification) -> NotificationListItem * override;
        auto create(const notifications::NotSeenCallNotification *notification) -> NotificationListItem * override;
    };
} // namespace gui

D module-apps/application-desktop/widgets/NotificationsBox.cpp => module-apps/application-desktop/widgets/NotificationsBox.cpp +0 -204
@@ 1,204 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationsBox.hpp"

#include "application-desktop/data/Style.hpp"

#include "TextFixedSize.hpp"
#include "RichTextParser.hpp"
#include "FontManager.hpp"

using namespace gui;
using namespace style::desktop;

NotificationsBox::NotificationsBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
    : VBox(parent, x, y, w, h)
{
    this->setAlignment(Alignment(gui::Alignment::Horizontal::Center));
    this->setPenWidth(style::window::default_border_no_focus_w);
    this->setPenFocusWidth(style::window::default_border_no_focus_w);

    auto getNextNotification = [this]() -> Item * {
        auto focusedItem = getFocusItem();
        if (focusedItem == nullptr) {
            return nullptr;
        }
        auto nextItem = focusedItem->getNavigationItem(NavigationDirection::UP);

        if (nextItem == nullptr) {
            nextItem = focusedItem->getNavigationItem(NavigationDirection::DOWN);
        }
        return nextItem;
    };

    auto setNextFocusedItemAfterErase = [this](Item *item) -> bool {
        if (item == nullptr) {
            setFocus(false);
        }
        else {
            item->clearNavigationItem(NavigationDirection::DOWN);
            item->clearNavigationItem(NavigationDirection::UP);
            setNavigation();
            setFocusItem(item);
        }
        return true;
    };

    inputCallback = [this, setNextFocusedItemAfterErase, getNextNotification](Item &, const InputEvent &event) -> bool {
        if (event.isShortPress() && event.is(KeyCode::KEY_RF)) {
            LOG_DEBUG("Removing single notification");
            auto ptr = getFocusItem();
            if (ptr == nullptr || focusItem == this) {
                return false;
            }
            auto nextItem = getNextNotification();
            ptr->inputCallback(*this, event);
            erase(ptr);
            return setNextFocusedItemAfterErase(nextItem);
        }
        return false;
    };
}

namespace
{
    auto buildImageInactive(const UTF8 &img) -> gui::Image *
    {
        auto thumbnail        = new gui::Image(img);
        thumbnail->activeItem = false;
        return thumbnail;
    }

    auto buildNotificationIcon(UTF8 icon) -> gui::Image *
    {
        auto thumbnail = buildImageInactive(icon);
        thumbnail->setMinimumWidth(notifications::IconWidth);
        thumbnail->setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
        thumbnail->setMargins(
            gui::Margins(style::window::default_left_margin, 0, style::window::default_right_margin, 0));
        return thumbnail;
    }

    auto buildNotificationNameLabel(const UTF8 &name, uint32_t width) -> gui::TextFixedSize *
    {
        auto text = new gui::TextFixedSize(
            nullptr, 0, 0, style::desktop::notifications::TextMaxWidth, style::window::label::default_h);
        text->setMaximumSize(width, Axis::X);

        TextFormat format(FontManager::getInstance().getFont(style::window::font::medium));
        text::RichTextParser rtParser;
        auto parsedText = rtParser.parse(name, &format);

        text->setText(std::move(parsedText));
        text->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        text->setPenWidth(style::window::default_border_no_focus_w);
        text->setUnderline(false);
        text->activeItem = false;
        return text;
    }

    constexpr auto maxNotificationValue = "99+";

    auto buildNotificationCountText(const UTF8 &indicator) -> gui::Text *
    {
        auto number = new gui::Text();
        if (indicator.length() > 2) {
            number->setText(maxNotificationValue);
        }
        else {
            number->setText(indicator);
        }

        number->setMinimumWidth(number->getText().length() * notifications::DigitSize);
        number->setFont(style::window::font::mediumbold);
        number->setPenWidth(style::window::default_border_no_focus_w);
        number->setMargins(gui::Margins(0, 0, style::window::default_right_margin, 0));
        number->setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
        number->activeItem = false;
        return number;
    }

    void setNotificationHboxLayoutProperties(gui::HBox *hbox)
    {
        hbox->setAlignment(Alignment(gui::Alignment::Vertical::Center));
        hbox->setMargins(gui::Margins(0, 0, 0, 10));
        hbox->setPenWidth(style::window::default_border_no_focus_w);
        hbox->setPenFocusWidth(style::window::default_border_focus_w);
        hbox->setEdges(RectangleEdge::Bottom | RectangleEdge::Top);
    }

} // namespace

auto NotificationsBox::addNotification(const UTF8 &icon,
                                       const UTF8 &name,
                                       const UTF8 &indicator,
                                       std::function<bool()> showCallback,
                                       std::function<bool()> clearCallback,
                                       std::function<void(bool)> onFocus) -> bool
{
    // 1. create hbox for all elements
    auto el = new gui::HBox(nullptr, 0, 0, style::window::default_body_width, style::window::label::default_h);

    // 2. Add all elements to hbox layout
    el->addWidget(buildNotificationIcon(icon));

    el->addWidget(buildNotificationNameLabel(name, el->area().w));
    el->addWidget(buildImageInactive("dot_12px_hard_alpha_W_G"));
    el->addWidget(buildNotificationCountText(indicator));

    // 3. Set hbox layout properties
    setNotificationHboxLayoutProperties(el);

    el->focusChangedCallback = [el, onFocus](Item &) -> bool {
        onFocus(el->focus);
        return true;
    };
    el->activatedCallback = [showCallback](Item &) { return showCallback(); };
    el->inputCallback     = [showCallback, clearCallback, this](Item &, const InputEvent &event) -> bool {
        if (event.isShortPress() && event.is(KeyCode::KEY_RF) && clearCallback) {
            return clearCallback();
        }
        return false;
    };

    this->addWidget(el);
    return el->visible;
}

auto NotificationsBox::addNotification(const UTF8 &icon, const UTF8 &name) -> bool
{
    auto el = new gui::HBox(this, 0, 0, style::window::default_body_width, style::window::label::default_h);

    el->addWidget(buildNotificationIcon(icon));
    el->addWidget(buildNotificationNameLabel(name, el->area().w));
    setNotificationHboxLayoutProperties(el);

    return el->visible;
}

bool NotificationsBox::onInput(const InputEvent &inputEvent)
{

    if (inputEvent.isShortPress() && (inputEvent.is(KeyCode::KEY_UP) || inputEvent.is(KeyCode::KEY_DOWN))) {
        auto handled = handleNavigation(inputEvent);
        if (!handled) {
            setFocus(false);
        }
        return true;
    }
    return VBox::onInput(inputEvent);
}

bool NotificationsBox::clearAll(const InputEvent &event)
{
    while (!children.empty()) {
        inputCallback(*this, event);
    }
    return true;
}

void NotificationsBox::navigateToBottom()
{
    setFocusItem(children.back());
}

D module-apps/application-desktop/widgets/NotificationsBox.hpp => module-apps/application-desktop/widgets/NotificationsBox.hpp +0 -30
@@ 1,30 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "BoxLayout.hpp"
#include "Icon.hpp"

namespace gui
{
    class NotificationsBox : public VBox
    {
      public:
        NotificationsBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h);

        auto addNotification(const UTF8 &icon,
                             const UTF8 &name,
                             const UTF8 &indicator,
                             std::function<bool()> showCallback,
                             std::function<bool()> clearCallback,
                             std::function<void(bool)> onFocus) -> bool;

        auto addNotification(const UTF8 &icon, const UTF8 &name) -> bool;

        bool onInput(const InputEvent &inputEvent) override;
        bool clearAll(const InputEvent &event);

        void navigateToBottom();
    };
} // namespace gui

M module-apps/application-desktop/windows/DesktopMainWindow.cpp => module-apps/application-desktop/windows/DesktopMainWindow.cpp +73 -91
@@ 5,13 5,11 @@

#include "Alignment.hpp"
#include "BottomBar.hpp"
#include "Common.hpp"
#include "DesktopMainWindow.hpp"
#include "GuiTimer.hpp"
#include "application-desktop/ApplicationDesktop.hpp"
#include "application-desktop/data/LockPhoneData.hpp"
#include "application-desktop/data/Style.hpp"
#include "application-desktop/widgets/NotificationsBox.hpp"
#include "application-messages/ApplicationMessages.hpp"
#include "gui/widgets/Image.hpp"
#include <service-appmgr/Controller.hpp>


@@ 21,7 19,6 @@
#include <i18n/i18n.hpp>
#include "log/log.hpp"

#include <application-settings-new/ApplicationSettings.hpp>
#include <application-settings/ApplicationSettings.hpp>
#include <cassert>
#include <time/time_conversion.hpp>


@@ 35,7 32,8 @@ namespace gui

        bottomBar->setActive(BottomBar::Side::CENTER, true);

        using namespace style::desktop;
        namespace timeLabel = style::desktop::timeLabel;
        namespace dayLabel  = style::desktop::dayLabel;

        time = new gui::Label(this, timeLabel::X, timeLabel::Y, timeLabel::Width, timeLabel::Height);
        time->setFilled(false);


@@ 49,11 47,31 @@ namespace gui
        dayText->setFont(style::window::font::biglight);
        dayText->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Top));

        activatedCallback = [this](Item &) {
        activatedCallback = [this]([[maybe_unused]] Item &item) {
            application->switchWindow(app::window::name::desktop_menu);
            return true;
        };

        notificationsList                    = new ListView(this,
                                         style::notifications::model::x,
                                         style::notifications::model::y,
                                         style::notifications::model::w,
                                         style::notifications::model::h,
                                         notificationsModel,
                                         listview::ScrollBarType::Fixed);
        notificationsList->emptyListCallback = [this]() {
            setFocusItem(nullptr);
            setVisibleState();
        };
        notificationsList->notEmptyListCallback = [this]() {
            if (focusItem == nullptr) {
                setVisibleState();
            }
        };

        applyToTopBar(
            [this](top_bar::Configuration configuration) { return configureTopBar(std::move(configuration)); });

        setVisibleState();
    }



@@ 67,7 85,6 @@ namespace gui
    {
        time          = nullptr;
        dayText       = nullptr;
        notifications = nullptr;
    }

    top_bar::Configuration DesktopMainWindow::configureTopBar(top_bar::Configuration appConfiguration)


@@ 80,8 97,10 @@ namespace gui
        return appConfiguration;
    }

    DesktopMainWindow::DesktopMainWindow(app::Application *app) : AppWindow(app, app::window::name::desktop_main_window)
    DesktopMainWindow::DesktopMainWindow(app::Application *app, std::shared_ptr<NotificationsModel> model)
        : AppWindow(app, app::window::name::desktop_main_window), notificationsModel(std::move(model))
    {
        notificationsModel->setParentWindow(this);
        osUpdateVer  = getAppDesktop()->getOsUpdateVersion();
        osCurrentVer = getAppDesktop()->getOsCurrentVersion();



@@ 90,13 109,15 @@ namespace gui
        preBuildDrawListHook = [this](std::list<Command> &cmd) { updateTime(); };
    }

    void DesktopMainWindow::setVisibleState()
    DesktopMainWindow::~DesktopMainWindow()
    {
        auto app = getAppDesktop();
        applyToTopBar(
            [this](top_bar::Configuration configuration) { return configureTopBar(std::move(configuration)); });
        notificationsModel->clearAll();
        notificationsModel->list = nullptr;
    }

        if (app->lockHandler.isScreenLocked()) {
    void DesktopMainWindow::setVisibleState()
    {
        if (auto app = getAppDesktop(); app->lockHandler.isScreenLocked()) {
            bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_desktop_unlock"));
            bottomBar->setActive(BottomBar::Side::RIGHT, false);
            bottomBar->setText(


@@ 104,15 125,11 @@ namespace gui

            inputCallback = nullptr;
            setFocusItem(nullptr);
            buildNotifications(app);

            application->bus.sendUnicast(std::make_shared<TimersProcessingStopMessage>(), service::name::service_time);
        }
        else {
            if (!buildNotifications(app)) {
                LOG_ERROR("Couldn't fit in all notifications");
            }
            setActiveState(app);
            setActiveState();

            if (osUpdateVer == osCurrentVer && osUpdateVer != updateos::initSysVer &&
                osCurrentVer != updateos::initSysVer) {


@@ 133,6 150,7 @@ namespace gui

    void DesktopMainWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        app::manager::Controller::requestNotifications(application);
        setVisibleState();
    }



@@ 161,6 179,12 @@ namespace gui
        return AppWindow::onInput(inputEvent);
    }

    namespace
    {
        constexpr auto pageFirstNotificationIdx = 0;
        constexpr auto pageLastNotificationIdx  = style::notifications::model::maxNotificationsPerPage - 1;
    } // namespace

    bool DesktopMainWindow::processShortPressEventOnUnlocked(const InputEvent &inputEvent)
    {
        auto code = translator.handle(inputEvent.key, InputMode({InputMode::phone}).get());


@@ 170,17 194,30 @@ namespace gui
            return app::manager::Controller::sendAction(
                application, app::manager::actions::Dial, std::make_unique<app::EnterNumberData>(number));
        }
        else if (inputEvent.is(KeyCode::KEY_UP) && focusItem == nullptr) {
            setFocusItem(notifications);
            notifications->navigateToBottom();
        else if (const auto notificationsNotFocused = (focusItem == nullptr);
                 notificationsNotFocused && !notificationsModel->isEmpty()) {
            if (inputEvent.is(KeyCode::KEY_UP)) {
                notificationsList->rebuildList(listview::RebuildType::OnPageElement, pageLastNotificationIdx);
                setFocusItem(notificationsList);
                return true;
            }
            else if (inputEvent.is(KeyCode::KEY_DOWN)) {
                notificationsList->rebuildList(listview::RebuildType::OnPageElement, pageFirstNotificationIdx);
                setFocusItem(notificationsList);
                return true;
            }
        }
        // check if any of the lower inheritance onInput methods catch the event
        if (AppWindow::onInput(inputEvent)) {
            return true;
        }
        else if (inputEvent.is(KeyCode::KEY_DOWN) && focusItem == nullptr) {
            setFocusItem(notifications);
        if ((inputEvent.is(KeyCode::KEY_UP) || inputEvent.is(KeyCode::KEY_DOWN)) && focusItem != nullptr) {
            LOG_DEBUG("Notification box lost focus");
            setFocusItem(nullptr);
            setActiveState();
            return true;
        }
        // check if any of the lower inheritance onInput methods catch the event
        return AppWindow::onInput(inputEvent);
        return false;
    }

    bool DesktopMainWindow::processShortPressEventOnLocked(const InputEvent &inputEvent)


@@ 232,79 269,24 @@ namespace gui
        buildInterface();
    }

    auto DesktopMainWindow::buildNotifications(app::ApplicationDesktop *app) -> bool
    {
        erase(notifications);
        using namespace style::desktop;
        notifications = new NotificationsBox(this,
                                             notifications::X,
                                             notifications::Y,
                                             notifications::Width,
                                             bottomBar->widgetArea.pos(Axis::Y) - notifications::Y);

        addWidget(notifications);
        if (!notifications->visible) {
            LOG_ERROR("Can't fit notifications box!");
            return false;
        }

        auto onNotificationFocus = [this](bool isFocused) -> void {
            bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_desktop_show"), isFocused);
            bottomBar->setText(BottomBar::Side::RIGHT, utils::translate("app_desktop_clear"), isFocused);
            bottomBar->setActive(BottomBar::Side::LEFT, !isFocused);
        };

        if (app->notifications.notSeen.Calls > 0) {
            notifications->addNotification(
                "phone",
                utils::translate("app_desktop_missed_calls"),
                std::to_string(app->notifications.notSeen.Calls),
                [app]() -> bool { return app->showCalls(); },
                [app]() -> bool { return app->clearCallsNotification(); },
                onNotificationFocus);
        }
        if (app->notifications.notSeen.SMS > 0) {
            notifications->addNotification(
                "mail",
                utils::translate("app_desktop_unread_messages"),
                std::to_string(app->notifications.notSeen.SMS),
                [this]() -> bool {
                    return app::manager::Controller::sendAction(
                        application,
                        app::manager::actions::Launch,
                        std::make_unique<app::ApplicationLaunchData>(app::name_messages));
                },
                [app, this]() -> bool { return app->clearMessagesNotification(); },
                onNotificationFocus);
        }

        notifications->focusChangedCallback = [this, app](Item &) -> bool {
            if (notifications->focus == false) {
                LOG_DEBUG("Notification box lost focus");
                setFocusItem(nullptr);
                setActiveState(app);
                return true;
            }
            return false;
        };

        return true;
    }
    auto DesktopMainWindow::setActiveState(app::ApplicationDesktop *app) -> bool
    auto DesktopMainWindow::setActiveState() -> bool
    {
        bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_desktop_menu"));
        bottomBar->setText(BottomBar::Side::LEFT, utils::translate("app_desktop_calls"));
        auto hasNotifications = !app->notifications.notSeen.areEmpty();
        bottomBar->setText(BottomBar::Side::RIGHT, utils::translate("app_desktop_clear_all"), hasNotifications);

        inputCallback = [this, app, hasNotifications](Item &, const InputEvent &inputEvent) -> bool {
            if (!inputEvent.isShortPress()) {
        const auto hasDismissibleNotification = notificationsModel->hasDismissibleNotification();
        bottomBar->setText(
            BottomBar::Side::RIGHT, utils::translate("app_desktop_clear_all"), hasDismissibleNotification);

        auto app      = getAppDesktop();
        inputCallback = [this, app, hasDismissibleNotification]([[maybe_unused]] Item &item,
                                                                const InputEvent &inputEvent) -> bool {
            if (!inputEvent.isShortPress() || notificationsList->focus) {
                return false;
            }
            if (inputEvent.is(KeyCode::KEY_RF) && hasNotifications) {
            if (inputEvent.is(KeyCode::KEY_RF) && hasDismissibleNotification) {
                LOG_DEBUG("KEY_RF pressed to clear all notifications");
                setFocusItem(notifications);
                return notifications->clearAll(inputEvent);
                notificationsModel->dismissAll(inputEvent);
                return true;
            }
            if (inputEvent.is(gui::KeyCode::KEY_LF)) {
                LOG_DEBUG("KEY_LF pressed to navigate to calls");

M module-apps/application-desktop/windows/DesktopMainWindow.hpp => module-apps/application-desktop/windows/DesktopMainWindow.hpp +8 -6
@@ 8,6 8,8 @@
#include "gui/widgets/Window.hpp"

#include "Translator.hpp"
#include <notifications/NotificationsModel.hpp>
#include <ListView.hpp>

namespace app
{


@@ 16,14 18,14 @@ namespace app

namespace gui
{
    class NotificationsBox;

    class DesktopMainWindow : public AppWindow
    {
      protected:
        gui::Label *time          = nullptr;
        gui::Label *dayText       = nullptr;
        gui::NotificationsBox *notifications = nullptr;

        gui::ListView *notificationsList                            = nullptr;
        std::shared_ptr<gui::NotificationsModel> notificationsModel = nullptr;

        /// Timed enter value cache, could be templated to any value really
        class EnterCache


@@ 64,15 66,15 @@ namespace gui

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

      public:
        DesktopMainWindow(app::Application *app);
        DesktopMainWindow(app::Application *app, std::shared_ptr<NotificationsModel> model);
        ~DesktopMainWindow();

        // virtual methods gui::Window
        bool onInput(const InputEvent &inputEvent) override;

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

#include "NotificationData.hpp"

uint32_t notifications::Notification::priorityPool = 0;

using namespace notifications;

Notification::Notification(NotificationType type, NotificationPriority priorityType) : type{type}
{
    switch (priorityType) {
    case NotificationPriority::Next:
        if (priorityPool == highestPriority) {
            priorityPool = 0;
        }
        priority = ++priorityPool;
        break;
    case NotificationPriority::Highest:
        priority = highestPriority;
        break;
    case NotificationPriority::Lowest:
        priority = lowestPriority;
        break;
    }
}

auto Notification::getType() const noexcept -> NotificationType
{
    return type;
}

auto Notification::getPriority() const noexcept -> uint32_t
{
    return priority;
}

NotSeenSMSNotification::NotSeenSMSNotification(unsigned value)
    : Notification(NotificationType::NotSeenSms), value{value}
{}

auto NotSeenSMSNotification::getValue() const noexcept -> unsigned
{
    return value;
}

NotSeenCallNotification::NotSeenCallNotification(unsigned value, std::unique_ptr<ContactRecord> record)
    : Notification(NotificationType::NotSeenCall), value{value}, record{std::move(record)}
{}

bool NotSeenCallNotification::hasRecord() const noexcept
{
    return record != nullptr;
}

auto NotSeenCallNotification::getRecord() const noexcept -> const std::unique_ptr<ContactRecord> &
{
    return record;
}

auto NotSeenCallNotification::getValue() const noexcept -> unsigned
{
    return value;
}

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

#pragma once

#include <limits>
#include <list>

#include <ContactRecord.hpp>

namespace notifications
{
    enum class NotificationType
    {
        Unknown,
        NotSeenSms,
        NotSeenCall,
    };

    enum class NotificationPriority
    {
        Next,
        Highest,
        Lowest
    };

    class Notification
    {
        static constexpr auto highestPriority = std::numeric_limits<uint32_t>::max();
        static constexpr auto lowestPriority  = 0;
        static uint32_t priorityPool;

        NotificationType type;
        uint32_t priority;

      protected:
        explicit Notification(NotificationType type, NotificationPriority priorityType = NotificationPriority::Next);

      public:
        [[nodiscard]] auto getType() const noexcept -> NotificationType;
        [[nodiscard]] auto getPriority() const noexcept -> uint32_t;

        virtual ~Notification() = default;
    };

    class NotSeenSMSNotification : public Notification
    {
        unsigned value = 0;

      public:
        explicit NotSeenSMSNotification(unsigned value);
        [[nodiscard]] auto getValue() const noexcept -> unsigned;
    };

    class NotSeenCallNotification : public Notification
    {
        unsigned value = 0;
        std::unique_ptr<ContactRecord> record;

      public:
        explicit NotSeenCallNotification(unsigned value, std::unique_ptr<ContactRecord> record = nullptr);

        [[nodiscard]] bool hasRecord() const noexcept;
        [[nodiscard]] auto getRecord() const noexcept -> const std::unique_ptr<ContactRecord> &;
        [[nodiscard]] auto getValue() const noexcept -> unsigned;
    };

} // namespace notifications

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

#include "NotificationListItem.hpp"

#include "TextFixedSize.hpp"
#include "RichTextParser.hpp"
#include "FontManager.hpp"
#include "Image.hpp"

#include <widgets/Style.hpp>
#include <map>

using namespace gui;
// using namespace style::desktop;

namespace
{
    auto buildImageInactive(const UTF8 &img) -> gui::Image *
    {
        auto thumbnail        = new gui::Image(img);
        thumbnail->activeItem = false;
        return thumbnail;
    }

    auto buildNotificationIcon(const UTF8 &icon) -> gui::Image *
    {
        auto thumbnail = buildImageInactive(icon);
        thumbnail->setMinimumWidth(style::notifications::iconWidth);
        thumbnail->setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
        thumbnail->setMargins(
            gui::Margins(style::window::default_left_margin, 0, style::window::default_right_margin, 0));
        return thumbnail;
    }

    auto buildNotificationNameLabel(uint32_t width) -> gui::TextFixedSize *
    {
        auto text =
            new gui::TextFixedSize(nullptr, 0, 0, style::notifications::textMaxWidth, style::notifications::itemHeight);
        text->setMaximumSize(width, Axis::X);

        text->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        text->setPenWidth(style::window::default_border_no_focus_w);
        text->setUnderline(false);
        text->setFont(style::window::font::medium);
        text->activeItem = false;
        return text;
    }

    void setNotificationHboxLayoutProperties(gui::HBox *hbox)
    {
        hbox->setAlignment(Alignment(gui::Alignment::Vertical::Center));
        hbox->setMargins(gui::Margins(0, 0, 0, 10));
        hbox->setPenWidth(style::window::default_border_no_focus_w);
        hbox->setPenFocusWidth(style::window::default_border_focus_w);
        hbox->setEdges(RectangleEdge::None);
        hbox->setMaximumSize(style::window::default_body_width, style::notifications::itemHeight);
    }

    constexpr auto maxNotificationValue = "99+";
    constexpr auto singleNotification   = "1";

    auto buildNotificationCountText(const UTF8 &indicator) -> gui::Text *
    {
        auto number = new gui::Text();
        if (indicator.length() > 2) {
            number->setText(maxNotificationValue);
        }
        else if (indicator == singleNotification) {
            number->clear();
        }
        else {
            number->setText(indicator);
        }

        number->setMinimumWidth(number->getText().length() * style::notifications::digitSize);
        number->setFont(style::window::font::mediumbold);
        number->setPenWidth(style::window::default_border_no_focus_w);
        number->setMargins(gui::Margins(0, 0, style::window::default_right_margin, 0));
        number->setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
        number->activeItem = false;
        return number;
    }

    std::map<notifications::NotificationType, UTF8> typeToIcon{
        {notifications::NotificationType::Unknown, "phone"},
        {notifications::NotificationType::NotSeenSms, "mail"},
        {notifications::NotificationType::NotSeenCall, "phone"},
    };
} // namespace

NotificationListItem::NotificationListItem(NotificationType type) : type{type}
{
    this->setMinimumHeight(style::notifications::itemHeight);
    this->setMaximumWidth(style::window::default_body_width);

    box = new gui::HBox(this, 0, 0, style::window::default_body_width, style::notifications::itemHeight);
    box->addWidget(buildNotificationIcon(typeToIcon[type]));
    text = buildNotificationNameLabel(box->area().w);
    box->addWidget(text);

    setNotificationHboxLayoutProperties(box);
}

void NotificationListItem::setName(const UTF8 &name, bool isRich, gui::text::RichTextParser::TokenMap &&tokens)
{
    if (isRich) {
        TextFormat format(FontManager::getInstance().getFont(style::window::font::medium));
        text::RichTextParser rtParser;
        auto parsedText = rtParser.parse(name, &format, std::move(tokens));
        text->setText(std::move(parsedText));
    }
    else {
        text->setText(name);
    }
}

void NotificationListItem::setDismissible(bool isDismissible) noexcept
{
    dismissible = isDismissible;
}

bool NotificationListItem::isDismissible() const noexcept
{
    return dismissible;
}

notifications::NotificationType NotificationListItem::getType() const noexcept
{
    return type;
}

NotificationWithEventCounter::NotificationWithEventCounter(notifications::NotificationType type, const UTF8 &indicator)
    : NotificationListItem(type)
{
    box->addWidget(buildImageInactive("dot_12px_hard_alpha_W_G"));
    box->addWidget(buildNotificationCountText(indicator));
}

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

#pragma once

#include <ListItem.hpp>
#include <BoxLayout.hpp>
#include <Text.hpp>
#include <RichTextParser.hpp>

#include "NotificationData.hpp"

namespace gui
{

    class TextFixedSize;

    class NotificationListItem : public ListItem
    {
        using NotificationType      = notifications::NotificationType;
        const NotificationType type = NotificationType::Unknown;
        bool dismissible            = false;

      protected:
        gui::HBox *box           = nullptr;
        gui::TextFixedSize *text = nullptr;

      public:
        explicit NotificationListItem(NotificationType type);

        [[nodiscard]] bool isDismissible() const noexcept;
        [[nodiscard]] NotificationType getType() const noexcept;
        void setName(const UTF8 &name, bool isRich = false, gui::text::RichTextParser::TokenMap &&tokens = {});
        void setDismissible(bool isDismissible) noexcept;
    };

    class NotificationWithEventCounter : public NotificationListItem
    {
      public:
        NotificationWithEventCounter(notifications::NotificationType type, const UTF8 &indicator);
    };

} // namespace gui

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

#include "NotificationProvider.hpp"
#include <Service/Service.hpp>
#include <module-db/queries/notifications/QueryNotificationsGetAll.hpp>
#include <service-appmgr/Controller.hpp>
#include <service-appmgr/data/NotificationsChangedActionsParams.hpp>
#include <service-db/DBNotificationMessage.hpp>

using namespace notifications;

NotificationProvider::NotificationProvider(sys::Service *ownerService) : ownerService{ownerService}
{}

template <NotificationType type, typename T> bool NotificationProvider::handleNotSeenWithCounter(unsigned int value)
{
    if (notifications.count(type) > 0) {
        if (value == 0) {
            notifications.erase(type);
            return true;
        }
        if (auto notification = static_cast<T *>(notifications[type].get()); value > notification->getValue()) {
            notifications[type] = std::make_shared<T>(value);
            return true;
        }
    }
    else if (value > 0) {
        notifications[type] = std::make_shared<T>(value);
        return true;
    }
    return false;
}

void NotificationProvider::handle(db::query::notifications::GetAllResult *msg)
{
    assert(msg);
    auto records = *msg->getResult();

    bool notificationsChanged = false;
    for (auto record : records) {
        switch (record.key) {
        case NotificationsRecord::Key::Calls:
            notificationsChanged |=
                handleNotSeenWithCounter<NotificationType::NotSeenCall, NotSeenCallNotification>(record.value);
            break;
        case NotificationsRecord::Key::Sms:
            notificationsChanged |=
                handleNotSeenWithCounter<NotificationType::NotSeenSms, NotSeenSMSNotification>(record.value);
            break;
        default:
            break;
        }
    }

    if (notificationsChanged) {
        send();
    }
}

void NotificationProvider::handle(db::NotificationMessage *msg)
{
    if (msg->interface == db::Interface::Name::Notifications && msg->type == db::Query::Type::Update) {
        requestNotSeenNotifications();
    }
}

void NotificationProvider::requestNotSeenNotifications()
{
    DBServiceAPI::GetQuery(
        ownerService, db::Interface::Name::Notifications, std::make_unique<db::query::notifications::GetAll>());
}

namespace
{
    using mapType    = NotificationProvider::Notifications;
    using returnType = std::shared_ptr<Notification>;
    struct get_second : public std::unary_function<mapType::value_type, returnType>
    {
        returnType operator()(const mapType::value_type &value) const
        {
            return value.second;
        }
    };
} // namespace

void NotificationProvider::send()
{
    std::list<std::shared_ptr<const Notification>> toSendNotifications;
    transform(notifications.begin(), notifications.end(), back_inserter(toSendNotifications), get_second());

    toSendNotifications.sort(
        [](const std::shared_ptr<const Notification> &lhs, const std::shared_ptr<const Notification> &rhs) {
            return (lhs->getPriority() > rhs->getPriority());
        });

    app::manager::Controller::sendAction(
        ownerService,
        app::manager::actions::NotificationsChanged,
        std::make_unique<app::manager::actions::NotificationsChangedParams>(std::move(toSendNotifications)));
}

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

#pragma once

#include "NotificationData.hpp"

namespace sys
{
    class Service;
}

namespace db
{
    class NotificationMessage;
    namespace query::notifications
    {
        class GetAllResult;
    }
} // namespace db

namespace notifications
{

    class NotificationProvider
    {
        template <NotificationType type, typename T> bool handleNotSeenWithCounter(unsigned int value);

      public:
        explicit NotificationProvider(sys::Service *ownerService);

        void handle(db::query::notifications::GetAllResult *msg);
        void handle(db::NotificationMessage *msg);
        void requestNotSeenNotifications();
        void send();

        using Notifications = std::map<NotificationType, std::shared_ptr<Notification>>;

      private:
        sys::Service *ownerService;
        Notifications notifications;
    };

} // namespace notifications

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

#include "NotificationsModel.hpp"
#include <ListView.hpp>

using namespace gui;

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

gui::ListItem *NotificationsModel::getItem(gui::Order order)
{
    return getRecord(order);
}

void NotificationsModel::requestRecords(uint32_t offset, uint32_t limit)
{
    setupModel(offset, limit);
    list->onProviderDataUpdate();
}

unsigned int NotificationsModel::getMinimalItemHeight() const
{
    return style::notifications::itemHeight;
}

bool NotificationsModel::hasDismissibleNotification() const noexcept
{
    for (const auto notification : internalData) {
        if (notification->isDismissible()) {
            return true;
        }
    }
    return false;
}

void NotificationsModel::dismissAll(const InputEvent &event)
{
    for (auto it = std::begin(internalData); it != std::end(internalData); it++) {
        if (auto item = *it; item->isDismissible()) {
            item->onInput(event);
        }
    }
}

bool NotificationsModel::isEmpty() const noexcept
{
    return internalData.empty();
}

auto NotificationsModel::create(const notifications::NotSeenSMSNotification *notification) -> NotificationListItem *
{
    auto item = new NotificationWithEventCounter(notifications::NotificationType::NotSeenSms,
                                                 utils::to_string(notification->getValue()));
    item->setName(utils::translate("app_desktop_unread_messages"), true);
    item->deleteByList = false;
    return item;
}
auto NotificationsModel::create(const notifications::NotSeenCallNotification *notification) -> NotificationListItem *
{
    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);
    }
    item->deleteByList = false;
    return item;
}

void NotificationsModel::updateData(app::manager::actions::NotificationsChangedParams *params)
{
    if (list == nullptr) {
        LOG_ERROR("ListView object not provided");
    }

    list->prepareRebuildCallback = [this, toRemove = std::move(internalData)] {
        list->clear();
        for (auto item : toRemove) {
            delete item;
        }
    };

    for (const auto &notification : params->getNotifications()) {
        if (typeid(*notification) == typeid(notifications::NotSeenSMSNotification)) {
            auto sms = static_cast<const notifications::NotSeenSMSNotification *>(notification.get());
            internalData.push_back(create(sms));
        }
        else if (typeid(*notification) == typeid(notifications::NotSeenCallNotification)) {
            auto call = static_cast<const notifications::NotSeenCallNotification *>(notification.get());
            internalData.push_back(create(call));
        }
    }

    list->rebuildList(listview::RebuildType::InPlace);
    list->prepareRebuildCallback = nullptr;
}

void NotificationsModel::clearAll()
{
    list->reset();
    eraseInternalData();
}

void NotificationsModel::setParentWindow(AppWindow *parentWindow) noexcept
{
    parent = parentWindow;
}

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

#pragma once

#include "NotificationListItem.hpp"
#include "NotificationData.hpp"
#include "ListItemProvider.hpp"

#include "Application.hpp"
#include "InternalModel.hpp"
#include <service-appmgr/data/NotificationsChangedActionsParams.hpp>

namespace gui
{

    class NotificationsModel : public app::InternalModel<gui::NotificationListItem *>, public gui::ListItemProvider
    {
        [[nodiscard]] unsigned int requestRecordsCount() final;
        [[nodiscard]] unsigned int getMinimalItemHeight() const final;
        ListItem *getItem(Order order) final;
        void requestRecords(uint32_t offset, uint32_t limit) final;

      protected:
        AppWindow *parent = nullptr;
        [[nodiscard]] virtual auto create(const notifications::NotSeenSMSNotification *notification)
            -> NotificationListItem *;
        [[nodiscard]] virtual auto create(const notifications::NotSeenCallNotification *notification)
            -> NotificationListItem *;

      public:
        [[nodiscard]] bool isEmpty() const noexcept;
        [[nodiscard]] bool hasDismissibleNotification() const noexcept;

        void updateData(app::manager::actions::NotificationsChangedParams *params);
        void dismissAll(const InputEvent &event);
        void clearAll();
        void setParentWindow(AppWindow *parentWindow) noexcept;
    };

} // namespace gui

M module-gui/gui/widgets/ListView.cpp => module-gui/gui/widgets/ListView.cpp +14 -0
@@ 161,6 161,16 @@ namespace gui
            }
        };

        focusChangedCallback = [this]([[maybe_unused]] Item &item) -> bool {
            if (focus) {
                setFocus();
            }
            else {
                setFocusItem(nullptr);
            }
            return true;
        };

        body->parentOnRequestedResizeCallback = [this]() {
            if (pageLoaded)
                recalculateOnBoxRequestedResize();


@@ 575,6 585,10 @@ namespace gui

    void ListView::setFocus()
    {
        if (!focus) {
            return;
        }

        setFocusItem(body);

        if (storedFocusIndex != listview::nPos) {

M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +18 -0
@@ 244,4 244,22 @@ namespace style
        inline constexpr auto rightMargin = 10U;
    } // namespace widgets

    namespace notifications
    {
        inline constexpr auto spanSize     = 8;
        inline constexpr auto digitSize    = 16;
        inline constexpr auto iconWidth    = 35;
        inline constexpr auto textMaxWidth = 250;
        inline constexpr auto itemHeight   = 55;

        namespace model
        {
            inline constexpr auto maxNotificationsPerPage = 4;
            inline constexpr auto x                       = 20;
            inline constexpr auto y                       = 284;
            inline constexpr auto w                       = style::window::default_body_width;
            inline constexpr auto h                       = itemHeight * maxNotificationsPerPage;
        } // namespace model
    }     // namespace notifications

}; // namespace style

M module-gui/test/mock/TestListViewProvider.hpp => module-gui/test/mock/TestListViewProvider.hpp +1 -1
@@ 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

M module-services/service-appmgr/CMakeLists.txt => module-services/service-appmgr/CMakeLists.txt +1 -0
@@ 7,6 7,7 @@ set(SOURCES
    data/UsbActionsParams.cpp
    data/SimActionsParams.cpp
    data/MmiActionsParams.cpp
    data/NotificationsChangedActionsParams.cpp
    model/ApplicationManager.cpp
    model/ApplicationHandle.cpp
    model/ApplicationsRegistry.cpp

M module-services/service-appmgr/Controller.cpp => module-services/service-appmgr/Controller.cpp +8 -1
@@ 4,6 4,7 @@
#include "service-appmgr/Controller.hpp"

#include "service-appmgr/model/ApplicationManager.hpp"
#include "service-appmgr/messages/GetAllNotificationsRequest.hpp"

#include <Service/Service.hpp>



@@ 36,7 37,7 @@ namespace app::manager
    {
        setOnSwitchBehaviour(data, onSwitchBehaviour);
        auto msg = std::make_shared<app::manager::ActionRequest>(sender->GetName(), actionId, std::move(data));
        return sender->bus.sendUnicast(msg, ApplicationManager::ServiceName);
        return sender->bus.sendUnicast(std::move(msg), ApplicationManager::ServiceName);
    }

    auto Controller::switchBack(sys::Service *sender, std::unique_ptr<SwitchBackRequest> msg) -> bool


@@ 111,4 112,10 @@ namespace app::manager
        auto msg = std::make_shared<app::manager::PowerSaveModeInitRequest>(sender->GetName());
        return sender->bus.sendUnicast(msg, ApplicationManager::ServiceName);
    }

    auto Controller::requestNotifications(sys::Service *sender) -> bool
    {
        auto msg = std::make_shared<app::manager::GetAllNotificationsRequest>();
        return sender->bus.sendUnicast(std::move(msg), ApplicationManager::ServiceName);
    }
} // namespace app::manager

A module-services/service-appmgr/data/NotificationsChangedActionsParams.cpp => module-services/service-appmgr/data/NotificationsChangedActionsParams.cpp +17 -0
@@ 0,0 1,17 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <service-appmgr/data/NotificationsChangedActionsParams.hpp>

#include <module-apps/notifications/NotificationData.hpp>

using namespace app::manager::actions;

NotificationsChangedParams::NotificationsChangedParams(Notifications notifications)
    : notifications{std::move(notifications)}
{}

auto NotificationsChangedParams::getNotifications() const noexcept -> const Notifications &
{
    return notifications;
}

M module-services/service-appmgr/model/ApplicationManager.cpp => module-services/service-appmgr/model/ApplicationManager.cpp +33 -5
@@ 36,6 36,7 @@
#include <module-utils/time/DateAndTimeSettings.hpp>
#include <module-services/service-db/agents/settings/SystemSettings.hpp>
#include <service-appmgr/messages/DOMRequest.hpp>
#include <service-appmgr/messages/GetAllNotificationsRequest.hpp>

#include "module-services/service-appmgr/service-appmgr/messages/ApplicationStatus.hpp"



@@ 131,14 132,15 @@ namespace app::manager
                                           const ApplicationName &_rootApplicationName)
        : Service{serviceName, {}, ApplicationManagerStackDepth},
          ApplicationManagerBase(std::move(launchers)), rootApplicationName{_rootApplicationName},
          actionsRegistry{[this](ActionEntry &action) { return handleAction(action); }}, autoLockEnabled(false),
          settings(std::make_unique<settings::Settings>(this)),
          actionsRegistry{[this](ActionEntry &action) { return handleAction(action); }}, notificationProvider(this),
          autoLockEnabled(false), settings(std::make_unique<settings::Settings>(this)),
          phoneModeObserver(std::make_unique<sys::phone_modes::Observer>())
    {
        autoLockTimer = sys::TimerFactory::createSingleShotTimer(
            this, timerBlock, sys::timer::InfiniteTimeout, [this](sys::Timer &) { onPhoneLocked(); });
        bus.channels.push_back(sys::BusChannel::PhoneModeChanges);
        bus.channels.push_back(sys::BusChannel::ServiceAudioNotifications);
        bus.channels.push_back(sys::BusChannel::ServiceDBNotifications);
        registerMessageHandlers();
    }



@@ 382,6 384,21 @@ namespace app::manager
            handleSetOsUpdateVersionChange(msg);
            return sys::msgHandled();
        });
        connect(typeid(GetAllNotificationsRequest), [&](sys::Message *request) {
            notificationProvider.requestNotSeenNotifications();
            notificationProvider.send();
            return sys::msgHandled();
        });
        connect(typeid(db::NotificationMessage), [&](sys::Message *msg) {
            auto msgl = static_cast<db::NotificationMessage *>(msg);
            notificationProvider.handle(msgl);
            return sys::msgHandled();
        });
        connect(typeid(db::QueryResponse), [&](sys::Message *msg) {
            auto response = static_cast<db::QueryResponse *>(msg);
            handleDBResponse(response);
            return sys::msgHandled();
        });

        connect(typeid(app::manager::DOMRequest), [&](sys::Message *request) { return handleDOMRequest(request); });



@@ 578,7 595,9 @@ namespace app::manager
        case actions::ShowPopup:
            [[fallthrough]];
        case actions::AbortPopup:
            return handlePopupAction(action);
            return handleActionOnFocusedApp(action);
        case actions::NotificationsChanged:
            return handleActionOnFocusedApp(action);
        default:
            return handleCustomAction(action);
        }


@@ 621,14 640,13 @@ namespace app::manager
        return ActionProcessStatus::Skipped;
    }

    auto ApplicationManager::handlePopupAction(ActionEntry &action) -> ActionProcessStatus
    auto ApplicationManager::handleActionOnFocusedApp(ActionEntry &action) -> ActionProcessStatus
    {
        auto targetApp = getFocusedApplication();
        if (targetApp == nullptr) {
            return ActionProcessStatus::Skipped;
        }
        action.setTargetApplication(targetApp->name());

        auto &params = action.params;
        app::Application::requestAction(this, targetApp->name(), action.actionId, std::move(params));
        return ActionProcessStatus::Accepted;


@@ 846,6 864,16 @@ namespace app::manager
        return true;
    }

    auto ApplicationManager::handleDBResponse(db::QueryResponse *msg) -> bool
    {
        auto result = msg->getResult();
        if (auto response = dynamic_cast<db::query::notifications::GetAllResult *>(result.get())) {
            notificationProvider.handle(response);
            return true;
        }
        return false;
    }

    void ApplicationManager::rebuildActiveApplications()
    {
        for (const auto &app : getApplications()) {

M module-services/service-appmgr/service-appmgr/Actions.hpp => module-services/service-appmgr/service-appmgr/Actions.hpp +1 -0
@@ 61,6 61,7 @@ namespace app::manager
            SMSRejectedByOfflineNotification,
            CallRejectedByOfflineNotification,
            PhoneModeChanged,
            NotificationsChanged,
            ShowPopup,
            AbortPopup,
            UserAction // The last enumerator in the Action enum.

M module-services/service-appmgr/service-appmgr/Controller.hpp => module-services/service-appmgr/service-appmgr/Controller.hpp +1 -0
@@ 53,5 53,6 @@ namespace app::manager
        static auto changeDateFormat(sys::Service *sender, utils::time::Locale::DateFormat dateFormat) -> bool;
        static auto changePowerSaveMode(sys::Service *sender) -> bool;
        static auto preventBlockingDevice(sys::Service *sender) -> bool;
        static auto requestNotifications(sys::Service *sender) -> bool;
    };
} // namespace app::manager

A module-services/service-appmgr/service-appmgr/data/NotificationsChangedActionsParams.hpp => module-services/service-appmgr/service-appmgr/data/NotificationsChangedActionsParams.hpp +27 -0
@@ 0,0 1,27 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <service-appmgr/Actions.hpp>
#include <list>

namespace notifications
{
    class Notification;
}

namespace app::manager::actions
{
    class NotificationsChangedParams : public app::manager::actions::ActionParams
    {
      public:
        using Notifications = std::list<std::shared_ptr<const notifications::Notification>>;
        explicit NotificationsChangedParams(Notifications notifications);

        [[nodiscard]] auto getNotifications() const noexcept -> const Notifications &;

      private:
        Notifications notifications;
    };
} // namespace app::manager::actions

A module-services/service-appmgr/service-appmgr/messages/GetAllNotificationsRequest.hpp => module-services/service-appmgr/service-appmgr/messages/GetAllNotificationsRequest.hpp +10 -0
@@ 0,0 1,10 @@
// 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::manager
{
    class GetAllNotificationsRequest : public sys::DataMessage
    {};
} // namespace app::manager

M module-services/service-appmgr/service-appmgr/model/ApplicationManager.hpp => module-services/service-appmgr/service-appmgr/model/ApplicationManager.hpp +5 -1
@@ 30,6 30,8 @@
#include <service-gui/Common.hpp>
#include <service-eink/Common.hpp>

#include <notifications/NotificationProvider.hpp>

namespace app
{
    class ApplicationLauncher;


@@ 123,7 125,7 @@ namespace app::manager
        void handleActionRequest(ActionRequest *actionMsg);
        auto handleHomeAction(ActionEntry &action) -> ActionProcessStatus;
        auto handleLaunchAction(ActionEntry &action) -> ActionProcessStatus;
        auto handlePopupAction(ActionEntry &action) -> ActionProcessStatus;
        auto handleActionOnFocusedApp(ActionEntry &action) -> ActionProcessStatus;
        auto handlePhoneModeChangedAction(ActionEntry &action) -> ActionProcessStatus;
        void handlePhoneModeChanged(sys::phone_modes::PhoneMode phoneMode);
        void changePhoneMode(sys::phone_modes::PhoneMode phoneMode, const ApplicationHandle *app);


@@ 140,6 142,7 @@ namespace app::manager
        auto handleTimeFormatChange(TimeFormatChangeRequest *msg) -> bool;
        auto handleDateFormatChange(DateFormatChangeRequest *msg) -> bool;
        auto handleSetOsUpdateVersionChange(SetOsUpdateVersion *msg) -> bool;
        auto handleDBResponse(db::QueryResponse *msg) -> bool;
        auto handlePowerSavingModeInit() -> bool;
        auto handleMessageAsAction(sys::Message *request) -> std::shared_ptr<sys::ResponseMessage>;
        /// handles dom request by passing this request to application which should provide the dom


@@ 165,6 168,7 @@ namespace app::manager

        ApplicationName rootApplicationName;
        ActionsRegistry actionsRegistry;
        notifications::NotificationProvider notificationProvider;

        bool autoLockEnabled; ///< a flag which indicates whether the autoLockTimer should be armed.
                              /// @note: The flag value depends on database settings "gs_lock_time". If the