~aleteoryx/muditaos

1839b2c6b498e13b2a339cac3646b84b0d6f3b00 — Dawid Wojtas 1 year, 11 months ago 7088002
[BH-1861] Backend for quotes on home screen

Implement a backend for getting and shuffle
the quote for home screen clock face.
47 files changed, 554 insertions(+), 82 deletions(-)

M harmony_changelog.md
M module-apps/apps-common/popups/presenter/QuotesPresenter.hpp
M module-services/service-appmgr/CMakeLists.txt
A module-services/service-appmgr/include/service-appmgr/messages/InformDateChanged.hpp
M module-services/service-appmgr/include/service-appmgr/messages/Message.hpp
M module-services/service-appmgr/model/ApplicationManagerCommon.cpp
M module-services/service-db/DBServiceAPI.cpp
M module-services/service-db/include/service-db/DBServiceAPI.hpp
M module-services/service-db/include/service-db/QuotesMessages.hpp
M module-services/service-db/test/test-service-db-quotes.cpp
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/windows/BellHomeScreenWindow.cpp
M products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.hpp
M products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.cpp
M products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.hpp
M products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp
M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/DateTimeUnitsModel.hpp
M products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp
M products/BellHybrid/apps/application-bell-settings/presenter/LayoutWindowPresenter.cpp
M products/BellHybrid/apps/application-bell-settings/presenter/LayoutWindowPresenter.hpp
M products/BellHybrid/apps/application-bell-settings/presenter/TimeUnitsPresenter.cpp
M products/BellHybrid/apps/common/CMakeLists.txt
M products/BellHybrid/apps/common/include/common/layouts/BaseHomeScreenLayoutProvider.hpp
M products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutClassic.hpp
M products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutClassicWithQuotes.hpp
M products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutVertical.hpp
A products/BellHybrid/apps/common/include/common/models/QuoteModel.hpp
A products/BellHybrid/apps/common/src/QuoteModel.cpp
M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassic.cpp
M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithQuotes.cpp
M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutVertical.cpp
M products/BellHybrid/services/appmgr/ApplicationManager.cpp
M products/BellHybrid/services/appmgr/include/appmgr/ApplicationManager.hpp
M products/BellHybrid/services/db/CMakeLists.txt
M products/BellHybrid/services/db/ServiceDB.cpp
A products/BellHybrid/services/db/agents/QuotesAgent.cpp
A products/BellHybrid/services/db/agents/QuotesAgent.hpp
A products/BellHybrid/services/db/agents/QuotesQueries.hpp
A products/BellHybrid/services/db/agents/ShuffleQuoteModel.cpp
A products/BellHybrid/services/db/agents/ShuffleQuoteModel.hpp
M products/BellHybrid/services/db/databases/migration/quotes/0/down.sql
M products/BellHybrid/services/db/include/db/ServiceDB.hpp
M products/PurePhone/services/appmgr/ApplicationManager.cpp
M products/PurePhone/services/appmgr/include/appmgr/ApplicationManager.hpp
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 7,6 7,7 @@
### Added
* Added new 32px and 170px fonts
* Added new clock face with quotes
* Added a backend for quotes on home screen

### Changed / Improved
* Updated FSL drivers from NXP

M module-apps/apps-common/popups/presenter/QuotesPresenter.hpp => module-apps/apps-common/popups/presenter/QuotesPresenter.hpp +1 -1
@@ 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

M module-services/service-appmgr/CMakeLists.txt => module-services/service-appmgr/CMakeLists.txt +1 -0
@@ 51,6 51,7 @@ target_sources(service-appmgr
        include/service-appmgr/messages/GetCurrentDisplayLanguageRequest.hpp
        include/service-appmgr/messages/GetCurrentDisplayLanguageResponse.hpp
        include/service-appmgr/messages/LanguageChangeRequest.hpp
        include/service-appmgr/messages/InformDateChanged.hpp
        include/service-appmgr/messages/Message.hpp
        include/service-appmgr/messages/PowerSaveModeInitRequest.hpp
        include/service-appmgr/messages/PreventBlockingRequest.hpp

A module-services/service-appmgr/include/service-appmgr/messages/InformDateChanged.hpp => module-services/service-appmgr/include/service-appmgr/messages/InformDateChanged.hpp +13 -0
@@ 0,0 1,13 @@
// 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 "BaseMessage.hpp"

namespace app::manager
{
    class InformDateChanged : public BaseMessage
    {};

} // namespace app::manager

M module-services/service-appmgr/include/service-appmgr/messages/Message.hpp => module-services/service-appmgr/include/service-appmgr/messages/Message.hpp +2 -1
@@ 1,4 1,4 @@
// 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


@@ 14,6 14,7 @@
#include "ApplicationInitialised.hpp"
#include "ApplicationStatus.hpp"
#include "LanguageChangeRequest.hpp"
#include "InformDateChanged.hpp"
#include "PowerSaveModeInitRequest.hpp"
#include "PreventBlockingRequest.hpp"
#include "ShutdownRequest.hpp"

M module-services/service-appmgr/model/ApplicationManagerCommon.cpp => module-services/service-appmgr/model/ApplicationManagerCommon.cpp +2 -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 "ApplicationManagerCommon.hpp"


@@ 696,6 696,7 @@ namespace app::manager
        settings->setValue(
            settings::SystemProperties::displayLanguage, requestedLanguage, settings::SettingsScope::Global);
        rebuildActiveApplications();
        DBServiceAPI::InformLanguageChanged(this);
        return true;
    }


M module-services/service-db/DBServiceAPI.cpp => module-services/service-db/DBServiceAPI.cpp +7 -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 "service-db/DBServiceAPI.hpp"


@@ 311,6 311,12 @@ void DBServiceAPI::InformLanguageChanged(sys::Service *serv)
    DBServiceAPI::GetQuery(serv, db::Interface::Name::Quotes, std::move(query));
}

void DBServiceAPI::InformDateChanged(sys::Service *serv)
{
    auto query = std::make_unique<Quotes::Messages::InformDateChanged>();
    DBServiceAPI::GetQuery(serv, db::Interface::Name::Quotes, std::move(query));
}

auto DBServiceAPI::hasContactSameNumbers(sys::Service *serv, const ContactRecord &rec) -> bool
{
    std::shared_ptr<DBContactMessage> msg =

M module-services/service-db/include/service-db/DBServiceAPI.hpp => module-services/service-db/include/service-db/DBServiceAPI.hpp +2 -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


@@ 127,4 127,5 @@ class DBServiceAPI
    static bool AddSMS(sys::Service *serv, const SMSRecord &record, std::unique_ptr<db::QueryListener> &&listener);

    static void InformLanguageChanged(sys::Service *serv);
    static void InformDateChanged(sys::Service *serv);
};

