~aleteoryx/muditaos

cd54ba058903b2c518f246cfdbc46ee33f41c1c3 — Maciej-Mudita 2 years ago 4f7ec2e
[MOS-202] Add meditation parameters to non-volatile memory

The meditation parameters set by the user will be remembered.

style check fix
24 files changed, 371 insertions(+), 98 deletions(-)

M module-apps/application-meditation/ApplicationMeditation.cpp
M module-apps/application-meditation/CMakeLists.txt
A module-apps/application-meditation/data/Constants.hpp
A module-apps/application-meditation/data/MeditationParams.hpp
A module-apps/application-meditation/data/OptionsData.cpp
A module-apps/application-meditation/data/OptionsData.hpp
M module-apps/application-meditation/include/application-meditation/ApplicationMeditation.hpp
D module-apps/application-meditation/include/application-meditation/OptionsData.hpp
M module-apps/application-meditation/widgets/IntervalBox.cpp
M module-apps/application-meditation/widgets/IntervalBox.hpp
M module-apps/application-meditation/widgets/TimerProperty.cpp
M module-apps/application-meditation/widgets/TimerProperty.hpp
M module-apps/application-meditation/windows/MeditationOptionsWindows.cpp
M module-apps/application-meditation/windows/MeditationOptionsWindows.hpp
M module-apps/application-meditation/windows/MeditationWindow.cpp
M module-apps/application-meditation/windows/MeditationWindow.hpp
M module-apps/apps-common/widgets/TextSpinnerBox.cpp
M module-apps/apps-common/widgets/TextSpinnerBox.hpp
M module-apps/apps-common/widgets/TextSpinnerBoxWithLabel.cpp
M module-apps/apps-common/widgets/TextSpinnerBoxWithLabel.hpp
M module-services/service-db/agents/settings/SystemSettings.hpp
M products/PurePhone/services/db/databases/migration/settings_v2/0/devel.sql
M products/PurePhone/services/db/databases/migration/settings_v2/0/up.sql
M pure_changelog.md
M module-apps/application-meditation/ApplicationMeditation.cpp => module-apps/application-meditation/ApplicationMeditation.cpp +57 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ApplicationMeditation.hpp"


@@ 6,6 6,11 @@
#include "MeditationTimerWindow.hpp"
#include "MeditationWindow.hpp"
#include "Names.hpp"
#include "MeditationParams.hpp"
#include "Constants.hpp"

#include <service-db/Settings.hpp>
#include <service-db/agents/settings/SystemSettings.hpp>

namespace app
{


@@ 13,7 18,7 @@ namespace app
                                                 std::string parent,
                                                 StatusIndicators statusIndicators,
                                                 StartInBackground startInBackground)
        : Application{name, parent, statusIndicators, startInBackground}, state{std::make_unique<gui::OptionsData>()}
        : Application{name, parent, statusIndicators, startInBackground}
    {}

    auto ApplicationMeditation::InitHandler() -> sys::ReturnCodes


@@ 22,6 27,26 @@ namespace app
        if (ret != sys::ReturnCodes::Success)
            return ret;

        auto counterVisible =
            getLocalSettingsValue(settings::Meditation::showCounter, Constants::Params::defaultCounterVisible);
        auto counterVisibleOnChangeCallback = [this](bool newValue) {
            settings->setValue(
                settings::Meditation::showCounter, utils::to_string(newValue), settings::SettingsScope::AppLocal);
        };

        auto preparationTime                 = std::chrono::seconds(getLocalSettingsValue(
            settings::Meditation::preparationTime, Constants::Params::defaultPreparationTime.count()));
        auto preparationTimeOnChangeCallback = [this](std::chrono::seconds newValue) {
            settings->setValue(settings::Meditation::preparationTime,
                               utils::to_string(newValue.count()),
                               settings::SettingsScope::AppLocal);
        };
        gui::OptionsData::OptionParams params = {
            .preparationTime{std::move(preparationTime), std::move(preparationTimeOnChangeCallback)},
            .showCounter{std::move(counterVisible), std::move(counterVisibleOnChangeCallback)}};

        state = std::make_unique<gui::OptionsData>(std::move(params));

        createUserInterface();

        return ret;


