~aleteoryx/muditaos

3cc3f50f7b9364ffac38349238cd6d9aa243dc23 — Maciej Gibowicz 2 years ago a96ed0e
[BH-1809][BH-1835] Add date format setting

Added date format selection between DD/MM and MM/DD.
The time setting has been updated.
23 files changed, 408 insertions(+), 265 deletions(-)

M harmony_changelog.md
M image/system_a/data/lang/Deutsch.json
M image/system_a/data/lang/English.json
M image/system_a/data/lang/Espanol.json
M image/system_a/data/lang/Francais.json
M image/system_a/data/lang/Polski.json
M module-apps/apps-common/widgets/DateSetSpinner.cpp
M module-apps/apps-common/widgets/DateSetSpinner.hpp
M module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp
M module-utils/time/time/dateCommon.hpp
M products/BellHybrid/apps/application-bell-settings/CMakeLists.txt
M products/BellHybrid/apps/application-bell-settings/data/BellSettingsStyle.hpp
M products/BellHybrid/apps/application-bell-settings/include/application-bell-settings/models/DateTimeUnitsModel.hpp
M products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp
A products/BellHybrid/apps/application-bell-settings/widgets/DateFormatSetListItem.cpp
A products/BellHybrid/apps/application-bell-settings/widgets/DateFormatSetListItem.hpp
M products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.cpp
M products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.hpp
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/widgets/TimeSetListItem.hpp
M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithDate.cpp
M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutVerticalWithDate.cpp
M harmony_changelog.md => harmony_changelog.md +2 -0
@@ 12,11 12,13 @@
* Added shortcuts instruction to settings
* Added brightness fade in functionality
* Added labels to Relaxation
* Added date format setting

### Changed / Improved
* Optimize the way Relaxation is loading music files
* Disabled Address Sanitizer for the Linux simulator
* Changed misleading factory reset translations in Polish
* Time setting updated

## [2.2.2 2023-11-14]


M image/system_a/data/lang/Deutsch.json => image/system_a/data/lang/Deutsch.json +1 -0
@@ 104,6 104,7 @@
    "app_bell_settings_temp_scale": "Temperaturskala",
    "app_bell_settings_time_units": "Zeit",
    "app_bell_settings_time_units_time_fmt_top_message": "Zeitformat",
    "app_bell_settings_time_units_date_fmt_top_message": "Datumsformat",
    "app_bell_settings_time_units_time_message": "Zeit",
    "app_bell_settings_turn_off": "Ausschalten",
    "app_bell_turn_off_question": "Schalten Sie das Ger\u00e4t aus?",

M image/system_a/data/lang/English.json => image/system_a/data/lang/English.json +1 -0
@@ 138,6 138,7 @@
    "app_bell_settings_temp_scale": "Temperature scale",
    "app_bell_settings_time_units": "Time",
    "app_bell_settings_time_units_time_fmt_top_message": "Time format",
    "app_bell_settings_time_units_date_fmt_top_message": "Date format",
    "app_bell_settings_time_units_time_message": "Time",
    "app_bell_settings_turn_off": "Turn off",
    "app_bell_turn_off_question": "Turn off Mudita Harmony?",

M image/system_a/data/lang/Espanol.json => image/system_a/data/lang/Espanol.json +1 -0
@@ 103,6 103,7 @@
    "app_bell_settings_temp_scale": "Escala de temperatura",
    "app_bell_settings_time_units": "Hora",
    "app_bell_settings_time_units_time_fmt_top_message": "Formato de hora",
    "app_bell_settings_time_units_date_fmt_top_message": "Formato de fecha",
    "app_bell_settings_time_units_time_message": "Hora",
    "app_bell_settings_turn_off": "Apagar",
    "app_bell_turn_off_question": "\u00bfApagar Mudita Harmony?",

M image/system_a/data/lang/Francais.json => image/system_a/data/lang/Francais.json +1 -0
@@ 105,6 105,7 @@
    "app_bell_settings_temp_scale": "\u00c9chelle de temp\u00e9rature",
    "app_bell_settings_time_units": "Temps",
    "app_bell_settings_time_units_time_fmt_top_message": "Format de l'heure",
    "app_bell_settings_time_units_date_fmt_top_message": "Format de la date",
    "app_bell_settings_time_units_time_message": "Heure",
    "app_bell_settings_turn_off": "\u00c9teindre",
    "app_bell_turn_off_question": "\u00c9teindre l'appareil ?",

