~aleteoryx/muditaos

5e919448eb2af24c675bbad96669d2bc0a3afaf2 — Adam Dobrowolski 4 years ago 74fc2bf
[EGD-7501] Rrules presenter added

- handles most of our rrule manipulation needs for alarm
- unittests for transformations
M module-apps/application-alarm-clock/ApplicationAlarmClock.cpp => module-apps/application-alarm-clock/ApplicationAlarmClock.cpp +0 -1
@@ 38,7 38,6 @@ namespace app

        auto msg = dynamic_cast<db::NotificationMessage *>(msgl);
        if (msg != nullptr) {
            LOG_DEBUG("Received notification: %s", msg->to_string().c_str());
            // window-specific actions
            if (msg->interface == db::Interface::Name::AlarmEvents) {
                for (auto &[name, window] : windowsStack.windows) {

M module-apps/application-alarm-clock/CMakeLists.txt => module-apps/application-alarm-clock/CMakeLists.txt +6 -0
@@ 14,9 14,11 @@ target_sources( ${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmTimeItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmOptionsItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/CustomCheckBoxWithLabel.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/addedit/AlarmOptionRepeat.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/presenter/AlarmClockMainWindowPresenter.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/presenter/AlarmClockEditWindowPresenter.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/presenter/CustomRepeatWindowPresenter.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/presenter/AlarmPresenter.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/data/AlarmsData.cpp"
    PUBLIC
        "${CMAKE_CURRENT_LIST_DIR}/ApplicationAlarmClock.hpp"


@@ 24,3 26,7 @@ target_sources( ${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmInternalListItem.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/data/AlarmsData.hpp"
        )

if (${ENABLE_TESTS})
    add_subdirectory(tests)
endif()

M module-apps/application-alarm-clock/data/AlarmsData.hpp => module-apps/application-alarm-clock/data/AlarmsData.hpp +0 -7
@@ 7,13 7,6 @@
#include <module-db/Interface/AlarmEventRecord.hpp>
#include <SwitchData.hpp>

enum class AlarmRepeat
{
    never,
    everyday,
    weekDays
};

enum class AlarmSnooze
{
    FiveMinutes    = 5,

M module-apps/application-alarm-clock/models/AlarmsModel.cpp => module-apps/application-alarm-clock/models/AlarmsModel.cpp +2 -7
@@ 44,14 44,9 @@ namespace app::alarmClock
            return nullptr;
        }

        auto item               = new gui::AlarmItem(record);
        auto item               = new gui::AlarmItem(AlarmPresenter(record));
        item->activatedCallback = [this, record](gui::Item &) {
            if (record->enabled) {
                record->enabled = false;
            }
            else {
                record->enabled = false;
            }
            record->enabled = !record->enabled;
            alarmsRepository->update(*record, nullptr);
            return true;
        };

M module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp => module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp +6 -5
@@ 4,6 4,7 @@
#include "NewEditAlarmModel.hpp"
#include "application-alarm-clock/widgets/AlarmTimeItem.hpp"
#include "application-alarm-clock/widgets/AlarmOptionsItem.hpp"
#include "application-alarm-clock/widgets/addedit/AlarmOptionRepeat.hpp"
#include "application-alarm-clock/widgets/AlarmClockStyle.hpp"
#include <ListView.hpp>



@@ 36,7 37,7 @@ namespace app::alarmClock
        return getRecord(order);
    }

    void NewEditAlarmModel::createData()
    void NewEditAlarmModel::createData(std::shared_ptr<AlarmEventRecord> record)
    {
        auto app = application;
        assert(app != nullptr);


@@ 57,12 58,12 @@ namespace app::alarmClock
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));

        repeatOption = new gui::AlarmOptionsItem(
        internalData.push_back(new gui::AlarmOptionRepeat(
            application,
            AlarmOptionItemName::Repeat,
            AlarmPresenter(record),
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });
        internalData.push_back(repeatOption);
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));

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


@@ 74,7 75,7 @@ namespace app::alarmClock
        list->reset();
        eraseInternalData();

        createData();
        createData(record);

        for (auto &item : internalData) {
            if (item->onLoadCallback) {

M module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp => module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp +1 -1
@@ 39,7 39,7 @@ namespace app::alarmClock
        void loadData(std::shared_ptr<AlarmEventRecord> record) override;
        void saveData(std::shared_ptr<AlarmEventRecord> alarm, AlarmAction action) override;
        void loadRepeat(std::shared_ptr<AlarmEventRecord> record) override;
        void createData();
        void createData(std::shared_ptr<AlarmEventRecord> record);

        [[nodiscard]] unsigned int getMinimalItemSpaceRequired() const override;
        [[nodiscard]] unsigned int requestRecordsCount() override;

A module-apps/application-alarm-clock/presenter/AlarmPresenter.cpp => module-apps/application-alarm-clock/presenter/AlarmPresenter.cpp +145 -0
@@ 0,0 1,145 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "AlarmPresenter.hpp"
#include "log.hpp"

namespace app::alarmClock
{

    utl::Day AlarmPresenter::dayToDay(uint32_t day_no)
    {
        if (day_no == uint8_t(rrule::RRule::RRuleWeekday::SUNDAY_WEEKDAY)) {
            return utl::Sun;
        }
        return utl::Day(day_no - 1);
    }

    uint8_t AlarmPresenter::dayToDay(utl::Day day)
    {
        if (day == utl::Sun) {
            return uint8_t(rrule::RRule::RRuleWeekday::SUNDAY_WEEKDAY);
        }
        return day + 1;
    }

    uint8_t AlarmPresenter::set_bit_days()
    {
        const auto &rr = rrule::RRule(alarm->rruleText);
        uint8_t days   = 0;
        if (rr.freq != rrule::RRule::RRuleFrequency::WEEKLY_RECURRENCE) {
            return 0;
        }
        for (const auto &day : rr.byDay) {
            if (auto real = dayToDay(day); real < utl::num_days) {
                days |= 1 << real;
            }
        }
        return days;
    }

    UTF8 AlarmPresenter::getDescription()
    {
        auto setDays = set_bit_days();
        if (setDays == weekdaysMask) {
            return utils::translate("app_alarm_clock_repeat_week_days");
        }
        if (setDays == weekMask) {
            return utils::translate("app_alarm_clock_repeat_everyday");
        }
        UTF8 retval = "";
        if (setDays > 0) {
            for (unsigned int i = 0; i < utl::num_days; ++i) {
                if ((0x1 & (setDays >> i)) != 0) {
                    auto dayname = utils::time::Locale::get_short_day(i);
                    retval += dayname + ",";
                }
            }
            if (retval.length() > 0) {
                retval.removeChar(retval.length() - 1);
            }
        }
        return retval;
    }

    AlarmPresenter::Spinner AlarmPresenter::getSpinner()
    {
        auto setDays = set_bit_days();
        if (setDays == 0) {
            return Spinner::Never;
        }
        if (setDays == weekdaysMask) {
            return Spinner::Weekdays;
        }
        if (setDays == weekMask) {
            return Spinner::Weekly;
        }
        auto get_set_days_count = [&]() {
            uint8_t singleday = 0;
            for (unsigned int i = 0; i < utl::num_days; ++i) {
                singleday += 0x1 & (setDays >> i);
            }
            return singleday;
        };
        if (get_set_days_count() == 1) {
            return Spinner::OnDay;
        }
        return Spinner::Custom;
    }

    void AlarmPresenter::setSpinner(AlarmPresenter::Spinner spin,
                                    const std::function<void(AlarmPresenter::Spinner)> &cb)
    {
        switch (spin) {
        case AlarmPresenter::Spinner::Never:
            setDays({});
            break;
        case AlarmPresenter::Spinner::OnDay:
            if (cb != nullptr) {
                cb(spin);
            }
            break;
        case AlarmPresenter::Spinner::Custom:
            if (cb != nullptr) {
                cb(spin);
            }
            break;
        case AlarmPresenter::Spinner::Weekly:
            setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat, utl::Sun});
            break;
        case AlarmPresenter::Spinner::Weekdays:
            setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri});
            break;
        }
    }

    void AlarmPresenter::setDays(const std::list<utl::Day> &days)
    {
        if (days.empty()) {
            alarm->rruleText = "";
        }
        else {
            auto rr = rrule::RRule();
            // interval each week
            rr.interval = 1;
            rr.freq     = rrule::RRule::RRuleFrequency::WEEKLY_RECURRENCE;
            for (const auto &day : days) {
                rr.byDay.push_back(dayToDay(day));
            }
            alarm->rruleText = rr.parseToString();
        }
    }

    std::list<utl::Day> AlarmPresenter::getDays()
    {
        std::list<utl::Day> ret;
        auto setDays = set_bit_days();
        for (unsigned int i = 0; i < utl::num_days; ++i) {
            if ((0x1 & (setDays >> i)) != 0) {
                ret.push_back(utl::Day(i));
            }
        }
        return ret;
    }

} // namespace app::alarmClock

A module-apps/application-alarm-clock/presenter/AlarmPresenter.hpp => module-apps/application-alarm-clock/presenter/AlarmPresenter.hpp +94 -0
@@ 0,0 1,94 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "log.hpp"

#include <AlarmEventRecord.hpp>
#include <time/time_locale.hpp>
#include <rrule/rrule/rrule.hpp>
#include <utf8/UTF8.hpp>

#include <functional>
#include <list>
#include <memory>

namespace app::alarmClock
{
    typedef utils::time::Locale utl;

    class AlarmPresenter;

    class AlarmEventItem;

    class AlarmPresenter
    {
      private:
        /// our model
        std::shared_ptr<AlarmEventRecord> alarm;
        /// view managed

      protected:
        /// changes day from lib to our days from monday
        utl::Day dayToDay(uint32_t day_no);
        /// changes day from our days to lib format
        uint8_t dayToDay(utl::Day);
        uint8_t set_bit_days();
        static constexpr uint8_t weekdaysMask = 0b0111110;
        static constexpr uint8_t weekMask     = 0b1111111;

      public:
        enum class Spinner
        {
            Never,    /// we do not have any events set
            OnDay,    /// we have event on some - single day
            Custom,   /// there is some custom setup i.e. weekly + sunday - monday
            Weekly,   /// everyday occurrence
            Weekdays, /// every monday to friday occurrence
        };

        virtual ~AlarmPresenter() = default;
        explicit AlarmPresenter(std::shared_ptr<AlarmEventRecord> alarm) : alarm(std::move(alarm)){};

        /// get description for days in week as a coma separated string days, or weekday etc
        UTF8 getDescription();
        /// get information what time of occurrences we have in rrule
        /// please see `enum::Spinner` for more details
        Spinner getSpinner();
        /// set proper rrule from spinner
        void setSpinner(Spinner, const std::function<void(Spinner)> &cb);

        /// set values in model based on list of Enums (Mon, Tue etc)
        void setDays(const std::list<utl::Day> &days);
        /// get values in model as a list
        std::list<utl::Day> getDays();

        /// if rrule is empty we can safely assume that there is no recurrence in the event
        bool hasRecurrence() const
        {
            return alarm && !alarm->rruleText.empty();
        }

        const auto &getAlarm()
        {
            return alarm;
        }
    };

    class AlarmEventItem
    {
        AlarmPresenter p;

      protected:
        auto &presenter()
        {
            return p;
        }

      public:
        explicit AlarmEventItem(AlarmPresenter p) : p(p)
        {}
    };

} // namespace app::alarmClock

A module-apps/application-alarm-clock/tests/CMakeLists.txt => module-apps/application-alarm-clock/tests/CMakeLists.txt +9 -0
@@ 0,0 1,9 @@
add_catch2_executable(
    NAME
        app-alarm
    SRCS
        tests-main.cpp
        test-AlarmPresenter.cpp
    LIBS
        module-apps
)

A module-apps/application-alarm-clock/tests/test-AlarmPresenter.cpp => module-apps/application-alarm-clock/tests/test-AlarmPresenter.cpp +139 -0
@@ 0,0 1,139 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include <application-alarm-clock/presenter/AlarmPresenter.hpp>

using namespace app::alarmClock;

class PubPresenter : public AlarmPresenter
{
  public:
    PubPresenter() : AlarmPresenter(std::make_shared<AlarmEventRecord>())
    {}

    utl::Day dayToDay(uint32_t day_no)
    {
        return AlarmPresenter::dayToDay(day_no);
    }

    uint8_t dayToDay(utl::Day day)
    {
        return AlarmPresenter::dayToDay(day);
    }
};

TEST_CASE("transformations test")
{
    auto p = PubPresenter();
    REQUIRE(p.dayToDay(uint8_t(rrule::RRule::RRuleWeekday::MONDAY_WEEKDAY) == utl::Mon));
}

TEST_CASE("empty rrule")
{
    std::shared_ptr<AlarmEventRecord> alarm;
    AlarmPresenter presenter(alarm);
    REQUIRE(!presenter.hasRecurrence());
}

/// libcal has days set in format starting with Sunday on 1,
TEST_CASE("recurrence rule get & set")
{
    auto alarm = std::make_shared<AlarmEventRecord>();
    AlarmPresenter presenter(alarm);

    SECTION("no recurrence")
    {
        REQUIRE(!presenter.hasRecurrence());
    }

    SECTION("everyday recurrence")
    {
        alarm->rruleText = "FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;INTERVAL=1";
        REQUIRE(presenter.hasRecurrence());
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::Weekly);
        REQUIRE(presenter.getDays().size() == 7);
    }

    SECTION("clearing recurrence")
    {
        alarm->rruleText = "FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;INTERVAL=1";
        presenter.setDays({});
        REQUIRE(presenter.getDays().empty());
    }

    SECTION("changing recurrence")
    {
        alarm->rruleText = "FREQ=WEEKLY;BYDAY=MO,FR;INTERVAL=1";
        presenter.setDays({utl::Tue});
        REQUIRE(presenter.getDays().size() == 1);
        REQUIRE(presenter.getDays().front() == utl::Tue);
    }

    SECTION("changing recurrence to sunday")
    {
        presenter.setDays({utl::Sun});
        REQUIRE(presenter.getDays().size() == 1);
        REQUIRE(presenter.getDays().front() == utl::Sun);
    }
}