@@ 46,10 71,29 @@ namespace app

    void ApplicationMeditation::createUserInterface()
    {
        windowsFactory.attach(app::window::name::meditation_main_window,
                              [](ApplicationCommon *app, const std::string &name) {
                                  return std::make_unique<gui::MeditationWindow>(app);
                              });
        windowsFactory.attach(
            app::window::name::meditation_main_window, [&](ApplicationCommon *app, const std::string &name) {
                auto durationInitValue =
                    getLocalSettingsValue(settings::Meditation::duration, Constants::Params::defaultMeditationDuration);
                auto durationOnChangeCallback = [this](std::int32_t newValue) {
                    settings->setValue(
                        settings::Meditation::duration, utils::to_string(newValue), settings::SettingsScope::AppLocal);
                };
                auto intervalChimeInitValue        = std::chrono::minutes(getLocalSettingsValue(
                    settings::Meditation::intervalChime, Constants::Params::defaultChimeInterval.count()));
                auto intervalChimeOnChangeCallback = [this](std::chrono::minutes newValue) {
                    settings->setValue(settings::Meditation::intervalChime,
                                       utils::to_string(newValue.count()),
                                       settings::SettingsScope::AppLocal);
                };
                MeditationParams params = {
                    .meditationDuration{.initValue        = std::move(durationInitValue),
                                        .onChangeCallback = std::move(durationOnChangeCallback)},
                    .intervalChime{.initValue        = std::move(intervalChimeInitValue),
                                   .onChangeCallback = std::move(intervalChimeOnChangeCallback)}};

                return std::make_unique<gui::MeditationWindow>(app, std::move(params));
            });
        windowsFactory.attach(app::window::name::meditation_timer, [](ApplicationCommon *app, const std::string &name) {
            return std::make_unique<gui::MeditationTimerWindow>(app);
        });


@@ 73,4 117,11 @@ namespace app

    void ApplicationMeditation::destroyUserInterface()
    {}

    template <typename T>
    T ApplicationMeditation::getLocalSettingsValue(const std::string &variableName, T defaultValue)
    {
        const std::string value = settings->getValue(variableName, settings::SettingsScope::AppLocal);
        return value.empty() ? defaultValue : utils::getNumericValue<T>(value);
    }
} // namespace app