M image/system_a/data/lang/Polski.json => image/system_a/data/lang/Polski.json +1 -0
@@ 104,6 104,7 @@
    "app_bell_settings_temp_scale": "Skala temperatury",
    "app_bell_settings_time_units": "Czas",
    "app_bell_settings_time_units_time_fmt_top_message": "Format czasu",
    "app_bell_settings_time_units_date_fmt_top_message": "Format daty",
    "app_bell_settings_time_units_time_message": "Czas",
    "app_bell_settings_turn_off": "Wy\u0142\u0105cz",
    "app_bell_turn_off_question": "Wy\u0142\u0105czy\u0107 Mudita Harmony?",

M module-apps/apps-common/widgets/DateSetSpinner.cpp => module-apps/apps-common/widgets/DateSetSpinner.cpp +77 -173
@@ 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 "DateSetSpinner.hpp"


@@ 16,8 16,9 @@ namespace gui
        constexpr std::uint8_t step    = 1;
        constexpr std::uint8_t dayMin  = 1;
        constexpr std::uint8_t dayMax  = 31;
        constexpr auto focusFontName   = style::window::font::large;
        constexpr auto noFocusFontName = style::window::font::largelight;
        constexpr std::uint8_t monthMin = 1;
        constexpr std::uint8_t monthMax = 12;
        constexpr auto fontName         = style::window::font::supersizeme;

        void setFont(TextFixedSize *elem, const std::string &fontName)
        {


@@ 26,221 27,124 @@ namespace gui
            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}
    DateSetSpinner::DateSetSpinner(
        Item *parent, Type type, TextFixedSize *title, Length x, Length y, Length w, Length h)
        : HBox(parent, x, y, w, h), type{type}, title{title}
    {
        constexpr std::uint8_t monthMin = 1;
        constexpr std::uint8_t monthMax = 12;

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

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

        month->onValueChanged = [this](auto) { correctDayOfMonth(); };
        year->onValueChanged  = [this](auto) { correctDayOfMonth(); };
        switch (type) {
        case Type::year:
            attachDateField(
                year,
                U16IntegerSpinnerFixed::range{utils::time::Locale::min_years, utils::time::Locale::max_years, step});
            year->onValueChanged = [this](auto) { updateDate(); };
            setFocusItem(year);
            break;
        case Type::month:
            attachDateField(dayMonth, U8IntegerSpinnerFixed::range{monthMin, monthMax, step});
            dayMonth->onValueChanged = [this](auto) { updateDate(); };
            setFocusItem(dayMonth);
            break;
        case Type::day:
            attachDateField(dayMonth, U8IntegerSpinnerFixed::range{dayMin, dayMax, step});
            dayMonth->onValueChanged = [this](auto) { updateDate(); };
            setFocusItem(dayMonth);
            break;
        }

        resizeItems();

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

        updateFocus(year);
        applySizeRestrictions();
    }

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

    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()));
        this->date = date;
        switch (type) {
        case Type::year:
            year->set_value(static_cast<int>(date.year()));
            break;
        case Type::month:
            dayMonth->set_value(static_cast<unsigned>(date.month()));
            break;
        case Type::day:
            const auto lastDayInMonth =
                static_cast<unsigned>((date::year(date.year()) / date::month(date.month()) / date::last).day());
            const auto day = static_cast<unsigned>(date.day());
            dayMonth->set_range({dayMin, static_cast<std::uint8_t>(lastDayInMonth), step});
            dayMonth->set_value(std::min(day, lastDayInMonth));
            break;
        }
        applySizeRestrictions();
    }

    void DateSetSpinner::updateDate()
    {
        switch (type) {
        case Type::year:
            date = date::year(year->get_value()) / date::month(date.month()) / date::day(date.day());
            break;
        case Type::month:
            date = date::year(date.year()) / date::month(dayMonth->get_value()) / date::day(date.day());
            break;
        case Type::day:
            date = date::year(date.year()) / date::month(date.month()) / date::day(dayMonth->get_value());
            break;
        }
    }

    void DateSetSpinner::applySizeRestrictions()
    {
        day->setMinimumWidthToFitText();
        firstSlash->setMinimumWidthToFitText();
        month->setMinimumWidthToFitText();
        secondSlash->setMinimumWidthToFitText();
        year->setMinimumWidthToFitText();
        if (year != nullptr) {
            year->setMinimumWidthToFitText();
        }
        if (dayMonth != nullptr) {
            dayMonth->setMinimumWidthToFitText();
        }

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

        HBox::informContentChanged();
    }

    std::uint32_t DateSetSpinner::getWidgetMinimumAreaWidth()
    std::uint32_t DateSetSpinner::getWidgetMinimumAreaWidth() const
    {
        return day->widgetMinimumArea.w + firstSlash->widgetMinimumArea.w + month->widgetMinimumArea.w +
               secondSlash->widgetMinimumArea.w + year->widgetMinimumArea.w;
        return (type == Type::year) ? year->widgetMinimumArea.w : dayMonth->widgetMinimumArea.w;
    }

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

    template <typename Spinner>
    void DateSetSpinner::attachDateField(Spinner *&field, typename Spinner::range &&range)
    {
        field = new Spinner(std::move(range), Boundaries::Continuous);
        setFont(field, noFocusFontName);
        setFont(field, fontName);
        field->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        field->setEdges(RectangleEdge::None);
        field->setPenFocusWidth(4);
        field->setPenFocusWidth(0);
        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::correctDayOfMonth()
    {
        const 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);
        }

        const auto currentValue = day->get_value();
        day->set_range({dayMin, static_cast<std::uint8_t>(dayCountInMonth), step});
        day->set_value(currentValue);
    }

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

    bool DateSetSpinner::handleRightFunctionKey()
    {
        if (focusItem == month) {
            updateFocus(year);
            return true;
        }
        if (focusItem == day) {
            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 */

M module-apps/apps-common/widgets/DateSetSpinner.hpp => module-apps/apps-common/widgets/DateSetSpinner.hpp +20 -19
@@ 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


@@ 15,31 15,32 @@ namespace gui
    class DateSetSpinner : public HBox
    {
      public:
        DateSetSpinner(Item *parent, TextFixedSize *titleBox, Length x, Length y, Length w, Length h);
        enum class Type
        {
            year,
            month,
            day
        };

        date::year_month_day getDate();
        DateSetSpinner(Item *parent, Type type, TextFixedSize *titleBox, Length x, Length y, Length w, Length h);

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

      private:
        void applySizeRestrictions();
        std::uint32_t getWidgetMinimumAreaWidth();
        std::uint32_t getWidgetMinimumAreaWidth() const;
        std::uint16_t getFontHeight(const std::string &fontName) const;

        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);
        void updateDate();
        bool onInput(const InputEvent &inputEvent) override;
        void correctDayOfMonth();
        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;

        Type type;
        date::year_month_day date;
        TextFixedSize *title{nullptr};
        U16IntegerSpinnerFixed *year{nullptr};
        U8IntegerSpinnerFixed *dayMonth{nullptr};
    };
} /* namespace gui */