TEST_CASE("getSpinner")
{
    auto alarm = std::make_shared<AlarmEventRecord>();
    AlarmPresenter presenter(alarm);

    SECTION("nothing")
    {
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::Never);
    }

    SECTION("weekdays")
    {
        presenter.setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri});
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::Weekdays);
    }

    SECTION("week")
    {
        presenter.setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat, utl::Sun});
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::Weekly);
    }

    SECTION("custom")
    {
        presenter.setDays({utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat, utl::Sun});
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::Custom);
    }

    SECTION("signle day")
    {
        presenter.setDays({utl::Tue});
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::OnDay);
    }
}

TEST_CASE("setSpinner")
{
    auto alarm = std::make_shared<AlarmEventRecord>();
    AlarmPresenter presenter(alarm);

    SECTION("nothing")
    {
        presenter.setSpinner(AlarmPresenter::Spinner::Never, nullptr);
        REQUIRE(presenter.getDays().empty());
    }

    SECTION("weekdays")
    {
        presenter.setSpinner(AlarmPresenter::Spinner::Weekdays, nullptr);
        REQUIRE(presenter.getDays() == std::list<utl::Day>{utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri});
    }

    SECTION("week")
    {
        presenter.setSpinner(AlarmPresenter::Spinner::Weekly, nullptr);
        REQUIRE(presenter.getDays() ==
                std::list<utl::Day>{utl::Sun, utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat});
    }
}

