~aleteoryx/muditaos

4c1118b48cb2eeabc5181891e965336004b92250 — rrandomsky 2 years ago 8ee57a4
[BH-1635] Low battery notification on the home screen

A low battery warning window has been added to the main screen.
This notification will appear only once when the battery level falls to 15%.
It will also appear when the battery level falls below 10% and 5%.
22 files changed, 455 insertions(+), 64 deletions(-)

M harmony_changelog.md
M image/system_a/data/lang/Deutsch.json
M image/system_a/data/lang/English.json
M image/system_a/data/lang/Espanol.json
M image/system_a/data/lang/Francais.json
M image/system_a/data/lang/Polski.json
M products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp
M products/BellHybrid/apps/application-bell-main/include/application-bell-main/ApplicationBellMain.hpp
M products/BellHybrid/apps/application-bell-main/include/application-bell-main/presenters/HomeScreenPresenter.hpp
M products/BellHybrid/apps/application-bell-main/presenters/HomeScreenPresenter.cpp
M products/BellHybrid/apps/application-bell-main/presenters/StateController.cpp
M products/BellHybrid/apps/application-bell-main/presenters/StateController.hpp
M products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.cpp
M products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.hpp
M products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.cpp
M products/BellHybrid/apps/common/CMakeLists.txt
A products/BellHybrid/apps/common/include/common/models/BatteryLevelNotificationModel.hpp
A products/BellHybrid/apps/common/include/common/models/UserSessionModel.hpp
A products/BellHybrid/apps/common/src/BatteryLevelNotificationModel.cpp
A products/BellHybrid/apps/common/src/UserSessionModel.cpp
M products/BellHybrid/services/appmgr/include/appmgr/IdleHandler.hpp
M products/PurePhone/services/evtmgr/WorkerEvent.cpp
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 14,6 14,7 @@
* Added gradual alarm volume increase
* Added progress bar for all volume control windows
* Improved factory reset procedure to remove user files
* Added low battery notification on the home screen

### Changed / Improved
* Increased clock font in Relaxation, Meditation, Power nap mode

M image/system_a/data/lang/Deutsch.json => image/system_a/data/lang/Deutsch.json +3 -1
@@ 734,5 734,7 @@
    "tethering_menu_access_decline": "<text>Tethering ist eingeschaltet.<br /><br />Schalten Sie Tethering aus,<br />um auf das Men\u00fc zuzugreifen</text>",
    "tethering_phone_mode_change_prohibited": "<text>Tethering ist eingeschaltet.<br /><br />Andere Betriebsarten (Verbunden, DND,<br />Offline) werden von dieser Betriebsart \u00fcberlagert<br />und funktionieren nicht.</text>",
    "tethering_turn_off_question": "Tethering ausschalten?",
    "unsaved_changes": "Ungespeicherte \u00c4nderungen"
    "unsaved_changes": "Ungespeicherte \u00c4nderungen",
    "battery_low": "Niedriger Batteriestand",
    "battery_remaining": "<text><token>$BATTERY</token>% Batterie verbleibend</text>"
}

M image/system_a/data/lang/English.json => image/system_a/data/lang/English.json +3 -1
@@ 745,5 745,7 @@
    "tethering_menu_access_decline": "<text>Tethering is on.<br /><br />To access menu,<br />turn tethering off.</text>",
    "tethering_phone_mode_change_prohibited": "<text>Tethering is on.<br /><br />Other modes (Connected, DND,<br />Offline) are overriden by this mode<br />and are not working.</text>",
    "tethering_turn_off_question": "Turn tethering off?",
    "unsaved_changes": "Unsaved changes"
    "unsaved_changes": "Unsaved4 changes",
    "battery_low": "Low battery",
    "battery_remaining": "<text><token>$BATTERY</token>% battery remaining</text>"
}

M image/system_a/data/lang/Espanol.json => image/system_a/data/lang/Espanol.json +3 -1
@@ 734,5 734,7 @@
    "tethering_menu_access_decline": "<text>El anclaje de red est\u00e1 activado.<br /><br />Para acceder al men\u00fa,<br />desactiva el tethering.</text>",
    "tethering_phone_mode_change_prohibited": "<text>El anclaje de red est\u00e1 activado.<br /><br />Este modo anula otros modos (Conectado, No molestar,<br />Desconectado) <br />y hace que dejen de funcionar.</text>",
    "tethering_turn_off_question": "\u00bfDesactivar el anclaje de red?",
    "unsaved_changes": "Cambios no guardados"
    "unsaved_changes": "Cambios no guardados",
    "battery_low": "Bater\u00EDa baja",
    "battery_remaining": "<text>Queda un <token>$BATTERY</token>% de bater\u00EDa</text>"
}

M image/system_a/data/lang/Francais.json => image/system_a/data/lang/Francais.json +3 -1
@@ 705,5 705,7 @@
    "tethering_menu_access_decline": "<text>Le partage de connexion est activ\u00e9.<br /><br />Pour acc\u00e9der au menu, veuillez<br />d\u00e9sactiver le partage de connextion.</text>",
    "tethering_phone_mode_change_prohibited": "<text>Le partage de connexion est activ\u00e9.<br /><br />Ce mode-ci remplace et d\u00e9sactive les autres modes<br />(Connect\u00e9, NPD, Hors ligne)</text>",
    "tethering_turn_off_question": "Voulez-vous d\u00e9sactiver le partage de connexion?",
    "unsaved_changes": "Modifications non enregistr\u00e9es"
    "unsaved_changes": "Modifications non enregistr\u00e9es",
    "battery_low": "Batterie faible",
    "battery_remaining": "<text><token>$BATTERY</token>% de batterie restant</text>"
}