M module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp => module-apps/apps-common/widgets/TimeSetFmtSpinner.hpp +6 -6
@@ 83,13 83,13 @@ namespace gui
        auto handleRightFunctionKey() -> bool;
        void handleContentChanged() override;

        TimeSetSpinner *timeSetSpinner = nullptr;
        StringSpinner *fmt             = nullptr;
        EditMode editMode              = EditMode::Edit;
        std::string focusFontName      = style::window::font::supersizeme;
        std::string noFocusFontName    = style::window::font::supersizemelight;
        TimeSetSpinner *timeSetSpinner{nullptr};
        StringSpinner *fmt{nullptr};
        EditMode editMode{EditMode::Edit};
        std::string focusFontName{style::window::font::supersizeme};
        std::string noFocusFontName{style::window::font::supersizemelight};

        utils::time::Locale::TimeFormat timeFormat = utils::time::Locale::TimeFormat::FormatTime12H;
        utils::time::Locale::TimeFormat timeFormat{utils::time::Locale::TimeFormat::FormatTime12H};
    };

} // namespace gui

M module-utils/time/time/dateCommon.hpp => module-utils/time/time/dateCommon.hpp +24 -0
@@ 375,6 375,30 @@ inline TimePoint nextTimePointFromHHMM(std::chrono::hours hours, std::chrono::mi
    return GetFollowingDayTime(nextTime, from);
}

