~aleteoryx/muditaos

dcfe367b4e6c95d7506e93d8f9ae2014880a4231 — Mateusz Piesta 3 years ago ded7e1a
[BH-1543] I want to set a date

*DateSetSpinner and DateSetListItem added (based od TimeSetSpinner and
TimeSetListItem)
*BellSideListItem ctor without title added
*TimeUnitsModel changed to more accurate DateTimeUnitsModel name
41 files changed, 542 insertions(+), 177 deletions(-)

M module-apps/apps-common/CMakeLists.txt
M module-apps/apps-common/widgets/BellSideListItem.cpp
M module-apps/apps-common/widgets/BellSideListItem.hpp
A module-apps/apps-common/widgets/BellSideListItemStyle.hpp
A module-apps/apps-common/widgets/DateSetSpinner.cpp
A module-apps/apps-common/widgets/DateSetSpinner.hpp
M module-apps/apps-common/widgets/DateWidget.cpp
M module-apps/apps-common/widgets/TimeSetFmtSpinner.cpp
M module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp
M module-apps/apps-common/widgets/TimeSetSpinner.cpp
M module-apps/apps-common/widgets/TimeSetSpinner.hpp
M module-apps/apps-common/widgets/spinners/Spinners.hpp
M module-apps/apps-common/widgets/spinners/StringOutputSpinner.hpp
M module-gui/gui/widgets/CMakeLists.txt
D module-gui/gui/widgets/ListItemWithDescription.cpp
M products/BellHybrid/apps/application-bell-background-sounds/windows/BGSoundsVolumeWindow.cpp
M products/BellHybrid/apps/application-bell-background-sounds/windows/BGSoundsVolumeWindow.hpp
M products/BellHybrid/apps/application-bell-meditation-timer/presenter/SettingsPresenter.cpp
M products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.cpp
M products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationTimerWindow.cpp
M products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationTimerWindow.hpp
M products/BellHybrid/apps/application-bell-onboarding/ApplicationBellOnBoarding.cpp
M products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.cpp
M products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.hpp
M products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp
M products/BellHybrid/apps/application-bell-settings/CMakeLists.txt
R products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/{TimeUnitsModel => DateTimeUnitsModel}.hpp
M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/presenter/TimeUnitsPresenter.hpp
M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/windows/BellSettingsTimeUnitsWindow.hpp
R products/BellHybrid/apps/application-bell-settings/models/{TimeUnitsModel => DateTimeUnitsModel}.cpp
M products/BellHybrid/apps/application-bell-settings/models/alarm_settings/SnoozeListItemProvider.cpp
M products/BellHybrid/apps/application-bell-settings/presenter/TimeUnitsPresenter.cpp
A products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.cpp
R {module-gui/gui/widgets/ListItemWithDescription => products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem}.hpp
M products/BellHybrid/apps/application-bell-settings/widgets/TemperatureUnitListItem.cpp
M products/BellHybrid/apps/application-bell-settings/widgets/TimeFormatSetListItem.cpp
M products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.cpp
M products/BellHybrid/apps/application-bell-settings/windows/BellSettingsWindow.cpp
M products/BellHybrid/apps/common/include/common/widgets/list_items/Numeric.hpp
M products/BellHybrid/apps/common/include/common/widgets/list_items/details.hpp
M products/BellHybrid/apps/common/src/BellSideListItemWithCallbacks.cpp
M module-apps/apps-common/CMakeLists.txt => module-apps/apps-common/CMakeLists.txt +1 -0
@@ 46,6 46,7 @@ target_sources(apps-common
        widgets/BellBaseLayout.cpp
        widgets/BellSideListItem.cpp
        widgets/ClockDateWidget.cpp
        widgets/DateSetSpinner.cpp
        widgets/DateWidget.cpp
        widgets/InputBox.cpp
        widgets/ModesBox.cpp

M module-apps/apps-common/widgets/BellSideListItem.cpp => module-apps/apps-common/widgets/BellSideListItem.cpp +27 -23
@@ 1,46 1,50 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TextFixedSize.hpp"
#include "ListItemWithDescription.hpp"
#include "BellSideListItem.hpp"
#include "Style.hpp"

namespace gui
{
    BellSideListItem::BellSideListItem(const std::string &description) : ListItemWithDescription(description)

    BellSideListItem::BellSideListItem()
    {
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);
        setEdges(RectangleEdge::None);
        body = new BellBaseLayout(this);
    }

    void BellSideListItem::setBottomDescriptionText(const std::string &description)
    {
        if (bottomText != nullptr) {
            bottomText->setText(description);
        }
    }

        auto topMessage = new TextFixedSize(body->firstBox);
        topMessage->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::outer_layouts_h);
        topMessage->setFont(style::bell_sidelist_item::title_font);
        topMessage->setEdges(gui::RectangleEdge::None);
        topMessage->activeItem = false;
        topMessage->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        topMessage->setRichText(description);
        topMessage->drawUnderline(false);
    void BellSideListItem::setupTopTextBox(const std::string &description)
    {
        auto topText = new TextFixedSize(body->firstBox);
        setupTextBox(topText, style::bell_sidelist_item::title_font, description);
    }

    void BellSideListItem::setupBottomDescription(const std::string &description)
    void BellSideListItem::setupBottomTextBox(const std::string &description)
    {
        bottomText = new TextFixedSize(body->lastBox);
        bottomText->setMaximumSize(::style::bell_base_layout::w, ::style::bell_base_layout::outer_layouts_h);
        bottomText->setFont(::style::bell_sidelist_item::description_font);
        bottomText->setEdges(RectangleEdge::None);
        bottomText->activeItem = false;
        bottomText->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        bottomText->setRichText(description);
        bottomText->drawUnderline(false);
        setupTextBox(bottomText, style::bell_sidelist_item::description_font, description);
    }

    void BellSideListItem::setBottomDescribtionText(const std::string &description)
    void BellSideListItem::setupTextBox(TextFixedSize *textBox,
                                        const std::string &fontName,
                                        const std::string &description)
    {
        if (bottomText != nullptr) {
            bottomText->setText(description);
        }
        textBox->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::outer_layouts_h);
        textBox->setFont(fontName);
        textBox->setEdges(gui::RectangleEdge::None);
        textBox->activeItem = false;
        textBox->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        textBox->setRichText(description);
        textBox->drawUnderline(false);
    }

} /* namespace gui */

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

#pragma once

#include "BellBaseLayout.hpp"
#include <ListItemWithDescription.hpp>

namespace style::bell_sidelist_item
{
    inline constexpr auto title_font       = style::window::font::largelight;
    inline constexpr auto description_font = style::window::font::verybiglight;

} // namespace style::bell_sidelist_item
#include "BellSideListItemStyle.hpp"
#include <ListItem.hpp>

namespace gui
{
    class TextFixedSize;
    class BellSideListItem : public ListItemWithDescription
    class BellSideListItem : public ListItem
    {
      public:
        BellBaseLayout *body = nullptr;