M module-apps/application-meditation/CMakeLists.txt => module-apps/application-meditation/CMakeLists.txt +2 -1
@@ 20,6 20,8 @@ target_sources(application-meditation
        ApplicationMeditation.cpp
        data/MeditationTimerData.hpp
        data/Style.hpp
        data/OptionsData.hpp
        data/OptionsData.cpp
        widgets/IntervalBox.cpp
        widgets/IntervalBox.hpp
        widgets/MeditationTimer.cpp


@@ 35,7 37,6 @@ target_sources(application-meditation
        windows/Names.hpp
    PUBLIC
        include/application-meditation/ApplicationMeditation.hpp
        include/application-meditation/OptionsData.hpp
)

target_link_libraries(application-meditation

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

#pragma once

#include <cstdint>
#include <chrono>
#include <array>

namespace Constants
{
    namespace Params
    {
        using minutes = std::chrono::minutes;
        constexpr auto defaultChimeInterval{minutes{2}};
        constexpr std::array<minutes, 6> chimeIntervals{
            minutes{2}, minutes{5}, minutes{10}, minutes{15}, minutes{30}, minutes{0}};

        constexpr int defaultMeditationDuration                = 15;
        constexpr std::array<const int, 4> meditationDurations = {15, 30, 60, 90};

        using namespace std::chrono_literals;
        constexpr auto defaultPreparationTime{5s};
        constexpr auto defaultCounterVisible{true};
        constexpr std::array<std::chrono::seconds, 7> preparationTimes{5s, 10s, 30s, 1min, 2min, 5min, 10min};
    } // namespace Params
} // namespace Constants

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

#pragma once

#include <cstdint>
#include <functional>

template <typename T>
struct SettingsParam
{
    T initValue;
    std::function<void(T newValue)> onChangeCallback{nullptr};
};

struct MeditationParams
{
    SettingsParam<std::int32_t> meditationDuration;
    SettingsParam<std::chrono::minutes> intervalChime;
};

A module-apps/application-meditation/data/OptionsData.cpp => module-apps/application-meditation/data/OptionsData.cpp +39 -0
@@ 0,0 1,39 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "OptionsData.hpp"
#include "Constants.hpp"

namespace gui
{
    OptionsData::OptionsData(OptionParams params) : params(params)
    {}

    [[nodiscard]] std::chrono::seconds OptionsData::getPreparationTime() const noexcept
    {
        return params.preparationTime.get();
    }

    bool OptionsData::setPreparationTime(std::chrono::seconds value)
    {
        bool result   = true;
        const auto it = std::find(
            std::begin(Constants::Params::preparationTimes), std::end(Constants::Params::preparationTimes), value);
        if (it == std::end(Constants::Params::preparationTimes)) {
            value  = Constants::Params::defaultPreparationTime;
            result = false;
        }
        params.preparationTime.set(value);
        return result;
    }

    [[nodiscard]] bool OptionsData::isCounterVisible() const noexcept
    {
        return params.showCounter.get();
    }

    void OptionsData::setCounterVisible(bool value)
    {
        params.showCounter.set(value);
    }
} // namespace gui

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

#pragma once

#include <gui/SwitchData.hpp>
#include <functional>
#include <chrono>

namespace gui
{
    template <class T>
    class OptionParam
    {
      public:
        OptionParam(T value, std::function<void(T newValue)> callback) : value(value), onChangeCallback(callback)
        {}
        [[nodiscard]] T get() const noexcept
        {
            return value;
        }
        void set(T newValue)
        {
            value = newValue;
            if (onChangeCallback != nullptr) {
                onChangeCallback(newValue);
            }
        }

      private:
        T value;
        std::function<void(T newValue)> onChangeCallback{nullptr};
    };

    class OptionsData : public gui::SwitchData
    {
      public:
        struct OptionParams
        {
            OptionParam<std::chrono::seconds> preparationTime;
            OptionParam<bool> showCounter;
        };

        OptionsData(OptionParams params);

        [[nodiscard]] std::chrono::seconds getPreparationTime() const noexcept;
        bool setPreparationTime(std::chrono::seconds value);

        [[nodiscard]] bool isCounterVisible() const noexcept;
        void setCounterVisible(bool value);

      private:
        OptionParams params;
    };
} // namespace gui

M module-apps/application-meditation/include/application-meditation/ApplicationMeditation.hpp => module-apps/application-meditation/include/application-meditation/ApplicationMeditation.hpp +6 -2
@@ 1,9 1,9 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <application-meditation/OptionsData.hpp>
#include <application-meditation/data/OptionsData.hpp>

#include <Application.hpp>



@@ 28,6 28,10 @@ namespace app
        void createUserInterface() override;
        void destroyUserInterface() override;
        std::unique_ptr<gui::OptionsData> state;

      private:
        template <typename T>
        T getLocalSettingsValue(const std::string &variableName, T defaultValue);
    };

    template <>

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

#pragma once

#include <gui/SwitchData.hpp>

#include <chrono>

namespace gui
{
    class OptionsData : public gui::SwitchData
    {
      public:
        OptionsData() = default;
        OptionsData(std::chrono::seconds preparation, bool showCounter)
            : preparationTime(preparation), showCounter(showCounter)
        {}

        std::chrono::seconds preparationTime{5};
        bool showCounter = true;
    };
} // namespace gui

M module-apps/application-meditation/widgets/IntervalBox.cpp => module-apps/application-meditation/widgets/IntervalBox.cpp +30 -10
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "IntervalBox.hpp"


@@ 7,18 7,11 @@

#include <gui/input/InputEvent.hpp>
#include <i18n/i18n.hpp>

#include <Constants.hpp>
#include <cassert>

using namespace gui;

namespace
{
    using minutes = std::chrono::minutes;
    const std::vector<minutes> chimeIntervals{
        minutes{2}, minutes{5}, minutes{10}, minutes{15}, minutes{30}, minutes{0}};
} // namespace

IntervalBox::IntervalBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h, TimerProperty *timerSetter)
    : HBox(parent, x, y, w, h), timerSetter(timerSetter)
{


@@ 49,7 42,7 @@ void IntervalBox::updateIntervals()
    auto setTime = timerSetter->getTime();

    std::vector<UTF8> chimeOptions;
    for (auto chime : chimeIntervals) {
    for (auto chime : Constants::Params::chimeIntervals) {
        if (chime < setTime) {
            chimeOptions.push_back(intervalToString(chime));
        }


@@ 73,6 66,33 @@ std::chrono::seconds IntervalBox::getIntervalValue() noexcept
    }
}

bool IntervalBox::setIntervalValue(std::chrono::minutes newValue)
{
    bool result = true;
    const auto it =
        std::find(std::begin(Constants::Params::chimeIntervals), std::end(Constants::Params::chimeIntervals), newValue);
    if (it == std::end(Constants::Params::chimeIntervals)) {
        newValue = Constants::Params::defaultChimeInterval;
        result   = false;
    }

    if (const auto setTime = timerSetter->getTime(); newValue < setTime) {
        chimeSpinner->setCurrentValue(intervalToString(newValue));
    }
    return result;
}

void IntervalBox::setOnChangeCallback(OnChangeCallback &callback)
{
    if (chimeSpinner != nullptr) {
        chimeSpinner->setOnValueChangeCallback([&](const UTF8 &newValue) {
            if (callback != nullptr) {
                callback(stringToInterval(newValue));
            }
        });
    }
}

std::string IntervalBox::intervalToString(std::chrono::minutes time)
{
    if (time.count() == 0) {

M module-apps/application-meditation/widgets/IntervalBox.hpp => module-apps/application-meditation/widgets/IntervalBox.hpp +5 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 28,8 28,12 @@ namespace gui
        void rescaleIntervals();

      public:
        using OnChangeCallback = std::function<void(std::chrono::minutes newValue)>;

        IntervalBox(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h, TimerProperty *timerSetter);

        [[nodiscard]] std::chrono::seconds getIntervalValue() noexcept;
        bool setIntervalValue(std::chrono::minutes newValue);
        void setOnChangeCallback(OnChangeCallback &callback);
    };
} // namespace gui

M module-apps/application-meditation/widgets/TimerProperty.cpp => module-apps/application-meditation/widgets/TimerProperty.cpp +39 -7
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "Style.hpp"


@@ 7,10 7,12 @@
#include <InputEvent.hpp>
#include <i18n/i18n.hpp>
#include <Utils.hpp>
#include <Constants.hpp>

using namespace gui;

TimerProperty::TimerProperty(Item *parent, const uint32_t x, const uint32_t y, const uint32_t w, const uint32_t h)
TimerProperty::TimerProperty(
    Item *parent, const std::uint32_t x, const std::uint32_t y, const std::uint32_t w, const std::uint32_t h)
    : Rect(parent, x, y, w, h)
{
    build();


@@ 110,6 112,9 @@ void TimerProperty::setMeditationTime()
    else {
        timeUnitLabel->setText(utils::translate("app_meditation_minutes"));
    }
    if (onChangeCallback != nullptr) {
        onChangeCallback(meditationTime);
    }
}

std::chrono::minutes TimerProperty::getTime() noexcept


@@ 118,6 123,29 @@ std::chrono::minutes TimerProperty::getTime() noexcept
    return state.getTime();
}