/// @brief Time conversion to date in DDMM format
/// @param time - a pointer to a time structure
/// @return date string in DD/MM format
inline std::string GetDateInDDMMFormat(const struct tm *time)
{
    std::stringstream ss;
    ss << std::setfill('0') << std::setw(2) << time->tm_mday;
    ss << '/';
    ss << std::setfill('0') << std::setw(2) << (time->tm_mon + 1);
    return ss.str();
}

/// @brief Time conversion to date in MMDD format
/// @param time - a pointer to a time structure
/// @return date string in MM/DD format
inline std::string GetDateInMMDDFormat(const struct tm *time)
{
    std::stringstream ss;
    ss << std::setfill('0') << std::setw(2) << (time->tm_mon + 1);
    ss << '/';
    ss << std::setfill('0') << std::setw(2) << time->tm_mday;
    return ss.str();
}

inline std::string createUID()
{
    constexpr auto bufferSize = 16;

M products/BellHybrid/apps/application-bell-settings/CMakeLists.txt => products/BellHybrid/apps/application-bell-settings/CMakeLists.txt +2 -0
@@ 50,6 50,7 @@ target_sources(application-bell-settings

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


@@ 92,6 93,7 @@ target_sources(application-bell-settings

        widgets/TemperatureUnitListItem.hpp
        widgets/TimeFormatSetListItem.hpp
        widgets/DateFormatSetListItem.hpp
        widgets/TimeSetListItem.hpp
        widgets/AboutYourBellListItem.hpp
        widgets/DialogYesNo.hpp

M products/BellHybrid/apps/application-bell-settings/data/BellSettingsStyle.hpp => products/BellHybrid/apps/application-bell-settings/data/BellSettingsStyle.hpp +6 -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


@@ 22,6 22,11 @@ namespace gui
            inline constexpr auto font = style::window::font::supersizeme;
        } // namespace time_fmt_set_list_item

        namespace date_fmt_set_list_item
        {
            inline constexpr auto font = style::window::font::supersizeme;
        } // namespace date_fmt_set_list_item

        namespace top_text
        {
            inline constexpr auto font = style::window::font::largelight;

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


@@ 13,6 13,7 @@ namespace gui
    class DateSetListItem;
    class TimeSetListItem;
    class TimeFormatSetListItem;
    class DateFormatSetListItem;
    class TemperatureUnitListItem;
} // namespace gui



@@ 38,13 39,17 @@ namespace app::bell_settings

      protected:
        app::ApplicationCommon *application{};
        gui::DateSetListItem *dateSetListItem{};
        gui::DateSetListItem *yearSetListItem{};
        gui::DateSetListItem *monthSetListItem{};
        gui::DateSetListItem *daySetListItem{};
        gui::TimeSetListItem *timeSetListItem{};
        gui::DateFormatSetListItem *dateFmtSetListItem{};
        gui::TimeFormatSetListItem *timeFmtSetListItem{};
        gui::TemperatureUnitListItem *temperatureUnitListItem{};

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

    class DateTimeUnitsModelFactoryResetValues : public DateTimeUnitsModel

M products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp => products/BellHybrid/apps/application-bell-settings/models/DateTimeUnitsModel.cpp +65 -30
@@ 4,6 4,7 @@
#include "models/DateTimeUnitsModel.hpp"
#include "widgets/DateSetListItem.hpp"
#include "widgets/TimeFormatSetListItem.hpp"
#include "widgets/DateFormatSetListItem.hpp"
#include "widgets/TimeSetListItem.hpp"
#include "widgets/TemperatureUnitListItem.hpp"
#include "ProductConfig.hpp"


@@ 45,26 46,50 @@ namespace app::bell_settings

    void DateTimeUnitsModel::createData()
    {
        dateSetListItem = new gui::DateSetListItem();
        internalData.push_back(dateSetListItem);
        yearSetListItem = new gui::YearSetListItem(utils::translate("app_settings_title_year"));
        internalData.push_back(yearSetListItem);

        timeFmtSetListItem = new gui::TimeFormatSetListItem(
            0, 0, 0, 0, utils::translate("app_bell_settings_time_units_time_fmt_top_message"));
        internalData.push_back(timeFmtSetListItem);
        monthSetListItem = new gui::MonthSetListItem(utils::translate("app_settings_title_month"));
        internalData.push_back(monthSetListItem);

        daySetListItem = new gui::DaySetListItem(utils::translate("app_settings_title_day"));
        internalData.push_back(daySetListItem);

        dateFmtSetListItem = new gui::DateFormatSetListItem(
            0, 0, 0, 0, utils::translate("app_bell_settings_time_units_date_fmt_top_message"));
        internalData.push_back(dateFmtSetListItem);

        timeSetListItem =
            new gui::TimeSetListItem(0U, 0U, 0, 0, utils::translate("app_bell_settings_time_units_time_message"));
        internalData.push_back(timeSetListItem);

        timeFmtSetListItem->onNextCallback = [this](gui::Item &) {
            timeSetListItem->timeSetFmtSpinner->setTimeFormat(timeFmtSetListItem->getTimeFmt());
        };
        timeFmtSetListItem = new gui::TimeFormatSetListItem(
            0, 0, 0, 0, utils::translate("app_bell_settings_time_units_time_fmt_top_message"));
        internalData.push_back(timeFmtSetListItem);

#if CONFIG_ENABLE_TEMP == 1
        temperatureUnitListItem = new gui::TemperatureUnitListItem(utils::translate("app_bell_settings_temp_scale"));
        internalData.push_back(temperatureUnitListItem);
#endif

        yearSetListItem->onNextCallback = [this](gui::Item &) {
            const auto date = yearSetListItem->dateSetSpinner->getDate();
            monthSetListItem->dateSetSpinner->setDate(date);
            daySetListItem->dateSetSpinner->setDate(date);
        };

        monthSetListItem->onNextCallback = [this](gui::Item &) {
            const auto date = monthSetListItem->dateSetSpinner->getDate();
            daySetListItem->dateSetSpinner->setDate(date);
            yearSetListItem->dateSetSpinner->setDate(date);
        };

        daySetListItem->onNextCallback = [this](gui::Item &) {
            const auto date = daySetListItem->dateSetSpinner->getDate();
            yearSetListItem->dateSetSpinner->setDate(date);
            monthSetListItem->dateSetSpinner->setDate(date);
        };

        for (auto item : internalData) {
            item->deleteByList = false;
        }


@@ 77,38 102,42 @@ namespace app::bell_settings

    void DateTimeUnitsModel::saveData()
    {
        const auto date   = dateSetListItem->dateSetSpinner->getDate();
        const auto date       = daySetListItem->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 hour       = timeSetListItem->timeSetSpinner->getHour();
        const auto minute     = timeSetListItem->timeSetSpinner->getMinute();
        const auto timeFormat = timeFmtSetListItem->getTimeFmt();
        const auto dateFormat = dateFmtSetListItem->getDateFmt();

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

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

        sendRtcUpdateTimeMessage(newTime);
        sendTimeFmtUpdateMessage(fmt);
        sendTimeFmtUpdateMessage(timeFormat);
        sendDateFmtUpdateMessage(dateFormat);
    }

    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(
        const auto dateFormat = stm::api::dateFormat();
        timeSetListItem->timeSetSpinner->setTime(now);
        yearSetListItem->dateSetSpinner->setDate(
            date::year_month_day{date::floor<date::days>(std::chrono::system_clock::now())});

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

    auto DateTimeUnitsModel::requestRecords(uint32_t offset, uint32_t limit) -> void


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

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

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


@@ 156,13 191,13 @@ namespace app::bell_settings

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

        dateSetListItem->dateSetSpinner->setDate(factoryResetDate);
        timeSetListItem->timeSetFmtSpinner->setTimeFormat(factoryRestTimeFmt);
        timeSetListItem->timeSetFmtSpinner->setHour(12);
        timeSetListItem->timeSetFmtSpinner->setMinute(0);
        timeSetListItem->timeSetFmtSpinner->set12HPeriod(gui::TimeSetFmtSpinner::Period::PM);
        timeFmtSetListItem->setTimeFmt(factoryRestTimeFmt);
        const auto factoryResetTimeFmt = utils::time::Locale::TimeFormat::FormatTime24H;
        const auto factoryResetDateFmt = utils::time::Locale::DateFormat::DD_MM_YYYY;

        yearSetListItem->dateSetSpinner->setDate(factoryResetDate);
        timeSetListItem->timeSetSpinner->setHour(12);
        timeSetListItem->timeSetSpinner->setMinute(0);
        timeFmtSetListItem->setTimeFmt(factoryResetTimeFmt);
        dateFmtSetListItem->setDateFmt(factoryResetDateFmt);
    }
} // namespace app::bell_settings

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

#include "DateFormatSetListItem.hpp"
#include "BellSettingsStyle.hpp"

#include <gui/core/FontManager.hpp>
#include <gui/core/RawFont.hpp>
#include <gui/widgets/text/Label.hpp>

#include <widgets/TimeSetFmtSpinner.hpp>

namespace
{
    constexpr auto fmtSpinner_DD_MM = "DD / MM";
    constexpr auto fmtSpinner_MM_DD = "MM / DD";
} // namespace

namespace gui
{
    DateFormatSetListItem::DateFormatSetListItem(
        Length x, Length y, Length w, Length h, const UTF8 &topDesc, const UTF8 &botDesc)
    {
        setupTopTextBox(topDesc);
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);
        setEdges(RectangleEdge::None);
        setFocusItem(body);

        dateFormat = new StringSpinner({fmtSpinner_DD_MM, fmtSpinner_MM_DD}, Boundaries::Fixed);
        dateFormat->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::center_layout_h);
        dateFormat->setFont(bell_settings_style::date_fmt_set_list_item::font);
        dateFormat->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        dateFormat->setFocusEdges(RectangleEdge::None);
        dateFormat->onValueChanged = [this](const auto &) {
            body->setMinMaxArrowsVisibility(dateFormat->is_min(), dateFormat->is_max());
        };

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

        focusChangedCallback = [&](Item &) {
            setFocusItem(focus ? body : nullptr);
            if (focus) {
                setFocusItem(body);
            }
            else {
                setFocusItem(nullptr);
                if (onNextCallback) {
                    onNextCallback(*this);
                }
            }
            return true;
        };

        inputCallback = [this](Item &, const InputEvent &inputEvent) -> bool {
            const auto ret = body->onInput(inputEvent);
            return ret;
        };
    }

    auto DateFormatSetListItem::getDateFmt() const noexcept -> utils::time::Locale::DateFormat
    {
        return (dateFormat->value() == fmtSpinner_DD_MM) ? utils::time::Locale::DateFormat::DD_MM_YYYY
                                                         : utils::time::Locale::DateFormat::MM_DD_YYYY;
    }

    auto DateFormatSetListItem::setDateFmt(utils::time::Locale::DateFormat fmt) noexcept -> void
    {
        using namespace utils::time;
        if (fmt == Locale::DateFormat::MM_DD_YYYY) {
            dateFormat->set_value(fmtSpinner_MM_DD);
        }
        else {
            dateFormat->set_value(fmtSpinner_DD_MM);
        }
        body->setMinMaxArrowsVisibility(dateFormat->is_min(), dateFormat->is_max());
    }
} // namespace gui