        explicit BellSideListItem(const std::string &description);
        void setupBottomDescription(const std::string &description);
        void setBottomDescribtionText(const std::string &description);
        void setBottomDescriptionText(const std::string &description);

      protected:
        BellSideListItem();
        void setupBottomTextBox(const std::string &description);
        void setupTopTextBox(const std::string &description);

        BellBaseLayout *body      = nullptr;
        TextFixedSize *bottomText = nullptr;

      private:
        void setupTextBox(TextFixedSize *textBox, const std::string &fontName, const std::string &description);
    };
} /* namespace gui */

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

#pragma once

namespace style::bell_sidelist_item
{
    inline constexpr auto title_font       = style::window::font::largelight;
    inline constexpr auto description_font = style::window::font::verybiglight;

} // namespace style::bell_sidelist_item

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

#include "DateSetSpinner.hpp"

#include <FontManager.hpp>
#include <RawFont.hpp>
#include <gui/widgets/ImageBox.hpp>
#include <gui/widgets/text/Label.hpp>
#include <time/time_locale.hpp>

namespace gui
{
    namespace
    {
        constexpr auto focusFontName   = style::window::font::large;
        constexpr auto noFocusFontName = style::window::font::largelight;

        void setFont(TextFixedSize *elem, const std::string &fontName)
        {
            elem->setFont(fontName);
            elem->setMinimumHeightToFitText();
            elem->setMinimumWidthToFitText();
            elem->setText(elem->getText());
        }

        class FocusHelper
        {
          public:
            FocusHelper &year()
            {
                year_ = focusFontName;
                return *this;
            }

            FocusHelper &month()
            {
                month_ = focusFontName;
                return *this;
            }

            FocusHelper &day()
            {
                day_ = focusFontName;
                return *this;
            }

            void focus(U8IntegerSpinnerFixed *elementDay,
                       U8IntegerSpinnerFixed *elementMonth,
                       U16IntegerSpinnerFixed *elementYear)
            {
                setFont(elementDay, day_);
                setFont(elementMonth, month_);
                setFont(elementYear, year_);
            }

          private:
            std::string year_{noFocusFontName};
            std::string month_{noFocusFontName};
            std::string day_{noFocusFontName};
        };
    } // namespace

    DateSetSpinner::DateSetSpinner(Item *parent, TextFixedSize *title, Length x, Length y, Length w, Length h)
        : HBox(parent, x, y, w, h), title{title}
    {
        constexpr std::uint8_t step     = 1;
        constexpr std::uint8_t dayMin   = 1;
        constexpr std::uint8_t dayMax   = 31;
        constexpr std::uint8_t monthMin = 1;
        constexpr std::uint8_t monthMax = 12;

        setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        setEdges(RectangleEdge::None);

        attachDateField(day, U8IntegerSpinnerFixed::range{dayMin, dayMax, step});
        attachSlash(firstSlash);
        attachDateField(month, U8IntegerSpinnerFixed::range{monthMin, monthMax, step});
        attachSlash(secondSlash);
        attachDateField(
            year, U16IntegerSpinnerFixed::range{utils::time::Locale::min_years, utils::time::Locale::max_years, step});

        resizeItems();

        focusChangedCallback = [&](Item &) {
            if (lastFocus != nullptr) {
                updateFocus(lastFocus);
            }
            return true;
        };

        updateFocus(day);
    }

    date::year_month_day DateSetSpinner::getDate()
    {
        return date::year(year->get_value()) / date::month(month->get_value()) / date::day(day->get_value());
    }

    void DateSetSpinner::setDate(const date::year_month_day date)
    {
        day->set_value(static_cast<unsigned>(date.day()));
        month->set_value(static_cast<unsigned>(date.month()));
        year->set_value(static_cast<int>(date.year()));
        applySizeRestrictions();
    }

    void DateSetSpinner::applySizeRestrictions()
    {
        day->setMinimumWidthToFitText();
        firstSlash->setMinimumWidthToFitText();
        month->setMinimumWidthToFitText();
        secondSlash->setMinimumWidthToFitText();
        year->setMinimumWidthToFitText();

        setMinimumSize(getWidgetMinimumAreaWidth(), getFontHeight(noFocusFontName));
        setMaximumWidth(widgetMaximumArea.w);

        HBox::informContentChanged();
    }

    std::uint32_t DateSetSpinner::getWidgetMinimumAreaWidth()
    {
        return day->widgetMinimumArea.w + firstSlash->widgetMinimumArea.w + month->widgetMinimumArea.w +
               secondSlash->widgetMinimumArea.w + year->widgetMinimumArea.w;
    }

    template <typename Spinner>
    void DateSetSpinner::attachDateField(Spinner *&field, typename Spinner::range &&range)
    {
        field = new Spinner(std::move(range), Boundaries::Continuous);
        setFont(field, noFocusFontName);
        field->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        field->setEdges(RectangleEdge::None);
        field->setPenFocusWidth(4);
        field->set_value(0);
        addWidget(field);
    }

    void DateSetSpinner::attachSlash(gui::Label *&slash)
    {
        slash = new gui::Label(this, 0, 0, 0, 0);
        setFont(slash, noFocusFontName);
        slash->setEdges(gui::RectangleEdge::None);
        slash->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        slash->setEdges(RectangleEdge::None);
        slash->setText("/");
        slash->activeItem = false;
    }

    std::uint16_t DateSetSpinner::getFontHeight(const std::string &fontName) const
    {
        const RawFont *font = FontManager::getInstance().getFont(fontName);
        return font->info.line_height;
    }

    bool DateSetSpinner::onInput(const InputEvent &inputEvent)
    {
        if (auto ret = this->focusItem->onInput(inputEvent)) {
            applySizeRestrictions();
            return ret;
        }

        if (inputEvent.isShortRelease()) {
            switch (inputEvent.getKeyCode()) {
            case KeyCode::KEY_ENTER:
                return handleEnterKey();
            case KeyCode::KEY_RF:
                return handleRightFunctionKey();
            default:
                break;
            }
        }
        return false;
    }

    void DateSetSpinner::clampDayOfMonth()
    {
        auto dayCountInMonth =
            static_cast<unsigned>((date::year(year->get_value()) / date::month(month->get_value()) / date::last).day());

        if (day->get_value() > dayCountInMonth) {
            day->set_value(dayCountInMonth);
        }
    }

    bool DateSetSpinner::handleEnterKey()
    {
        if (focusItem == day) {
            updateFocus(month);
            return true;
        }
        if (focusItem == month) {
            clampDayOfMonth();
            updateFocus(year);
            return true;
        }
        return false;
    }

    bool DateSetSpinner::handleRightFunctionKey()
    {
        if (focusItem == month) {
            updateFocus(day);
            return true;
        }
        if (focusItem == year) {
            updateFocus(month);
            return true;
        }
        return false;
    }