bool TimerProperty::setTime(std::int32_t newValue)
{
    bool result = state.setTime(newValue);
    setMeditationTime();
    return result;
}
void TimerProperty::setOnChangeCallback(OnChangeCallback callback)
{
    onChangeCallback = callback;
}

bool TimerProperty::State::setTime(int value)
{
    const auto it = std::find(
        std::begin(Constants::Params::meditationDurations), std::end(Constants::Params::meditationDurations), value);
    if (it != std::end(Constants::Params::meditationDurations)) {
        timeInMinutes = *it;
        return true;
    }
    timeInMinutes = Constants::Params::defaultMeditationDuration;
    return false;
}

void TimerProperty::State::checkBounds() noexcept
{
    timeInMinutes       = std::clamp(timeInMinutes, minimalValue, maximalValue);


@@ 152,8 180,10 @@ void TimerProperty::State::delNumericValue() noexcept

void TimerProperty::State::increment() noexcept
{
    auto it = std::upper_bound(std::begin(timeArr), std::end(timeArr), timeInMinutes);
    if (it == std::end(timeArr)) {
    auto it = std::upper_bound(std::begin(Constants::Params::meditationDurations),
                               std::end(Constants::Params::meditationDurations),
                               timeInMinutes);
    if (it == std::end(Constants::Params::meditationDurations)) {
        it--;
    }
    timeInMinutes       = *it;


@@ 162,9 192,11 @@ void TimerProperty::State::increment() noexcept

void TimerProperty::State::decrement() noexcept
{
    auto it =
        std::upper_bound(std::rbegin(timeArr), std::rend(timeArr), timeInMinutes, [](int a, int b) { return a > b; });
    if (it == std::rend(timeArr)) {
    auto it = std::upper_bound(std::rbegin(Constants::Params::meditationDurations),
                               std::rend(Constants::Params::meditationDurations),
                               timeInMinutes,
                               [](int a, int b) { return a > b; });
    if (it == std::rend(Constants::Params::meditationDurations)) {
        it--;
    }
    timeInMinutes       = *it;

M module-apps/application-meditation/widgets/TimerProperty.hpp => module-apps/application-meditation/widgets/TimerProperty.hpp +17 -11
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 19,17 19,16 @@ namespace gui
            static constexpr int counterMaxDigits             = 2;
            static constexpr int minimalValue                 = 1;
            static constexpr int maximalValue                 = 99;
            static constexpr int defaultMeditationTime        = 15;
            static constexpr std::array<const int, 4> timeArr = {15, 30, 60, 90};

            bool resetValueOnNumeric = true;
            int timeInMinutes        = defaultMeditationTime;
            int timeInMinutes;

          public:
            [[nodiscard]] std::chrono::minutes getTime() const noexcept
            {
                return std::chrono::minutes{timeInMinutes};
            }
            bool setTime(int value);
            void checkBounds() noexcept;
            void putNumericValue(int digit) noexcept;
            void delNumericValue() noexcept;


@@ 42,21 41,28 @@ namespace gui
            }
        } state;

      public:
        using OnChangeCallback = std::function<void(std::int32_t newValue)>;

        TimerProperty(
            Item *parent, const std::uint32_t x, const std::uint32_t y, const std::uint32_t w, const std::uint32_t h);

        bool onFocus(bool isFocused) final;
        bool onInput(const InputEvent &inputEvent) final;
        [[nodiscard]] std::chrono::minutes getTime() noexcept;
        bool setTime(std::int32_t newValue);
        void setOnChangeCallback(OnChangeCallback callback);

      private:
        Circle *circle       = nullptr;
        VBox *centerBody     = nullptr;
        Label *timeLabel     = nullptr;
        Rect *divRect        = nullptr;
        Label *timeUnitLabel = nullptr;
        OnChangeCallback onChangeCallback;

        void build();
        void setMeditationTime();

      public:
        TimerProperty(Item *parent, const uint32_t x, const uint32_t y, const uint32_t w, const uint32_t h);

        bool onFocus(bool isFocused) final;
        bool onInput(const InputEvent &inputEvent) final;
        [[nodiscard]] std::chrono::minutes getTime() noexcept;
    };

} // namespace gui

M module-apps/application-meditation/windows/MeditationOptionsWindows.cpp => module-apps/application-meditation/windows/MeditationOptionsWindows.cpp +17 -18
@@ 1,10 1,11 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ApplicationMeditation.hpp"
#include "MeditationOptionsWindows.hpp"
#include "Names.hpp"

#include <Constants.hpp>
#include <OptionSetting.hpp>
#include <i18n/i18n.hpp>



@@ 12,9 13,6 @@ using namespace gui;

namespace
{
    using namespace std::chrono_literals;
    constexpr std::array<std::chrono::seconds, 7> preparationTimes{5s, 10s, 30s, 1min, 2min, 5min, 10min};

    std::string toString(std::chrono::seconds duration)
    {
        if (duration.count() >= 60) {


@@ 45,7 43,7 @@ void MeditationOptionsWindow::addCounterOption(std::list<Option> &options)
    options.emplace_back(std::make_unique<option::OptionSettings>(
        utils::translate("app_meditation_option_show_counter"),
        [=](Item &item) {
            app->state->showCounter = !app->state->showCounter;
            app->state->setCounterVisible(!app->state->isCounterVisible());
            refreshOptions(buildOptionsList());
            return true;
        },


@@ 56,7 54,7 @@ void MeditationOptionsWindow::addCounterOption(std::list<Option> &options)
            return true;
        },
        nullptr,
        app->state->showCounter ? option::SettingRightItem::On : option::SettingRightItem::Off));
        app->state->isCounterVisible() ? option::SettingRightItem::On : option::SettingRightItem::Off));
}