A products/BellHybrid/apps/application-bell-settings/widgets/DateFormatSetListItem.hpp => products/BellHybrid/apps/application-bell-settings/widgets/DateFormatSetListItem.hpp +36 -0
@@ 0,0 1,36 @@
// 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 <time/time_locale.hpp>
#include <utf8/UTF8.hpp>
#include <widgets/BellSideListItem.hpp>
#include <widgets/spinners/Spinners.hpp>

#include <functional>

namespace gui
{
    class Label;

    class DateFormatSetListItem : public gui::BellSideListItem
    {
      public:
        DateFormatSetListItem() = delete;
        DateFormatSetListItem(
            gui::Length x, gui::Length y, gui::Length w, gui::Length h, const UTF8 &topDesc, const UTF8 &botDesc = "");

        auto getDateFmt() const noexcept -> utils::time::Locale::DateFormat;
        auto setDateFmt(utils::time::Locale::DateFormat fmt) noexcept -> void;

        /// called before next SideListItem is activated
        /// @param `this` : item
        std::function<void(Item &)> onNextCallback;

      private:
        Label *bottomDescription{};
        StringSpinner *dateFormat;
    };

} // namespace gui

M products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.cpp => products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.cpp +20 -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 "DateSetListItem.hpp"


@@ 9,7 9,7 @@