    void DateSetSpinner::updateFocus(Item *newFocus)
    {
        auto set_title = [this](std::string text) {
            if (title != nullptr) {
                title->setRichText(text);
            }
        };

        setFocusItem(newFocus);
        lastFocus = newFocus;

        if (month->focus) {
            set_title(utils::translate("app_settings_title_month"));
            FocusHelper{}.month().focus(day, month, year);
        }
        else if (day->focus) {
            set_title(utils::translate("app_settings_title_day"));
            FocusHelper{}.day().focus(day, month, year);
        }
        else if (year->focus) {
            set_title(utils::translate("app_settings_title_year"));
            FocusHelper{}.year().focus(day, month, year);
        }

        applySizeRestrictions();
    }

} /* namespace gui */

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

#pragma once

#include <widgets/spinners/Spinners.hpp>
#include <gui/widgets/Style.hpp>
#include <gui/widgets/text/TextConstants.hpp>
#include <time/dateCommon.hpp>

#include <string>

namespace gui
{
    class DateSetSpinner : public HBox
    {
      public:
        DateSetSpinner(Item *parent, TextFixedSize *titleBox, Length x, Length y, Length w, Length h);

        date::year_month_day getDate();
        void setDate(date::year_month_day date);

      private:
        void applySizeRestrictions();
        std::uint32_t getWidgetMinimumAreaWidth();
        template <typename Spinner>
        void attachDateField(Spinner *&field, typename Spinner::range &&range);
        void attachSlash(gui::Label *&slash);
        std::uint16_t getFontHeight(const std::string &fontName) const;
        void updateFocus(Item *newFocus);
        bool onInput(const InputEvent &inputEvent) override;
        void clampDayOfMonth();
        bool handleEnterKey();
        bool handleRightFunctionKey();

        TextFixedSize *title         = nullptr;
        U8IntegerSpinnerFixed *day   = nullptr;
        Label *firstSlash            = nullptr;
        U8IntegerSpinnerFixed *month = nullptr;
        Label *secondSlash           = nullptr;
        U16IntegerSpinnerFixed *year = nullptr;

        Item *lastFocus = nullptr;
    };
} /* namespace gui */

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

#include "DateWidget.hpp"

M module-apps/apps-common/widgets/TimeSetFmtSpinner.cpp => module-apps/apps-common/widgets/TimeSetFmtSpinner.cpp +15 -6
@@ 73,12 73,7 @@ namespace gui

            if (timeFormat != newFormat) {
                timeSetSpinner->setHour(date::make12(hours).count());
                if (date::is_pm(hours)) {
                    fmt->set_value(time::Locale::getPM());
                }
                else {
                    fmt->set_value(time::Locale::getAM());
                }
                set12HPeriod(date::is_pm(hours) ? Period::PM : Period::AM);
            }

        } break;


@@ 276,5 271,19 @@ namespace gui

        HBox::handleContentChanged();
    }
    auto TimeSetFmtSpinner::set12HPeriod(const Period period) -> void
    {
        period == Period::AM ? fmt->set_value(utils::time::Locale::getAM())
                             : fmt->set_value(utils::time::Locale::getPM());
    }
    auto TimeSetFmtSpinner::getHour24Format() const noexcept -> std::chrono::hours
    {
        using namespace utils::time;
        if (timeFormat == Locale::TimeFormat::FormatTime24H) {
            return std::chrono::hours{timeSetSpinner->getHour()};
        }

        return date::make24(std::chrono::hours{timeSetSpinner->getHour()}, isPM());
    }

} // namespace gui

M module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp => module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp +8 -0
@@ 30,6 30,11 @@ namespace gui
    class TimeSetFmtSpinner : public HBox
    {
      public:
        enum class Period
        {
            AM,
            PM
        };
        explicit TimeSetFmtSpinner(
            Item *parent                               = nullptr,
            uint32_t x                                 = 0U,


@@ 43,6 48,7 @@ namespace gui
        auto setTimeFormatSpinnerVisibility(bool visibility) noexcept -> void;
        auto setHour(int value) noexcept -> void;
        auto setMinute(int value) noexcept -> void;
        auto set12HPeriod(Period period) -> void;
        auto setTime(std::time_t time) noexcept -> void;
        auto setEditMode(EditMode newEditMode) noexcept -> void;
        auto setFont(std::string newFontName) noexcept -> void;


@@ 51,6 57,8 @@ namespace gui

        [[nodiscard]] auto getTime() const noexcept -> std::time_t;
        [[nodiscard]] auto getHour() const noexcept -> int;
        /// Always returns current hour in 24-hour format, even if the currently set format is set to 12-hour clock.
        [[nodiscard]] auto getHour24Format() const noexcept -> std::chrono::hours;
        [[nodiscard]] auto getMinute() const noexcept -> int;
        [[nodiscard]] auto isPM() const noexcept -> bool;


M module-apps/apps-common/widgets/TimeSetSpinner.cpp => module-apps/apps-common/widgets/TimeSetSpinner.cpp +4 -4
@@ 26,7 26,7 @@ namespace gui
        setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        setEdges(RectangleEdge::None);

        hour = new UIntegerSpinner(UIntegerSpinner::range{hourMin, hourMax, hourStep}, Boundaries::Continuous);
        hour = new U8IntegerSpinner(U8IntegerSpinner::range{hourMin, hourMax, hourStep}, Boundaries::Continuous);
        updateFont(hour, noFocusFontName);

        hour->setAlignment(Alignment(Alignment::Horizontal::Right, Alignment::Vertical::Center));


@@ 42,8 42,8 @@ namespace gui
        colon->setEdges(RectangleEdge::None);
        colon->activeItem = false;

        minute = new UIntegerSpinnerFixed(UIntegerSpinnerFixed::range{minuteMin, minuteMax, minuteStep},
                                          Boundaries::Continuous);
        minute = new U8IntegerSpinnerFixed(U8IntegerSpinnerFixed::range{minuteMin, minuteMax, minuteStep},
                                           Boundaries::Continuous);
        updateFont(minute, noFocusFontName);
        minute->setPenFocusWidth(style::time_set_spinner::focus::size);



@@ 221,7 221,7 @@ namespace gui
    auto TimeSetSpinner::setHourRange(std::uint32_t min, std::uint32_t max) -> void
    {
        hour->set_range(
            UIntegerSpinner::range{static_cast<std::uint8_t>(min), static_cast<std::uint8_t>(max), hourStep});
            U8IntegerSpinner::range{static_cast<std::uint8_t>(min), static_cast<std::uint8_t>(max), hourStep});
    }

    auto TimeSetSpinner::getColonImage(const std::string &colonFont) const noexcept -> std::string

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

#pragma once


@@ 62,9 62,9 @@ namespace gui
            {style::window::font::huge,
             {style::time_set_spinner::big_margin, 0, style::time_set_spinner::big_margin, 0}}};

        UIntegerSpinner *hour        = nullptr;
        U8IntegerSpinner *hour        = nullptr;
        ImageBox *colon              = nullptr;
        UIntegerSpinnerFixed *minute = nullptr;
        U8IntegerSpinnerFixed *minute = nullptr;
        EditMode editMode            = EditMode::Edit;
        Item *lastFocus              = nullptr;
        std::string focusFontName    = style::window::font::supersizeme;

