~aleteoryx/muditaos

c6192fd9120effd28a9ac5b2391e7bfec7964e47 — Lefucjusz 1 year, 7 months ago 5a2a02a
[BH-1989][BH-1990] Core functionality of What's New app

Implemented core functionality of What's New
application.
40 files changed, 705 insertions(+), 294 deletions(-)

M image/system_a/data/lang/English.json
M module-apps/apps-common/widgets/BellSideListItem.hpp
M module-db/Common/Types.hpp
D products/BellHybrid/apps/application-bell-background-sounds/ApplicationBellBackgroundSounds.cpp
M products/BellHybrid/apps/application-bell-meditation-timer/CMakeLists.txt
D products/BellHybrid/apps/application-bell-meditation-timer/presenter/SessionEndedPresenter.cpp
M products/BellHybrid/apps/application-bell-onboarding/ApplicationBellOnBoarding.cpp
M products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.hpp
M products/BellHybrid/apps/application-bell-settings/windows/AboutYourBellWindow.cpp
M products/BellHybrid/apps/application-bell-whats-new/ApplicationWhatsNew.cpp
M products/BellHybrid/apps/application-bell-whats-new/CMakeLists.txt
M products/BellHybrid/apps/application-bell-whats-new/data/WhatsNewCommon.hpp
M products/BellHybrid/apps/application-bell-whats-new/data/WhatsNewStyle.hpp
M products/BellHybrid/apps/application-bell-whats-new/include/application-bell-whats-new/ApplicationWhatsNew.hpp
R products/BellHybrid/apps/application-bell-whats-new/models/{WhatsNewModel => WhatsNewFeaturesModel}.cpp
R products/BellHybrid/apps/application-bell-whats-new/models/{WhatsNewModel => WhatsNewFeaturesModel}.hpp
A products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewFeaturesPresenter.cpp
A products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewFeaturesPresenter.hpp
A products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewMainPresenter.cpp
R products/BellHybrid/apps/{application-bell-meditation-timer/presenter/SessionEndedPresenter => application-bell-whats-new/presenter/WhatsNewMainPresenter}.hpp
D products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewPresenter.cpp
D products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewPresenter.hpp
A products/BellHybrid/apps/application-bell-whats-new/widgets/WhatsNewFeaturesLayout.cpp
A products/BellHybrid/apps/application-bell-whats-new/widgets/WhatsNewFeaturesLayout.hpp
A products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewFeaturesWindow.cpp
A products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewFeaturesWindow.hpp
A products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewMainWindow.cpp
R products/BellHybrid/apps/application-bell-whats-new/windows/{WhatsNewWindow => WhatsNewMainWindow}.hpp
D products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewWindow.cpp
M products/BellHybrid/apps/common/include/common/layouts/UpdateInstructionLayoutClassic.hpp
M products/BellHybrid/apps/common/include/common/options/BellOptionWithDescriptionWindow.hpp
M products/BellHybrid/apps/common/include/common/windows/UpdateInstructionWindow.hpp
M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithQuotes.cpp
M products/BellHybrid/apps/common/src/options/BellOptionWithDescriptionWindow.cpp
M products/BellHybrid/services/appmgr/ApplicationManager.cpp
M products/BellHybrid/services/db/ServiceDB.cpp
M products/BellHybrid/services/db/agents/ShuffleQuoteModel.cpp
M products/BellHybrid/services/db/agents/WhatsNewAgent.cpp
M products/BellHybrid/services/db/agents/WhatsNewAgent.hpp
M products/BellHybrid/services/db/include/db/WhatsNewMessages.hpp
M image/system_a/data/lang/English.json => image/system_a/data/lang/English.json +5 -0
@@ 103,6 103,11 @@
    "app_bell_onboarding_shortcuts_step_restart": "Press both side buttons for 10s to restart the device",
    "app_bell_onboarding_shortcuts_step_rotate": "Rotate to select",
    "app_bell_onboarding_shortcuts_step_turn_off": "Press back for 10s to turn off the device",
    "app_bell_whatsnew_title": "What's New",
    "app_bell_whatsnew_version": "<text>OS version: <token>$VERSION</token></text>",
    "app_bell_whatsnew_continue": "Continue",
    "app_bell_whatsnew_skip": "Skip",
    "app_bell_whatsnew_end_screen_text": "<text>We'd love to hear your feedback about this update at<br/><b>mudita.com/forum</b></text>",
    "app_bell_relaxation_loop": "loop",
    "app_bell_relaxation_loop_description": "the song will play until you turn it off",
    "app_bell_relaxation_looped": "looped",

M module-apps/apps-common/widgets/BellSideListItem.hpp => module-apps/apps-common/widgets/BellSideListItem.hpp +1 -1
@@ 17,7 17,7 @@ namespace gui
        void setBottomDescriptionText(const std::string &description);

      protected:
        BellSideListItem(BellBaseLayout::LayoutType type = BellBaseLayout::LayoutType::WithArrows);
        explicit BellSideListItem(BellBaseLayout::LayoutType type = BellBaseLayout::LayoutType::WithArrows);
        void setupBottomTextBox(const std::string &description);
        void setupTopTextBox(const std::string &description);


M module-db/Common/Types.hpp => module-db/Common/Types.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, 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


@@ 10,4 10,4 @@
/// Zero terminated string with single-quotes on both ends, for instance: 'my string'
#define str_ "%Q"
/// The same as above with additional comma at the end, for instance: 'my string',
#define str_c "%Q,"
\ No newline at end of file
#define str_c "%Q,"