A module-apps/application-alarm-clock/tests/tests-main.cpp => module-apps/application-alarm-clock/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch2/catch.hpp>

M module-apps/application-alarm-clock/widgets/AlarmItem.cpp => module-apps/application-alarm-clock/widgets/AlarmItem.cpp +6 -6
@@ 10,9 10,8 @@

namespace gui
{
    AlarmItem::AlarmItem(std::shared_ptr<AlarmEventRecord> record) : alarm(std::move(record))
    AlarmItem::AlarmItem(app::alarmClock::AlarmPresenter p) : AlarmEventItem(p)
    {
        assert(alarm != nullptr);
        setMinimumSize(style::window::default_body_width, style::alarmClock::window::item::height);
        setMargins(gui::Margins(0, style::margins::small, 0, style::alarmClock::window::item::botMargin));



@@ 52,11 51,12 @@ namespace gui

    void AlarmItem::setAlarm()
    {
        timeLabel->setText(TimePointToLocalizedTimeString(alarm->startDate));
        if (alarm->enabled) {
            onOffImage->switchState(ButtonState::Off);
        timeLabel->setText(TimePointToLocalizedTimeString(presenter().getAlarm()->startDate));
        onOffImage->switchState(presenter().getAlarm()->enabled ? ButtonState::Off : ButtonState::On);

        if (presenter().hasRecurrence()) {
            periodLabel->setText(presenter().getDescription());
        }
        if (alarm->rruleText != "") {}

        if (periodLabel->getText().empty()) {
            periodLabel->setMaximumSize(0, 0);

M module-apps/application-alarm-clock/widgets/AlarmItem.hpp => module-apps/application-alarm-clock/widgets/AlarmItem.hpp +3 -3
@@ 9,20 9,20 @@
#include <ListItem.hpp>
#include <BoxLayout.hpp>
#include <Label.hpp>
#include <application-alarm-clock/presenter/AlarmPresenter.hpp>

namespace gui
{
    class AlarmItem : public ListItem
    class AlarmItem : public ListItem, public app::alarmClock::AlarmEventItem
    {
        gui::HBox *hBox              = nullptr;
        gui::VBox *vBox              = nullptr;
        gui::ButtonOnOff *onOffImage = nullptr;
        gui::Label *timeLabel        = nullptr;
        gui::Label *periodLabel      = nullptr;
        std::shared_ptr<AlarmEventRecord> alarm;
        void setAlarm();

      public:
        explicit AlarmItem(std::shared_ptr<AlarmEventRecord> record);
        explicit AlarmItem(app::alarmClock::AlarmPresenter);
    };
} // namespace gui

M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp +4 -4
@@ 226,10 226,10 @@ namespace gui
                alarm->snoozeDuration = static_cast<uint32_t>(snoozeOptions[actualVectorIndex]);
                break;
            }
            case AlarmOptionItemName::Repeat: {
            case AlarmOptionItemName::Repeat:
                // repead handled in class
                break;
            }
            }
        };

        onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {


@@ 258,10 258,10 @@ namespace gui
                }
                break;
            }
            case AlarmOptionItemName::Repeat: {
            case AlarmOptionItemName::Repeat:
                // repead handled in class
                break;
            }
            }
            optionLabel->setText(optionsNames[actualVectorIndex]);
        };