M module-services/service-db/include/service-db/QuotesMessages.hpp => module-services/service-db/include/service-db/QuotesMessages.hpp +47 -4
@@ 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


@@ 15,7 15,7 @@ namespace Quotes
{
    struct CategoryRecord
    {
        unsigned int category_id;
        std::int32_t category_id;
        std::string category_name;
        bool enabled;



@@ 39,7 39,7 @@ namespace Quotes

    struct QuoteRecord
    {
        unsigned int quote_id;
        std::int32_t quote_id;
        std::string quote;
        std::string author;
        bool enabled;


@@ 55,6 55,37 @@ namespace Quotes
        }
    };

    struct IdQuoteAuthorRecord
    {
        std::int32_t quote_id;
        std::string quote;
        std::string author;

        IdQuoteAuthorRecord() = default;

        explicit IdQuoteAuthorRecord(QueryResult *query)
        {
            quote_id = (*query)[0].getInt32();
            quote    = (*query)[1].getString();
            author   = (*query)[2].getString();
        }
    };

    struct IdRecord
    {
        std::int32_t quote_id;

        IdRecord() = default;

        explicit IdRecord(QueryResult *query)
        {
            quote_id = (*query)[0].getInt32();
        }
    };

    struct IdsList : PagedData<IdRecord>
    {};

    struct QuotesList : PagedData<QuoteRecord>
    {};



@@ 309,7 340,7 @@ namespace Quotes
        class InformLanguageChangeRequest : public db::Query
        {
          public:
            InformLanguageChangeRequest() : Query(Query::Type::Read)
            explicit InformLanguageChangeRequest() : Query(Query::Type::Read)
            {}

            auto debugInfo() const -> std::string


@@ 318,6 349,18 @@ namespace Quotes
            }
        };

        class InformDateChanged : public db::Query
        {
          public:
            explicit InformDateChanged() : Query(Query::Type::Read)
            {}

            auto debugInfo() const -> std::string
            {
                return "InformDateChanged";
            }
        };

        class WriteQuoteRequest : public db::Query
        {
          public:

M module-services/service-db/test/test-service-db-quotes.cpp => module-services/service-db/test/test-service-db-quotes.cpp +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

#include <catch2/catch.hpp>


@@ 142,7 142,7 @@ TEST_CASE("Quotes")
        auto oldRandomizedSequence = quotesString;

        // Add a new quote
        auto quoteId = tester->addQuote(quote, author, enabled);
        auto quoteId = static_cast<std::int32_t>(tester->addQuote(quote, author, enabled));
        REQUIRE(oldRandomizedSequence != quotesString);

        // Check if quotes count has increased

M products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp => products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp +3 -1
@@ 112,6 112,7 @@ namespace app
        userSessionModel              = std::make_unique<app::UserSessionModel>(this);
        batteryLevelNotificationModel = std::make_unique<app::BatteryLevelNotificationModel>();
        usbStatusModel                = std::make_unique<app::UsbStatusModel>();
        quoteModel                    = std::make_unique<app::QuoteModel>(this);
        homeScreenPresenter           = std::make_shared<app::home_screen::HomeScreenPresenter>(this,
                                                                                      *alarmModel,
                                                                                      *batteryModel,


@@ 119,7 120,8 @@ namespace app
                                                                                      *timeModel,
                                                                                      *userSessionModel,
                                                                                      *batteryLevelNotificationModel,
                                                                                      *usbStatusModel);
                                                                                      *usbStatusModel,
                                                                                      *quoteModel);

        createUserInterface();


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 +1 -0
@@ 54,6 54,7 @@ namespace app
        std::unique_ptr<AbstractUserSessionModel> userSessionModel;
        std::unique_ptr<AbstractBatteryLevelNotificationModel> batteryLevelNotificationModel;
        std::unique_ptr<AbstractUsbStatusModel> usbStatusModel;
        std::unique_ptr<AbstractQuoteModel> quoteModel;
        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 +7 -1
@@ 7,6 7,7 @@
#include <common/models/AbstractAlarmModel.hpp>
#include <common/models/UserSessionModel.hpp>
#include <common/models/BatteryLevelNotificationModel.hpp>
#include <common/models/QuoteModel.hpp>
#include <common/layouts/BaseHomeScreenLayoutProvider.hpp>
#include <common/layouts/HomeScreenLayouts.hpp>
#include <gui/Common.hpp>


@@ 69,6 70,7 @@ namespace app::home_screen
        virtual void setTextDescription(const UTF8 &desc)                       = 0;
        virtual void setBatteryLevelState(const Store::Battery &batteryContext) = 0;
        virtual void updateUsbStatus(bool isConnected)                          = 0;
        virtual void setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor) = 0;

        /// Various
        virtual void setLayout(gui::LayoutGenerator layoutGenerator) = 0;


@@ 110,6 112,7 @@ namespace app::home_screen
        virtual void updateBatteryLevelInterval()                                                = 0;
        virtual void refreshUserSession()                                                        = 0;
        virtual bool isLowBatteryLevel() const                                                   = 0;
        virtual void requestQuote() const                                                        = 0;

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


@@ 124,7 127,8 @@ namespace app::home_screen
                            AbstractTimeModel &timeModel,
                            AbstractUserSessionModel &userSessionModel,
                            AbstractBatteryLevelNotificationModel &batteryLevelNotificationModel,
                            AbstractUsbStatusModel &usbStatusModel);
                            AbstractUsbStatusModel &usbStatusModel,
                            AbstractQuoteModel &quoteModel);
        virtual ~HomeScreenPresenter();
        HomeScreenPresenter()        = delete;
        HomeScreenPresenter &operator=(const HomeScreenPresenter &oth) = delete;


@@ 165,6 169,7 @@ namespace app::home_screen
        void updateBatteryLevelInterval() override;
        void refreshUserSession() override;
        bool isLowBatteryLevel() const override;
        void requestQuote() const override;

        void setLayout(gui::LayoutGenerator layoutGenerator) override;



@@ 178,6 183,7 @@ namespace app::home_screen
        AbstractUserSessionModel &userSessionModel;
        AbstractBatteryLevelNotificationModel &batteryLevelNotificationModel;
        AbstractUsbStatusModel &usbStatusModel;
        AbstractQuoteModel &quoteModel;
        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 +14 -3