namespace gui
{
    DateSetListItem::DateSetListItem() : BellSideListItem()
    DateSetListItem::DateSetListItem(const UTF8 &topDesc, DateSetSpinner::Type type) : BellSideListItem()
    {
        setMinimumSize(style::sidelistview::list_item::w, style::sidelistview::list_item::h);



@@ 19,10 19,10 @@ namespace gui
        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->setRichText(topDesc);
        title->drawUnderline(false);

        dateSetSpinner = new DateSetSpinner(body->getCenterBox(), title, 0, 0, 0, 0);
        dateSetSpinner = new DateSetSpinner(body->getCenterBox(), type, 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);


@@ 34,9 34,25 @@ namespace gui

        focusChangedCallback = [&](Item &) {
            setFocusItem(focus ? body : nullptr);
            if (focus) {
                setFocusItem(body);
            }
            else {
                setFocusItem(nullptr);
                if (onNextCallback) {
                    onNextCallback(*this);
                }
            }
            return true;
        };

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

    YearSetListItem::YearSetListItem(const UTF8 &topDesc) : DateSetListItem(topDesc, DateSetSpinner::Type::year){};

    MonthSetListItem::MonthSetListItem(const UTF8 &topDesc) : DateSetListItem(topDesc, DateSetSpinner::Type::month){};

    DaySetListItem::DaySetListItem(const UTF8 &topDesc) : DateSetListItem(topDesc, DateSetSpinner::Type::day){};

} /* namespace gui */

M products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.hpp => products/BellHybrid/apps/application-bell-settings/widgets/DateSetListItem.hpp +27 -6
@@ 1,21 1,42 @@
// 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 <widgets/BellSideListItem.hpp>

#include <string>
#include <widgets/DateSetSpinner.hpp>
#include <utf8/UTF8.hpp>

namespace gui
{
    class DateSetSpinner;