D products/BellHybrid/apps/application-bell-background-sounds/ApplicationBellBackgroundSounds.cpp => products/BellHybrid/apps/application-bell-background-sounds/ApplicationBellBackgroundSounds.cpp +0 -0
M products/BellHybrid/apps/application-bell-meditation-timer/CMakeLists.txt => products/BellHybrid/apps/application-bell-meditation-timer/CMakeLists.txt +0 -1
@@ 27,7 27,6 @@ target_sources(application-bell-meditation-timer
        presenter/MeditationProgressPresenter.cpp
        presenter/MeditationTimerPresenter.cpp
        presenter/ReadyGoingPresenter.cpp
        presenter/SessionEndedPresenter.cpp
        presenter/SettingsPresenter.cpp
        presenter/StatisticsPresenter.cpp
        windows/MeditationMainWindow.cpp

D products/BellHybrid/apps/application-bell-meditation-timer/presenter/SessionEndedPresenter.cpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/SessionEndedPresenter.cpp +0 -17
@@ 1,17 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "SessionEndedPresenter.hpp"

#include <service-appmgr/Controller.hpp>

namespace app::meditation
{
    SessionEndedPresenter::SessionEndedPresenter(app::ApplicationCommon *app) : app{app}
    {}

    void SessionEndedPresenter::activate()
    {
        app::manager::Controller::sendAction(app, app::manager::actions::Home);
    }
} // namespace app::meditation

M products/BellHybrid/apps/application-bell-onboarding/ApplicationBellOnBoarding.cpp => products/BellHybrid/apps/application-bell-onboarding/ApplicationBellOnBoarding.cpp +0 -1
@@ 97,7 97,6 @@ namespace app

    void ApplicationBellOnBoarding::createUserInterface()
    {

        windowsFactory.attach(gui::name::window::main_window, [this](ApplicationCommon *app, const std::string &name) {
            auto powerOffPresenter = std::make_unique<gui::BellPowerOffPresenter>(app);
            return std::make_unique<gui::OnBoardingOnOffWindow>(app, std::move(powerOffPresenter), name);

M products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.hpp => products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.hpp +2 -1
@@ 1,7 1,8 @@
// Copyright (c) 2017-2021, 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/widgets/BellSideListItem.hpp>
#include <apps-common/widgets/spinners/Spinners.hpp>


M products/BellHybrid/apps/application-bell-settings/windows/AboutYourBellWindow.cpp => products/BellHybrid/apps/application-bell-settings/windows/AboutYourBellWindow.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 "AboutYourBellWindow.hpp"


@@ 11,7 11,7 @@ namespace gui
{
    namespace
    {
        static constexpr auto top_margin = 41U;
        constexpr auto top_margin = 41U;
    }

    AboutYourBellWindow::AboutYourBellWindow(

M products/BellHybrid/apps/application-bell-whats-new/ApplicationWhatsNew.cpp => products/BellHybrid/apps/application-bell-whats-new/ApplicationWhatsNew.cpp +22 -13
@@ 3,11 3,13 @@

#include "ApplicationWhatsNew.hpp"
#include "WhatsNewCommon.hpp"
#include "WhatsNewMainWindow.hpp"
#include "WhatsNewMainPresenter.hpp"
#include "WhatsNewFeaturesWindow.hpp"
#include "WhatsNewFeaturesPresenter.hpp"
#include "WhatsNewFeaturesModel.hpp"

#include "windows/WhatsNewWindow.hpp"
#include "presenter/WhatsNewPresenter.hpp"
#include "models/WhatsNewModel.hpp"

#include <common/windows/BellFinishedWindow.hpp>
#include <common/windows/AppsBatteryStatusWindow.hpp>
#include <system/messages/SentinelRegistrationMessage.hpp>



@@ 33,10 35,6 @@ namespace app
            return ret;
        }

        whatsNewModel = std::make_unique<whatsNew::models::WhatsNewModel>(this);

        batteryModel                 = std::make_unique<app::BatteryModel>(this);
        lowBatteryInfoModel          = std::make_unique<app::LowBatteryInfoModel>();
        cpuSentinel                  = std::make_shared<sys::CpuSentinel>(applicationWhatsNewName, this);
        auto sentinelRegistrationMsg = std::make_shared<sys::SentinelRegistrationMessage>(cpuSentinel);
        bus.sendUnicast(std::move(sentinelRegistrationMsg), service::name::system_manager);


@@ 49,16 47,27 @@ namespace app

    void ApplicationWhatsNew::createUserInterface()
    {
        windowsFactory.attach(whatsNew::window::name::main, [this](ApplicationCommon *app, const std::string &name) {
            auto presenter = std::make_unique<app::whatsNew::WhatsNewPresenter>(*batteryModel, *lowBatteryInfoModel);
            return std::make_unique<whatsNew::WhatsNewWindow>(app, std::move(presenter));
        windowsFactory.attach(whatsnew::window::name::main, [this](ApplicationCommon *app, const std::string &name) {
            auto presenter = std::make_unique<whatsnew::WhatsNewMainPresenter>(settings.get());
            return std::make_unique<whatsnew::WhatsNewMainWindow>(app, std::move(presenter), name);
        });

        windowsFactory.attach(whatsNew::window::name::whatsNewLowBattery,
        windowsFactory.attach(
            whatsnew::window::name::features, [this](ApplicationCommon *app, const std::string &name) {
                auto model     = std::make_unique<whatsnew::models::WhatsNewFeaturesModel>(this, settings.get());
                auto presenter = std::make_unique<whatsnew::WhatsNewFeaturesPresenter>(std::move(model));
                return std::make_unique<whatsnew::WhatsNewFeaturesWindow>(app, std::move(presenter), name);
            });
        windowsFactory.attach(gui::window::bell_finished::defaultName,
                              [](ApplicationCommon *app, const std::string &name) {
                                  return std::make_unique<gui::AppsBatteryStatusWindow>(app, name);
                                  return std::make_unique<gui::BellFinishedWindow>(app, name);
                              });

        //        windowsFactory.attach(whatsnew::window::name::whatsNewLowBattery,
        //                              [](ApplicationCommon *app, const std::string &name) {
        //                                  return std::make_unique<gui::AppsBatteryStatusWindow>(app, name);
        //                              });

        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,

M products/BellHybrid/apps/application-bell-whats-new/CMakeLists.txt => products/BellHybrid/apps/application-bell-whats-new/CMakeLists.txt +11 -4
@@ 12,6 12,7 @@ target_include_directories(application-bell-whats-new
            data
            models
            presenter
            widgets
            windows
        >
    PUBLIC


@@ 21,10 22,16 @@ target_include_directories(application-bell-whats-new
target_sources(application-bell-whats-new
    PRIVATE
        ApplicationWhatsNew.cpp
        
        windows/WhatsNewWindow.cpp
        presenter/WhatsNewPresenter.cpp
        models/WhatsNewModel.cpp

        models/WhatsNewFeaturesModel.cpp

        presenter/WhatsNewFeaturesPresenter.cpp
        presenter/WhatsNewMainPresenter.cpp

        widgets/WhatsNewFeaturesLayout.cpp

        windows/WhatsNewMainWindow.cpp
        windows/WhatsNewFeaturesWindow.cpp

    PUBLIC
        include/application-bell-whats-new/ApplicationWhatsNew.hpp

M products/BellHybrid/apps/application-bell-whats-new/data/WhatsNewCommon.hpp => products/BellHybrid/apps/application-bell-whats-new/data/WhatsNewCommon.hpp +4 -4
@@ 5,12 5,12 @@

#include <AppWindowConstants.hpp>

namespace app::whatsNew
namespace app::whatsnew
{
    namespace window::name
    {
        inline constexpr auto main               = gui::name::window::main_window;
        inline constexpr auto whatsNewLowBattery = "WhatsNewLowBatteryWindow";
        inline constexpr auto features           = "WhatsNewFeaturesWindow";
        //        inline constexpr auto whatsNewLowBattery = "WhatsNewLowBatteryWindow";
    } // namespace window::name

} // namespace app::whatsNew
} // namespace app::whatsnew

M products/BellHybrid/apps/application-bell-whats-new/data/WhatsNewStyle.hpp => products/BellHybrid/apps/application-bell-whats-new/data/WhatsNewStyle.hpp +53 -2
@@ 6,5 6,56 @@
#include <Style.hpp>
#include "widgets/BellBaseLayout.hpp"

namespace app::whatsNew
{} // namespace app::whatsNew
namespace gui::whats_new_style
{
    namespace main_window
    {
        inline constexpr auto list_title_font    = style::window::font::large;
        inline constexpr auto description_font   = style::window::font::mediumbigbold;
        inline constexpr auto description_height = 136U;
    } // namespace main_window

    namespace features_window
    {
        namespace layout
        {
            inline constexpr auto width  = style::window_width;
            inline constexpr auto height = style::window_height;
        } // namespace layout

        namespace container
        {
            inline constexpr auto width      = 544U;
            inline constexpr auto height     = 436U;
            inline constexpr auto margin_top = 42U;
        } // namespace container

        namespace center_box
        {
            inline constexpr auto width  = 448U;
            inline constexpr auto height = 436U;
        } // namespace center_box

        namespace icon
        {
            inline constexpr auto width  = center_box::width;
            inline constexpr auto height = 120U;
        } // namespace icon

        namespace title
        {
            inline constexpr auto font       = style::window::font::large;
            inline constexpr auto width      = center_box::width;
            inline constexpr auto height     = 56U;
            inline constexpr auto margin_top = 16U;
        } // namespace title

        namespace description
        {
            inline constexpr auto font       = style::window::font::verybiglight;
            inline constexpr auto width      = center_box::width;
            inline constexpr auto height     = 168U;
            inline constexpr auto margin_top = 40U;
        } // namespace description
    }     // namespace features_window
} // namespace gui::whats_new_style

M products/BellHybrid/apps/application-bell-whats-new/include/application-bell-whats-new/ApplicationWhatsNew.hpp => products/BellHybrid/apps/application-bell-whats-new/include/application-bell-whats-new/ApplicationWhatsNew.hpp +12 -11
@@ 7,25 7,26 @@
#include <common/models/BatteryModel.hpp>
#include <common/models/LowBatteryInfoModel.hpp>

namespace app::whatsNew::models
namespace app::whatsnew::models
{
    class WhatsNewModel;
} // namespace app::whatsNew::models
    class WhatsNewFeaturesModel;
}

namespace app
{
    inline constexpr auto applicationWhatsNewName      = "ApplicationWhatsNew";
    inline constexpr auto applicationWhatsNewStackSize = 1024 * 8;
    inline constexpr auto applicationWhatsNewStackSize = 1024 * 10;

    class ApplicationWhatsNew : public Application
    {
      public:
        ApplicationWhatsNew(std::string name                    = applicationWhatsNewName,
                            std::string parent                  = "",
                            StatusIndicators statusIndicators   = StatusIndicators{},
                            StartInBackground startInBackground = false,
                            std::uint32_t stackDepth            = applicationWhatsNewStackSize);
        ~ApplicationWhatsNew();
        explicit ApplicationWhatsNew(std::string name                    = applicationWhatsNewName,
                                     std::string parent                  = "",
                                     StatusIndicators statusIndicators   = StatusIndicators{},
                                     StartInBackground startInBackground = false,
                                     std::uint32_t stackDepth            = applicationWhatsNewStackSize);
        ~ApplicationWhatsNew() override;

        sys::ReturnCodes InitHandler() override;

        void createUserInterface() override;


@@ 40,7 41,7 @@ namespace app
        }

      private:
        std::unique_ptr<whatsNew::models::WhatsNewModel> whatsNewModel;
        std::unique_ptr<whatsnew::models::WhatsNewFeaturesModel> whatsNewModel;

        std::unique_ptr<AbstractBatteryModel> batteryModel;
        std::unique_ptr<AbstractLowBatteryInfoModel> lowBatteryInfoModel;

R products/BellHybrid/apps/application-bell-whats-new/models/WhatsNewModel.cpp => products/BellHybrid/apps/application-bell-whats-new/models/WhatsNewFeaturesModel.cpp +34 -19
@@ 1,38 1,40 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewModel.hpp"
#include "WhatsNewFeaturesModel.hpp"
#include <ApplicationCommon.hpp>
#include <db/ServiceDB.hpp>
#include <db/WhatsNewMessages.hpp>
#include <product/version.hpp>
#include <service-db/Settings.hpp>
#include <service-db/agents/settings/SystemSettings.hpp>
#include <Utils.hpp>

namespace
{
    using namespace service::db::whatsNew;
    constexpr auto versionSize{3U};
    using namespace service::db::whatsnew;

    std::optional<VersionNumber> getVersionNumber(std::string version)
    auto getVersionNumber(const std::string &version) -> std::optional<VersionNumber>
    {
        constexpr auto versionSize{3U};

        std::vector<std::string> strVector{utils::split(version, '.')};
        if (strVector.size() != versionSize) {
            return std::nullopt;
            return {};
        }

        std::vector<std::uint16_t> uintVector{};
        uintVector.reserve(versionSize);

        for (auto &str : strVector) {
        for (const auto &str : strVector) {
            if (!utils::is_number(str)) {
                return std::nullopt;
                return {};
            }
            uintVector.push_back(utils::getNumericValue<std::uint16_t>(str));
        }
        return VersionNumber{.major{uintVector[0]}, .minor{uintVector[1]}, .patch{uintVector[2]}};
        return VersionNumber{uintVector[0], uintVector[1], uintVector[2]};
    }

    std::optional<messages::Response> sendDBRequest(sys::Service *serv, std::shared_ptr<sys::Message> &&msg)
    auto sendDBRequest(sys::Service *serv, std::shared_ptr<sys::Message> &&msg) -> std::optional<messages::Response>
    {
        const auto ret = serv->bus.sendUnicastSync(std::move(msg), service::name::db, sys::BusProxy::defaultTimeout);
        if (ret.first == sys::ReturnCodes::Success) {


@@ 40,25 42,38 @@ namespace
                return *resp;
            }
        }
        return std::nullopt;
        return {};
    }
} // namespace

namespace app::whatsNew::models
namespace app::whatsnew::models
{
    WhatsNewModel::WhatsNewModel(app::ApplicationCommon *app) : app{app}
    WhatsNewFeaturesModel::WhatsNewFeaturesModel(app::ApplicationCommon *app, settings::Settings *settings)
        : settings{settings}
    {
        const auto version = getVersionNumber(VERSION);
        const auto &lastVersion =
            this->settings->getValue(settings::SystemProperties::osCurrentVersion, settings::SettingsScope::Global);
        const auto &version = getVersionNumber(lastVersion);
        if (!version.has_value()) {
            LOG_ERROR("Failed to parse last version string!");
            return;
        }
        const auto result = sendDBRequest(app, std::make_shared<messages::GetByVersion>(version.value()));

        const auto &result = sendDBRequest(app, std::make_shared<messages::GetByVersion>(version.value()));
        if (result.has_value()) {
            for (auto &record : result->records) {
                LOG_ERROR("*** changes: %s iconName: %s ***", record.description.c_str(), record.iconName.c_str());
                features.push_back({.description = record.description, .iconName = record.iconName});
            for (const auto &record : result->records) {
                features.push_back(Feature{record.title, record.description, record.iconName});
            }
        }
    }

} // namespace app::whatsNew::models
    auto WhatsNewFeaturesModel::getFeatures() -> std::vector<Feature>
    {
        return features;
    }

    auto WhatsNewFeaturesModel::setCurrentOsVersion(const std::string &version) -> void
    {
        settings->setValue(settings::SystemProperties::osCurrentVersion, version, settings::SettingsScope::Global);
    }
} // namespace app::whatsnew::models

R products/BellHybrid/apps/application-bell-whats-new/models/WhatsNewModel.hpp => products/BellHybrid/apps/application-bell-whats-new/models/WhatsNewFeaturesModel.hpp +16 -6
@@ 11,21 11,31 @@ namespace app
    class ApplicationCommon;
}

namespace app::whatsNew::models
namespace settings
{
    class Settings;
}

namespace app::whatsnew::models
{
    struct Feature
    {
        const std::string description;
        const std::string iconName;
        std::string title;
        std::string description;
        std::string iconName;
    };

    class WhatsNewModel
    class WhatsNewFeaturesModel
    {
      public:
        explicit WhatsNewModel(app::ApplicationCommon *app);
        WhatsNewFeaturesModel(app::ApplicationCommon *app, settings::Settings *settings);

        auto getFeatures() -> std::vector<Feature>;
        auto setCurrentOsVersion(const std::string &version) -> void;

      private:
        app::ApplicationCommon *app{nullptr};
        settings::Settings *settings{nullptr};
        std::vector<Feature> features;
    };
} // namespace app::whatsNew::models
} // namespace app::whatsnew::models

A products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewFeaturesPresenter.cpp => products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewFeaturesPresenter.cpp +49 -0
@@ 0,0 1,49 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewFeaturesPresenter.hpp"
#include "WhatsNewFeaturesLayout.hpp"
#include <product/version.hpp>

namespace app::whatsnew
{
    WhatsNewFeaturesPresenter::WhatsNewFeaturesPresenter(std::unique_ptr<models::WhatsNewFeaturesModel> &&model)
        : model{std::move(model)}
    {
        createLayouts();
    }

    auto WhatsNewFeaturesPresenter::getLayouts() const -> std::vector<gui::Item *>
    {
        return layouts;
    }

    auto WhatsNewFeaturesPresenter::isLastLayout(const gui::Item *layout) const -> bool
    {
        return !layouts.empty() && (layouts.back() == layout);
    }

    auto WhatsNewFeaturesPresenter::getFirstLayout() const -> gui::Item *
    {
        return layouts.empty() ? nullptr : layouts.front();
    }

    auto WhatsNewFeaturesPresenter::createLayouts() -> void
    {
        const auto &features = model->getFeatures();
        layouts.reserve(features.size());

        for (auto it = features.begin(); it != features.end(); ++it) {
            const auto isFirst = (it == features.begin());
            const auto isLast  = (it == std::prev(features.end()));

            auto layout = new gui::WhatsNewFeaturesLayout(it->title, it->description, it->iconName, !isFirst, !isLast);
            layouts.emplace_back(layout);
        }
    }

    auto WhatsNewFeaturesPresenter::setCurrentOsVersion() -> void
    {
        model->setCurrentOsVersion(VERSION);
    }
} // namespace app::whatsnew

A products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewFeaturesPresenter.hpp => products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewFeaturesPresenter.hpp +61 -0
@@ 0,0 1,61 @@
// 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 "WhatsNewFeaturesModel.hpp"
#include <apps-common/BasePresenter.hpp>
#include <memory>

namespace app
{
    class ApplicationCommon;
}

namespace gui
{
    class Item;
}

namespace app::whatsnew
{
    class WhatsNewFeaturesContract
    {
      public:
        class View
        {
          public:
            virtual ~View() = default;
        };

        class Presenter : public BasePresenter<WhatsNewFeaturesContract::View>
        {
          public:
            virtual ~Presenter() = default;

            [[nodiscard]] virtual auto getLayouts() const -> std::vector<gui::Item *>      = 0;
            [[nodiscard]] virtual auto isLastLayout(const gui::Item *layout) const -> bool = 0;
            [[nodiscard]] virtual auto getFirstLayout() const -> gui::Item *               = 0;

            virtual auto setCurrentOsVersion() -> void = 0;
        };
    };

    class WhatsNewFeaturesPresenter : public WhatsNewFeaturesContract::Presenter
    {
      public:
        explicit WhatsNewFeaturesPresenter(std::unique_ptr<models::WhatsNewFeaturesModel> &&model);

        [[nodiscard]] auto getLayouts() const -> std::vector<gui::Item *> override;
        [[nodiscard]] auto isLastLayout(const gui::Item *layout) const -> bool override;
        [[nodiscard]] auto getFirstLayout() const -> gui::Item * override;

        auto setCurrentOsVersion() -> void override;

      private:
        auto createLayouts() -> void;

        std::vector<gui::Item *> layouts;
        std::unique_ptr<models::WhatsNewFeaturesModel> model;
    };
} // namespace app::whatsnew

A products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewMainPresenter.cpp => products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewMainPresenter.cpp +18 -0
@@ 0,0 1,18 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewMainPresenter.hpp"
#include <service-db/Settings.hpp>
#include <service-db/agents/settings/SystemSettings.hpp>
#include <product/version.hpp>

namespace app::whatsnew
{
    WhatsNewMainPresenter::WhatsNewMainPresenter(settings::Settings *settings) : settings{settings}
    {}

    auto WhatsNewMainPresenter::setCurrentOsVersion() -> void
    {
        settings->setValue(settings::SystemProperties::osCurrentVersion, VERSION, settings::SettingsScope::Global);
    }
} // namespace app::whatsnew

R products/BellHybrid/apps/application-bell-meditation-timer/presenter/SessionEndedPresenter.hpp => products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewMainPresenter.hpp +17 -14
@@ 1,19 1,19 @@
// Copyright (c) 2017-2021, 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 <Application.hpp>
#include <apps-common/BasePresenter.hpp>
#include <string>

namespace app
namespace settings
{
    class ApplicationCommon;
    class Settings;
}

namespace app::meditation
namespace app::whatsnew
{
    class SessionEndedPresenterContract
    class WhatsNewMainContract
    {
      public:
        class View


@@ 21,19 21,22 @@ namespace app::meditation
          public:
            virtual ~View() = default;
        };
        class Presenter : public BasePresenter<SessionEndedPresenterContract::View>

        class Presenter : public BasePresenter<WhatsNewMainContract::View>
        {
          public:
            virtual void activate() = 0;
            virtual ~Presenter()                       = default;
            virtual auto setCurrentOsVersion() -> void = 0;
        };
    };

    class SessionEndedPresenter : public SessionEndedPresenterContract::Presenter
    class WhatsNewMainPresenter : public WhatsNewMainContract::Presenter
    {
        app::ApplicationCommon *app{};
        void activate() override;

      public:
        explicit SessionEndedPresenter(app::ApplicationCommon *app);
        explicit WhatsNewMainPresenter(settings::Settings *settings);
        auto setCurrentOsVersion() -> void override;

      private:
        settings::Settings *settings{nullptr};
    };
} // namespace app::meditation
} // namespace app::whatsnew

D products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewPresenter.cpp => products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewPresenter.cpp +0 -37
@@ 1,37 0,0 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewPresenter.hpp"

namespace app::whatsNew
{
    WhatsNewPresenter::WhatsNewPresenter(AbstractBatteryModel &batteryModel,
                                         AbstractLowBatteryInfoModel &lowBatteryInfoModel)
        : batteryModel{batteryModel}, lowBatteryInfoModel{lowBatteryInfoModel}
    {}

    Store::Battery WhatsNewPresenter::getBatteryState()
    {
        return batteryModel.getLevelState();
    }

    bool WhatsNewPresenter::isBatteryCharging(Store::Battery::State state) const
    {
        return batteryModel.isBatteryCharging(state);
    }

    bool WhatsNewPresenter::isBatteryBelowLowLevelThreshold(units::SOC soc) const
    {
        return soc < constants::lowBatteryInfoThreshold;
    }

    bool WhatsNewPresenter::isLowBatteryWindowHandled() const
    {
        return lowBatteryInfoModel.isInfoHandled();
    }

    void WhatsNewPresenter::handleLowBatteryWindow()
    {
        lowBatteryInfoModel.handleInfo();
    }
} // namespace app::whatsNew

D products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewPresenter.hpp => products/BellHybrid/apps/application-bell-whats-new/presenter/WhatsNewPresenter.hpp +0 -52
@@ 1,52 0,0 @@
// 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/BatteryModel.hpp>
#include <common/models/LowBatteryInfoModel.hpp>

namespace app
{
    class ApplicationCommon;
}

namespace app::whatsNew
{
    class WhatsNewContract
    {
      public:
        class View
        {
          public:
            virtual ~View() = default;
        };

        class Presenter : public BasePresenter<WhatsNewContract::View>
        {
          public:
            virtual ~Presenter()                                               = default;
            virtual Store::Battery getBatteryState()                           = 0;
            virtual bool isBatteryCharging(Store::Battery::State state) const  = 0;
            virtual bool isBatteryBelowLowLevelThreshold(units::SOC soc) const = 0;
            [[nodiscard]] virtual bool isLowBatteryWindowHandled() const       = 0;
            virtual void handleLowBatteryWindow()                              = 0;
        };
    };

    class WhatsNewPresenter : public WhatsNewContract::Presenter
    {
        AbstractBatteryModel &batteryModel;
        AbstractLowBatteryInfoModel &lowBatteryInfoModel;

        Store::Battery getBatteryState() override;
        [[nodiscard]] bool isBatteryCharging(Store::Battery::State state) const override;
        [[nodiscard]] bool isBatteryBelowLowLevelThreshold(units::SOC soc) const override;
        [[nodiscard]] bool isLowBatteryWindowHandled() const override;
        void handleLowBatteryWindow() override;

      public:
        WhatsNewPresenter(AbstractBatteryModel &batteryModel, AbstractLowBatteryInfoModel &lowBatteryInfoModel);
    };
} // namespace app::whatsNew

A products/BellHybrid/apps/application-bell-whats-new/widgets/WhatsNewFeaturesLayout.cpp => products/BellHybrid/apps/application-bell-whats-new/widgets/WhatsNewFeaturesLayout.cpp +111 -0
@@ 0,0 1,111 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewFeaturesLayout.hpp"
#include "WhatsNewStyle.hpp"
#include <TextFixedSize.hpp>

namespace gui
{
    WhatsNewFeaturesLayout::WhatsNewFeaturesLayout(const std::string &title,
                                                   const std::string &description,
                                                   const std::string &iconName,
                                                   bool leftArrowState,
                                                   bool rightArrowState)
        : VBox(nullptr,
               0,
               0,
               whats_new_style::features_window::layout::width,
               whats_new_style::features_window::layout::height)
    {
        buildInterface(title, description, iconName, leftArrowState, rightArrowState);
    }

    auto WhatsNewFeaturesLayout::buildInterface(const std::string &title,
                                                const std::string &description,
                                                const std::string &iconName,
                                                bool leftArrowState,
                                                bool rightArrowState) -> void
    {
        setAlignment(Alignment::Horizontal::Center);
        setEdges(rectangle_enums::RectangleEdge::None);

        /* Main container */
        auto mainContainer = new HThreeBox<HBox, VBox, HBox>(this);
        mainContainer->setMinimumSize(whats_new_style::features_window::container::width,
                                      whats_new_style::features_window::container::height);
        mainContainer->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        mainContainer->setMargins(Margins(0, whats_new_style::features_window::container::margin_top, 0, 0));
        mainContainer->setEdges(RectangleEdge::None);

        /* Left box - with arrow */
        mainContainer->firstBox = new HBox(mainContainer);
        mainContainer->firstBox->setAlignment(Alignment(Alignment::Vertical::Center));
        mainContainer->firstBox->setEdges(RectangleEdge::None);
        mainContainer->firstBox->activeItem = false;

        auto leftArrow = new ImageBox(nullptr, new Image("bell_arrow_left_W_M"));
        leftArrow->setAlignment(Alignment(Alignment::Horizontal::Right, Alignment::Vertical::Center));
        leftArrow->setMinimumSizeToFitImage();
        leftArrow->setVisible(leftArrowState);
        leftArrow->setEdges(RectangleEdge::None);
        mainContainer->firstBox->setMinimumSize(leftArrow->widgetMinimumArea.w, leftArrow->widgetMinimumArea.h);
        mainContainer->firstBox->addWidget(leftArrow);

        /* Center box - icon, title and description */
        mainContainer->centerBox = new VBox(mainContainer);
        mainContainer->centerBox->setEdges(RectangleEdge::None);
        mainContainer->centerBox->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        mainContainer->centerBox->setMinimumSize(whats_new_style::features_window::center_box::width,
                                                 whats_new_style::features_window::center_box::height);
        mainContainer->centerBox->setMaximumSize(whats_new_style::features_window::center_box::width,
                                                 whats_new_style::features_window::center_box::height);

        auto iconImage = new ImageBox(nullptr, new Image(iconName));
        iconImage->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        iconImage->setMinimumSizeToFitImage();
        iconImage->setMaximumSize(whats_new_style::features_window::icon::width,
                                  whats_new_style::features_window::icon::height);

        auto titleText = new Text(nullptr,
                                  0,
                                  0,
                                  whats_new_style::features_window::title::width,
                                  whats_new_style::features_window::title::height);
        titleText->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        titleText->setMargins(Margins(0, whats_new_style::features_window::title::margin_top, 0, 0));
        titleText->setTextType(TextType::SingleLine);
        titleText->setFont(whats_new_style::features_window::title::font);
        titleText->setText(title);

        auto descriptionText = new Text(nullptr,
                                        0,
                                        0,
                                        whats_new_style::features_window::description::width,
                                        whats_new_style::features_window::description::height);
        descriptionText->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        descriptionText->setMargins(Margins(0, whats_new_style::features_window::description::margin_top, 0, 0));
        descriptionText->setFont(whats_new_style::features_window::description::font);
        descriptionText->setText(description);

        mainContainer->centerBox->addWidget(iconImage);
        mainContainer->centerBox->addWidget(titleText);
        mainContainer->centerBox->addWidget(descriptionText);

        /* Right box */
        mainContainer->lastBox = new HBox(mainContainer);
        mainContainer->lastBox->setAlignment(Alignment(Alignment::Vertical::Center));
        mainContainer->lastBox->setEdges(RectangleEdge::None);
        mainContainer->lastBox->activeItem = false;

        auto rightArrow = new ImageBox(nullptr, new Image("bell_arrow_right_W_M"));
        rightArrow->setAlignment(Alignment(Alignment::Horizontal::Left, Alignment::Vertical::Center));
        rightArrow->setMinimumSizeToFitImage();
        rightArrow->setVisible(rightArrowState);
        rightArrow->setEdges(RectangleEdge::None);
        mainContainer->lastBox->setMinimumSize(rightArrow->widgetMinimumArea.w, rightArrow->widgetMinimumArea.h);
        mainContainer->lastBox->addWidget(rightArrow);

        resizeItems();
    }
} // namespace gui

A products/BellHybrid/apps/application-bell-whats-new/widgets/WhatsNewFeaturesLayout.hpp => products/BellHybrid/apps/application-bell-whats-new/widgets/WhatsNewFeaturesLayout.hpp +26 -0
@@ 0,0 1,26 @@
// 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 <ThreeBox.hpp>

namespace gui
{
    class WhatsNewFeaturesLayout : public VBox
    {
      public:
        WhatsNewFeaturesLayout(const std::string &title,
                               const std::string &description,
                               const std::string &iconName,
                               bool leftArrowState  = true,
                               bool rightArrowState = true);

      private:
        auto buildInterface(const std::string &title,
                            const std::string &description,
                            const std::string &iconName,
                            bool leftArrowState,
                            bool rightArrowState) -> void;
    };
} // namespace gui

A products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewFeaturesWindow.cpp => products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewFeaturesWindow.cpp +85 -0
@@ 0,0 1,85 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewFeaturesWindow.hpp"

#include <SideListView.hpp>
#include <common/options/OptionBellMenu.hpp>
#include <common/data/BatteryStatusSwitchData.hpp>
#include <common/windows/BellFinishedWindow.hpp>

namespace
{
    constexpr auto endWindowTimeout = std::chrono::seconds{5};
}

namespace app::whatsnew
{
    using namespace gui;

    WhatsNewFeaturesWindow::WhatsNewFeaturesWindow(app::ApplicationCommon *app,
                                                   std::unique_ptr<WhatsNewFeaturesContract::Presenter> &&presenter,
                                                   const std::string &name)
        : AppWindow(app, name), presenter{std::move(presenter)}
    {
        buildInterface();
    }

    auto WhatsNewFeaturesWindow::buildInterface() -> void
    {
        AppWindow::buildInterface();

        statusBar->setVisible(false);
        header->setTitleVisibility(false);
        navBar->setVisible(false);

        layouts = presenter->getLayouts();

        itemSpinner = new WidgetSpinner(this, {layouts.begin(), layouts.end()}, Boundaries::Fixed);
        itemSpinner->setSize(style::window_width, style::window_height);
        itemSpinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        itemSpinner->setFocusEdges(RectangleEdge::None);
        itemSpinner->setCurrentValue(presenter->getFirstLayout());

        itemSpinner->onValueChanged = [this]([[maybe_unused]] const auto &value) {
            getApplication()->render(gui::RefreshModes::GUI_REFRESH_DEEP);
        };

        setFocusItem(itemSpinner);
    }

    auto WhatsNewFeaturesWindow::onInput(const gui::InputEvent &inputEvent) -> bool
    {
        /* Prevent leaving by long-pressing back */
        if (inputEvent.isLongRelease(gui::KeyCode::KEY_RF)) {
            return true;
        }
        if (inputEvent.isShortRelease(gui::KeyCode::KEY_RF) ||
            (inputEvent.isShortRelease(gui::KeyCode::KEY_ENTER) && isLastLayout())) {
            presenter->setCurrentOsVersion();
            switchToEndWindow();
            return true;
        }
        if (itemSpinner->onInput(inputEvent)) {
            return true;
        }
        return AppWindow::onInput(inputEvent);
    }

    auto WhatsNewFeaturesWindow::isLastLayout() -> bool
    {
        return itemSpinner->getCurrentValue() == layouts.back();
    }

    auto WhatsNewFeaturesWindow::switchToEndWindow() -> void
    {
        using ExitBehaviour = gui::BellFinishedWindowData::ExitBehaviour;
        application->switchWindow(
            gui::window::bell_finished::defaultName,
            gui::BellFinishedWindowData::Factory::create("big_namaste_W_G",
                                                         "",
                                                         utils::translate("app_bell_whatsnew_end_screen_text"),
                                                         ExitBehaviour::ReturnToHomescreen,
                                                         endWindowTimeout));
    }
} // namespace app::whatsnew

A products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewFeaturesWindow.hpp => products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewFeaturesWindow.hpp +31 -0
@@ 0,0 1,31 @@
// 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 "WhatsNewCommon.hpp"
#include <presenter/WhatsNewFeaturesPresenter.hpp>
#include <apps-common/windows/AppWindow.hpp>
#include <widgets/spinners/Spinners.hpp>

namespace app::whatsnew
{
    class WhatsNewFeaturesWindow : public gui::AppWindow, public WhatsNewFeaturesContract::View
    {
      public:
        WhatsNewFeaturesWindow(app::ApplicationCommon *app,
                               std::unique_ptr<WhatsNewFeaturesContract::Presenter> &&presenter,
                               const std::string &name = window::name::features);

        auto buildInterface() -> void override;
        auto onInput(const gui::InputEvent &inputEvent) -> bool override;

      private:
        std::unique_ptr<WhatsNewFeaturesContract::Presenter> presenter;
        gui::WidgetSpinner *itemSpinner{nullptr};
        std::vector<gui::Item *> layouts;

        auto isLastLayout() -> bool;
        auto switchToEndWindow() -> void;
    };
} // namespace app::whatsnew

A products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewMainWindow.cpp => products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewMainWindow.cpp +51 -0
@@ 0,0 1,51 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewMainWindow.hpp"
#include "WhatsNewStyle.hpp"
#include <common/options/OptionBellMenu.hpp>
#include <service-appmgr/Controller.hpp>
#include <product/version.hpp>

namespace app::whatsnew
{
    using namespace gui;

    WhatsNewMainWindow::WhatsNewMainWindow(app::ApplicationCommon *app,
                                           std::unique_ptr<WhatsNewMainContract::Presenter> &&presenter,
                                           const std::string &name)
        : BellOptionWithDescriptionWindow(app, name), presenter{std::move(presenter)}
    {
        addOptions(settingsOptionsList());
        setListTitle(utils::translate("app_bell_whatsnew_title"), whats_new_style::main_window::list_title_font);
        setListDescription(utils::translate("app_bell_whatsnew_version"),
                           gui::BellOptionWithDescriptionWindow::TokenMap({{"$VERSION", std::string(VERSION)}}),
                           whats_new_style::main_window::description_font,
                           whats_new_style::main_window::description_height);
    }

    auto WhatsNewMainWindow::settingsOptionsList() -> std::list<Option>
    {
        std::list<Option> settingsOptionList;

        auto addWinSettings = [&](const UTF8 &name, const std::function<bool(Item &)> &activatedCallback) {
            settingsOptionList.emplace_back(std::make_unique<option::OptionBellMenu>(
                name, activatedCallback, []([[maybe_unused]] Item &item) { return true; }, this));
        };

        addWinSettings(utils::translate("app_bell_whatsnew_continue"), [this]([[maybe_unused]] Item &item) {
            application->switchWindow(window::name::features);
            return true;
        });
        addWinSettings(utils::translate("app_bell_whatsnew_skip"), [this]([[maybe_unused]] Item &item) {
            presenter->setCurrentOsVersion();
            app::manager::Controller::sendAction(application, app::manager::actions::Home);
            return true;
        });

        return settingsOptionList;
    }

    auto WhatsNewMainWindow::onBeforeShow([[maybe_unused]] ShowMode mode, [[maybe_unused]] SwitchData *data) -> void
    {}
} // namespace app::whatsnew

R products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewWindow.hpp => products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewMainWindow.hpp +12 -14
@@ 4,26 4,24 @@
#pragma once

#include "WhatsNewCommon.hpp"
#include <presenter/WhatsNewPresenter.hpp>
#include "WhatsNewMainPresenter.hpp"
#include <common/options/BellOptionWithDescriptionWindow.hpp>

#include <apps-common/windows/AppWindow.hpp>

namespace app::whatsNew
namespace app::whatsnew
{
    using namespace gui;

    class WhatsNewWindow : public gui::AppWindow, public WhatsNewContract::View
    class WhatsNewMainWindow : public BellOptionWithDescriptionWindow
    {
      public:
        WhatsNewWindow(app::ApplicationCommon *app,
                       std::unique_ptr<WhatsNewContract::Presenter> &&presenter,
                       const std::string &name = window::name::main);

        void buildInterface() override;
        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;
        bool onInput(const gui::InputEvent &inputEvent) override;
        WhatsNewMainWindow(app::ApplicationCommon *app,
                           std::unique_ptr<WhatsNewMainContract::Presenter> &&presenter,
                           const std::string &name = window::name::main);
        auto onBeforeShow(ShowMode mode, SwitchData *data) -> void override;

      private:
        std::unique_ptr<WhatsNewContract::Presenter> presenter;
        std::unique_ptr<WhatsNewMainContract::Presenter> presenter;

        auto settingsOptionsList() -> std::list<Option>;
    };
} // namespace app::whatsNew
} // namespace app::whatsnew

D products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewWindow.cpp => products/BellHybrid/apps/application-bell-whats-new/windows/WhatsNewWindow.cpp +0 -35
@@ 1,35 0,0 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WhatsNewWindow.hpp"

#include <common/options/OptionBellMenu.hpp>
#include <common/data/BatteryStatusSwitchData.hpp>

namespace app::whatsNew
{
    using namespace gui;

    WhatsNewWindow::WhatsNewWindow(app::ApplicationCommon *app,
                                   std::unique_ptr<WhatsNewContract::Presenter> &&presenter,
                                   const std::string &name)
        : AppWindow(app, name), presenter{std::move(presenter)}
    {
        buildInterface();
    }

    void WhatsNewWindow::buildInterface()
    {
        AppWindow::buildInterface();
    }

    void WhatsNewWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        AppWindow::onBeforeShow(mode, data);
    }

    bool WhatsNewWindow::onInput(const gui::InputEvent &inputEvent)
    {
        return AppWindow::onInput(inputEvent);
    }
} // namespace app::whatsNew

M products/BellHybrid/apps/common/include/common/layouts/UpdateInstructionLayoutClassic.hpp => products/BellHybrid/apps/common/include/common/layouts/UpdateInstructionLayoutClassic.hpp +0 -2
@@ 10,8 10,6 @@

namespace gui
{
    class BellBaseLayout;

    using Instruction = std::vector<std::pair<std::string, UTF8>>;

    class UpdateInstructionLayoutClassic : public UpdateInstructionLayoutProvider, VBox

M products/BellHybrid/apps/common/include/common/options/BellOptionWithDescriptionWindow.hpp => products/BellHybrid/apps/common/include/common/options/BellOptionWithDescriptionWindow.hpp +12 -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

#pragma once


@@ 12,17 12,23 @@ namespace gui
{
    class BellOptionWithDescriptionWindow : public AppWindow, protected OptionsList<ListView>
    {
      protected:
        BellBaseLayout *body{};

      public:
        using TokenMap                                 = std::optional<text::RichTextParser::TokenMap>;
        static constexpr auto defaultDescriptionHeight = 175U;

        BellOptionWithDescriptionWindow(app::ApplicationCommon *app, const std::string &name);
        void setListTitle(const std::string &title);
        void setListDescription(const std::string &title);
        void setListTitle(const std::string &title, const std::string &font = style::window::font::largelight);
        void setListDescription(const std::string &title,
                                TokenMap tokenMap       = {},
                                const std::string &font = style::window::font::verybiglight,
                                unsigned height         = defaultDescriptionHeight);

        void onBeforeShow(ShowMode mode, SwitchData *data) override;
        void onClose(CloseReason reason) override;
        void rebuild() override;
        void buildInterface() override;

      protected:
        BellBaseLayout *body{nullptr};
    };
}; // namespace gui

M products/BellHybrid/apps/common/include/common/windows/UpdateInstructionWindow.hpp => products/BellHybrid/apps/common/include/common/windows/UpdateInstructionWindow.hpp +1 -5
@@ 10,23 10,19 @@

namespace gui
{
    class SideListView;
    class UpdateInstructionWindow : public AppWindow, public UpdateInstructionWindowContract::View
    {
        std::unique_ptr<UpdateInstructionWindowContract::Presenter> presenter;
        SideListView *sideListView = nullptr;
        WidgetSpinner *spinner     = nullptr;

        bool onInput(const gui::InputEvent &inputEvent) override;
        void buildInterface() override;

        void onValueChanged(const std::uint32_t currentValue);

      public:
        UpdateInstructionWindow(app::ApplicationCommon *app,
                                std::unique_ptr<UpdateInstructionWindowContract::Presenter> &&presenter,
                                const std::string &name);

        bool isLastLayout() const;
        [[nodiscard]] bool isLastLayout() const;
    };
} // namespace gui

M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithQuotes.cpp => products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithQuotes.cpp +1 -2
@@ 148,5 148,4 @@ namespace gui
        connectionBox->setVisible(connectionStatus->isVisible());
        widgetBox->informContentChanged();
    }

}; // namespace gui
} // namespace gui

M products/BellHybrid/apps/common/src/options/BellOptionWithDescriptionWindow.cpp => products/BellHybrid/apps/common/src/options/BellOptionWithDescriptionWindow.cpp +17 -10
@@ 14,8 14,7 @@ namespace gui
    {
        constexpr auto one_option_height      = style::bell_options::h + 2U * style::bell_options::option_margin;
        constexpr auto option_layout_height   = 2U * one_option_height;
        constexpr auto description_height     = 175U;
        constexpr auto max_description_height = description_height + 16U;
        constexpr auto max_to_min_difference  = 16U;
        constexpr auto top_center_margin      = -14;
    } // namespace



@@ 62,12 61,12 @@ namespace gui
        setFocusItem(optionsList);
    }

    void BellOptionWithDescriptionWindow::setListTitle(const std::string &title)
    void BellOptionWithDescriptionWindow::setListTitle(const std::string &title, const std::string &font)
    {
        auto titleBody = new TextFixedSize(body->firstBox);
        titleBody->drawUnderline(false);
        titleBody->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        titleBody->setFont(style::window::font::largelight);
        titleBody->setFont(font);
        titleBody->setMinimumWidth(style::bell_base_layout::outer_layouts_w);
        titleBody->setEdges(RectangleEdge::None);
        titleBody->setEditMode(EditMode::Browse);


@@ 80,19 79,27 @@ namespace gui
        body->resizeItems();
    }

    void BellOptionWithDescriptionWindow::setListDescription(const std::string &title)
    void BellOptionWithDescriptionWindow::setListDescription(const std::string &title,
                                                             TokenMap tokenMap,
                                                             const std::string &font,
                                                             const unsigned height)
    {
        auto descriptionBody = new TextFixedSize(body->centerBox);
        descriptionBody->drawUnderline(false);
        descriptionBody->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        descriptionBody->setFont(style::window::font::verybiglight);
        descriptionBody->setMinimumSize(style::window_width, description_height);
        descriptionBody->setFont(font);
        descriptionBody->setMinimumSize(style::window_width, height);
        descriptionBody->setEditMode(EditMode::Browse);
        descriptionBody->setRichText(title);
        if (tokenMap.has_value()) {
            descriptionBody->setRichText(title, std::move(tokenMap.value()));
        }
        else {
            descriptionBody->setText(title);
        }

        body->centerBox->setMargins(gui::Margins{0, top_center_margin, 0, 0});
        body->centerBox->setMaximumHeight(max_description_height);
        body->centerBox->setMinimumHeight(description_height);
        body->centerBox->setMaximumHeight(height + max_to_min_difference);
        body->centerBox->setMinimumHeight(height);
        body->centerBox->resizeItems();
        body->resizeItems();
    }

M products/BellHybrid/services/appmgr/ApplicationManager.cpp => products/BellHybrid/services/appmgr/ApplicationManager.cpp +1 -2
@@ 127,9 127,8 @@ namespace app::manager

    auto ApplicationManager::isWhatsNewAvailable() -> bool
    {
        const auto lastVersionNumber =
        const auto &lastVersionNumber =
            settings->getValue(settings::SystemProperties::osCurrentVersion, settings::SettingsScope::Global);
        return lastVersionNumber != VERSION;
    }

} // namespace app::manager

M products/BellHybrid/services/db/ServiceDB.cpp => products/BellHybrid/services/db/ServiceDB.cpp +1 -1
@@ 29,7 29,7 @@ namespace
    constexpr auto multimediaDatabaseName{"multimedia.db"};
    constexpr auto settingsDatabaseName{"settings_bell.db"};
    constexpr auto meditationStatsDatabaseName{"meditation_stats.db"};
    constexpr auto whatsNewDatabaseName{"whats-new.db"};
    constexpr auto whatsNewDatabaseName{"whats_new.db"};
} // namespace