@@ 18,6 18,7 @@
#include <Timers/SystemTimer.hpp>
#include <Timers/TimerFactory.hpp>
#include <service-db/DBNotificationMessage.hpp>
#include <service-db/QuotesMessages.hpp>
#include <service-evtmgr/ServiceEventManagerName.hpp>
#include <switches/LatchStatusRequest.hpp>



@@ 80,15 81,17 @@ namespace app::home_screen
                                             AbstractTimeModel &timeModel,
                                             AbstractUserSessionModel &userSessionModel,
                                             AbstractBatteryLevelNotificationModel &batteryLevelNotificationModel,
                                             AbstractUsbStatusModel &usbStatusModel)
                                             AbstractUsbStatusModel &usbStatusModel,
                                             AbstractQuoteModel &quoteModel)
        : app{app}, alarmModel{alarmModel}, batteryModel{batteryModel},
          temperatureModel{temperatureModel}, timeModel{timeModel}, userSessionModel{userSessionModel},
          batteryLevelNotificationModel{batteryLevelNotificationModel},
          usbStatusModel{usbStatusModel}, rngEngine{std::make_unique<std::mt19937>(bsp::trng::getRandomValue())}
          batteryLevelNotificationModel{batteryLevelNotificationModel}, usbStatusModel{usbStatusModel},
          quoteModel{quoteModel}, rngEngine{std::make_unique<std::mt19937>(bsp::trng::getRandomValue())}
    {}

    gui::RefreshModes HomeScreenPresenter::handleUpdateTimeEvent()
    {
        requestQuote();
        getView()->setTime(timeModel.getCurrentTime());
        stateController->handleTimeUpdateEvent();
        return handleCyclicDeepRefresh();


@@ 129,6 132,7 @@ namespace app::home_screen

    void HomeScreenPresenter::onBeforeShow()
    {
        requestQuote();
        stateController->resetStateMachine();
        getView()->setTimeFormat(timeModel.getTimeFormat());
        getView()->setTime(timeModel.getCurrentTime());


@@ 321,4 325,11 @@ namespace app::home_screen
               (batteryStatus.level < constants::lowBatteryInfoThreshold);
    }

    void HomeScreenPresenter::requestQuote() const
    {
        quoteModel.setCallback(
            [this](std::string quote, std::string author) { getView()->setQuoteText(quote, author); });
        quoteModel.sendQuery();
    }

} // namespace app::home_screen

M products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.cpp => products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.cpp +7 -0
@@ 190,4 190,11 @@ namespace gui
        }
    }

    void BellHomeScreenWindow::setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor)
    {
        if (currentLayout) {
            currentLayout->setQuoteText(quoteContent, quoteAuthor);
        }
    }

} // namespace gui

M products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.hpp => products/BellHybrid/apps/application-bell-main/windows/BellHomeScreenWindow.hpp +1 -0
@@ 41,6 41,7 @@ namespace gui
        void setSnoozeFormat(utils::time::Locale::TimeFormat fmt) override;
        bool updateBatteryStatus() override;
        void updateUsbStatus(bool isConnected) override;
        void setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor) override;

        std::shared_ptr<app::home_screen::AbstractPresenter> presenter;


M products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.cpp => products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.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 "RelaxationSongsRepository.hpp"
#include <module-db/queries/multimedia_files/QueryMultimediaFilesGetLimited.hpp>

M products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.hpp => products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.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/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp => products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp +3 -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 "ApplicationBellSettings.hpp"


@@ 99,8 99,9 @@ namespace app
            gui::window::name::bellSettingsLayout, [this](ApplicationCommon *app, const std::string &name) {
                auto layoutModel = std::make_unique<bell_settings::LayoutModel>(this);
                auto timeModel   = std::make_unique<app::TimeModel>();
                auto quoteModel  = std::make_unique<app::QuoteModel>(this);
                auto presenter   = std::make_unique<bell_settings::LayoutWindowPresenter>(
                    this, std::move(layoutModel), std::move(timeModel));
                    this, std::move(layoutModel), std::move(timeModel), std::move(quoteModel));
                return std::make_unique<gui::BellSettingsLayoutWindow>(app, std::move(presenter), name);
            });


M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/DateTimeUnitsModel.hpp => products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/DateTimeUnitsModel.hpp +9 -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


@@ 7,6 7,7 @@
#include <apps-common/InternalModel.hpp>
#include <Temperature.hpp>
#include <time/time_locale.hpp>
#include <date/date.h>

namespace gui
{


@@ 28,8 29,9 @@ namespace app::bell_settings
        auto clearData() -> void;
        auto saveData() -> void;
        virtual auto loadData() -> void;
        virtual auto isDateChanged() -> bool;
        auto createData() -> void;
        auto requestRecords(uint32_t offset, uint32_t limit) -> void override;
        auto requestRecords(std::uint32_t offset, std::uint32_t limit) -> void override;
        [[nodiscard]] auto getItem(gui::Order order) -> gui::ListItem * override;
        [[nodiscard]] auto requestRecordsCount() -> unsigned int override;
        [[nodiscard]] auto getMinimalItemSpaceRequired() const -> unsigned int override;


@@ 47,9 49,12 @@ namespace app::bell_settings
        gui::TimeFormatSetListItem *timeFmtSetListItem{};
        gui::TemperatureUnitListItem *temperatureUnitListItem{};

        void sendRtcUpdateTimeMessage(time_t newTime);
        void sendRtcUpdateTimeMessage(std::time_t newTime);
        void sendTimeFmtUpdateMessage(utils::time::Locale::TimeFormat newFmt);
        void sendDateFmtUpdateMessage(utils::time::Locale::DateFormat newFmt);

      private:
        date::year_month_day dateLoaded{};
    };

    class DateTimeUnitsModelFactoryResetValues : public DateTimeUnitsModel


@@ 57,5 62,6 @@ namespace app::bell_settings
      public:
        using DateTimeUnitsModel::DateTimeUnitsModel;
        auto loadData() -> void override;
        auto isDateChanged() -> bool override;
    };
} // namespace app::bell_settings

M products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp => products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp +16 -5
@@ 11,8 11,8 @@

#include <gui/widgets/ListViewEngine.hpp>
#include <gui/widgets/Style.hpp>
#include <service-time/ServiceTimeName.hpp>
#include <service-time/api/TimeSettingsApi.hpp>
#include <service-time/ServiceTimeName.hpp>
#include <service-time/service-time/TimeMessage.hpp>
#include <widgets/DateSetSpinner.hpp>