    class DateSetListItem : public BellSideListItem
    {
      public:
        DateSetSpinner *dateSetSpinner = nullptr;

        DateSetListItem();
        DateSetListItem(const UTF8 &topDesc = "", DateSetSpinner::Type type = DateSetSpinner::Type::year);

        /// called before next SideListItem is activated
        /// @param `this` : item
        std::function<void(Item &)> onNextCallback;
    };

    class YearSetListItem : public DateSetListItem
    {
      public:
        YearSetListItem(const UTF8 &topDesc = "");
    };

    class MonthSetListItem : public DateSetListItem
    {
      public:
        MonthSetListItem(const UTF8 &topDesc = "");
    };

    class DaySetListItem : public DateSetListItem
    {
      public:
        DaySetListItem(const UTF8 &topDesc = "");
    };

} /* namespace gui */

M products/BellHybrid/apps/application-bell-settings/widgets/TimeFormatSetListItem.cpp => products/BellHybrid/apps/application-bell-settings/widgets/TimeFormatSetListItem.cpp +2 -2
@@ 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 "TimeFormatSetListItem.hpp"


@@ 26,7 26,7 @@ namespace gui
        setEdges(RectangleEdge::None);
        setFocusItem(body);

        timeFormat = new StringSpinner({fmtSpinner12H, fmtSpinner24H}, Boundaries::Fixed);
        timeFormat = new StringSpinner({fmtSpinner24H, fmtSpinner12H}, Boundaries::Fixed);
        timeFormat->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::center_layout_h);
        timeFormat->setFont(bell_settings_style::time_fmt_set_list_item::font);
        timeFormat->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));

M products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.cpp => products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.cpp +12 -4
@@ 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

#include "TimeSetListItem.hpp"


@@ 7,6 7,12 @@
#include <gui/input/InputEvent.hpp>
#include <widgets/TimeSetFmtSpinner.hpp>