M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp +1 -0
@@ 19,6 19,7 @@ namespace gui

    class AlarmOptionsItem : public AlarmInternalListItem
    {
      protected:
        enum class MusicStatus
        {
            Stop,

A module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.cpp => module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.cpp +48 -0
@@ 0,0 1,48 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "AlarmOptionRepeat.hpp"
#include <time/time/time_locale.hpp>

namespace gui
{
    AlarmOptionRepeat::AlarmOptionRepeat(app::ApplicationCommon *app,
                                         AlarmOptionItemName name,
                                         app::alarmClock::AlarmPresenter p,
                                         std::function<void(const UTF8 &text)> mode,
                                         std::function<void()> restore)
        : AlarmOptionsItem(app, name, std::move(mode), std::move(restore)), app::alarmClock::AlarmEventItem(p)
    {
        onSaveCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) { onSave(alarm); };
        onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) { onLoad(alarm); };
    }

    void AlarmOptionRepeat::onSave(std::shared_ptr<AlarmEventRecord> alarm)
    {
        typedef utils::time::Locale::Day utl;
        static int i = 0;
        if (i == 0) {
            presenter().setDays({utl::Mon, utl::Tue});
            i++;
        }
        else if (i == 1) {
            presenter().setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri});
            i++;
        }
        else if (i == 2) {
            presenter().setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat, utl::Sun});
            i++;
        }
        else if (i == 3) {
            presenter().setDays({});
            i = 0;
        }
        LOG_DEBUG("TEXT: %s .... %s", presenter().getDescription().c_str(), alarm->rruleText.c_str());
    }

    void AlarmOptionRepeat::onLoad(std::shared_ptr<AlarmEventRecord> alarm)
    {
        LOG_INFO("loading option repeat");
        descriptionLabel->setText(presenter().getDescription());
    }
}; // namespace gui