M image/system_a/data/lang/Polski.json => image/system_a/data/lang/Polski.json +3 -1
@@ 733,5 733,7 @@
    "tethering_menu_access_decline": "<text>Tethering w\u0142\u0105czony.<br /><br />Aby przej\u015b\u0107 do menu,<br />wy\u0142\u0105cz tethering.</text>",
    "tethering_phone_mode_change_prohibited": "<text>Tethering w\u0142\u0105czony.<br /><br />Ten tryb powoduje, \u017ce inne tryby (Po\u0142\u0105czony, Nie przeszkadza\u0107,<br />Offline) nie dzia\u0142aj\u0105.</text>",
    "tethering_turn_off_question": "Wy\u0142\u0105czy\u0107 tethering?",
    "unsaved_changes": "Niezapisane zmiany"
    "unsaved_changes": "Niezapisane zmiany",
    "battery_low": "Niski poziom baterii",
    "battery_remaining": "<text>Pozosta\u0142o <token>$BATTERY</token>% baterii</text>"
}

M products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp => products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp +19 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "include/application-bell-main/ApplicationBellMain.hpp"


@@ 104,11 104,18 @@ namespace app
            return ret;
        }

        timeModel           = std::make_unique<app::TimeModel>();
        batteryModel        = std::make_unique<app::BatteryModel>(this);
        temperatureModel    = std::make_unique<app::home_screen::TemperatureModel>(this);
        homeScreenPresenter = std::make_shared<app::home_screen::HomeScreenPresenter>(
            this, *alarmModel, *batteryModel, *temperatureModel, *timeModel);
        timeModel                     = std::make_unique<app::TimeModel>();
        batteryModel                  = std::make_unique<app::BatteryModel>(this);
        temperatureModel              = std::make_unique<app::home_screen::TemperatureModel>(this);
        userSessionModel              = std::make_unique<app::UserSessionModel>(this);
        batteryLevelNotificationModel = std::make_unique<app::BatteryLevelNotificationModel>();
        homeScreenPresenter           = std::make_shared<app::home_screen::HomeScreenPresenter>(this,
                                                                                      *alarmModel,
                                                                                      *batteryModel,
                                                                                      *temperatureModel,
                                                                                      *timeModel,
                                                                                      *userSessionModel,
                                                                                      *batteryLevelNotificationModel);

        createUserInterface();



@@ 191,6 198,7 @@ namespace app
            const auto newWindowName = msg->getWindowName();
            if (newWindowName == gui::name::window::main_window) {
                stopIdleTimer();
                startUserSessionEndTimer();
            }
            else if (newWindowName == gui::window::name::bell_main_menu ||
                     newWindowName == gui::window::name::bell_main_menu_dialog ||


@@ 213,5 221,10 @@ namespace app
        return true;
    }

    void ApplicationBellMain::startUserSessionEndTimer()
    {
        userSessionModel->deactivateUserSessionWithDelay();
    }

    ApplicationBellMain::~ApplicationBellMain() = default;
} // namespace app

M products/BellHybrid/apps/application-bell-main/include/application-bell-main/ApplicationBellMain.hpp => products/BellHybrid/apps/application-bell-main/include/application-bell-main/ApplicationBellMain.hpp +4 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 46,10 46,13 @@ namespace app
        void onStart() override;
        sys::MessagePointer handleSwitchWindow(sys::Message *msgl) override;
        bool setHomeScreenLayout(std::string layoutName);
        void startUserSessionEndTimer();

        std::unique_ptr<AbstractTimeModel> timeModel;
        std::unique_ptr<AbstractBatteryModel> batteryModel;
        std::unique_ptr<home_screen::AbstractTemperatureModel> temperatureModel;
        std::unique_ptr<AbstractUserSessionModel> userSessionModel;
        std::unique_ptr<AbstractBatteryLevelNotificationModel> batteryLevelNotificationModel;
        std::shared_ptr<app::home_screen::HomeScreenPresenter> homeScreenPresenter;
    };


M products/BellHybrid/apps/application-bell-main/include/application-bell-main/presenters/HomeScreenPresenter.hpp => products/BellHybrid/apps/application-bell-main/include/application-bell-main/presenters/HomeScreenPresenter.hpp +25 -7
@@ 1,10 1,12 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <apps-common/BasePresenter.hpp>
#include <common/models/AbstractAlarmModel.hpp>
#include <common/models/UserSessionModel.hpp>
#include <common/models/BatteryLevelNotificationModel.hpp>
#include <common/layouts/BaseHomeScreenLayoutProvider.hpp>
#include <common/layouts/HomeScreenLayouts.hpp>
#include <gui/Common.hpp>


@@ 22,8 24,10 @@ namespace app
{
    class AbstractTimeModel;
    class AbstractBatteryModel;
    class AbstractBatteryLevelNotificationModel;
    class ApplicationCommon;
    class TemperatureModel;
    class AbstractUserSessionModel;
    class ProgressTimerWithSnoozeTimer;
} // namespace app



@@ 97,8 101,13 @@ namespace app::home_screen
        virtual void decAlarmMinute()                                                            = 0;
        virtual void switchToMenu()                                                              = 0;
        virtual void switchToBatteryStatus()                                                     = 0;
        virtual void switchToLowBatteryWarning()                                                 = 0;
        virtual UTF8 getGreeting()                                                               = 0;
        virtual void setUSBStatusConnected()                                                     = 0;
        virtual void handleLowBatteryWarning()                                                   = 0;
        virtual bool isLowBatteryWarningNeeded()                                                 = 0;
        virtual void updateBatteryLevelInterval()                                                = 0;
        virtual void refreshUserSession()                                                        = 0;

        static constexpr auto defaultTimeout = std::chrono::milliseconds{5000};
    };