M module-apps/apps-common/widgets/spinners/Spinners.hpp => module-apps/apps-common/widgets/spinners/Spinners.hpp +9 -5
@@ 24,12 24,16 @@ namespace gui

    using StringContainer = Model<UTF8>;
    using UINT8Container  = Model<std::uint8_t>;
    using UINT16Container = Model<std::uint16_t>;

    using StringSpinner        = StringOutputSpinner<StringContainer>;
    using UIntegerSpinner      = StringOutputSpinner<UINT8Container>;
    using UIntegerSpinnerFixed = StringOutputSpinner<UINT8Container, FixedIntegerFormatter<std::uint32_t, 2>>;
    using WidgetSpinner        = ItemSpinner<Model<Item *>>;
    using StringSpinner    = StringOutputSpinner<StringContainer>;
    using U8IntegerSpinner = StringOutputSpinner<UINT8Container>;

    using U8IntegerSpinnerFixed  = StringOutputSpinner<UINT8Container, FixedIntegerFormatter<std::uint32_t, 2>>;
    using U16IntegerSpinnerFixed = StringOutputSpinner<UINT16Container, FixedIntegerFormatter<std::uint32_t, 4>>;

    using WidgetSpinner = ItemSpinner<Model<Item *>>;
    template <typename T>
    using UIntegerSpinnerWithFormatter = StringOutputSpinner<UINT8Container, T>;
    using U8IntegerSpinnerWithFormatter = StringOutputSpinner<UINT8Container, T>;

} // namespace gui

M module-apps/apps-common/widgets/spinners/StringOutputSpinner.hpp => module-apps/apps-common/widgets/spinners/StringOutputSpinner.hpp +7 -0
@@ 74,6 74,7 @@ namespace gui

        void set_range(const range &range);
        void set_value(const value_type &value);
        auto get_value() -> value_type;
        [[nodiscard]] auto value() const noexcept;

      private:


@@ 187,6 188,12 @@ namespace gui
    }

    template <typename Container, typename Formatter>
    auto StringOutputSpinner<Container, Formatter>::get_value() -> StringOutputSpinner<Container, Formatter>::value_type
    {
        return container.get();
    }

    template <typename Container, typename Formatter>
    void StringOutputSpinner<Container, Formatter>::set_range(const range &range)
    {
        container.set_range(range);

M module-gui/gui/widgets/CMakeLists.txt => module-gui/gui/widgets/CMakeLists.txt +0 -2
@@ 12,7 12,6 @@ target_sources( ${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/ImageBoxWithText.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/Item.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListItemWithDescription.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListView.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListViewWithArrows.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListViewEngine.cpp"


@@ 58,7 57,6 @@ target_sources( ${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/ImageBoxWithText.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/Item.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListItem.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListItemWithDescription.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListItemProvider.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListView.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/ListViewWithArrows.hpp"

D module-gui/gui/widgets/ListItemWithDescription.cpp => module-gui/gui/widgets/ListItemWithDescription.cpp +0 -17
@@ 1,17 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ListItemWithDescription.hpp"

namespace gui
{
    auto ListItemWithDescription::getDescription() const noexcept -> std::string
    {
        return description;
    }

    auto ListItemWithDescription::setDescription(std::string description) -> void
    {
        this->description = std::move(description);
    }
} // namespace gui

M products/BellHybrid/apps/application-bell-background-sounds/windows/BGSoundsVolumeWindow.cpp => products/BellHybrid/apps/application-bell-background-sounds/windows/BGSoundsVolumeWindow.cpp +5 -5
@@ 36,15 36,15 @@ namespace gui
        topMessage->drawUnderline(false);

        auto data = presenter->getVolumeData();
        spinner   = new UIntegerSpinner({static_cast<UIntegerSpinner::value_type>(data.min),
                                       static_cast<UIntegerSpinner::value_type>(data.max),
                                       static_cast<UIntegerSpinner::value_type>(data.step)},
                                      Boundaries::Fixed);
        spinner   = new U8IntegerSpinner({static_cast<U8IntegerSpinner::value_type>(data.min),
                                        static_cast<U8IntegerSpinner::value_type>(data.max),
                                        static_cast<U8IntegerSpinner::value_type>(data.step)},
                                       Boundaries::Fixed);
        spinner->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::center_layout_h);
        spinner->setFont(bgSoundsStyle::valumeValueFont);
        spinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        spinner->setFocusEdges(RectangleEdge::None);
        spinner->set_value(static_cast<UIntegerSpinner::value_type>(presenter->getVolume()));
        spinner->set_value(static_cast<U8IntegerSpinner::value_type>(presenter->getVolume()));
        body->getCenterBox()->addWidget(spinner);

        spinner->onValueChanged = [this](const auto &value) { presenter->setVolume(value); };

M products/BellHybrid/apps/application-bell-background-sounds/windows/BGSoundsVolumeWindow.hpp => products/BellHybrid/apps/application-bell-background-sounds/windows/BGSoundsVolumeWindow.hpp +1 -1
@@ 16,7 16,7 @@ namespace gui
    {
        std::unique_ptr<app::bgSounds::AbstractBGSoundsVolumePresenter> presenter;
        BellBaseLayout *body{};
        UIntegerSpinner *spinner = nullptr;
        U8IntegerSpinner *spinner = nullptr;

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

M products/BellHybrid/apps/application-bell-meditation-timer/presenter/SettingsPresenter.cpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/SettingsPresenter.cpp +1 -1
@@ 33,7 33,7 @@ namespace app::list_items
        std::string none;
    };

    using StartDelaySpinner = gui::UIntegerSpinnerWithFormatter<StartDelayFormatter>;
    using StartDelaySpinner = gui::U8IntegerSpinnerWithFormatter<StartDelayFormatter>;
    class StartDelay : public app::list_items::details::ListItemBase<StartDelaySpinner>
    {
      public:

M products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.cpp => products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.cpp +1 -1
@@ 62,7 62,7 @@ namespace app::meditation
        centerText2->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Bottom));
        centerText2->setRichText(utils::translate("app_meditation_summary_average"));

        setupBottomDescription(format(average));
        setupBottomTextBox(format(average));
        bottomText->setFont(style::bell_sidelist_item::title_font);
        bottomText->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        bottomText->setEdges(RectangleEdge::None);

M products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationTimerWindow.cpp => products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationTimerWindow.cpp +2 -2
@@ 37,8 37,8 @@ namespace app::meditation
        topMessage->setText(utils::translate("app_bell_meditation_timer"));
        topMessage->drawUnderline(false);

        spinner = new UIntegerSpinner(
            UIntegerSpinner::range{presenter->getMinValue(), presenter->getMaxValue(), presenter->getStepValue()},
        spinner = new U8IntegerSpinner(
            U8IntegerSpinner::range{presenter->getMinValue(), presenter->getMaxValue(), presenter->getStepValue()},
            gui::Boundaries::Fixed);
        spinner->onValueChanged = [this](const auto val) { this->onValueChanged(val); };
        spinner->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::h);

M products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationTimerWindow.hpp => products/BellHybrid/apps/application-bell-meditation-timer/windows/MeditationTimerWindow.hpp +1 -1
@@ 31,7 31,7 @@ namespace app::meditation
      private:
        std::unique_ptr<app::meditation::MeditationTimerContract::Presenter> presenter;
        gui::BellBaseLayout *body{};
        gui::UIntegerSpinner *spinner{};
        gui::U8IntegerSpinner *spinner{};
        gui::Label *bottomDescription{};
    };
} // namespace app::meditation

M products/BellHybrid/apps/application-bell-onboarding/ApplicationBellOnBoarding.cpp => products/BellHybrid/apps/application-bell-onboarding/ApplicationBellOnBoarding.cpp +2 -2
@@ 21,7 21,7 @@
#include <common/models/LayoutModel.hpp>

#include <application-bell-settings/models/TemperatureUnitModel.hpp>
#include <application-bell-settings/models/TimeUnitsModel.hpp>
#include <application-bell-settings/models/DateTimeUnitsModel.hpp>
#include <application-bell-settings/presenter/TimeUnitsPresenter.hpp>
#include <Timers/TimerFactory.hpp>
#include <AppMessage.hpp>


@@ 78,7 78,7 @@ namespace app
            gui::window::name::onBoardingSettingsWindow, [this](ApplicationCommon *app, const std::string &name) {
                auto layoutModel          = std::make_unique<bell_settings::LayoutModel>(this);
                auto temperatureUnitModel = std::make_unique<bell_settings::TemperatureUnitModel>(app);
                auto timeUnitsProvider    = std::make_shared<bell_settings::TimeUnitsModelFactoryResetValues>(app);
                auto timeUnitsProvider    = std::make_shared<bell_settings::DateTimeUnitsModelFactoryResetValues>(app);
                auto presenter            = std::make_unique<bell_settings::TimeUnitsWindowPresenter>(
                    this, timeUnitsProvider, std::move(temperatureUnitModel), std::move(layoutModel));
                return std::make_unique<gui::OnBoardingSettingsWindow>(app, std::move(presenter), name);

M products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.cpp => products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.cpp +6 -5
@@ 14,8 14,9 @@ namespace
} // namespace
namespace gui
{
    PowerNapListItem::PowerNapListItem() : BellSideListItem(utils::translate("app_bellmain_power_nap"))
    PowerNapListItem::PowerNapListItem()
    {
        setupTopTextBox(utils::translate("app_bellmain_power_nap"));
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);
        setEdges(RectangleEdge::None);
        setFocusItem(body);


@@ 27,7 28,7 @@ namespace gui

    void PowerNapListItem::createSpinner()
    {
        spinner = new UIntegerSpinner(UIntegerSpinner::range{spinnerMin, spinnerMax, spinnerStep}, Boundaries::Fixed);
        spinner = new U8IntegerSpinner(U8IntegerSpinner::range{spinnerMin, spinnerMax, spinnerStep}, Boundaries::Fixed);
        spinner->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::h);
        spinner->setFont(powerNapStyle::napPeriodFont);
        spinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));