ServiceDB::~ServiceDB()

M products/BellHybrid/services/db/agents/ShuffleQuoteModel.cpp => products/BellHybrid/services/db/agents/ShuffleQuoteModel.cpp +6 -5
@@ 14,14 14,15 @@
namespace
{
    using namespace std::chrono;
    static constexpr auto zeroOffset = 0;
    static constexpr auto maxLimit   = std::numeric_limits<unsigned>::max();
    static constexpr auto dayInSec   = duration_cast<seconds>(24h).count();

    constexpr auto zeroOffset = 0;
    constexpr auto maxLimit   = std::numeric_limits<unsigned>::max();
    constexpr auto dayInSec   = duration_cast<seconds>(24h).count();

    bool hasCrossedMidnight(std::time_t current, std::time_t last)
    {
        const std::tm lastTime    = *std::localtime(&last);
        const std::tm currentTime = *std::localtime(&current);
        const auto lastTime    = *std::localtime(&last);
        const auto currentTime = *std::localtime(&current);
        if (currentTime.tm_mday != lastTime.tm_mday) {
            return true;
        }

M products/BellHybrid/services/db/agents/WhatsNewAgent.cpp => products/BellHybrid/services/db/agents/WhatsNewAgent.cpp +37 -21
@@ 4,27 4,43 @@
#include "WhatsNewAgent.hpp"
#include "db/WhatsNewMessages.hpp"

#include <i18n/i18n.hpp>
#include <Service/Service.hpp>
#include <purefs/filesystem_paths.hpp>
#include <i18n/i18n.hpp>

namespace
{
    using namespace service::db::whatsNew;
    using namespace service::db::whatsnew;

    inline std::string createTitleColumnName(const std::string &language)
    {
        constexpr auto titleSuffix = "_title";
        return language + titleSuffix;
    }

    constexpr auto query = "SELECT %s, Icon FROM WhatsNew WHERE Major > %d OR (Major = %d AND Minor > %d) OR (Major = "
                           "%d AND Minor = %d AND Patch > %d)";
    inline std::string createDescriptionColumnName(const std::string &language)
    {
        constexpr auto descriptionSuffix = "_desc";
        return language + descriptionSuffix;
    }

    std::vector<Record> getRecordsByVersion(Database *db, VersionNumber version)
    std::vector<Record> getRecordsByVersion(Database &db, const VersionNumber &version)
    {
        const auto retQuery = db->query(query,
                                        utils::getDisplayLanguage().c_str(),
                                        version.major,
                                        version.major,
                                        version.minor,
                                        version.major,
                                        version.minor,
                                        version.patch);
        constexpr auto query = "SELECT %s, %s, Icon FROM whats_new "
                               "WHERE Major > %u OR "
                               "(Major = %u AND Minor > %u) OR "
                               "(Major = %u AND Minor = %u AND Patch > %u) "
                               "ORDER BY Major ASC, Minor ASC, Patch ASC;";

        const auto retQuery = db.query(query,
                                       createTitleColumnName(utils::getDisplayLanguage()).c_str(),
                                       createDescriptionColumnName(utils::getDisplayLanguage()).c_str(),
                                       version.major,
                                       version.major,
                                       version.minor,
                                       version.major,
                                       version.minor,
                                       version.patch);

        if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
            return {};


@@ 34,7 50,7 @@ namespace
        ret.reserve(retQuery->getRowCount());

        do {
            ret.push_back({.description{(*retQuery)[0].getString()}, .iconName{(*retQuery)[1].getString()}});
            ret.push_back(Record{(*retQuery)[0].getString(), (*retQuery)[1].getString(), (*retQuery)[2].getString()});
        } while (retQuery->nextRow());

        return ret;


@@ 43,19 59,19 @@ namespace

namespace service::db::agents
{
    WhatsNew::WhatsNew(sys::Service *parentService, const std::string dbName)
    WhatsNew::WhatsNew(sys::Service *parentService, const std::string &dbName)
        : DatabaseAgent{parentService}, dbName{dbName}, db{(purefs::dir::getDatabasesPath() / dbName).c_str()}
    {}

    void WhatsNew::registerMessages()
    {
        parentService->connect(whatsNew::messages::GetByVersion({}),
        parentService->connect(whatsnew::messages::GetByVersion({}),
                               [this](const auto &req) { return handleGetRecordsByVersion(req); });
    }

    void WhatsNew::unRegisterMessages()
    {
        parentService->disconnect(typeid(whatsNew::messages::GetByVersion));
        parentService->disconnect(typeid(whatsnew::messages::GetByVersion));
    }

    auto WhatsNew::getAgentName() -> const std::string


@@ 65,10 81,10 @@ namespace service::db::agents

    sys::MessagePointer WhatsNew::handleGetRecordsByVersion(const sys::Message *req)
    {
        if (auto msg = dynamic_cast<const whatsNew::messages::GetByVersion *>(req)) {
            const auto records = getRecordsByVersion(&db, msg->version);
            return std::make_shared<whatsNew::messages::Response>(records);
        if (const auto msg = dynamic_cast<const whatsnew::messages::GetByVersion *>(req)) {
            const auto &records = getRecordsByVersion(db, msg->version);
            return std::make_shared<whatsnew::messages::Response>(records);
        }
        return std::make_shared<sys::ResponseMessage>();
    }
} // namespace service::db::agents
\ No newline at end of file
} // namespace service::db::agents

M products/BellHybrid/services/db/agents/WhatsNewAgent.hpp => products/BellHybrid/services/db/agents/WhatsNewAgent.hpp +1 -1
@@ 19,7 19,7 @@ namespace service::db::agents
    class WhatsNew : public DatabaseAgent
    {
      public:
        WhatsNew(sys::Service *parentService, std::string dbName);
        WhatsNew(sys::Service *parentService, const std::string &dbName);

        void registerMessages() override;
        void unRegisterMessages() override;

M products/BellHybrid/services/db/include/db/WhatsNewMessages.hpp => products/BellHybrid/services/db/include/db/WhatsNewMessages.hpp +3 -3
@@ 7,10 7,11 @@
#include <Service/Message.hpp>
#include <vector>

namespace service::db::whatsNew
namespace service::db::whatsnew
{
    struct Record
    {
        std::string title;
        std::string description;
        std::string iconName;
    };


@@ 39,6 40,5 @@ namespace service::db::whatsNew

            std::vector<Record> records{};
        };

    } // namespace messages
} // namespace service::db::whatsNew
\ No newline at end of file
} // namespace service::db::whatsnew