@@ 110,7 119,9 @@ namespace app::home_screen
                            AbstractAlarmModel &alarmModel,
                            AbstractBatteryModel &batteryModel,
                            AbstractTemperatureModel &temperatureModel,
                            AbstractTimeModel &timeModel);
                            AbstractTimeModel &timeModel,
                            AbstractUserSessionModel &userSessionModel,
                            AbstractBatteryLevelNotificationModel &batteryLevelNotificationModel);
        virtual ~HomeScreenPresenter();
        HomeScreenPresenter()        = delete;
        HomeScreenPresenter &operator=(const HomeScreenPresenter &oth) = delete;


@@ 140,11 151,16 @@ namespace app::home_screen
        bool isAlarmActivatedByLatch() const override;
        void setUSBStatusConnected() override;

        void incAlarmMinute();
        void decAlarmMinute();
        void switchToMenu();
        void switchToBatteryStatus();
        UTF8 getGreeting();
        void incAlarmMinute() override;
        void decAlarmMinute() override;
        void switchToMenu() override;
        void switchToBatteryStatus() override;
        void switchToLowBatteryWarning() override;
        UTF8 getGreeting() override;
        void handleLowBatteryWarning() override;
        bool isLowBatteryWarningNeeded() override;
        void updateBatteryLevelInterval() override;
        void refreshUserSession() override;

        void setLayout(gui::LayoutGenerator layoutGenerator) override;



@@ 155,6 171,8 @@ namespace app::home_screen
        AbstractBatteryModel &batteryModel;
        AbstractTemperatureModel &temperatureModel;
        AbstractTimeModel &timeModel;
        AbstractUserSessionModel &userSessionModel;
        AbstractBatteryLevelNotificationModel &batteryLevelNotificationModel;
        std::unique_ptr<AbstractController> stateController;
        std::unique_ptr<ProgressTimerWithSnoozeTimer> snoozeTimer;
        std::unique_ptr<std::mt19937> rngEngine;

M products/BellHybrid/apps/application-bell-main/presenters/HomeScreenPresenter.cpp => products/BellHybrid/apps/application-bell-main/presenters/HomeScreenPresenter.cpp +47 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "application-bell-main/presenters/HomeScreenPresenter.hpp"


@@ 75,9 75,13 @@ namespace app::home_screen
                                             AbstractAlarmModel &alarmModel,
                                             AbstractBatteryModel &batteryModel,
                                             AbstractTemperatureModel &temperatureModel,
                                             AbstractTimeModel &timeModel)
        : app{app}, alarmModel{alarmModel}, batteryModel{batteryModel}, temperatureModel{temperatureModel},
          timeModel{timeModel}, rngEngine{std::make_unique<std::mt19937>(bsp::trng::getRandomValue())}
                                             AbstractTimeModel &timeModel,
                                             AbstractUserSessionModel &userSessionModel,
                                             AbstractBatteryLevelNotificationModel &batteryLevelNotificationModel)
        : app{app}, alarmModel{alarmModel}, batteryModel{batteryModel},
          temperatureModel{temperatureModel}, timeModel{timeModel}, userSessionModel{userSessionModel},
          batteryLevelNotificationModel{batteryLevelNotificationModel}, rngEngine{std::make_unique<std::mt19937>(
                                                                            bsp::trng::getRandomValue())}
    {}

    gui::RefreshModes HomeScreenPresenter::handleUpdateTimeEvent()


@@ 131,8 135,8 @@ namespace app::home_screen
    }
    void HomeScreenPresenter::createData()
    {
        stateController =
            std::make_unique<StateController>(*getView(), *this, batteryModel, temperatureModel, alarmModel, timeModel);
        stateController = std::make_unique<StateController>(
            *getView(), *this, batteryModel, temperatureModel, alarmModel, timeModel, userSessionModel);
    }

    void HomeScreenPresenter::refreshWindow()


@@ 250,6 254,13 @@ namespace app::home_screen
                          std::make_unique<gui::BellBatteryStatusWindow::Data>(getBatteryLvl(), isBatteryCharging()));
    }

    void HomeScreenPresenter::switchToLowBatteryWarning()
    {
        app->switchWindow(
            gui::BellBatteryStatusWindow::name,
            std::make_unique<gui::BellBatteryStatusWindow::Data>(getBatteryLvl(), isBatteryCharging(), true));
    }

    UTF8 HomeScreenPresenter::getGreeting()
    {
        const auto greetingCollection = utils::translate_array("app_bell_greeting_msg");


@@ 267,4 278,34 @@ namespace app::home_screen
        getView()->setUSBStatusConnected();
    }

    bool HomeScreenPresenter::isLowBatteryWarningNeeded()
    {
        batteryLevelNotificationModel.updateBatteryLevelInterval(getBatteryLvl());
        return !userSessionModel.isActiveUserSessionHandled() &&
               !batteryLevelNotificationModel.isBatteryIntervalHandled() && !isBatteryCharging();
    }

    void HomeScreenPresenter::handleLowBatteryWarning()
    {
        if (batteryLevelNotificationModel.getBatteryLevelInterval() != BatteryLevelInterval::Unknown &&
            batteryLevelNotificationModel.getBatteryLevelInterval() != BatteryLevelInterval::Above15Percent) {
            switchToLowBatteryWarning();
        }
        else {
            userSessionModel.setCurrentUserSessionAsHandled();
        }
        batteryLevelNotificationModel.setCurrentBatteryIntervalAsHandled();
        userSessionModel.setCurrentUserSessionAsHandled();
    }

    void HomeScreenPresenter::refreshUserSession()
    {
        userSessionModel.activateUserSession();
    }

    void HomeScreenPresenter::updateBatteryLevelInterval()
    {
        batteryLevelNotificationModel.updateBatteryLevelInterval(getBatteryLvl());
    }

} // namespace app::home_screen