@@ 140,22 140,22 @@ namespace app::bell_settings
        const auto now        = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        const auto timeFormat = stm::api::timeFormat();
        const auto dateFormat = stm::api::dateFormat();
        dateLoaded            = date::year_month_day{date::floor<date::days>(std::chrono::system_clock::now())};
        timeSetListItem->timeSetSpinner->setTimeFormat(timeFormat);
        timeSetListItem->timeSetSpinner->setTime(now);
        yearSetListItem->dateSetSpinner->setDate(
            date::year_month_day{date::floor<date::days>(std::chrono::system_clock::now())});
        yearSetListItem->dateSetSpinner->setDate(dateLoaded);

        timeFmtSetListItem->setTimeFmt(timeFormat);
        dateFmtSetListItem->setDateFmt(dateFormat);
    }

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

    void DateTimeUnitsModel::sendRtcUpdateTimeMessage(time_t newTime)
    void DateTimeUnitsModel::sendRtcUpdateTimeMessage(std::time_t newTime)
    {
        auto msg = std::make_shared<stm::message::TimeChangeRequestMessage>(newTime);
        application->bus.sendUnicast(std::move(msg), service::name::service_time);


@@ 173,6 173,12 @@ namespace app::bell_settings
        application->bus.sendUnicast(std::move(msg), service::name::service_time);
    }

    auto DateTimeUnitsModel::isDateChanged() -> bool
    {
        const auto date = daySetListItem->dateSetSpinner->getDate();
        return dateLoaded != date;
    }

    auto DateTimeUnitsModel::getTemperatureUnit() const -> utils::temperature::Temperature::Unit
    {
#if CONFIG_ENABLE_TEMP == 1


@@ 210,4 216,9 @@ namespace app::bell_settings
        timeFmtSetListItem->setTimeFmt(factoryResetTimeFmt);
        dateFmtSetListItem->setDateFmt(factoryResetDateFmt);
    }

    auto DateTimeUnitsModelFactoryResetValues::isDateChanged() -> bool
    {
        return false;
    }
} // namespace app::bell_settings

M products/BellHybrid/apps/application-bell-settings/presenter/LayoutWindowPresenter.cpp => products/BellHybrid/apps/application-bell-settings/presenter/LayoutWindowPresenter.cpp +22 -15
@@ 1,9 1,8 @@
// 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 "LayoutWindowPresenter.hpp"
#include <service-appmgr/Controller.hpp>
#include <common/layouts/HomeScreenLayouts.hpp>
#include <common/layouts/BaseHomeScreenLayoutProvider.hpp>
#include <appmgr/messages/ChangeHomescreenLayoutMessage.hpp>



@@ 27,8 26,10 @@ namespace app::bell_settings
{
    LayoutWindowPresenter::LayoutWindowPresenter(app::ApplicationCommon *app,
                                                 std::unique_ptr<AbstractLayoutModel> &&layoutModel,
                                                 std::unique_ptr<AbstractTimeModel> &&timeModel)
        : app(app), layoutModel{std::move(layoutModel)}, timeModel{std::move(timeModel)}
                                                 std::unique_ptr<AbstractTimeModel> &&timeModel,
                                                 std::unique_ptr<AbstractQuoteModel> &&quoteModel)
        : app(app), layoutModel{std::move(layoutModel)}, timeModel{std::move(timeModel)}, quoteModel{
                                                                                              std::move(quoteModel)}
    {
        initLayoutOptions();
    }