void MeditationOptionsWindow::addPreparationTimeOption(std::list<Option> &options)


@@ 90,7 88,7 @@ std::list<Option> PreparationTimeWindow::buildOptionsList()
{
    auto app = static_cast<app::ApplicationMeditation *>(application);
    std::list<Option> options;
    for (auto &&duration : preparationTimes) {
    for (auto &&duration : Constants::Params::preparationTimes) {
        addPreparationTimeOption(duration, app, options);
    }
    return options;


@@ 103,14 101,16 @@ void PreparationTimeWindow::addPreparationTimeOption(std::chrono::seconds durati
    options.emplace_back(std::make_unique<gui::option::OptionSettings>(
        toString(duration),
        [=](const gui::Item &item) {
            app->state->preparationTime = duration;
            if (!app->state->setPreparationTime(duration)) {
                LOG_ERROR("Incorrect preparation time value! The default value is set.");
            }
            refreshOptions(buildOptionsList());
            return true;
        },
        nullptr,
        this,
        app->state->preparationTime == duration ? gui::option::SettingRightItem::Checked
                                                : gui::option::SettingRightItem::Disabled));
        app->state->getPreparationTime() == duration ? gui::option::SettingRightItem::Checked
                                                     : gui::option::SettingRightItem::Disabled));
}