M products/BellHybrid/apps/application-bell-main/presenters/StateController.cpp => products/BellHybrid/apps/application-bell-main/presenters/StateController.cpp +59 -26
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "StateController.hpp"


@@ 65,9 65,15 @@ namespace app::home_screen
                return true;
            };

            auto switchToMenu          = [](AbstractPresenter &presenter) { presenter.switchToMenu(); };
            auto switchToBatteryStatus = [](AbstractPresenter &presenter) { presenter.switchToBatteryStatus(); };
            auto updateTemperature     = [](AbstractView &view, AbstractTemperatureModel &temperatureModel) {
            auto switchToMenu              = [](AbstractPresenter &presenter) { presenter.switchToMenu(); };
            auto isLowBatteryWarningToShow = [](AbstractPresenter &presenter) {
                return presenter.isLowBatteryWarningNeeded();
            };
            auto showLowBatteryWarning   = [](AbstractPresenter &presenter) { presenter.handleLowBatteryWarning(); };
            auto endUserSessionWithDelay = [](AbstractUserSessionModel &userSession) {
                userSession.deactivateUserSessionWithDelay();
            };
            auto updateTemperature = [](AbstractView &view, AbstractTemperatureModel &temperatureModel) {
                view.setTemperature(temperatureModel.getTemperature());
            };
            auto setNewAlarmTime = [](AbstractView &view, AbstractAlarmModel &alarmModel) {


@@ 84,6 90,7 @@ namespace app::home_screen
            auto updateBatteryStatus = [](AbstractView &view, AbstractBatteryModel &batteryModel) {
                view.setBatteryLevelState(batteryModel.getLevelState());
            };
            auto switchToBatteryStatus = [](AbstractPresenter &presenter) { presenter.switchToBatteryStatus(); };

        } // namespace Helpers



@@ 115,6 122,8 @@ namespace app::home_screen
            {};
            struct BatteryUpdate
            {};
            struct LowBatteryWarning
            {};
        } // namespace Events

        namespace Init


@@ 135,11 144,13 @@ namespace app::home_screen
            auto entry = [](AbstractController &controller,
                            AbstractView &view,
                            AbstractTemperatureModel &temperatureModel,
                            AbstractBatteryModel &batteryModel) {
                            AbstractBatteryModel &batteryModel,
                            AbstractUserSessionModel &userSession) {
                controller.snooze(false);
                view.setViewState(ViewState::Deactivated);
                view.setTemperature(temperatureModel.getTemperature());
                view.setBatteryLevelState(batteryModel.getLevelState());
                userSession.deactivateUserSessionWithDelay();
            };
        } // namespace Deactivated



@@ 212,12 223,14 @@ namespace app::home_screen
                            AbstractView &view,
                            AbstractAlarmModel &alarmModel,
                            AbstractTemperatureModel &temperatureModel,
                            AbstractBatteryModel &batteryModel) {
                            AbstractBatteryModel &batteryModel,
                            AbstractUserSessionModel &userSession) {
                controller.snooze(false);
                view.setTemperature(temperatureModel.getTemperature());
                view.setViewState(ViewState::Activated);
                view.setAlarmTime(alarmModel.getAlarmTime());
                view.setBatteryLevelState(batteryModel.getLevelState());
                userSession.deactivateUserSessionWithDelay();
            };
        } // namespace Activated



@@ 262,11 275,13 @@ namespace app::home_screen
            auto entry = [](AbstractView &view,
                            AbstractAlarmModel &alarmModel,
                            AbstractTemperatureModel &temperatureModel,
                            AbstractBatteryModel &batteryModel) {
                            AbstractBatteryModel &batteryModel,
                            AbstractUserSessionModel &userSession) {
                view.setViewState(ViewState::AlarmSnoozed);
                view.setSnoozeTime(Clock::to_time_t(alarmModel.getTimeOfNextSnooze()));
                view.setTemperature(temperatureModel.getTemperature());
                view.setBatteryLevelState(batteryModel.getLevelState());
                userSession.deactivateUserSessionWithDelay();
            };
            auto exit = [](AbstractPresenter &presenter) { presenter.stopSnoozeTimer(); };
        } // namespace AlarmSnoozed