A module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.hpp => module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.hpp +29 -0
@@ 0,0 1,29 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "log.hpp"
#pragma once

#include <application-alarm-clock/widgets/AlarmOptionsItem.hpp>
#include <application-alarm-clock/presenter/AlarmPresenter.hpp>

namespace gui
{
    class AlarmOptionRepeat : public AlarmOptionsItem, public app::alarmClock::AlarmEventItem
    {
      public:
        AlarmOptionRepeat(app::ApplicationCommon *app,
                          AlarmOptionItemName name,
                          app::alarmClock::AlarmPresenter p,
                          std::function<void(const UTF8 &text)> mode = nullptr,
                          std::function<void()> restore              = nullptr);

        /// Will be changed - just example on how to save data
        /// sets value by presenter to model - to be set in db
        void onSave(std::shared_ptr<AlarmEventRecord> alarm);
        /// Will be changed - just example on usage how to load data
        /// loads value to UI
        void onLoad(std::shared_ptr<AlarmEventRecord> alarm);
    };

} /* namespace gui */

M module-utils/rrule/rrule/rrule.hpp => module-utils/rrule/rrule/rrule.hpp +2 -0
@@ 27,6 27,8 @@ namespace rrule
            YEARLY_RECURRENCE,
            NO_RECURRENCE,
        };

        /// Information: by definition weekday = monday - friday, therefore our weekday is not a proper weekday
        enum class RRuleWeekday
        {
            NO_WEEKDAY,

M module-utils/time/time/time_locale.hpp => module-utils/time/time/time_locale.hpp +4 -1
@@ 19,7 19,10 @@ namespace utils

        class Locale
        {
            static const int num_days       = 7;
          public:
            static constexpr int num_days = 7;

          private:
            static const int num_monts      = 12;
            static const int num_formatters = 9;
            // imo it would be nicer to have datetime locales in different json with thiny bit nicer and more effective