@@ 37,8 38,8 @@ namespace app::bell_settings
    {
        std::vector<gui::Item *> layouts;

        for (auto const &option : layoutOptions) {
            layouts.push_back(option.first);
        for (auto const &[option, _] : layoutOptions) {
            layouts.push_back(option->getLayout());
        }

        return layouts;


@@ 48,21 49,21 @@ namespace app::bell_settings
    {
        const auto layoutSelected = layoutModel->getValue();

        for (auto const &option : layoutOptions) {
            if (option.second == layoutSelected) {
                return option.first;
        for (auto const &[option, name] : layoutOptions) {
            if (name == layoutSelected) {
                return option->getLayout();
            }
        }

        return layoutOptions.at(0).first;
        return layoutOptions.at(0).first->getLayout();
    }

    void LayoutWindowPresenter::setLayout(gui::Item *selectedLayout)
    {
        for (auto const &option : layoutOptions) {
            if (option.first == selectedLayout) {
                layoutModel->setValue(option.second);
                auto layoutChangeRequest = std::make_shared<ChangeHomescreenLayoutMessage>(option.second);
        for (auto const &[option, name] : layoutOptions) {
            if (option->getLayout() == selectedLayout) {
                layoutModel->setValue(name);
                auto layoutChangeRequest = std::make_shared<ChangeHomescreenLayoutMessage>(name);
                app->bus.sendUnicast(layoutChangeRequest, service::name::appmgr);
                break;
            }


@@ 71,6 72,12 @@ namespace app::bell_settings

    void LayoutWindowPresenter::initLayoutOptions()
    {
        quoteModel->setCallback([=](std::string quote, std::string author) {
            for (auto &[layout, _] : layoutOptions) {
                layout->setQuoteText(quote, author);
            }
        });
        quoteModel->sendQuery();
        const auto timeFormat = timeModel->getTimeFormat();
        auto layoutsList      = timeFormat == utils::time::Locale::TimeFormat::FormatTime24H
                                    ? gui::factory::getLayoutsFormat24h()


@@ 85,7 92,7 @@ namespace app::bell_settings
            layout->setAlarmTime(alarmTime);
            layout->setBatteryLevelState(batteryState);
            layout->setTemperature(temperature);
            layoutOptions.push_back({layout->getLayout(), layoutEntry.first});
            layoutOptions.push_back({layout, layoutEntry.first});
        }
    }
} // namespace app::bell_settings

M products/BellHybrid/apps/application-bell-settings/presenter/LayoutWindowPresenter.hpp => products/BellHybrid/apps/application-bell-settings/presenter/LayoutWindowPresenter.hpp +7 -3
@@ 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


@@ 7,6 7,8 @@
#include <apps-common/ApplicationCommon.hpp>
#include <common/models/LayoutModel.hpp>
#include <common/models/TimeModel.hpp>
#include <common/models/QuoteModel.hpp>
#include <common/layouts/HomeScreenLayouts.hpp>
#include <common/layouts/HomeScreenLayouts.hpp>

#include <vector>


@@ 39,13 41,15 @@ namespace app::bell_settings
        app::ApplicationCommon *app{};
        std::unique_ptr<AbstractLayoutModel> layoutModel;
        std::unique_ptr<AbstractTimeModel> timeModel;
        std::vector<std::pair<gui::Item *, const std::string>> layoutOptions;
        std::unique_ptr<AbstractQuoteModel> quoteModel;
        std::vector<std::pair<gui::BaseHomeScreenLayoutProvider *, const std::string>> layoutOptions;
        void initLayoutOptions();

      public:
        explicit LayoutWindowPresenter(app::ApplicationCommon *app,
                                       std::unique_ptr<AbstractLayoutModel> &&layoutModel,
                                       std::unique_ptr<AbstractTimeModel> &&timeModel);
                                       std::unique_ptr<AbstractTimeModel> &&timeModel,
                                       std::unique_ptr<AbstractQuoteModel> &&quoteModel);

        std::vector<gui::Item *> getLayouts() const override;
        gui::Item *getSelectedLayout() const override;

M products/BellHybrid/apps/application-bell-settings/presenter/TimeUnitsPresenter.cpp => products/BellHybrid/apps/application-bell-settings/presenter/TimeUnitsPresenter.cpp +6 -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 "models/TemperatureUnitModel.hpp"


@@ 10,6 10,7 @@

#include <appmgr/messages/ChangeHomescreenLayoutMessage.hpp>
#include <service-appmgr/ServiceApplicationManagerName.hpp>
#include <service-appmgr/messages/InformDateChanged.hpp>

namespace
{


@@ 66,6 67,10 @@ namespace app::bell_settings
                app->bus.sendUnicast(layoutChangeRequest, service::name::appmgr);
            }
        }
        if (pagesProvider->isDateChanged()) {
            auto msg = std::make_shared<app::manager::InformDateChanged>();
            app->bus.sendUnicast(std::move(msg), service::name::appmgr);
        }
    }

    auto TimeUnitsWindowPresenter::loadData() -> void

M products/BellHybrid/apps/common/CMakeLists.txt => products/BellHybrid/apps/common/CMakeLists.txt +2 -0
@@ 20,6 20,7 @@ target_sources(application-bell-common
        src/TimeModel.cpp
        src/UserSessionModel.cpp
        src/BatteryModel.cpp
        src/QuoteModel.cpp
        src/BatteryLevelNotificationModel.cpp
        src/SoundsRepository.cpp
        src/BellListItemProvider.cpp


@@ 114,6 115,7 @@ target_sources(application-bell-common
        include/common/models/AbstractAlarmSettingsModel.hpp
        include/common/models/LayoutModel.hpp
        include/common/models/LowBatteryInfoModel.hpp
        include/common/models/QuoteModel.hpp
        include/common/popups/presenter/AlarmActivatedPresenter.hpp
        include/common/popups/AlarmActivatedWindow.hpp
        include/common/popups/AlarmDeactivatedWindow.hpp

M products/BellHybrid/apps/common/include/common/layouts/BaseHomeScreenLayoutProvider.hpp => products/BellHybrid/apps/common/include/common/layouts/BaseHomeScreenLayoutProvider.hpp +1 -0
@@ 62,5 62,6 @@ namespace gui
        virtual void setTimeFormat(utils::time::Locale::TimeFormat fmt){};
        virtual void setSnoozeFormat(utils::time::Locale::TimeFormat fmt){};
        virtual void setSnoozeTime(std::time_t newTime){};
        virtual void setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor){};
    };
}; // namespace gui

M products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutClassic.hpp => products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutClassic.hpp +1 -0
@@ 67,6 67,7 @@ namespace gui
        auto setAlarmTime(std::time_t newTime) -> void override;
        auto setSnoozeTime(std::time_t newTime) -> void override;
        auto updateUsbStatus(bool isConnected) -> void override;
        auto setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor) -> void override;

        auto getSnoozeTimer() -> SnoozeTimer * override;
        auto getLayout() -> Item * override;

M products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutClassicWithQuotes.hpp => products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutClassicWithQuotes.hpp +1 -0
@@ 11,6 11,7 @@ namespace gui
    {
      public:
        explicit HomeScreenLayoutClassicWithQuotes(std::string name);
        void setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor) override;

      protected:
        void buildInterface() override;

M products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutVertical.hpp => products/BellHybrid/apps/common/include/common/layouts/HomeScreenLayoutVertical.hpp +1 -0
@@ 45,6 45,7 @@ namespace gui
        auto getAlarmTime() const -> std::time_t override;
        auto setAlarmTime(std::time_t newTime) -> void override;
        auto updateUsbStatus(bool isConnected) -> void override;
        void setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor) override;

        auto getSnoozeTimer() -> SnoozeTimer * override;
        auto getLayout() -> Item * override;

A products/BellHybrid/apps/common/include/common/models/QuoteModel.hpp => products/BellHybrid/apps/common/include/common/models/QuoteModel.hpp +36 -0
@@ 0,0 1,36 @@
// 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/AbstractSettingsModel.hpp"

#include <service-db/QuotesMessages.hpp>
#include <ApplicationCommon.hpp>

namespace app
{
    using QuoteCallback = std::function<void(const UTF8 &quote, const UTF8 &author)>;

    class AbstractQuoteModel
    {
      public:
        virtual ~AbstractQuoteModel() noexcept                      = default;
        virtual void sendQuery()                                    = 0;
        virtual bool onQuoteRetreived(db::QueryResult *queryResult) = 0;
        virtual void setCallback(QuoteCallback callback)            = 0;
    };

    class QuoteModel : public AbstractQuoteModel, public app::AsyncCallbackReceiver
    {
      public:
        explicit QuoteModel(app::ApplicationCommon *app);
        void sendQuery() override;
        bool onQuoteRetreived(db::QueryResult *queryResult) override;
        void setCallback(QuoteCallback callback) override;

      private:
        app::ApplicationCommon *app;
        QuoteCallback quoteCallback;
    };
} // namespace app

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

#include "models/QuoteModel.hpp"

namespace app
{
    QuoteModel::QuoteModel(app::ApplicationCommon *app) : app::AsyncCallbackReceiver(app), app(app)
    {
        sendQuery();
    }

    void QuoteModel::setCallback(QuoteCallback callback)
    {
        quoteCallback = callback;
    }