@@ 285,13 300,15 @@ namespace app::home_screen
                                             "Init"_s + event<Events::ModelReady> [Helpers::shouldSwitchToActivated] = "Activated"_s,

                                             "Deactivated"_s + sml::on_entry<_> / Deactivated::entry,
                                             "Deactivated"_s + event<Events::LightPress>/ Helpers::switchToMenu,
                                             "Deactivated"_s + event<Events::RotateLeftPress> = "DeactivatedEdit"_s,
                                             "Deactivated"_s + event<Events::RotateRightPress> = "DeactivatedEdit"_s,
                                             "Deactivated"_s + event<Events::DeepUpPress> = "ActivatedWait"_s,
                                             "Deactivated"_s + event<Events::LightPress> [not Helpers::isLowBatteryWarningToShow] / Helpers::switchToMenu,
                                             "Deactivated"_s + event<Events::RotateLeftPress> [not Helpers::isLowBatteryWarningToShow] = "DeactivatedEdit"_s,
                                             "Deactivated"_s + event<Events::RotateRightPress> [not Helpers::isLowBatteryWarningToShow] = "DeactivatedEdit"_s,
                                             "Deactivated"_s + event<Events::DeepUpPress> [not Helpers::isLowBatteryWarningToShow] = "ActivatedWait"_s,
                                             "Deactivated"_s + event<Events::TimeUpdate> / Helpers::updateTemperature,
                                             "Deactivated"_s + event<Events::LongBackPress>  / Helpers::switchToBatteryStatus,
                                             "Deactivated"_s + event<Events::BackPress> / Helpers::endUserSessionWithDelay,
                                             "Deactivated"_s + event<Events::LongBackPress> [not Helpers::isLowBatteryWarningToShow]  / Helpers::switchToBatteryStatus,
                                             "Deactivated"_s + event<Events::BatteryUpdate>  / Helpers::updateBatteryStatus,
                                             "Deactivated"_s + event<Events::LowBatteryWarning> [Helpers::isLowBatteryWarningToShow] / Helpers::showLowBatteryWarning,

                                             "DeactivatedWait"_s + sml::on_entry<_> / DeactivatedWait::entry,
                                             "DeactivatedWait"_s + sml::on_exit<_> / DeactivatedWait::exit,


@@ 334,14 351,16 @@ namespace app::home_screen

                                             "Activated"_s + sml::on_entry<_> / Activated::entry,
                                             "Activated"_s [not Helpers::isAlarmActive] = "Deactivated"_s,
                                             "Activated"_s + event<Events::LightPress>/ Helpers::switchToMenu = "Activated"_s,
                                             "Activated"_s + event<Events::RotateLeftPress> = "ActivatedEdit"_s,
                                             "Activated"_s + event<Events::RotateRightPress> = "ActivatedEdit"_s,
                                             "Activated"_s + event<Events::LightPress> [not Helpers::isLowBatteryWarningToShow]/ Helpers::switchToMenu = "Activated"_s,
                                             "Activated"_s + event<Events::RotateLeftPress> [not Helpers::isLowBatteryWarningToShow] = "ActivatedEdit"_s,
                                             "Activated"_s + event<Events::RotateRightPress> [not Helpers::isLowBatteryWarningToShow] = "ActivatedEdit"_s,
                                             "Activated"_s + event<Events::TimeUpdate> / Helpers::updateTemperature,
                                             "Activated"_s + event<Events::DeepDownPress>  = "DeactivatedWait"_s,
                                             "Activated"_s + event<Events::DeepDownPress> [not Helpers::isLowBatteryWarningToShow]  = "DeactivatedWait"_s,
                                             "Activated"_s + event<Events::AlarmRinging>  = "AlarmRinging"_s,
                                             "Activated"_s + event<Events::LongBackPress>  / Helpers::switchToBatteryStatus,
                                             "Activated"_s + event<Events::BackPress>  / Helpers::endUserSessionWithDelay,
                                             "Activated"_s + event<Events::LongBackPress> [not Helpers::isLowBatteryWarningToShow]  / Helpers::switchToBatteryStatus,
                                             "Activated"_s + event<Events::BatteryUpdate>  / Helpers::updateBatteryStatus,
                                             "Activated"_s + event<Events::LowBatteryWarning> [Helpers::isLowBatteryWarningToShow] / Helpers::showLowBatteryWarning,

                                             "ActivatedEdit"_s + sml::on_entry<_> / AlarmEdit::entry,
                                             "ActivatedEdit"_s + sml::on_exit<_> / AlarmEdit::exit,


@@ 389,11 408,12 @@ namespace app::home_screen
                                             "AlarmSnoozed"_s + sml::on_entry<_> / AlarmSnoozed::exit,
                                             "AlarmSnoozed"_s + event<Events::ModelReady> [not Helpers::isSnoozeActive] = "Activated"_s,
                                             "AlarmSnoozed"_s + event<Events::AlarmRinging>  = "AlarmRinging"_s,
                                             "AlarmSnoozed"_s + event<Events::DeepDownPress> = "DeactivatedWait"_s,
                                             "AlarmSnoozed"_s + event<Events::LightPress>/Helpers::switchToMenu,
                                             "AlarmSnoozed"_s + event<Events::DeepDownPress> [not Helpers::isLowBatteryWarningToShow] = "DeactivatedWait"_s,
                                             "AlarmSnoozed"_s + event<Events::LightPress> [not Helpers::isLowBatteryWarningToShow]/Helpers::switchToMenu,
                                             "AlarmSnoozed"_s + event<Events::TimeUpdate> / Helpers::updateTemperature,
                                             "AlarmSnoozed"_s + event<Events::BatteryUpdate>  / Helpers::updateBatteryStatus,
                                             "AlarmSnoozed"_s + event<Events::LongBackPress>  / Helpers::switchToBatteryStatus
                                             "AlarmSnoozed"_s + event<Events::BatteryUpdate> / Helpers::updateBatteryStatus,
                                             "AlarmSnoozed"_s + event<Events::LongBackPress> [not Helpers::isLowBatteryWarningToShow]  / Helpers::switchToBatteryStatus,
                                             "AlarmSnoozed"_s + event<Events::LowBatteryWarning> [Helpers::isLowBatteryWarningToShow] / Helpers::showLowBatteryWarning
                    );
                // clang-format on
            }


@@ 416,9 436,11 @@ namespace app::home_screen
             AbstractBatteryModel &batteryModel,
             AbstractTemperatureModel &temperatureModel,
             AbstractAlarmModel &alarmModel,
             AbstractTimeModel &timeModel)
             AbstractTimeModel &timeModel,
             AbstractUserSessionModel &userSessionModel)
            : controller{controller}, view{view}, presenter{presenter}, batteryModel{batteryModel},
              temperatureModel{temperatureModel}, alarmModel{alarmModel}, timeModel{timeModel}
              temperatureModel{temperatureModel}, alarmModel{alarmModel}, timeModel{timeModel}, userSessionModel{
                                                                                                    userSessionModel}
        {
            resetSM();
        }