void PreparationTimeWindow::onBeforeShow([[maybe_unused]] ShowMode mode, [[maybe_unused]] SwitchData *data)


@@ 119,14 119,13 @@ void PreparationTimeWindow::onBeforeShow([[maybe_unused]] ShowMode mode, [[maybe
    refreshOptions(buildOptionsList(), selectedItemIndex);
}

unsigned int PreparationTimeWindow::getSelectedItemIndex() const
std::size_t PreparationTimeWindow::getSelectedItemIndex() const
{
    auto app                = static_cast<app::ApplicationMeditation *>(application);
    const auto selectedTime = app->state->preparationTime;
    for (unsigned int i = 0; i < preparationTimes.size(); ++i) {
        if (selectedTime == preparationTimes[i]) {
            return i;
        }
    }
    return 0;
    const auto selectedTime = app->state->getPreparationTime();
    const auto it           = std::find(
        std::begin(Constants::Params::preparationTimes), std::end(Constants::Params::preparationTimes), selectedTime);
    return it == std::end(Constants::Params::preparationTimes)
               ? 0
               : std::distance(std::begin(Constants::Params::preparationTimes), it);
}

M module-apps/application-meditation/windows/MeditationOptionsWindows.hpp => module-apps/application-meditation/windows/MeditationOptionsWindows.hpp +3 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 31,8 31,8 @@ namespace gui
                                      app::ApplicationMeditation *app,
                                      std::list<Option> &options);

        [[nodiscard]] unsigned int getSelectedItemIndex() const;
        [[nodiscard]] std::size_t getSelectedItemIndex() const;

        unsigned int selectedItemIndex{0};
        std::size_t selectedItemIndex{0};
    };
} // namespace gui