    void QuoteModel::sendQuery()
    {
        auto query = std::make_unique<Quotes::Messages::ReadRandomizedQuoteRequest>();
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->setCallback([this](auto response) { return onQuoteRetreived(response); });
        task->execute(app, this);
    }

    bool QuoteModel::onQuoteRetreived(db::QueryResult *queryResult)
    {
        auto response = dynamic_cast<Quotes::Messages::ReadRandomizedQuoteResponse *>(queryResult);
        if (response && quoteCallback) {
            quoteCallback(response->quote, response->author);
        }
        return true;
    }
} // namespace app

M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassic.cpp => products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassic.cpp +5 -2
@@ 361,8 361,7 @@ namespace gui
        if (isConnected && (onShowMessage != nullptr)) {
            onShowMessage();
        }
        else if (!isConnected && (onHideMessage != nullptr))
        {
        else if (!isConnected && (onHideMessage != nullptr)) {
            onHideMessage();
        }
        connectionStatus->show(isConnected);


@@ 392,4 391,8 @@ namespace gui
            }
        }
    }

    auto HomeScreenLayoutClassic::setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor) -> void
    {}

}; // namespace gui

M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithQuotes.cpp => products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithQuotes.cpp +17 -4
@@ 61,10 61,14 @@ namespace gui
        quoteImg->setMargins({0, imgTopMargin, 0, 0});
        quoteImg->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));

        // We do not display information about the battery status at any time
        // only about the status of the USB connection
        /* We do not display information about the battery status at any time
         * only about the status of the USB connection. */
        widgetBox->removeWidget(infoBox);
        widgetBox->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        infoBox->setVisible(false);
        /* Add item to body even if it won't fit to avoid manual memory
         * management for item. */
        widgetBox->addWidget(infoBox);

        textBox = new VBox(nullptr);
        textBox->setMinimumSize(style::bell_base_layout::last_layout_w, textBoxHeight);


@@ 76,7 80,7 @@ namespace gui
        quotes->setMaximumSize(style::bell_base_layout::last_layout_w, quoteHeight);
        quotes->setMargins({0, 0, 0, 0});
        quotes->setFont(quoteFont);
        quotes->setText("Wherever you are, and whatever you do, be in love.");
        quotes->setText("");
        quotes->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        quotes->setEdges(RectangleEdge::None);
        quotes->activeItem = false;


@@ 87,7 91,7 @@ namespace gui
        author->setMaximumSize(style::bell_base_layout::last_layout_w, authorHeight);
        author->setMargins({0, 0, 0, 0});
        author->setFont(authorFont);
        author->setText("-Rumi");
        author->setText("");
        author->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        author->setEdges(RectangleEdge::None);
        author->activeItem = false;


@@ 124,4 128,13 @@ namespace gui
        }
    }

    void HomeScreenLayoutClassicWithQuotes::setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor)
    {
        constexpr auto dash{'-'};
        UTF8 authorWithDash = quoteAuthor;
        authorWithDash.insert(&dash, 0);
        author->setText(authorWithDash);
        quotes->setText(quoteContent);
    }

}; // namespace gui

M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutVertical.cpp => products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutVertical.cpp +3 -0
@@ 143,6 143,9 @@ namespace gui
        alarmActivatedDeactivatedScreen->text->informContentChanged();
    }

    void HomeScreenLayoutVertical::setQuoteText(const UTF8 &quoteContent, const UTF8 &quoteAuthor)
    {}

    bool HomeScreenLayoutVertical::isBatteryVisibilityAllowed(const Store::Battery &batteryContext)
    {
        return (batteryContext.level < dischargingLevelShowTop) || isBatteryCharging(batteryContext.state);

M products/BellHybrid/services/appmgr/ApplicationManager.cpp => products/BellHybrid/services/appmgr/ApplicationManager.cpp +6 -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 <appmgr/ApplicationManager.hpp>


@@ 64,11 64,6 @@ namespace app::manager
        }
    }

    auto ApplicationManager::handleDisplayLanguageChange(app::manager::DisplayLanguageChangeRequest *msg) -> bool
    {
        return ApplicationManagerCommon::handleDisplayLanguageChange(msg);
    }

    void ApplicationManager::registerMessageHandlers()
    {
        ApplicationManagerCommon::registerMessageHandlers();


@@ 113,6 108,11 @@ namespace app::manager
            return sys::msgHandled();
        });

        connect(typeid(InformDateChanged), [this]([[maybe_unused]] sys::Message *request) {
            DBServiceAPI::InformDateChanged(this);
            return sys::msgHandled();
        });

        connect(typeid(AlarmActivated), convertibleToActionHandler);
        connect(typeid(AlarmDeactivated), convertibleToActionHandler);
        connect(typeid(BedtimeNotification), convertibleToActionHandler);

M products/BellHybrid/services/appmgr/include/appmgr/ApplicationManager.hpp => products/BellHybrid/services/appmgr/include/appmgr/ApplicationManager.hpp +1 -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

#pragma once


@@ 18,7 18,6 @@ namespace app::manager
      protected:
        void handleStart(StartAllowedMessage *msg) override;
        auto handleAction(ActionEntry &action) -> ActionProcessStatus override;
        auto handleDisplayLanguageChange(DisplayLanguageChangeRequest *msg) -> bool override;

      private:
        sys::TimerHandle idleTimer;