@@ 445,7 467,8 @@ namespace app::home_screen
                batteryModel,
                temperatureModel,
                alarmModel,
                timeModel};
                timeModel,
                userSessionModel};
        }

        AbstractController &controller;


@@ 455,6 478,7 @@ namespace app::home_screen
        AbstractTemperatureModel &temperatureModel;
        AbstractAlarmModel &alarmModel;
        AbstractTimeModel &timeModel;
        AbstractUserSessionModel &userSessionModel;
    };

    StateController::StateController(AbstractView &view,


@@ 462,9 486,10 @@ namespace app::home_screen
                                     AbstractBatteryModel &batteryModel,
                                     AbstractTemperatureModel &temperatureModel,
                                     AbstractAlarmModel &alarmModel,
                                     AbstractTimeModel &timeModel)
                                     AbstractTimeModel &timeModel,
                                     AbstractUserSessionModel &userSessionModel)
        : pimpl{std::make_unique<StateController::Impl>(
              *this, view, presenter, batteryModel, temperatureModel, alarmModel, timeModel)},
              *this, view, presenter, batteryModel, temperatureModel, alarmModel, timeModel, userSessionModel)},
          presenter{presenter}
    {}



@@ 474,6 499,10 @@ namespace app::home_screen
    {
        using namespace sml;
        const auto key = mapKey(inputEvent.getKeyCode());

        presenter.refreshUserSession();
        presenter.updateBatteryLevelInterval();

        switch (key) {
        case KeyMap::Back:
            if (inputEvent.getState() == gui::InputEvent::State::keyReleasedLong) {


@@ 502,6 531,10 @@ namespace app::home_screen
            break;
        }

        if (key != KeyMap::DeepPressUp && key != KeyMap::DeepPressDown && presenter.isLowBatteryWarningNeeded()) {
            pimpl->sm->process_event(Events::LowBatteryWarning{});
        }

        return true;
    }


M products/BellHybrid/apps/application-bell-main/presenters/StateController.hpp => products/BellHybrid/apps/application-bell-main/presenters/StateController.hpp +4 -2
@@ 1,9 1,10 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <common/models/AbstractAlarmModel.hpp>
#include <common/models/UserSessionModel.hpp>
#include <gui/input/InputEvent.hpp>

#include <chrono>


@@ 46,7 47,8 @@ namespace app::home_screen
                        AbstractBatteryModel &batteryModel,
                        AbstractTemperatureModel &temperatureModel,
                        AbstractAlarmModel &alarmModel,
                        AbstractTimeModel &timeModel);
                        AbstractTimeModel &timeModel,
                        AbstractUserSessionModel &userSessionModel);
        ~StateController();

        void resetStateMachine() override;

M products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.cpp => products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.cpp +12 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BellBatteryStatusWindow.hpp"


@@ 53,7 53,6 @@ namespace gui
        topDescription->setEdges(RectangleEdge::None);
        topDescription->activeItem = false;
        topDescription->drawUnderline(false);
        topDescription->setText(utils::translate("app_settings_tech_info_battery"));

        hbox = new HBox(body->lastBox);
        hbox->setMinimumSize(style::bell_base_layout::outer_layouts_w, style::bell_base_layout::outer_layouts_h);