@@ 40,7 41,7 @@ namespace gui

    void PowerNapListItem::createBottomDescription()
    {
        setupBottomDescription("");
        setupBottomTextBox("");
        bottomText->setMaximumSize(style::bell_base_layout::outer_layouts_w, style::bell_base_layout::outer_layouts_h);
        bottomText->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
    }


@@ 65,7 66,7 @@ namespace gui

        inputCallback = [&](Item &, const InputEvent &inputEvent) -> bool {
            if (body->onInput(inputEvent)) {
                setBottomDescribtionText(utils::language::getCorrectMinutesNumeralForm(spinner->value()));
                setBottomDescriptionText(utils::language::getCorrectMinutesNumeralForm(spinner->value()));
                return true;
            }
            return false;


@@ 80,7 81,7 @@ namespace gui
    void PowerNapListItem::setSpinnerValue(int value)
    {
        spinner->set_value(value);
        setBottomDescribtionText(utils::language::getCorrectMinutesNumeralForm(spinner->value()));
        setBottomDescriptionText(utils::language::getCorrectMinutesNumeralForm(spinner->value()));
        onValueChanged(value);
    }


M products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.hpp => products/BellHybrid/apps/application-bell-powernap/data/PowerNapListItem.hpp +1 -1
@@ 11,7 11,7 @@ namespace gui

    class PowerNapListItem : public BellSideListItem
    {
        gui::UIntegerSpinner *spinner{};
        gui::U8IntegerSpinner *spinner{};
        Label *bottomDescription{};

        void createSpinner();

M products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp => products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp +1 -1
@@ 137,7 137,7 @@ namespace app
            gui::window::name::bellSettingsTimeUnits, [this](ApplicationCommon *app, const std::string &name) {
                auto layoutModel          = std::make_unique<bell_settings::LayoutModel>(this);
                auto temperatureUnitModel = std::make_unique<bell_settings::TemperatureUnitModel>(app);
                auto timeUnitsProvider    = std::make_shared<bell_settings::TimeUnitsModel>(app);
                auto timeUnitsProvider    = std::make_shared<bell_settings::DateTimeUnitsModel>(app);
                auto presenter            = std::make_unique<bell_settings::TimeUnitsWindowPresenter>(
                    this, timeUnitsProvider, std::move(temperatureUnitModel), std::move(layoutModel));
                return std::make_unique<gui::BellSettingsTimeUnitsWindow>(app, std::move(presenter));

M products/BellHybrid/apps/application-bell-settings/CMakeLists.txt => products/BellHybrid/apps/application-bell-settings/CMakeLists.txt +3 -2
@@ 23,7 23,7 @@ target_sources(application-bell-settings
    PRIVATE
        ApplicationBellSettings.cpp
        models/TemperatureUnitModel.cpp
        models/TimeUnitsModel.cpp
        models/DateTimeUnitsModel.cpp
        models/AboutYourBellModel.cpp
        models/FrontlightListItemProvider.cpp
        models/FrontlightModel.cpp


@@ 49,6 49,7 @@ target_sources(application-bell-settings

        widgets/TemperatureUnitListItem.cpp
        widgets/TimeFormatSetListItem.cpp
        widgets/DateSetListItem.cpp
        widgets/TimeSetListItem.cpp
        widgets/AboutYourBellListItem.cpp
        widgets/DialogYesNo.cpp


@@ 110,7 111,7 @@ target_sources(application-bell-settings
        data/FinishedWindowMessageData.hpp

    PUBLIC
        include/application-bell-settings/models/TimeUnitsModel.hpp
        include/application-bell-settings/models/DateTimeUnitsModel.hpp
        include/application-bell-settings/presenter/TimeUnitsPresenter.hpp
        include/application-bell-settings/windows/BellSettingsTimeUnitsWindow.hpp
        include/application-bell-settings/ApplicationBellSettings.hpp

R products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/TimeUnitsModel.hpp => products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/DateTimeUnitsModel.hpp +8 -24
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 10,63 10,47 @@

namespace gui
{
    class DateSetListItem;
    class TimeSetListItem;

    class TimeFormatSetListItem;

    class TemperatureUnitListItem;
} // namespace gui

namespace app::bell_settings
{
    class TimeUnitsModel : public app::InternalModel<gui::ListItem *>, public gui::ListItemProvider
    class DateTimeUnitsModel : public app::InternalModel<gui::ListItem *>, public gui::ListItemProvider
    {
      public:
        explicit TimeUnitsModel(app::ApplicationCommon *app);

        ~TimeUnitsModel();
        explicit DateTimeUnitsModel(app::ApplicationCommon *app);
        ~DateTimeUnitsModel();

        auto clearData() -> void;

        auto saveData() -> void;

        virtual auto loadData() -> void;

        auto createData() -> void;

        auto requestRecords(uint32_t offset, 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;

        [[nodiscard]] auto getTemperatureUnit() const -> utils::temperature::Temperature::Unit;

        [[nodiscard]] auto getTimeFormat() const -> utils::time::Locale::TimeFormat;

        auto setTemperatureUnit(utils::temperature::Temperature::Unit unit) -> void;

      protected:
        app::ApplicationCommon *application{};
        gui::DateSetListItem *dateSetListItem{};
        gui::TimeSetListItem *timeSetListItem{};
        gui::TimeFormatSetListItem *timeFmtSetListItem{};
        gui::TemperatureUnitListItem *temperatureUnitListItem{};

        void sendRtcUpdateTimeMessage(time_t newTime);

        void sendTimeFmtUpdateMessage(utils::time::Locale::TimeFormat newFmt);
    };

    class TimeUnitsModelFactoryResetValues : public TimeUnitsModel
    class DateTimeUnitsModelFactoryResetValues : public DateTimeUnitsModel
    {
      public:
        using TimeUnitsModel::TimeUnitsModel;
        using DateTimeUnitsModel::DateTimeUnitsModel;
        auto loadData() -> void override;

      private:
        static constexpr int factoryResetTime    = 12 * 60 * 60; // 12:00 PM
        static constexpr auto factoryRestTimeFmt = utils::time::Locale::TimeFormat::FormatTime12H;
    };
} // namespace app::bell_settings

M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/presenter/TimeUnitsPresenter.hpp => products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/presenter/TimeUnitsPresenter.hpp +3 -3
@@ 3,7 3,7 @@

#pragma once

#include <application-bell-settings/models/TimeUnitsModel.hpp>
#include <application-bell-settings/models/DateTimeUnitsModel.hpp>

#include <apps-common/BasePresenter.hpp>



@@ 37,7 37,7 @@ namespace app::bell_settings
    {
      public:
        explicit TimeUnitsWindowPresenter(app::ApplicationCommon *app,
                                          std::shared_ptr<TimeUnitsModel> pagesProvider,
                                          std::shared_ptr<DateTimeUnitsModel> pagesProvider,
                                          std::unique_ptr<AbstractTemperatureUnitModel> temperatureUnitModel,
                                          std::unique_ptr<AbstractLayoutModel> layoutModel);



@@ 49,7 49,7 @@ namespace app::bell_settings

      private:
        app::ApplicationCommon *app{};
        std::shared_ptr<TimeUnitsModel> pagesProvider;
        std::shared_ptr<DateTimeUnitsModel> pagesProvider;
        std::unique_ptr<AbstractTemperatureUnitModel> temperatureUnitModel;
        std::unique_ptr<AbstractLayoutModel> layoutModel;
    };

M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/windows/BellSettingsTimeUnitsWindow.hpp => products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/windows/BellSettingsTimeUnitsWindow.hpp +1 -1
@@ 5,7 5,7 @@

#include <application-bell-settings/ApplicationBellSettings.hpp>
#include <application-bell-settings/presenter/TimeUnitsPresenter.hpp>
#include <application-bell-settings/models/TimeUnitsModel.hpp>
#include <application-bell-settings/models/DateTimeUnitsModel.hpp>

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


R products/BellHybrid/apps/application-bell-settings/models/TimeUnitsModel.cpp => products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp +54 -28
@@ 1,7 1,8 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "models/TimeUnitsModel.hpp"
#include "models/DateTimeUnitsModel.hpp"
#include "widgets/DateSetListItem.hpp"
#include "widgets/TimeFormatSetListItem.hpp"
#include "widgets/TimeSetListItem.hpp"
#include "widgets/TemperatureUnitListItem.hpp"


@@ 12,37 13,40 @@
#include <service-time/Constants.hpp>
#include <service-time/api/TimeSettingsApi.hpp>
#include <service-time/service-time/TimeMessage.hpp>
#include <widgets/DateSetSpinner.hpp>
#include <widgets/TimeSetFmtSpinner.hpp>

#include <ctime>

namespace app::bell_settings
{
    TimeUnitsModel::TimeUnitsModel(app::ApplicationCommon *app) : application(app)
    DateTimeUnitsModel::DateTimeUnitsModel(app::ApplicationCommon *app) : application(app)
    {}

    TimeUnitsModel::~TimeUnitsModel()
    DateTimeUnitsModel::~DateTimeUnitsModel()
    {
        clearData();
    }

    auto TimeUnitsModel::requestRecordsCount() -> unsigned int
    auto DateTimeUnitsModel::requestRecordsCount() -> unsigned int
    {
        return internalData.size();
    }

    auto TimeUnitsModel::getMinimalItemSpaceRequired() const -> unsigned int
    auto DateTimeUnitsModel::getMinimalItemSpaceRequired() const -> unsigned int
    {
        return style::sidelistview::list_item::w;
    }

    auto TimeUnitsModel::getItem(gui::Order order) -> gui::ListItem *
    auto DateTimeUnitsModel::getItem(gui::Order order) -> gui::ListItem *
    {
        return getRecord(order);
    }

    void TimeUnitsModel::createData()
    void DateTimeUnitsModel::createData()
    {
        dateSetListItem = new gui::DateSetListItem();
        internalData.push_back(dateSetListItem);

        timeFmtSetListItem = new gui::TimeFormatSetListItem(
            0, 0, 0, 0, utils::translate("app_bell_settings_time_units_time_fmt_top_message"));


@@ 66,53 70,66 @@ namespace app::bell_settings
        }
    }

    void TimeUnitsModel::clearData()
    void DateTimeUnitsModel::clearData()
    {
        eraseInternalData();
    }

    void TimeUnitsModel::saveData()
    void DateTimeUnitsModel::saveData()
    {
        const auto newTime = timeSetListItem->timeSetFmtSpinner->getTime();
        auto time_tm       = std::localtime(&newTime);
        const auto newFmt  = timeFmtSetListItem->getTimeFmt();
        LOG_INFO("Setting new time: %d:%d fmt: %s",
                 time_tm->tm_hour,
                 time_tm->tm_min,
                 utils::time::Locale::format(newFmt).c_str());
        time_tm->tm_sec = 0;
        sendRtcUpdateTimeMessage(std::mktime(time_tm));
        sendTimeFmtUpdateMessage(newFmt);
        const auto date   = dateSetListItem->dateSetSpinner->getDate();
        const auto year   = date.year().operator int();
        const auto month  = static_cast<int>(date.month().operator unsigned int());
        const auto day    = static_cast<int>(date.day().operator unsigned int());
        const auto hour   = timeSetListItem->timeSetFmtSpinner->getHour24Format();
        const auto minute = timeSetListItem->timeSetFmtSpinner->getMinute();
        const auto fmt    = timeFmtSetListItem->getTimeFmt();

        const auto newTime = GetAsUTCTime(year, month, day, hour.count(), minute);

        LOG_DEBUG("Setting a new date/time: %d/%d/%d %d:%d %s",
                  year,
                  month,
                  day,
                  static_cast<int>(hour.count()),
                  minute,
                  utils::time::Locale::get_time_format(fmt).data());

        sendRtcUpdateTimeMessage(newTime);
        sendTimeFmtUpdateMessage(fmt);
    }

    void TimeUnitsModel::loadData()
    void DateTimeUnitsModel::loadData()
    {
        const auto now        = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        const auto timeFormat = stm::api::timeFormat();
        timeSetListItem->timeSetFmtSpinner->setTimeFormat(timeFormat);
        timeSetListItem->timeSetFmtSpinner->setTime(now);
        dateSetListItem->dateSetSpinner->setDate(
            date::year_month_day{date::floor<date::days>(std::chrono::system_clock::now())});

        timeFmtSetListItem->setTimeFmt(timeFormat);
    }

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

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

    void TimeUnitsModel::sendTimeFmtUpdateMessage(utils::time::Locale::TimeFormat newFmt)
    void DateTimeUnitsModel::sendTimeFmtUpdateMessage(utils::time::Locale::TimeFormat newFmt)
    {
        auto msg = std::make_shared<stm::message::SetTimeFormatRequest>(newFmt);
        application->bus.sendUnicast(std::move(msg), service::name::service_time);
    }

    auto TimeUnitsModel::getTemperatureUnit() const -> utils::temperature::Temperature::Unit
    auto DateTimeUnitsModel::getTemperatureUnit() const -> utils::temperature::Temperature::Unit
    {
#if CONFIG_ENABLE_TEMP == 1
        return *utils::temperature::strToUnit(temperatureUnitListItem->getUnitAsStr());


@@ 121,22 138,31 @@ namespace app::bell_settings
#endif
    }

    auto TimeUnitsModel::getTimeFormat() const -> utils::time::Locale::TimeFormat
    auto DateTimeUnitsModel::getTimeFormat() const -> utils::time::Locale::TimeFormat
    {
        return timeFmtSetListItem->getTimeFmt();
    }

    auto TimeUnitsModel::setTemperatureUnit(const utils::temperature::Temperature::Unit unit) -> void
    auto DateTimeUnitsModel::setTemperatureUnit(const utils::temperature::Temperature::Unit unit) -> void
    {
#if CONFIG_ENABLE_TEMP == 1
        temperatureUnitListItem->setUnit(unit);
#endif
    }

    void TimeUnitsModelFactoryResetValues::loadData()
    void DateTimeUnitsModelFactoryResetValues::loadData()
    {
        using namespace date::literals;

        /// Default date/time after factory reset: 2022/01/01 12:00PM
        const auto factoryResetDate   = 2022_y / jan / 1_d;
        const auto factoryRestTimeFmt = utils::time::Locale::TimeFormat::FormatTime12H;

        dateSetListItem->dateSetSpinner->setDate(factoryResetDate);
        timeSetListItem->timeSetFmtSpinner->setTimeFormat(factoryRestTimeFmt);
        timeSetListItem->timeSetFmtSpinner->setTime(factoryResetTime);
        timeSetListItem->timeSetFmtSpinner->setHour(12);
        timeSetListItem->timeSetFmtSpinner->setMinute(0);
        timeSetListItem->timeSetFmtSpinner->set12HPeriod(gui::TimeSetFmtSpinner::Period::PM);
        timeFmtSetListItem->setTimeFmt(factoryRestTimeFmt);
    }
} // namespace app::bell_settings

M products/BellHybrid/apps/application-bell-settings/models/alarm_settings/SnoozeListItemProvider.cpp => products/BellHybrid/apps/application-bell-settings/models/alarm_settings/SnoozeListItemProvider.cpp +1 -1
@@ 77,7 77,7 @@ namespace app::bell_settings
            chimeLengthBottomDescription);

        chimeLength->set_on_value_change_cb([chimeLength](const auto &val) {
            chimeLength->setBottomDescribtionText(utils::language::getCorrectMinutesNumeralForm(val));
            chimeLength->setBottomDescriptionText(utils::language::getCorrectMinutesNumeralForm(val));
        });

        chimeLength->onEnter = [onOff, this]() {

M products/BellHybrid/apps/application-bell-settings/presenter/TimeUnitsPresenter.cpp => products/BellHybrid/apps/application-bell-settings/presenter/TimeUnitsPresenter.cpp +1 -1
@@ 14,7 14,7 @@ namespace app::bell_settings
{
    TimeUnitsWindowPresenter::TimeUnitsWindowPresenter(
        app::ApplicationCommon *app,
        std::shared_ptr<TimeUnitsModel> pagesProvider,
        std::shared_ptr<DateTimeUnitsModel> pagesProvider,
        std::unique_ptr<AbstractTemperatureUnitModel> temperatureUnitModel,
        std::unique_ptr<AbstractLayoutModel> layoutModel)
        : app{app}, pagesProvider(std::move(pagesProvider)), temperatureUnitModel{std::move(temperatureUnitModel)},

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

#include "DateSetListItem.hpp"
#include "BellSettingsStyle.hpp"

#include <gui/input/InputEvent.hpp>
#include <widgets/DateSetSpinner.hpp>

namespace gui
{
    DateSetListItem::DateSetListItem() : BellSideListItem()
    {
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);

        auto title = new TextFixedSize(body->firstBox);
        title->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::outer_layouts_h);
        title->setFont(style::bell_sidelist_item::title_font);
        title->setEdges(gui::RectangleEdge::None);
        title->activeItem = false;
        title->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        title->setRichText(utils::translate("app_settings_title_day"));
        title->drawUnderline(false);

        dateSetSpinner = new DateSetSpinner(body->getCenterBox(), title, 0, 0, 0, 0);
        dateSetSpinner->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::h);
        dateSetSpinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        setFocusItem(body);

        dimensionChangedCallback = [&](gui::Item &, const BoundingBox &newDim) -> bool {
            body->setArea({0, 0, newDim.w, newDim.h});
            return true;
        };

        focusChangedCallback = [&](Item &) {
            setFocusItem(focus ? body : nullptr);
            return true;
        };

        inputCallback = [&](Item &, const InputEvent &inputEvent) -> bool { return body->onInput(inputEvent); };
    }
} /* namespace gui */

R module-gui/gui/widgets/ListItemWithDescription.hpp => products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.hpp +10 -9
@@ 1,20 1,21 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "ListItem.hpp"
#include <widgets/BellSideListItem.hpp>

#include <string>

namespace gui
{
    class ListItemWithDescription : public ListItem
    {
      private:
        std::string description;
    class DateSetSpinner;

    class DateSetListItem : public BellSideListItem
    {
      public:
        ListItemWithDescription(std::string desc = "") : ListItem(), description(desc){};
        auto getDescription() const noexcept -> std::string;
        auto setDescription(std::string description) -> void;
        DateSetSpinner *dateSetSpinner = nullptr;

        DateSetListItem();
    };
} /* namespace gui */

M products/BellHybrid/apps/application-bell-settings/widgets/TemperatureUnitListItem.cpp => products/BellHybrid/apps/application-bell-settings/widgets/TemperatureUnitListItem.cpp +1 -1
@@ 9,8 9,8 @@
namespace gui
{
    TemperatureUnitListItem::TemperatureUnitListItem(const UTF8 &topDesc, Length x, Length y, Length w, Length h)
        : BellSideListItem(topDesc)
    {
        setupTopTextBox(topDesc);
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);
        setEdges(RectangleEdge::None);
        setFocusItem(body);

M products/BellHybrid/apps/application-bell-settings/widgets/TimeFormatSetListItem.cpp => products/BellHybrid/apps/application-bell-settings/widgets/TimeFormatSetListItem.cpp +2 -2
@@ 20,8 20,8 @@ namespace gui
{
    TimeFormatSetListItem::TimeFormatSetListItem(
        Length x, Length y, Length w, Length h, const UTF8 &topDesc, const UTF8 &botDesc)
        : BellSideListItem(topDesc)
    {
        setupTopTextBox(topDesc);
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);
        setEdges(RectangleEdge::None);
        setFocusItem(body);


@@ 36,7 36,7 @@ namespace gui
        };

        body->getCenterBox()->addWidget(timeFormat);
        setupBottomDescription(botDesc);
        setupBottomTextBox(botDesc);
        dimensionChangedCallback = [&](Item &, const BoundingBox &newDim) -> bool {
            body->setArea({0, 0, newDim.w, newDim.h});
            return true;

M products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.cpp => products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.cpp +1 -1
@@ 11,8 11,8 @@ namespace gui
{
    TimeSetListItem::TimeSetListItem(
        gui::Length x, gui::Length y, gui::Length w, gui::Length h, std::string description)
        : BellSideListItem(std::move(description))
    {
        setupTopTextBox(description);
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);
        timeSetFmtSpinner = new TimeSetFmtSpinner(body->getCenterBox());
        timeSetFmtSpinner->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::h);

M products/BellHybrid/apps/application-bell-settings/windows/BellSettingsWindow.cpp => products/BellHybrid/apps/application-bell-settings/windows/BellSettingsWindow.cpp +3 -2
@@ 74,6 74,8 @@ namespace gui
                },
                this));
        };
        const std::string bellSettingsTimeUnitsTitle{utils::translate("app_calllog_date") + " & " +
                                                     utils::translate("app_bell_settings_time_units")};

        addWinSettings(
            utils::translate("app_bell_settings_layout"), gui::window::name::bellSettingsLayout, defaultCallback);


@@ 82,8 84,7 @@ namespace gui
                       defaultCallback);
        addWinSettings(
            utils::translate("app_bell_settings_bedtime_tone"), window::name::bellSettingsBedtimeTone, defaultCallback);
        addWinSettings(
            utils::translate("app_bell_settings_time_units"), window::name::bellSettingsTimeUnits, defaultCallback);
        addWinSettings(bellSettingsTimeUnitsTitle, window::name::bellSettingsTimeUnits, defaultCallback);
        addWinSettings(
            utils::translate("app_bell_settings_language"), gui::window::name::bellSettingsLanguage, defaultCallback);
        addWinSettings(

M products/BellHybrid/apps/common/include/common/widgets/list_items/Numeric.hpp => products/BellHybrid/apps/common/include/common/widgets/list_items/Numeric.hpp +4 -4
@@ 9,14 9,14 @@

namespace app::list_items
{
    class Numeric : public details::ListItemBase<gui::UIntegerSpinner>
    class Numeric : public details::ListItemBase<gui::U8IntegerSpinner>
    {
      public:
        Numeric(gui::UIntegerSpinner::range &&range,
                gui::AbstractSettingsModel<gui::UIntegerSpinner::value_type> &model,
        Numeric(gui::U8IntegerSpinner::range &&range,
                gui::AbstractSettingsModel<gui::U8IntegerSpinner::value_type> &model,
                const std::string &topDescription    = "",
                const std::string &bottomDescription = "")
            : details::ListItemBase<gui::UIntegerSpinner>(std::move(range), model, topDescription, bottomDescription)
            : details::ListItemBase<gui::U8IntegerSpinner>(std::move(range), model, topDescription, bottomDescription)
        {}
    };
} // namespace app::list_items

M products/BellHybrid/apps/common/include/common/widgets/list_items/details.hpp => products/BellHybrid/apps/common/include/common/widgets/list_items/details.hpp +1 -1
@@ 66,7 66,7 @@ namespace app::list_items
                body->getCenterBox()->addWidget(spinner);

                if (not this->bottomDescription.empty()) {
                    setupBottomDescription(this->bottomDescription);
                    setupBottomTextBox(this->bottomDescription);
                }

                spinner->onValueChanged = [this](const auto &val) {

M products/BellHybrid/apps/common/src/BellSideListItemWithCallbacks.cpp => products/BellHybrid/apps/common/src/BellSideListItemWithCallbacks.cpp +1 -1
@@ 9,8 9,8 @@ namespace gui
{

    gui::BellSideListItemWithCallbacks::BellSideListItemWithCallbacks(const std::string &description)
        : BellSideListItem(description)
    {
        setupTopTextBox(description);
        focusChangedCallback = [&](Item &) {
            OnFocusChangedCallback();
            return true;