M products/BellHybrid/services/db/CMakeLists.txt => products/BellHybrid/services/db/CMakeLists.txt +5 -0
@@ 8,6 8,10 @@ target_sources(databases
        ServiceDB.cpp
        BellFactorySettings.cpp
        agents/MeditationStatsAgent.cpp
        agents/QuotesAgent.cpp
        agents/ShuffleQuoteModel.cpp
        agents/QuotesAgent.hpp
        agents/ShuffleQuoteModel.hpp
    PUBLIC
        include/db/ServiceDB.hpp
        include/db/SystemSettings.hpp


@@ 17,6 21,7 @@ target_sources(databases
target_include_directories(databases
    PUBLIC
        include
        agents
)

target_link_libraries(databases

M products/BellHybrid/services/db/ServiceDB.cpp => products/BellHybrid/services/db/ServiceDB.cpp +7 -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 <db/ServiceDB.hpp>


@@ 36,6 36,8 @@ db::Interface *ServiceDB::getInterface(db::Interface::Name interface)
        return alarmEventRecordInterface.get();
    case db::Interface::Name::MultimediaFiles:
        return multimediaFilesRecordInterface.get();
    case db::Interface::Name::Quotes:
        return quotesRecordInterface.get();
    default:
        LOG_INFO("Not supported interface");
    }


@@ 51,6 53,7 @@ sys::ReturnCodes ServiceDB::InitHandler()

    // Create databases
    eventsDB          = std::make_unique<EventsDB>((purefs::dir::getDatabasesPath() / "events.db").c_str());
    quotesDB          = std::make_unique<Database>((purefs::dir::getDatabasesPath() / "quotes.db").c_str());
    multimediaFilesDB = std::make_unique<db::multimedia_files::MultimediaFilesDB>(
        (purefs::dir::getDatabasesPath() / "multimedia.db").c_str());



@@ 67,7 70,7 @@ sys::ReturnCodes ServiceDB::InitHandler()
        dbAgent->registerMessages();
    }

    const auto settings = std::make_unique<settings::Settings>();
    auto settings = std::make_unique<settings::Settings>();
    settings->init(service::ServiceProxy(shared_from_this()));

    /* Save metadata for crashdump generation purpose */


@@ 79,5 82,7 @@ sys::ReturnCodes ServiceDB::InitHandler()
    Store::CrashdumpMetadata::getInstance().setOsVersion(VERSION);
    Store::CrashdumpMetadata::getInstance().setProductName("BellHybrid");

    quotesRecordInterface = std::make_unique<Quotes::QuotesAgent>(quotesDB.get(), std::move(settings));

    return sys::ReturnCodes::Success;
}

A products/BellHybrid/services/db/agents/QuotesAgent.cpp => products/BellHybrid/services/db/agents/QuotesAgent.cpp +45 -0
@@ 0,0 1,45 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesAgent.hpp"
#include "QuotesQueries.hpp"
#include <Common/Query.hpp>

namespace Quotes
{
    QuotesAgent::QuotesAgent(Database *quotesDB, std::unique_ptr<settings::Settings> settings)
        : quotesDB(quotesDB), shuffleQuoteModel(std::move(settings), quotesDB)
    {
        shuffleQuoteModel.updateList(ListUpdateMode::Normal);
    }

    auto QuotesAgent::runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (typeid(*query) == typeid(Messages::ReadRandomizedQuoteRequest)) {
            return handleReadRandomizedQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::InformLanguageChangeRequest) ||
                 (typeid(*query) == typeid(Messages::InformDateChanged))) {
            shuffleQuoteModel.updateList(ListUpdateMode::Forced);
        }
        return nullptr;
    }

    auto QuotesAgent::handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        const auto request = std::dynamic_pointer_cast<Messages::ReadRandomizedQuoteRequest>(query);
        if (request) {
            const auto id = shuffleQuoteModel.getId();
            LOG_DEBUG("Shuffle id: %d", id);
            const auto result = quotesDB->query(Queries::readQuote, id);
            if (result->getRowCount() > 0) {
                const IdQuoteAuthorRecord record(result.get());
                auto response = std::make_unique<Messages::ReadRandomizedQuoteResponse>(
                    record.quote_id, record.quote, record.author);
                response->setRequestQuery(query);
                return response;
            }
        }
        return nullptr;
    }
} // namespace Quotes

A products/BellHybrid/services/db/agents/QuotesAgent.hpp => products/BellHybrid/services/db/agents/QuotesAgent.hpp +32 -0
@@ 0,0 1,32 @@
// 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 "ShuffleQuoteModel.hpp"

#include <Interface/Record.hpp>
#include <service-db/Settings.hpp>

namespace Quotes
{
    enum class QuotesRecordField
    {
    };

    class QuotesAgent : public RecordInterface<QuoteRecord, QuotesRecordField>
    {
      public:
        explicit QuotesAgent(Database *quotesDB, std::unique_ptr<settings::Settings> settings);
        ~QuotesAgent() = default;

        auto runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;

      protected:
        auto handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;

      private:
        Database *quotesDB;
        ShuffleQuoteModel shuffleQuoteModel;
    };
} // namespace Quotes

A products/BellHybrid/services/db/agents/QuotesQueries.hpp => products/BellHybrid/services/db/agents/QuotesQueries.hpp +15 -0
@@ 0,0 1,15 @@
// 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/Types.hpp>

namespace Quotes::Queries
{
    inline constexpr auto getQuotes =
        "SELECT QT.quote_id FROM quote_table as QT, quote_languages as QL WHERE QL.lang_name=" str_
        " and QT.quote_lang=QL.lang_id GROUP BY QT.quote_id;";

    inline constexpr auto readQuote = "SELECT quote_id, quote, author FROM quote_table WHERE quote_id=" u32_ ";";
} // namespace Quotes::Queries

A products/BellHybrid/services/db/agents/ShuffleQuoteModel.cpp => products/BellHybrid/services/db/agents/ShuffleQuoteModel.cpp +119 -0
@@ 0,0 1,119 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ShuffleQuoteModel.hpp"

#include "QuotesQueries.hpp"
#include <log/log.hpp>
#include <bsp/trng/trng.hpp>
#include <service-db/agents/settings/SystemSettings.hpp>

#include <memory>
#include <ctime>

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();

    bool hasCrossedMidnight(std::time_t current, std::time_t last)
    {
        const std::tm lastTime    = *std::localtime(&last);
        const std::tm currentTime = *std::localtime(&current);
        if (currentTime.tm_mday != lastTime.tm_mday) {
            return true;
        }
        return (std::abs(current - last) > dayInSec); // If the day is the same but different month/year
    }
} // namespace

namespace Quotes
{
    ShuffleQuoteModel::ShuffleQuoteModel(std::unique_ptr<settings::Settings> settings, Database *quotesDB)
        : settings(std::move(settings)), quotesDB(quotesDB),
          serializer(std::make_unique<QuotesSettingsSerializer>()), rngEngine{std::make_unique<std::mt19937>(
                                                                        bsp::trng::getRandomValue())}
    {}

    void ShuffleQuoteModel::updateList(ListUpdateMode updateMode)
    {
        auto queryResult = quotesDB->query(Queries::getQuotes, utils::getDisplayLanguage().c_str());
        auto quotesList  = getList<IdsList, IdRecord>(zeroOffset, maxLimit, std::move(queryResult));
        populateList(std::move(quotesList), updateMode);
    }