@@ 113,10 112,19 @@ namespace gui
        if (data != nullptr) {
            const auto &switchData = static_cast<Data &>(*data);
            const auto image       = battery_utils::getBatteryLevelImage(batteryEntries, switchData.chargeLevel);
            if (image) {
                center->setImage(image->data(), imageType);
            if (switchData.isLowBatteryWarning) {
                topDescription->setText(utils::translate("battery_low"));
                auto tokenMap = text::RichTextParser::TokenMap({{"$BATTERY", std::to_string(switchData.chargeLevel)}});
                bottomDescription->setRichText(utils::translate("battery_remaining"), std::move(tokenMap));
                chargingIcon->setVisible(false);
            }
            else {
                topDescription->setText(utils::translate("app_settings_tech_info_battery"));
                bottomDescription->setText(std::to_string(switchData.chargeLevel) + "%");
                chargingIcon->setVisible(switchData.isCharging);
            }
            if (image) {
                center->setImage(image->data(), imageType);
                hbox->resizeItems();
                body->resize();
            }

M products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.hpp => products/BellHybrid/apps/application-bell-main/windows/BellBatteryStatusWindow.hpp +5 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 17,10 17,12 @@ namespace gui
      public:
        struct Data : public gui::SwitchData
        {
            Data(std::uint32_t lvl, bool chargeState) : chargeLevel{lvl}, isCharging{chargeState}
            Data(std::uint32_t lvl, bool chargeState, bool lowBatteryWaring = false)
                : chargeLevel{lvl}, isCharging{chargeState}, isLowBatteryWarning{lowBatteryWaring}
            {}
            const std::uint32_t chargeLevel;
            const bool isCharging;
            const bool isLowBatteryWarning;
        };
        static constexpr auto name = "BellBatteryStatusWindow";
        explicit BellBatteryStatusWindow(app::ApplicationCommon *app);


@@ 28,7 30,7 @@ namespace gui
      private:
        static constexpr auto top_description_font          = style::window::font::largelight;
        static constexpr auto bottom_description_font       = style::window::font::verybiglight;
        static constexpr auto bottom_description_max_size_w = 85U;
        static constexpr auto bottom_description_max_size_w = style::bell_base_layout::outer_layouts_w;
        static constexpr auto bottom_description_max_size_h = 85U;

        void buildInterface() override;

M products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.cpp => products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.cpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BellHomeScreenWindow.hpp"

M products/BellHybrid/apps/common/CMakeLists.txt => products/BellHybrid/apps/common/CMakeLists.txt +4 -0
@@ 18,7 18,9 @@ target_sources(application-bell-common
        src/AudioModel.cpp
        src/LanguageUtils.cpp
        src/TimeModel.cpp
        src/UserSessionModel.cpp
        src/BatteryModel.cpp
        src/BatteryLevelNotificationModel.cpp
        src/SoundsRepository.cpp
        src/BellListItemProvider.cpp
        src/SoundsProvider.cpp


@@ 95,7 97,9 @@ target_sources(application-bell-common
        include/common/models/SettingsModel.hpp
        include/common/models/BedtimeModel.hpp
        include/common/models/BatteryModel.hpp
        include/common/models/BatteryLevelNotificationModel.hpp
        include/common/models/TimeModel.hpp
        include/common/models/UserSessionModel.hpp
        include/common/models/AudioModel.hpp
        include/common/models/FrontlightModel.hpp
        include/common/models/AlarmSettingsModel.hpp

A products/BellHybrid/apps/common/include/common/models/BatteryLevelNotificationModel.hpp => products/BellHybrid/apps/common/include/common/models/BatteryLevelNotificationModel.hpp +60 -0
@@ 0,0 1,60 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-utils/EventStore/EventStore.hpp>
#include <service-db/Settings.hpp>

#include <cstdint>
#include <functional>
#include <string>
#include <Units.hpp>

namespace app
{
    class ApplicationCommon;
}

namespace app
{
    enum class BatteryLevelInterval
    {
        Unknown,
        Above15Percent, // 16-100%
        Below15Percent, // 10-15%
        Below10Percent, // 5-9%
        Below5Percent,  // 0-4%
    };

    class AbstractBatteryLevelNotificationModel
    {
      public:
        virtual ~AbstractBatteryLevelNotificationModel() noexcept = default;

        virtual void updateBatteryLevelInterval(units::SOC currentBatteryLevel)    = 0;
        [[nodiscard]] virtual BatteryLevelInterval getBatteryLevelInterval() const = 0;
        [[nodiscard]] virtual bool isBatteryIntervalHandled() const                = 0;
        virtual void setCurrentBatteryIntervalAsHandled()                          = 0;
    };

    class BatteryLevelNotificationModel : public AbstractBatteryLevelNotificationModel
    {
        static constexpr auto batteryLevelIntervalUpperHysteresis = 2;
        static constexpr auto above15PercentMinVal                = 16;
        static constexpr auto below15PercentMaxVal                = 15;
        static constexpr auto below10PercentMaxVal                = 9;
        static constexpr auto below5PercentMaxVal                 = 4;

      public:
        explicit BatteryLevelNotificationModel();
        void updateBatteryLevelInterval(units::SOC currentBatteryLevel) override;
        [[nodiscard]] BatteryLevelInterval getBatteryLevelInterval() const override;
        [[nodiscard]] bool isBatteryIntervalHandled() const override;
        void setCurrentBatteryIntervalAsHandled() override;

      private:
        BatteryLevelInterval currentBatteryLevelInterval = BatteryLevelInterval::Unknown;
        bool isCurrentBatteryIntervalHandled             = false;
    };
} // namespace app

A products/BellHybrid/apps/common/include/common/models/UserSessionModel.hpp => products/BellHybrid/apps/common/include/common/models/UserSessionModel.hpp +60 -0
@@ 0,0 1,60 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <cstdint>
#include <functional>
#include <string>
#include <Timers/TimerHandle.hpp>
#include <Timers/TimerFactory.hpp>

namespace app
{
    inline constexpr auto endOfUserSessionTimeout = std::chrono::seconds{5};

    class AbstractUserSessionModel
    {

      public:
        virtual ~AbstractUserSessionModel() noexcept = default;

        virtual void activateUserSession()            = 0;
        virtual void restartUserSession()             = 0;
        virtual void deactivateUserSessionWithDelay() = 0;
        virtual void setCurrentUserSessionAsHandled() = 0;

        virtual bool isUserSessionActive()        = 0;
        virtual bool isActiveUserSessionHandled() = 0;
    };

    class UserSessionModel : public AbstractUserSessionModel
    {
        enum class SessionState
        {
            Inactive = 0,
            Active,
            EndingWithDelay
        };

      public:
        explicit UserSessionModel(sys::Service *serv);

        void activateUserSession() override;
        void restartUserSession() override;
        void deactivateUserSessionWithDelay() override;
        void setCurrentUserSessionAsHandled() override;

        bool isUserSessionActive() override;
        bool isActiveUserSessionHandled() override;

      protected:
        void endOfSessionTimerCallback();

      private:
        SessionState sessionState = SessionState::Inactive;
        sys::Service *serv;
        sys::TimerHandle endSessionDelayTimer;
        bool isCurrentUserSessionHandled = true;
    };
} // namespace app

A products/BellHybrid/apps/common/src/BatteryLevelNotificationModel.cpp => products/BellHybrid/apps/common/src/BatteryLevelNotificationModel.cpp +72 -0
@@ 0,0 1,72 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "models/BatteryLevelNotificationModel.hpp"

namespace app
{
    BatteryLevelNotificationModel::BatteryLevelNotificationModel()
    {}

    void BatteryLevelNotificationModel::updateBatteryLevelInterval(units::SOC currentBatteryLevel)
    {
        auto updateBatteryIntervalState = [&](BatteryLevelInterval newBatteryLevelIntervalState) {
            if (currentBatteryLevelInterval == newBatteryLevelIntervalState) {
                return;
            }
            currentBatteryLevelInterval     = newBatteryLevelIntervalState;
            isCurrentBatteryIntervalHandled = (newBatteryLevelIntervalState == BatteryLevelInterval::Above15Percent);
        };

        auto updateBatteryIntervalStateWithHysteresis = [&](units::SOC above15PercentHysteresis,
                                                            units::SOC below15PercentHysteresis,
                                                            units::SOC below10PercentHysteresis) {
            if (currentBatteryLevel <= below5PercentMaxVal) {
                updateBatteryIntervalState(BatteryLevelInterval::Below5Percent);
            }
            else if (currentBatteryLevel <= below10PercentMaxVal + below10PercentHysteresis) {
                updateBatteryIntervalState(BatteryLevelInterval::Below10Percent);
            }
            else if (currentBatteryLevel <= below15PercentMaxVal + below15PercentHysteresis) {
                updateBatteryIntervalState(BatteryLevelInterval::Below15Percent);
            }
            else if (currentBatteryLevel >= above15PercentMinVal + above15PercentHysteresis) {
                updateBatteryIntervalState(BatteryLevelInterval::Above15Percent);
            }
        };

        switch (currentBatteryLevelInterval) {
        case (BatteryLevelInterval::Unknown):
        case (BatteryLevelInterval::Above15Percent):
            updateBatteryIntervalStateWithHysteresis(0, 0, 0);
            break;
        case (BatteryLevelInterval::Below15Percent):
            updateBatteryIntervalStateWithHysteresis(batteryLevelIntervalUpperHysteresis, 0, 0);
            break;
        case (BatteryLevelInterval::Below10Percent):
            updateBatteryIntervalStateWithHysteresis(0, batteryLevelIntervalUpperHysteresis, 0);
            break;
        case (BatteryLevelInterval::Below5Percent):
            updateBatteryIntervalStateWithHysteresis(0, 0, batteryLevelIntervalUpperHysteresis);
            break;
        default:
            break;
        }
    }

    BatteryLevelInterval BatteryLevelNotificationModel::getBatteryLevelInterval() const
    {
        return currentBatteryLevelInterval;
    }

    bool BatteryLevelNotificationModel::isBatteryIntervalHandled() const
    {
        return isCurrentBatteryIntervalHandled;
    }

    void BatteryLevelNotificationModel::setCurrentBatteryIntervalAsHandled()
    {
        isCurrentBatteryIntervalHandled = true;
    }

} // namespace app

A products/BellHybrid/apps/common/src/UserSessionModel.cpp => products/BellHybrid/apps/common/src/UserSessionModel.cpp +64 -0
@@ 0,0 1,64 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "models/UserSessionModel.hpp"
#include <log/log.hpp>

namespace app
{

    UserSessionModel::UserSessionModel(sys::Service *serv) : serv{serv}
    {
        endSessionDelayTimer = sys::TimerFactory::createPeriodicTimer(
            serv, "EndOfUserSessionDelay", endOfUserSessionTimeout, [this](sys::Timer &) {
                endOfSessionTimerCallback();
            });
    }

    void UserSessionModel::activateUserSession()
    {
        if (sessionState == SessionState::Inactive) {
            LOG_INFO("User session began");
            isCurrentUserSessionHandled = false;
        }
        sessionState = SessionState::Active;
        endSessionDelayTimer.stop();
    }

    void UserSessionModel::restartUserSession()
    {
        LOG_INFO("User session began (restarted)");
        isCurrentUserSessionHandled = false;
        sessionState                = SessionState::Active;
        endSessionDelayTimer.stop();
    }

    void UserSessionModel::deactivateUserSessionWithDelay()
    {
        sessionState = SessionState::EndingWithDelay;
        endSessionDelayTimer.restart(endOfUserSessionTimeout);
    }

    void UserSessionModel::setCurrentUserSessionAsHandled()
    {
        isCurrentUserSessionHandled = true;
    }

    bool UserSessionModel::isUserSessionActive()
    {
        return (sessionState != SessionState::Inactive);
    }

    bool UserSessionModel::isActiveUserSessionHandled()
    {
        return isCurrentUserSessionHandled && isUserSessionActive();
    }

    void UserSessionModel::endOfSessionTimerCallback()
    {
        LOG_INFO("User session is ended");
        endSessionDelayTimer.stop();
        sessionState = SessionState::Inactive;
    }

} // namespace app

M products/BellHybrid/services/appmgr/include/appmgr/IdleHandler.hpp => products/BellHybrid/services/appmgr/include/appmgr/IdleHandler.hpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

M products/PurePhone/services/evtmgr/WorkerEvent.cpp => products/PurePhone/services/evtmgr/WorkerEvent.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WorkerEvent.hpp"


@@ 79,7 79,7 @@ bool WorkerEvent::handleMessage(std::uint32_t queueID)
        if (notification == bsp::cellular::statusPin) {
            auto GSMstatus = bsp::cellular::status::getStatus();
            LOG_DEBUG("GSM Status pin change: %s",
                      (GSMstatus == bsp::cellular::status::value::ACTIVE ? "ACTIVE" : "INACTIVE"));
                      (GSMstatus == bsp::cellular::status::value::ACTIVE ? "Active" : "Inactive"));

            auto message   = std::make_shared<sevm::StatusStateMessage>(MessageType::EVMModemStatus);
            message->state = GSMstatus;