M module-apps/application-meditation/windows/MeditationWindow.cpp => module-apps/application-meditation/windows/MeditationWindow.cpp +13 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ApplicationMeditation.hpp"


@@ 16,7 16,8 @@

namespace gui
{
    MeditationWindow::MeditationWindow(app::ApplicationCommon *app) : AppWindow{app, name::window::main_window}
    MeditationWindow::MeditationWindow(app::ApplicationCommon *app, const MeditationParams params)
        : AppWindow{app, name::window::main_window}, meditationParams(params)
    {
        MeditationWindow::buildInterface();
    }


@@ 44,6 45,10 @@ namespace gui
                                       style::meditation::timer::Y,
                                       2 * style::meditation::timer::Radius,
                                       2 * style::meditation::timer::Radius);
        if (!timeSetter->setTime(meditationParams.meditationDuration.initValue)) {
            LOG_ERROR("Incorrect meditation duration value! The default value is set.");
        }
        timeSetter->setOnChangeCallback(meditationParams.meditationDuration.onChangeCallback);
        timeSetter->setEdges(RectangleEdge::None);
        setFocusItem(timeSetter);



@@ 53,6 58,10 @@ namespace gui
                                      style::meditation::intervalBox::Width,
                                      style::text_spinner_label::h,
                                      timeSetter);
        if (!intervalBox->setIntervalValue(meditationParams.intervalChime.initValue)) {
            LOG_ERROR("Incorrect chime interval value! The default value is set.");
        }
        intervalBox->setOnChangeCallback(meditationParams.intervalChime.onChangeCallback);
        intervalBox->setEdges(RectangleEdge::None);

        intervalBox->setNavigationItem(NavigationDirection::UP, timeSetter);


@@ 78,10 87,10 @@ namespace gui
                auto app = dynamic_cast<app::ApplicationMeditation *>(application);
                assert(app);

                auto timerSwitchData = std::make_unique<MeditationTimerData>(app->state->preparationTime,
                auto timerSwitchData = std::make_unique<MeditationTimerData>(app->state->getPreparationTime(),
                                                                             timeSetter->getTime(),
                                                                             intervalBox->getIntervalValue(),
                                                                             app->state->showCounter);
                                                                             app->state->isCounterVisible());
                application->switchWindow(app::window::name::meditation_timer, std::move(timerSwitchData));
                return true;
            }

M module-apps/application-meditation/windows/MeditationWindow.hpp => module-apps/application-meditation/windows/MeditationWindow.hpp +4 -2
@@ 1,9 1,10 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <AppWindow.hpp>
#include "MeditationParams.hpp"

namespace gui
{


@@ 13,7 14,7 @@ namespace gui
    class MeditationWindow : public AppWindow
    {
      public:
        explicit MeditationWindow(app::ApplicationCommon *app);
        MeditationWindow(app::ApplicationCommon *app, const MeditationParams params);

        auto onInput(const InputEvent &inputEvent) -> bool override;



@@ 25,5 26,6 @@ namespace gui
        void invalidate() noexcept;
        TimerProperty *timeSetter = nullptr;
        IntervalBox *intervalBox  = nullptr;
        MeditationParams meditationParams;
    };
} // namespace gui

M module-apps/apps-common/widgets/TextSpinnerBox.cpp => module-apps/apps-common/widgets/TextSpinnerBox.cpp +8 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TextSpinnerBox.hpp"


@@ 70,4 70,11 @@ namespace gui
    {
        spinner->set_value(val);
    }

    void TextSpinnerBox::setOnValueChangeCallback(OnValueChanged callback)
    {
        if (spinner != nullptr) {
            spinner->onValueChanged = callback;
        }
    }
} // namespace gui