    void ShuffleQuoteModel::populateList(std::unique_ptr<IdsList> idsList, ListUpdateMode updateMode)
    {
        IdList list;
        for (const auto &item : idsList->data) {
            list.push_back({QuoteType::Predefined, item.quote_id});
        }

        std::shuffle(std::begin(list), std::end(list), *rngEngine);
        if (updateMode == ListUpdateMode::Normal) {
            auto settingsList = settings->getValue(settings::Quotes::randomQuotesList, settings::SettingsScope::Global);
            if (not settingsList.empty()) {
                return;
            }
        }
        settings->setValue(
            settings::Quotes::randomQuotesList, serializer->serialize(list), settings::SettingsScope::Global);
        LOG_DEBUG("*** ID queue length: %zu", list.size());
    }

    auto ShuffleQuoteModel::getId() -> int
    {
        if (isQuoteExpired()) {
            shiftIdList();
        }

        auto list = serializer->deserialize(
            settings->getValue(settings::Quotes::randomQuotesList, settings::SettingsScope::Global));
        const auto [_, id] = list.front();
        return id;
    }

    void ShuffleQuoteModel::shiftIdList()
    {
        auto list = serializer->deserialize(
            settings->getValue(settings::Quotes::randomQuotesList, settings::SettingsScope::Global));

        if (!list.empty()) {
            list.pop_front();
        }

        if (list.empty()) {
            updateList(ListUpdateMode::Forced);
        }
        else {
            settings->setValue(
                settings::Quotes::randomQuotesList, serializer->serialize(list), settings::SettingsScope::Global);
        }
    }

    auto ShuffleQuoteModel::isQuoteExpired() -> bool
    {
        std::time_t lastTimestamp;
        const auto lastTimestampString =
            settings->getValue(settings::Quotes::randomQuoteIDUpdateTime, settings::SettingsScope::Global);

        try {
            lastTimestamp = stol(lastTimestampString);
        }
        catch (std::invalid_argument &e) {
            LOG_ERROR("Invalid timestamp, set to 0! Cause: %s", e.what());
            lastTimestamp = 0;
        }

        const auto currentTimestamp = std::time(nullptr);
        if (hasCrossedMidnight(currentTimestamp, lastTimestamp)) {
            settings->setValue(settings::Quotes::randomQuoteIDUpdateTime,
                               utils::to_string(currentTimestamp),
                               settings::SettingsScope::Global);
            return true;
        }
        return false;
    }
} // namespace Quotes

A products/BellHybrid/services/db/agents/ShuffleQuoteModel.hpp => products/BellHybrid/services/db/agents/ShuffleQuoteModel.hpp +38 -0
@@ 0,0 1,38 @@
// 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 <ApplicationCommon.hpp>
#include <service-db/Settings.hpp>
#include <service-db/QuotesMessages.hpp>
#include <service-db/agents/quotes/QuotesSettingsSerializer.hpp>

namespace Quotes
{
    enum class ListUpdateMode
    {
        Normal,
        Forced
    };

    class ShuffleQuoteModel
    {
      private:
        app::ApplicationCommon *app{nullptr};
        std::unique_ptr<settings::Settings> settings;
        Database *quotesDB{nullptr};
        std::unique_ptr<QuotesSettingsSerializer> serializer;
        std::unique_ptr<std::mt19937> rngEngine;

        auto populateList(std::unique_ptr<IdsList> idsList, ListUpdateMode updateMode) -> void;
        auto shiftIdList() -> void;
        auto isQuoteExpired() -> bool;

      public:
        ShuffleQuoteModel(std::unique_ptr<settings::Settings> settings, Database *quotesDB);
        auto updateList(ListUpdateMode updateMode) -> void;
        [[nodiscard]] auto getId() -> int;
    };

} // namespace Quotes

M products/BellHybrid/services/db/databases/migration/quotes/0/down.sql => products/BellHybrid/services/db/databases/migration/quotes/0/down.sql +2 -0
@@ 0,0 1,2 @@
-- Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
\ No newline at end of file

M products/BellHybrid/services/db/include/db/ServiceDB.hpp => products/BellHybrid/services/db/include/db/ServiceDB.hpp +5 -1
@@ 1,8 1,9 @@
// 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 <QuotesAgent.hpp>
#include <service-db/DBServiceName.hpp>
#include <service-db/ServiceDBCommon.hpp>
#include <service-db/ServiceDBDependencies.hpp>


@@ 11,6 12,7 @@ class AlarmEventRecordInterface;
class EventsDB;

class ThreadRecordInterface;

namespace db::multimedia_files
{
    class MultimediaFilesDB;


@@ 24,8 26,10 @@ class ServiceDB : public ServiceDBCommon

  private:
    std::unique_ptr<EventsDB> eventsDB;
    std::unique_ptr<Database> quotesDB;
    std::unique_ptr<db::multimedia_files::MultimediaFilesDB> multimediaFilesDB;

    std::unique_ptr<Quotes::QuotesAgent> quotesRecordInterface;
    std::unique_ptr<AlarmEventRecordInterface> alarmEventRecordInterface;
    std::unique_ptr<db::multimedia_files::MultimediaFilesRecordInterface> multimediaFilesRecordInterface;


M products/PurePhone/services/appmgr/ApplicationManager.cpp => products/PurePhone/services/appmgr/ApplicationManager.cpp +1 -16
@@ 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 <appmgr/ApplicationManager.hpp>


@@ 668,19 668,4 @@ namespace app::manager
            break;
        }
    }

    auto ApplicationManager::handleDisplayLanguageChange(app::manager::DisplayLanguageChangeRequest *msg) -> bool
    {
        const auto &requestedLanguage = msg->getLanguage();

        if (not utils::setDisplayLanguage(requestedLanguage)) {
            LOG_WARN("The selected language is already set. Ignore.");
            return false;
        }
        settings->setValue(
            settings::SystemProperties::displayLanguage, requestedLanguage, settings::SettingsScope::Global);
        rebuildActiveApplications();
        DBServiceAPI::InformLanguageChanged(this);
        return true;
    }
} // namespace app::manager

M products/PurePhone/services/appmgr/include/appmgr/ApplicationManager.hpp => products/PurePhone/services/appmgr/include/appmgr/ApplicationManager.hpp +1 -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

#pragma once


@@ 53,7 53,6 @@ namespace app::manager
        auto handleAction(ActionEntry &action) -> ActionProcessStatus override;
        void handleStart(StartAllowedMessage *msg) override;
        void runAppsInBackground();
        auto handleDisplayLanguageChange(DisplayLanguageChangeRequest *msg) -> bool override;

        std::shared_ptr<sys::phone_modes::Observer> phoneModeObserver;
        sys::bluetooth::BluetoothMode bluetoothMode = sys::bluetooth::BluetoothMode::Disabled;