namespace
{
    constexpr auto focusFontName   = style::window::font::supersizeme;
    constexpr auto noFocusFontName = style::window::font::supersizemelight;
} /* namespace */

namespace gui
{
    TimeSetListItem::TimeSetListItem(


@@ 14,9 20,11 @@ namespace gui
    {
        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);
        timeSetFmtSpinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        timeSetSpinner = new TimeSetSpinner(body->getCenterBox());
        timeSetSpinner->setMaximumSize(style::bell_base_layout::w, style::bell_base_layout::h);
        timeSetSpinner->setFont(focusFontName, noFocusFontName);
        timeSetSpinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));

        setFocusItem(body);

        dimensionChangedCallback = [&](gui::Item &, const BoundingBox &newDim) -> bool {

M products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.hpp => products/BellHybrid/apps/application-bell-settings/widgets/TimeSetListItem.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


@@ 9,12 9,12 @@

namespace gui
{
    class TimeSetFmtSpinner;
    class TimeSetSpinner;

    class TimeSetListItem : public BellSideListItem
    {
      public:
        TimeSetFmtSpinner *timeSetFmtSpinner = nullptr;
        TimeSetSpinner *timeSetSpinner = nullptr;

        TimeSetListItem(gui::Length x, gui::Length y, gui::Length w, gui::Length h, std::string description);
    };

M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithDate.cpp => products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutClassicWithDate.cpp +4 -6
@@ 10,6 10,8 @@
#include <gui/widgets/text/TextFixedSize.hpp>
#include <widgets/TimeSetFmtSpinner.hpp>

#include <service-time/api/TimeSettingsApi.hpp>

namespace gui
{
    HomeScreenLayoutClassicWithDate::HomeScreenLayoutClassicWithDate(std::string name)


@@ 55,14 57,10 @@ namespace gui
    void HomeScreenLayoutClassicWithDate::setTime(std::time_t newTime)
    {
        HomeScreenLayoutClassic::setTime(newTime);

        const auto t = std::localtime(&newTime);
        std::stringstream ss;
        ss << std::setfill('0') << std::setw(2) << t->tm_mday;
        ss << '/';
        ss << std::setfill('0') << std::setw(2) << (t->tm_mon + 1);

        date->setText(ss.str());
        date->setText((stm::api::dateFormat() == utils::time::Locale::DateFormat::DD_MM_YYYY) ? GetDateInDDMMFormat(t)
                                                                                              : GetDateInMMDDFormat(t));

        if (ampm->visible) {
            const auto hours = std::chrono::hours{t->tm_hour};

M products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutVerticalWithDate.cpp => products/BellHybrid/apps/common/src/layouts/HomeScreenLayoutVerticalWithDate.cpp +8 -9
@@ 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 "layouts/HomeScreenLayoutVerticalWithDate.hpp"


@@ 11,11 11,15 @@
#include <gui/widgets/Icon.hpp>
#include <gui/widgets/text/TextFixedSize.hpp>
#include <gui/widgets/Style.hpp>
#include <time/time_constants.hpp>

#include <widgets/AlarmIcon.hpp>
#include <widgets/AlarmSetSpinner.hpp>
#include <widgets/ClockVertical.hpp>

#include <time/time_constants.hpp>
#include <time/dateCommon.hpp>
#include <service-time/api/TimeSettingsApi.hpp>

namespace gui
{
    HomeScreenLayoutVerticalWithDate::HomeScreenLayoutVerticalWithDate(std::string name)


@@ 76,15 80,10 @@ namespace gui
    auto HomeScreenLayoutVerticalWithDate::setTime(std::time_t newTime) -> void
    {
        HomeScreenLayoutVertical::setTime(newTime);

        const auto t = std::localtime(&newTime);

        std::stringstream ss;
        ss << std::setfill('0') << std::setw(2) << t->tm_mday;
        ss << '/';
        ss << std::setfill('0') << std::setw(2) << (t->tm_mon + 1);

        date->setText(ss.str());
        date->setText((stm::api::dateFormat() == utils::time::Locale::DateFormat::DD_MM_YYYY) ? GetDateInDDMMFormat(t)
                                                                                              : GetDateInMMDDFormat(t));

        if (ampm->visible) {
            const auto hours = std::chrono::hours{t->tm_hour};