M module-apps/apps-common/widgets/TextSpinnerBox.hpp => module-apps/apps-common/widgets/TextSpinnerBox.hpp +4 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 11,6 11,8 @@

namespace gui
{
    using OnValueChanged = std::function<void(const UTF8 &)>;

    class TextSpinnerBox : public HBox
    {
      public:


@@ 18,6 20,7 @@ namespace gui
        void setData(const std::vector<UTF8> &data);
        [[nodiscard]] UTF8 getCurrentValue() const noexcept;
        void setCurrentValue(UTF8 val);
        void setOnValueChangeCallback(OnValueChanged callback);

      private:
        StringSpinner *spinner = nullptr;

M module-apps/apps-common/widgets/TextSpinnerBoxWithLabel.cpp => module-apps/apps-common/widgets/TextSpinnerBoxWithLabel.cpp +8 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TextSpinnerBoxWithLabel.hpp"


@@ 43,4 43,11 @@ namespace gui
    {
        optionSpinner->setCurrentValue(val);
    }

    void TextSpinnerBoxWithLabel::setOnValueChangeCallback(OnValueChanged callback)
    {
        if (optionSpinner != nullptr) {
            optionSpinner->setOnValueChangeCallback(callback);
        }
    }
} // namespace gui

M module-apps/apps-common/widgets/TextSpinnerBoxWithLabel.hpp => module-apps/apps-common/widgets/TextSpinnerBoxWithLabel.hpp +2 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 27,6 27,7 @@ namespace gui
        void setData(const std::vector<UTF8> &data);
        [[nodiscard]] UTF8 getCurrentValue() const noexcept;
        void setCurrentValue(UTF8 val);
        void setOnValueChangeCallback(OnValueChanged callback);

      protected:
        gui::TextSpinnerBox *optionSpinner = nullptr;

M module-services/service-db/agents/settings/SystemSettings.hpp => module-services/service-db/agents/settings/SystemSettings.hpp +12 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 45,9 45,9 @@ namespace settings

    namespace Cellular
    {
        constexpr inline auto apn_list    = "cl_apn_list";
        constexpr inline auto offlineMode = "cl_offline_mode";
        constexpr inline auto currentUID  = "cl_current_uid";
        constexpr inline auto apn_list     = "cl_apn_list";
        constexpr inline auto offlineMode  = "cl_offline_mode";
        constexpr inline auto currentUID   = "cl_current_uid";
        constexpr inline auto volteEnabled = "cl_volte_enabled";
    } // namespace Cellular



@@ 84,4 84,12 @@ namespace settings
        constexpr inline auto invertedMode              = "display_inverted_mode";
        constexpr inline auto lockScreenDeepRefreshRate = "display_lock_screen_deep_refresh_rate";
    } // namespace Display

    namespace Meditation
    {
        constexpr inline auto duration        = "meditation_duration";
        constexpr inline auto intervalChime   = "meditation_interval_chime";
        constexpr inline auto preparationTime = "meditation_preparation_time";
        constexpr inline auto showCounter     = "meditation_show_counter";
    } // namespace Meditation
};    // namespace settings

M products/PurePhone/services/db/databases/migration/settings_v2/0/devel.sql => products/PurePhone/services/db/databases/migration/settings_v2/0/devel.sql +1 -1
@@ 1,4 1,4 @@
-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-- Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

-- ----------- insert default values ----------------------

M products/PurePhone/services/db/databases/migration/settings_v2/0/up.sql => products/PurePhone/services/db/databases/migration/settings_v2/0/up.sql +1 -1
@@ 1,4 1,4 @@
-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
-- Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

--

M pure_changelog.md => pure_changelog.md +1 -0
@@ 8,6 8,7 @@
* Added days of the week to the list of SMS, calls and notes
* Added date formatting of received/sent SMS
* Added missing translations for EULA in French and Swedish
* Added meditation parameters to non-volatile memory

### Changed / Improved