~aleteoryx/muditaos

e3a057e15cb7884b2bc7ad681bcab5bdaeb68098 — Przemyslaw Brudny 4 years ago cd0cc6d
[EGD-7310] Alarm GUI and features update

Updated AlarmOptionsItem to use UTF8Spinner.
Created specialized widgets to cover options
sets. Updated GenericSpinner to handle Pure
navigation and content swap. Updated Alarm
RRule code to work with Custom Days selection
and new widgets. Added Tests. General GUI
stylistic fixes. Increased app Alarm Clock
and service Time stack sizes.
58 files changed, 986 insertions(+), 937 deletions(-)

M image/assets/lang/Deutsch.json
M image/assets/lang/English.json
M image/assets/lang/Espanol.json
M image/assets/lang/Francais.json
M image/assets/lang/Polski.json
M image/assets/lang/Svenska.json
M module-apps/application-alarm-clock/ApplicationAlarmClock.cpp
M module-apps/application-alarm-clock/ApplicationAlarmClock.hpp
M module-apps/application-alarm-clock/CMakeLists.txt
D module-apps/application-alarm-clock/data/AlarmsData.cpp
M module-apps/application-alarm-clock/data/AlarmsData.hpp
M module-apps/application-alarm-clock/models/AlarmsModel.cpp
M module-apps/application-alarm-clock/models/CustomRepeatModel.cpp
M module-apps/application-alarm-clock/models/CustomRepeatModel.hpp
M module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp
M module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp
M module-apps/application-alarm-clock/presenter/AlarmClockEditWindowPresenter.cpp
M module-apps/application-alarm-clock/presenter/AlarmClockEditWindowPresenter.hpp
R module-apps/application-alarm-clock/presenter/{AlarmPresenter => AlarmRRulePresenter}.cpp
R module-apps/application-alarm-clock/presenter/{AlarmPresenter => AlarmRRulePresenter}.hpp
M module-apps/application-alarm-clock/presenter/CustomRepeatWindowPresenter.cpp
M module-apps/application-alarm-clock/presenter/CustomRepeatWindowPresenter.hpp
M module-apps/application-alarm-clock/tests/test-AlarmPresenter.cpp
M module-apps/application-alarm-clock/widgets/AlarmClockStyle.hpp
M module-apps/application-alarm-clock/widgets/AlarmItem.cpp
M module-apps/application-alarm-clock/widgets/AlarmItem.hpp
A module-apps/application-alarm-clock/widgets/AlarmMusicOptionsItem.cpp
A module-apps/application-alarm-clock/widgets/AlarmMusicOptionsItem.hpp
M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp
M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp
A module-apps/application-alarm-clock/widgets/AlarmRRuleOptionsItem.cpp
A module-apps/application-alarm-clock/widgets/AlarmRRuleOptionsItem.hpp
A module-apps/application-alarm-clock/widgets/AlarmSnoozeOptionsItem.cpp
A module-apps/application-alarm-clock/widgets/AlarmSnoozeOptionsItem.hpp
M module-apps/application-alarm-clock/widgets/AlarmTimeItem.cpp
M module-apps/application-alarm-clock/widgets/CustomCheckBoxWithLabel.cpp
M module-apps/application-alarm-clock/widgets/CustomCheckBoxWithLabel.hpp
D module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.cpp
D module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.hpp
M module-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp
M module-apps/application-alarm-clock/windows/CustomRepeatWindow.cpp
M module-apps/application-alarm-clock/windows/CustomRepeatWindow.hpp
M module-apps/application-alarm-clock/windows/NewEditAlarmWindow.cpp
M module-apps/application-calendar/models/MonthModel.cpp
M module-apps/apps-common/CMakeLists.txt
M module-apps/apps-common/widgets/DateWidget.cpp
A module-apps/apps-common/widgets/TextSpinnerBox.cpp
A module-apps/apps-common/widgets/TextSpinnerBox.hpp
M module-apps/apps-common/widgets/spinners/GenericSpinner.hpp
M module-apps/apps-common/widgets/spinners/SpinnerContents.hpp
M module-apps/apps-common/widgets/spinners/SpinnerPolicies.hpp
M module-gui/gui/Common.hpp
M module-gui/gui/widgets/BoxLayout.cpp
M module-gui/gui/widgets/ImageBox.cpp
M module-gui/gui/widgets/ImageBox.hpp
M module-gui/gui/widgets/Style.hpp
M module-services/service-time/ServiceTime.hpp
M module-utils/time/time/time_locale.hpp
M image/assets/lang/Deutsch.json => image/assets/lang/Deutsch.json +1 -1
@@ 3,6 3,7 @@
  "common_open": "ÖFFNEN",
  "common_call": "ANRUFEN",
  "common_save": "SPEICHERN",
  "common_edit": "ÄNDERN",
  "common_send": "SENDEN",
  "common_confirm": "BESTÄTIGEN",
  "common_select": "AUSWÄHLEN",


@@ 108,7 109,6 @@
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_play_pause": "PAUSE",
  "app_alarm_clock_edit": "ÄNDERN",
  "app_calendar_title_main": "Kalender",
  "app_calendar_options_edit": "Ändern",
  "app_calendar_options_delete": "Löschen",

M image/assets/lang/English.json => image/assets/lang/English.json +3 -2
@@ 3,6 3,7 @@
  "common_open": "OPEN",
  "common_call": "CALL",
  "common_save": "SAVE",
  "common_edit": "EDIT",
  "common_import": "IMPORT",
  "common_send": "SEND",
  "common_confirm": "CONFIRM",


@@ 101,7 102,7 @@
  "app_alarm_clock_title_main": "Alarm clock",
  "app_alarm_clock_repeat_never": "Never",
  "app_alarm_clock_repeat_everyday": "Everyday",
  "app_alarm_clock_repeat_week_days": "Week Days",
  "app_alarm_clock_repeat_week_days": "Weekdays",
  "app_alarm_clock_repeat_custom": "Custom",
  "app_alarm_clock_no_alarms_information": "<text align='center' color='9'>No alarms yet.<p>Press <b>left arrow</b> to add new.</p></text>",
  "app_alarm_clock_options_title": "Options",


@@ 114,11 115,11 @@
  "app_alarm_clock_sound": "Sound",
  "app_alarm_clock_snooze": "Snooze",
  "app_alarm_clock_repeat": "Repeat",
  "app_alarm_clock_no_snooze": "None",
  "app_alarm_clock_snooze_5_min": "5 min",
  "app_alarm_clock_snooze_10_min": "10 min",
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_edit": "EDIT",
  "app_alarm_clock_custom_repeat_title": "Custom repeat",
  "app_calendar_title_main": "Calendar",
  "app_calendar_all_day": "All day",

M image/assets/lang/Espanol.json => image/assets/lang/Espanol.json +1 -1
@@ 3,6 3,7 @@
  "common_open": "ABRIR",
  "common_call": "LLAMAR",
  "common_save": "GUARDAR",
  "common_edit": "EDITAR",
  "common_send": "ENVIAR",
  "common_confirm": "CONFIRMAR",
  "common_select": "SELECCIONAR",


@@ 108,7 109,6 @@
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_play_pause": "REPRODUCIR/PAUSA",
  "app_alarm_clock_edit": "EDITAR",
  "app_calendar_title_main": "Calendario",
  "app_calendar_options_edit": "Editar",
  "app_calendar_options_delete": "Eliminar",

M image/assets/lang/Francais.json => image/assets/lang/Francais.json +1 -1
@@ 3,6 3,7 @@
  "common_open": "OUVRIR",
  "common_call": "APPELER",
  "common_save": "SAUVEGARDER",
  "common_edit": "MODIFIER",
  "common_send": "ENVOYER",
  "common_confirm": "CONFIRMER",
  "common_select": "SÉLECTIONNER",


@@ 112,7 113,6 @@
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_play_pause": "JOUER/PAUSE",
  "app_alarm_clock_edit": "MODIFIER",
  "app_alarm_clock_custom_repeat_title": "Custom repeat",
  "app_calendar_title_main": "Calendrier",
  "app_calendar_all_day": "Toute la journée",

M image/assets/lang/Polski.json => image/assets/lang/Polski.json +1 -1
@@ 3,6 3,7 @@
  "common_open": "OTWÓRZ",
  "common_call": "ZADZWOŃ",
  "common_save": "ZAPISZ",
  "common_edit": "EDYTUJ",
  "common_send": "WYŚLIJ",
  "common_confirm": "POTWIERDŹ",
  "common_select": "WYBIERZ",


@@ 108,7 109,6 @@
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_play_pause": "ODTWARZAJ/ZATRZYMAJ",
  "app_alarm_clock_edit": "EDYTUJ",
  "app_calendar_title_main": "Kalendarz",
  "app_calendar_options_edit": "Edytuj",
  "app_calendar_options_delete": "Usuń",

M image/assets/lang/Svenska.json => image/assets/lang/Svenska.json +1 -1
@@ 3,6 3,7 @@
  "common_open": "ÖPPNA",
  "common_call": "RING",
  "common_save": "SPARA",
  "common_edit": "REDIGERA",
  "common_send": "SKICKA",
  "common_confirm": "BEKRÄFTA",
  "common_select": "VÄLJ",


@@ 101,7 102,6 @@
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_play_pause": "SPELA/PAUSA",
  "app_alarm_clock_edit": "REDIGERA",
  "app_alarm_clock_custom_repeat_title": "Välj veckodagar",
  "app_calendar_title_main": "Kalender",
  "app_calendar_all_day": "Hela dagen",

M module-apps/application-alarm-clock/ApplicationAlarmClock.cpp => module-apps/application-alarm-clock/ApplicationAlarmClock.cpp +20 -14
@@ 69,20 69,26 @@ namespace app
            auto presenter        = std::make_unique<alarmClock::AlarmClockMainWindowPresenter>(alarmsProvider);
            return std::make_unique<alarmClock::AlarmClockMainWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(
            style::alarmClock::window::name::newEditAlarm, [](ApplicationCommon *app, const std::string &name) {
                auto alarmsRepository = std::make_unique<alarmClock::AlarmsDBRepository>(app);
                auto alarmsProvider = std::make_shared<alarmClock::NewEditAlarmModel>(app, std::move(alarmsRepository));
                auto presenter      = std::make_unique<alarmClock::AlarmClockEditWindowPresenter>(alarmsProvider);
                return std::make_unique<alarmClock::NewEditAlarmWindow>(app, std::move(presenter));
            });
        windowsFactory.attach(
            style::alarmClock::window::name::customRepeat, [](ApplicationCommon *app, const std::string &name) {
                auto alarmsRepository = std::make_unique<alarmClock::AlarmsDBRepository>(app);
                auto alarmsProvider = std::make_shared<alarmClock::CustomRepeatModel>(app, std::move(alarmsRepository));
                auto presenter      = std::make_unique<alarmClock::CustomRepeatWindowPresenter>(alarmsProvider);
                return std::make_unique<alarmClock::CustomRepeatWindow>(app, std::move(presenter));
            });

        auto rRulePresenter = std::make_shared<alarmClock::AlarmRRulePresenter>();

        windowsFactory.attach(style::alarmClock::window::name::newEditAlarm,
                              [rRulePresenter](ApplicationCommon *app, const std::string &name) {
                                  auto alarmsRepository = std::make_unique<alarmClock::AlarmsDBRepository>(app);
                                  auto alarmsProvider   = std::make_shared<alarmClock::NewEditAlarmModel>(
                                      app, rRulePresenter, std::move(alarmsRepository), !stm::api::isTimeFormat12h());
                                  auto presenter =
                                      std::make_unique<alarmClock::AlarmClockEditWindowPresenter>(alarmsProvider);
                                  return std::make_unique<alarmClock::NewEditAlarmWindow>(app, std::move(presenter));
                              });
        windowsFactory.attach(style::alarmClock::window::name::customRepeat,
                              [rRulePresenter](ApplicationCommon *app, const std::string &name) {
                                  auto alarmsProvider =
                                      std::make_shared<alarmClock::CustomRepeatModel>(app, rRulePresenter);
                                  auto presenter =
                                      std::make_unique<alarmClock::CustomRepeatWindowPresenter>(alarmsProvider);
                                  return std::make_unique<alarmClock::CustomRepeatWindow>(app, std::move(presenter));
                              });
        windowsFactory.attach(utils::translate("app_alarm_clock_options_title"),
                              [](ApplicationCommon *app, const std::string &name) {
                                  return std::make_unique<gui::OptionWindow>(app, name);

M module-apps/application-alarm-clock/ApplicationAlarmClock.hpp => module-apps/application-alarm-clock/ApplicationAlarmClock.hpp +1 -1
@@ 16,7 16,7 @@ namespace app
                              std::string parent,
                              sys::phone_modes::PhoneMode phoneMode       = sys::phone_modes::PhoneMode::Connected,
                              sys::bluetooth::BluetoothMode bluetoothMode = sys::bluetooth::BluetoothMode::Disabled,
                              uint32_t stackDepth                         = 4096,
                              uint32_t stackDepth                         = 4096 * 2,
                              sys::ServicePriority priority               = sys::ServicePriority::Idle);

        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;

M module-apps/application-alarm-clock/CMakeLists.txt => module-apps/application-alarm-clock/CMakeLists.txt +4 -3
@@ 13,13 13,14 @@ target_sources( ${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmTimeItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmOptionsItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmSnoozeOptionsItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmMusicOptionsItem.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmRRuleOptionsItem.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"
        "${CMAKE_CURRENT_LIST_DIR}/presenter/AlarmRRulePresenter.cpp"
    PUBLIC
        "${CMAKE_CURRENT_LIST_DIR}/ApplicationAlarmClock.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmClockStyle.hpp"

D module-apps/application-alarm-clock/data/AlarmsData.cpp => module-apps/application-alarm-clock/data/AlarmsData.cpp +0 -48
@@ 1,48 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 "AlarmsData.hpp"
#include <map>

static const std::map<WeekDayIso, const char *> weekDaysAbbreviation = {{WeekDayIso::Monday, "common_mon"},
                                                                        {WeekDayIso::Tuesday, "common_tue"},
                                                                        {WeekDayIso::Wednesday, "common_wed"},
                                                                        {WeekDayIso::Thursday, "common_thu"},
                                                                        {WeekDayIso::Friday, "common_fri"},
                                                                        {WeekDayIso::Saturday, "common_sat"},
                                                                        {WeekDayIso::Sunday, "common_sun"}};

CustomRepeatValueParser::CustomRepeatValueParser(uint32_t repeatValue)
{
    OptionParser parser;
    weekDayData = parser.setWeekDayOptions(repeatValue, std::make_unique<WeekDaysRepeatData>());
}

std::string CustomRepeatValueParser::getWeekDaysText() const
{
    std::string weekDaysText;
    for (auto const &[key, val] : weekDaysAbbreviation) {
        if (weekDayData->getData(static_cast<uint32_t>(key))) {
            weekDaysText += utils::translate(val) + ", ";
        }
    }
    if (!weekDaysText.empty()) {
        weekDaysText.erase(weekDaysText.end() - 2);
    }
    return weekDaysText;
}

bool CustomRepeatValueParser::isCustomValueWeekDays() const
{
    return weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Monday)) &&
           weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Tuesday)) &&
           weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Wednesday)) &&
           weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Thursday)) &&
           weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Friday));
}

bool CustomRepeatValueParser::isCustomValueEveryday() const
{
    return isCustomValueWeekDays() && weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Saturday)) &&
           weekDayData->getData(static_cast<uint32_t>(WeekDayIso::Sunday));
}

M module-apps/application-alarm-clock/data/AlarmsData.hpp => module-apps/application-alarm-clock/data/AlarmsData.hpp +0 -38
@@ 3,42 3,15 @@

#pragma once

#include "application-calendar/data/OptionParser.hpp"
#include <module-db/Interface/AlarmEventRecord.hpp>
#include <SwitchData.hpp>

enum class AlarmSnooze
{
    FiveMinutes    = 5,
    TenMinutes     = 10,
    FifteenMinutes = 15,
    ThirtyMinutes  = 30
};

enum class AlarmOptionItemName
{
    Sound,
    Snooze,
    Repeat
};

enum class AlarmAction
{
    Add,
    Edit
};

enum class WeekDayIso
{
    Monday = date::Monday.iso_encoding() - 1,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

class AlarmRecordData : public gui::SwitchData
{
  protected:


@@ 57,14 30,3 @@ class AlarmRecordData : public gui::SwitchData
    }
};

class CustomRepeatValueParser
{
    std::unique_ptr<WeekDaysRepeatData> weekDayData;

  public:
    explicit CustomRepeatValueParser(uint32_t repeatValue);

    [[nodiscard]] std::string getWeekDaysText() const;
    [[nodiscard]] bool isCustomValueWeekDays() const;
    [[nodiscard]] bool isCustomValueEveryday() const;
};

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

        auto item               = new gui::AlarmItem(AlarmPresenter(record));
        auto item               = new gui::AlarmItem(std::make_shared<AlarmRRulePresenter>(record));
        item->activatedCallback = [this, record](gui::Item &) {
            record->enabled = !record->enabled;
            alarmsRepository->update(*record, nullptr);

M module-apps/application-alarm-clock/models/CustomRepeatModel.cpp => module-apps/application-alarm-clock/models/CustomRepeatModel.cpp +29 -21
@@ 2,16 2,16 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CustomRepeatModel.hpp"
#include "application-alarm-clock/widgets/CustomCheckBoxWithLabel.hpp"
#include "application-alarm-clock/widgets/AlarmClockStyle.hpp"

#include <application-alarm-clock/widgets/AlarmClockStyle.hpp>
#include <ListView.hpp>
#include <time/time_locale.hpp>

namespace app::alarmClock
{

    CustomRepeatModel::CustomRepeatModel(app::ApplicationCommon *app,
                                         std::shared_ptr<AbstractAlarmsRepository> alarmsRepository)
        : application(app), alarmsRepository{std::move(alarmsRepository)}
                                         std::shared_ptr<alarmClock::AlarmRRulePresenter> rRulePresenter)
        : application(app), rRulePresenter(rRulePresenter)
    {}

    unsigned int CustomRepeatModel::requestRecordsCount()


@@ 21,7 21,7 @@ namespace app::alarmClock

    unsigned int CustomRepeatModel::getMinimalItemSpaceRequired() const
    {
        return style::alarmClock::window::item::checkBox::height;
        return style::window::label::big_h + style::margins::big;
    }

    void CustomRepeatModel::requestRecords(uint32_t offset, uint32_t limit)


@@ 35,10 35,15 @@ namespace app::alarmClock
        return getRecord(order);
    }

    void CustomRepeatModel::createData(const WeekDaysRepeatData &data)
    void CustomRepeatModel::createData()
    {
        for (auto const &[key, dayName] : gui::CustomCheckBoxWithLabel::weekDays) {
            internalData.push_back(new gui::CustomCheckBoxWithLabel(application, utils::translate(dayName), data));
        auto app = application;
        for (auto const &[day, selected] : rRulePresenter->getCustomDays()) {
            internalData.push_back(new gui::CustomCheckBoxWithLabel(
                utils::translate(day),
                selected,
                [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
                [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));
        }

        for (auto &item : internalData) {


@@ 46,30 51,33 @@ namespace app::alarmClock
        }
    }

    void CustomRepeatModel::loadData(const WeekDaysRepeatData &data)
    void CustomRepeatModel::loadData()
    {
        list->reset();
        eraseInternalData();

        createData(data);
        createData();

        list->rebuildList();
    }

    std::vector<bool> CustomRepeatModel::getIsCheckedData()
    void CustomRepeatModel::saveCheckedData()
    {
        std::vector<bool> isCheckedData;
        isCheckedData.reserve(internalData.size());
        for (const auto &item : internalData) {
            if (item->onContentChangedCallback) {
                isCheckedData.push_back(item->onContentChangedCallback());
            }
            else {
                isCheckedData.push_back(false);
        std::list<utl::Day> days = {};

        for (unsigned int i = 0; i < internalData.size(); i++) {
            if (internalData[i]->isChecked()) {
                days.emplace_back(magic_enum::enum_cast<utl::Day>(i).value());
            }
        }

        return isCheckedData;
        rRulePresenter->setOption(AlarmRRulePresenter::RRuleOptions::Custom, days);
    }

    void CustomRepeatModel::eraseData()
    {
        list->reset();
        eraseInternalData();
    }

} // namespace app::alarmClock

M module-apps/application-alarm-clock/models/CustomRepeatModel.hpp => module-apps/application-alarm-clock/models/CustomRepeatModel.hpp +15 -12
@@ 3,41 3,44 @@

#pragma once

#include "application-alarm-clock/widgets/AlarmInternalListItem.hpp"
#include "application-alarm-clock/models/AlarmsRepository.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "application-calendar/data/CalendarData.hpp"
#include "Application.hpp"
#include "InternalModel.hpp"
#include <application-alarm-clock/widgets/CustomCheckBoxWithLabel.hpp>
#include <application-alarm-clock/presenter/AlarmRRulePresenter.hpp>

#include <Application.hpp>
#include <InternalModel.hpp>
#include <ListItemProvider.hpp>

namespace app::alarmClock
{
    class CustomRepeatListItemProvider : public InternalModel<gui::AlarmInternalListItem *>,
    class CustomRepeatListItemProvider : public InternalModel<gui::CustomCheckBoxWithLabel *>,
                                         public gui::ListItemProvider
    {
      public:
        CustomRepeatListItemProvider() = default;

        virtual void loadData(const WeekDaysRepeatData &data) = 0;
        virtual std::vector<bool> getIsCheckedData()          = 0;
        virtual void loadData()        = 0;
        virtual void eraseData()       = 0;
        virtual void saveCheckedData() = 0;
    };

    class CustomRepeatModel : public CustomRepeatListItemProvider
    {
        app::ApplicationCommon *application = nullptr;
        std::shared_ptr<AbstractAlarmsRepository> alarmsRepository;
        void createData(const WeekDaysRepeatData &data);
        std::shared_ptr<app::alarmClock::AlarmRRulePresenter> rRulePresenter;
        void createData();

      public:
        CustomRepeatModel(app::ApplicationCommon *app, std::shared_ptr<AbstractAlarmsRepository> alarmsRepository);
        CustomRepeatModel(app::ApplicationCommon *app, std::shared_ptr<alarmClock::AlarmRRulePresenter> rRulePresenter);

        [[nodiscard]] unsigned int getMinimalItemSpaceRequired() const override;
        [[nodiscard]] unsigned int requestRecordsCount() override;
        [[nodiscard]] gui::ListItem *getItem(gui::Order order) override;
        void requestRecords(uint32_t offset, uint32_t limit) override;

        void loadData(const WeekDaysRepeatData &data) override;
        std::vector<bool> getIsCheckedData() override;
        void loadData() override;
        void saveCheckedData() override;
        void eraseData() override;
    };
} // namespace app::alarmClock

M module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp => module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp +22 -18
@@ 2,18 2,24 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#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 <application-alarm-clock/widgets/AlarmTimeItem.hpp>
#include <application-alarm-clock/widgets/AlarmSnoozeOptionsItem.hpp>
#include <application-alarm-clock/widgets/AlarmMusicOptionsItem.hpp>
#include <application-alarm-clock/widgets/AlarmRRuleOptionsItem.hpp>
#include <application-alarm-clock/presenter/AlarmRRulePresenter.hpp>
#include <application-alarm-clock/widgets/AlarmClockStyle.hpp>

#include <ListView.hpp>

namespace app::alarmClock
{
    NewEditAlarmModel::NewEditAlarmModel(app::ApplicationCommon *app,
                                         std::shared_ptr<alarmClock::AlarmRRulePresenter> rRulePresenter,
                                         std::shared_ptr<AbstractAlarmsRepository> alarmsRepository,
                                         bool mode24H)
        : application(app), alarmsRepository{std::move(alarmsRepository)}, mode24H(mode24H)
        : application(app), alarmsRepository{std::move(alarmsRepository)}, rRulePresenter(rRulePresenter),
          mode24H(mode24H)
    {}

    unsigned int NewEditAlarmModel::requestRecordsCount()


@@ 23,7 29,7 @@ namespace app::alarmClock

    unsigned int NewEditAlarmModel::getMinimalItemSpaceRequired() const
    {
        return style::alarmClock::window::item::options::height;
        return style::alarmClock::window::item::options::h;
    }

    void NewEditAlarmModel::requestRecords(uint32_t offset, uint32_t limit)


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

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


@@ 46,22 52,19 @@ namespace app::alarmClock
            mode24H,
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));
        internalData.push_back(new gui::AlarmOptionsItem(
            application,
            AlarmOptionItemName::Sound,
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));

        internalData.push_back(new gui::AlarmOptionsItem(
        internalData.push_back(new gui::AlarmMusicOptionsItem(
            application,
            AlarmOptionItemName::Snooze,
            utils::translate("app_alarm_clock_sound"),
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));

        internalData.push_back(new gui::AlarmOptionRepeat(
        internalData.push_back(new gui::AlarmSnoozeOptionsItem(utils::translate("app_alarm_clock_snooze")));

        internalData.push_back(new gui::AlarmRRuleOptionsItem(
            application,
            AlarmOptionItemName::Repeat,
            AlarmPresenter(record),
            utils::translate("app_alarm_clock_repeat"),
            rRulePresenter,
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));



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

        createData(record);
        rRulePresenter->loadRecord(record);
        createData();

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

M module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp => module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp +10 -6
@@ 3,11 3,13 @@

#pragma once

#include "application-alarm-clock/widgets/AlarmInternalListItem.hpp"
#include "application-alarm-clock/models/AlarmsRepository.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "Application.hpp"
#include "InternalModel.hpp"
#include <application-alarm-clock/widgets/AlarmInternalListItem.hpp>
#include <application-alarm-clock/models/AlarmsRepository.hpp>
#include <application-alarm-clock/data/AlarmsData.hpp>
#include <application-alarm-clock/presenter/AlarmRRulePresenter.hpp>

#include <Application.hpp>
#include <InternalModel.hpp>
#include <ListItemProvider.hpp>
#include <module-db/Interface/AlarmEventRecord.hpp>



@@ 28,18 30,20 @@ namespace app::alarmClock
    {
        app::ApplicationCommon *application = nullptr;
        std::shared_ptr<AbstractAlarmsRepository> alarmsRepository;
        std::shared_ptr<alarmClock::AlarmRRulePresenter> rRulePresenter;
        gui::AlarmInternalListItem *repeatOption = nullptr;
        bool mode24H = false;

      public:
        NewEditAlarmModel(app::ApplicationCommon *app,
                          std::shared_ptr<alarmClock::AlarmRRulePresenter> rRulePresenter,
                          std::shared_ptr<AbstractAlarmsRepository> alarmsRepository,
                          bool mode24H = false);

        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(std::shared_ptr<AlarmEventRecord> record);
        void createData();

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

M module-apps/application-alarm-clock/presenter/AlarmClockEditWindowPresenter.cpp => module-apps/application-alarm-clock/presenter/AlarmClockEditWindowPresenter.cpp +0 -8
@@ 25,12 25,4 @@ namespace app::alarmClock
        alarmFieldsProvider->saveData(std::move(record), action);
    }

    void AlarmClockEditWindowPresenter::loadRepeat(std::shared_ptr<AlarmEventRecord> record)
    {
        alarmFieldsProvider->loadRepeat(std::move(record));
    }

    void AlarmClockEditWindowPresenter::updateRepeat(std::shared_ptr<AlarmEventRecord> record, WeekDaysRepeatData data)
    {
    }
} // namespace app::alarmClock

M module-apps/application-alarm-clock/presenter/AlarmClockEditWindowPresenter.hpp => module-apps/application-alarm-clock/presenter/AlarmClockEditWindowPresenter.hpp +0 -4
@@ 25,8 25,6 @@ namespace app::alarmClock
            [[nodiscard]] virtual std::shared_ptr<gui::ListItemProvider> getAlarmsItemProvider() const = 0;
            virtual void loadData(std::shared_ptr<AlarmEventRecord> record)                              = 0;
            virtual void saveData(std::shared_ptr<AlarmEventRecord> record, AlarmAction action)          = 0;
            virtual void loadRepeat(std::shared_ptr<AlarmEventRecord> record)                            = 0;
            virtual void updateRepeat(std::shared_ptr<AlarmEventRecord> record, WeekDaysRepeatData data) = 0;
        };
    };



@@ 38,8 36,6 @@ namespace app::alarmClock
        [[nodiscard]] std::shared_ptr<gui::ListItemProvider> getAlarmsItemProvider() const override;
        void loadData(std::shared_ptr<AlarmEventRecord> record) override;
        void saveData(std::shared_ptr<AlarmEventRecord> record, AlarmAction action) override;
        void loadRepeat(std::shared_ptr<AlarmEventRecord> record) override;
        void updateRepeat(std::shared_ptr<AlarmEventRecord> record, WeekDaysRepeatData data) override;

      private:
        std::shared_ptr<AlarmsInternalListItemProvider> alarmFieldsProvider;

R module-apps/application-alarm-clock/presenter/AlarmPresenter.cpp => module-apps/application-alarm-clock/presenter/AlarmRRulePresenter.cpp +53 -31
@@ 1,13 1,20 @@
// 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 "AlarmRRulePresenter.hpp"
#include "log.hpp"

namespace app::alarmClock
{
    AlarmRRulePresenter::AlarmRRulePresenter(std::shared_ptr<AlarmEventRecord> recordToLoad) : alarm(recordToLoad)
    {}

    utl::Day AlarmPresenter::dayToDay(uint32_t day_no)
    void AlarmRRulePresenter::loadRecord(std::shared_ptr<AlarmEventRecord> recordToLoad)
    {
        alarm = std::move(recordToLoad);
    }

    utl::Day AlarmRRulePresenter::dayToDay(uint32_t day_no)
    {
        if (day_no == uint8_t(rrule::RRule::RRuleWeekday::SUNDAY_WEEKDAY)) {
            return utl::Sun;


@@ 15,7 22,7 @@ namespace app::alarmClock
        return utl::Day(day_no - 1);
    }

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


@@ 23,7 30,7 @@ namespace app::alarmClock
        return day + 1;
    }

    uint8_t AlarmPresenter::set_bit_days()
    uint8_t AlarmRRulePresenter::setBitDays()
    {
        const auto &rr = rrule::RRule(alarm->rruleText);
        uint8_t days   = 0;


@@ 38,15 45,19 @@ namespace app::alarmClock
        return days;
    }

    UTF8 AlarmPresenter::getDescription()
    UTF8 AlarmRRulePresenter::getDescription()
    {
        auto setDays = set_bit_days();
        auto setDays = setBitDays();
        if (setDays == 0) {
            return utils::translate("app_alarm_clock_repeat_never");
        }
        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) {


@@ 62,17 73,22 @@ namespace app::alarmClock
        return retval;
    }

    AlarmPresenter::Spinner AlarmPresenter::getSpinner()
    bool AlarmRRulePresenter::isDaySet(uint8_t &days, uint8_t day)
    {
        auto setDays = set_bit_days();
        return 0x1 & (days >> day);
    }

    AlarmRRulePresenter::RRuleOptions AlarmRRulePresenter::getOption()
    {
        auto setDays = setBitDays();
        if (setDays == 0) {
            return Spinner::Never;
            return RRuleOptions::Never;
        }
        if (setDays == weekdaysMask) {
            return Spinner::Weekdays;
            return RRuleOptions::Weekdays;
        }
        if (setDays == weekMask) {
            return Spinner::Weekly;
            return RRuleOptions::Everyday;
        }
        auto get_set_days_count = [&]() {
            uint8_t singleday = 0;


@@ 82,38 98,45 @@ namespace app::alarmClock
            return singleday;
        };
        if (get_set_days_count() == 1) {
            return Spinner::OnDay;
            return RRuleOptions::OnDay;
        }
        return Spinner::Custom;
        return RRuleOptions::Custom;
    }

    void AlarmPresenter::setSpinner(AlarmPresenter::Spinner spin,
                                    const std::function<void(AlarmPresenter::Spinner)> &cb)
    std::vector<std::pair<std::string, bool>> AlarmRRulePresenter::getCustomDays()
    {
        switch (spin) {
        case AlarmPresenter::Spinner::Never:
        std::vector<std::pair<std::string, bool>> selectedDays;
        auto setDays = setBitDays();

        for (unsigned int i = 0; i < utl::num_days; ++i) {
            selectedDays.push_back({utils::time::Locale::get_day(i), isDaySet(setDays, i)});
        }

        return selectedDays;
    }

    void AlarmRRulePresenter::setOption(AlarmRRulePresenter::RRuleOptions options, const std::list<utl::Day> &days)
    {
        switch (options) {
        case AlarmRRulePresenter::RRuleOptions::Never:
            setDays({});
            break;
        case AlarmPresenter::Spinner::OnDay:
            if (cb != nullptr) {
                cb(spin);
            }
        case AlarmRRulePresenter::RRuleOptions::OnDay:
            setDays(days);
            break;
        case AlarmPresenter::Spinner::Custom:
            if (cb != nullptr) {
                cb(spin);
            }
        case AlarmRRulePresenter::RRuleOptions::Custom:
            setDays(days);
            break;
        case AlarmPresenter::Spinner::Weekly:
        case AlarmRRulePresenter::RRuleOptions::Everyday:
            setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat, utl::Sun});
            break;
        case AlarmPresenter::Spinner::Weekdays:
        case AlarmRRulePresenter::RRuleOptions::Weekdays:
            setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri});
            break;
        }
    }

    void AlarmPresenter::setDays(const std::list<utl::Day> &days)
    void AlarmRRulePresenter::setDays(const std::list<utl::Day> &days)
    {
        if (days.empty()) {
            alarm->rruleText = "";


@@ 130,10 153,10 @@ namespace app::alarmClock
        }
    }

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


@@ 141,5 164,4 @@ namespace app::alarmClock
        }
        return ret;
    }

} // namespace app::alarmClock

R module-apps/application-alarm-clock/presenter/AlarmPresenter.hpp => module-apps/application-alarm-clock/presenter/AlarmRRulePresenter.hpp +26 -17
@@ 18,51 18,59 @@ namespace app::alarmClock
{
    typedef utils::time::Locale utl;

    class AlarmPresenter;
    class AlarmRRulePresenter;

    class AlarmEventItem;
    class AlarmRRuleItem;

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

        /// check if day is set
        [[nodiscard]] bool isDaySet(uint8_t &days, uint8_t day);

      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();
        uint8_t setBitDays();
        static constexpr uint8_t weekdaysMask = 0b0111110;
        static constexpr uint8_t weekMask     = 0b1111111;

      public:
        enum class Spinner
        enum class RRuleOptions
        {
            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
            Everyday, /// everyday occurrence
            Weekdays, /// every monday to friday occurrence
        };

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

        void loadRecord(std::shared_ptr<AlarmEventRecord> recordToLoad);

        /// 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);
        /// please see `enum::RRuleOptions` for more details
        RRuleOptions getOption();
        /// set proper rrule from Options spinner
        void setOption(RRuleOptions, const std::list<utl::Day> &days = {});

        /// 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();
        /// get custom days vector with selected days
        std::vector<std::pair<std::string, bool>> getCustomDays();

        /// if rrule is empty we can safely assume that there is no recurrence in the event
        bool hasRecurrence() const


@@ 76,18 84,19 @@ namespace app::alarmClock
        }
    };

    class AlarmEventItem
    class AlarmRRuleItem
    {
        AlarmPresenter p;
        std::shared_ptr<app::alarmClock::AlarmRRulePresenter> presenter;

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

      public:
        explicit AlarmEventItem(AlarmPresenter p) : p(p)
        explicit AlarmRRuleItem(std::shared_ptr<app::alarmClock::AlarmRRulePresenter> presenter)
            : presenter(std::move(presenter))
        {}
    };


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

#include "CustomRepeatWindowPresenter.hpp"


@@ 15,20 15,18 @@ namespace app::alarmClock
        return customRepeatProvider;
    }

    void CustomRepeatWindowPresenter::loadData(const WeekDaysRepeatData &data)
    void CustomRepeatWindowPresenter::loadData()
    {
        customRepeatProvider->loadData(data);
        customRepeatProvider->loadData();
    }

    WeekDaysRepeatData CustomRepeatWindowPresenter::getWeekDaysRepeatData()
    void CustomRepeatWindowPresenter::eraseProviderData()
    {
        auto weekDaysOptData = WeekDaysRepeatData();
        auto isCheckedData   = customRepeatProvider->getIsCheckedData();
        uint32_t i           = 0;
        for (const auto &checked : isCheckedData) {
            weekDaysOptData.setData(i, checked);
            ++i;
        }
        return weekDaysOptData;
        customRepeatProvider->eraseData();
    }

    void CustomRepeatWindowPresenter::saveData()
    {
        customRepeatProvider->saveCheckedData();
    }
} // namespace app::alarmClock

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

#pragma once


@@ 23,8 23,9 @@ namespace app::alarmClock
            virtual ~Presenter() noexcept = default;

            [[nodiscard]] virtual std::shared_ptr<gui::ListItemProvider> getItemProvider() = 0;
            virtual void loadData(const WeekDaysRepeatData &data)                          = 0;
            virtual WeekDaysRepeatData getWeekDaysRepeatData()                             = 0;
            virtual void loadData()                                                        = 0;
            virtual void saveData()                                                        = 0;
            virtual void eraseProviderData()                                               = 0;
        };
    };



@@ 34,8 35,9 @@ namespace app::alarmClock
        explicit CustomRepeatWindowPresenter(std::shared_ptr<CustomRepeatListItemProvider> itemProvider);

        [[nodiscard]] std::shared_ptr<gui::ListItemProvider> getItemProvider() override;
        void loadData(const WeekDaysRepeatData &data) override;
        WeekDaysRepeatData getWeekDaysRepeatData() override;
        void loadData() override;
        void saveData() override;
        void eraseProviderData() override;

      private:
        std::shared_ptr<CustomRepeatListItemProvider> customRepeatProvider;

M module-apps/application-alarm-clock/tests/test-AlarmPresenter.cpp => module-apps/application-alarm-clock/tests/test-AlarmPresenter.cpp +49 -20
@@ 2,24 2,24 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

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

using namespace app::alarmClock;

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

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

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



@@ 32,7 32,7 @@ TEST_CASE("transformations test")
TEST_CASE("empty rrule")
{
    std::shared_ptr<AlarmEventRecord> alarm;
    AlarmPresenter presenter(alarm);
    AlarmRRulePresenter presenter(alarm);
    REQUIRE(!presenter.hasRecurrence());
}



@@ 40,7 40,7 @@ TEST_CASE("empty rrule")
TEST_CASE("recurrence rule get & set")
{
    auto alarm = std::make_shared<AlarmEventRecord>();
    AlarmPresenter presenter(alarm);
    AlarmRRulePresenter presenter(alarm);

    SECTION("no recurrence")
    {


@@ 51,7 51,7 @@ TEST_CASE("recurrence rule get & set")
    {
        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.getOption() == AlarmRRulePresenter::RRuleOptions::Everyday);
        REQUIRE(presenter.getDays().size() == 7);
    }



@@ 78,62 78,91 @@ TEST_CASE("recurrence rule get & set")
    }
}

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

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

    SECTION("weekdays")
    {
        presenter.setDays({utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri});
        REQUIRE(presenter.getSpinner() == AlarmPresenter::Spinner::Weekdays);
        REQUIRE(presenter.getOption() == AlarmRRulePresenter::RRuleOptions::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);
        REQUIRE(presenter.getOption() == AlarmRRulePresenter::RRuleOptions::Everyday);
    }

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

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

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

    std::list<utl::Day> days = {utl::Sun, utl::Wed, utl::Fri};
    presenter.setOption(AlarmRRulePresenter::RRuleOptions::Custom, days);

    REQUIRE(presenter.getCustomDays()[0] == std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Sun), true});
    REQUIRE(presenter.getCustomDays()[1] ==
            std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Mon), false});
    REQUIRE(presenter.getCustomDays()[2] ==
            std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Tue), false});
    REQUIRE(presenter.getCustomDays()[3] == std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Wed), true});
    REQUIRE(presenter.getCustomDays()[4] ==
            std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Thu), false});
    REQUIRE(presenter.getCustomDays()[5] == std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Fri), true});
    REQUIRE(presenter.getCustomDays()[6] ==
            std::pair<std::string, bool>{utils::time::Locale::get_day(utl::Sat), false});
}

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

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

    SECTION("weekdays")
    {
        presenter.setSpinner(AlarmPresenter::Spinner::Weekdays, nullptr);
        presenter.setOption(AlarmRRulePresenter::RRuleOptions::Weekdays);
        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);
        presenter.setOption(AlarmRRulePresenter::RRuleOptions::Everyday);
        REQUIRE(presenter.getDays() ==
                std::list<utl::Day>{utl::Sun, utl::Mon, utl::Tue, utl::Wed, utl::Thu, utl::Fri, utl::Sat});
    }

    SECTION("custom")
    {
        std::list<utl::Day> days = {utl::Sun, utl::Wed, utl::Fri};

        presenter.setOption(AlarmRRulePresenter::RRuleOptions::Custom, days);
        REQUIRE(presenter.getDays() == days);
    }
}

M module-apps/application-alarm-clock/widgets/AlarmClockStyle.hpp => module-apps/application-alarm-clock/widgets/AlarmClockStyle.hpp +9 -19
@@ 21,43 21,33 @@ namespace style::alarmClock
        {
            inline constexpr auto newEditAlarm = "NewEditWindow";
            inline constexpr auto customRepeat = "CustomRepeat";
            inline constexpr auto dialogYesNo = "DialogYesNo";
        }
            inline constexpr auto dialogYesNo  = "DialogYesNo";
        } // namespace name

        namespace item
        {
            inline constexpr auto height         = 100;
            inline constexpr auto vBoxWidth      = 200;
            inline constexpr auto botMargin      = 4;
            inline constexpr auto vBoxLeftMargin = 10;
            inline constexpr auto imageMargin    = 150;
            inline constexpr auto timeHeight     = 60 - style::margins::small;
            inline constexpr auto periodHeight   = 40;

            namespace time
            {
                inline constexpr auto height       = 106;
                inline constexpr auto margin       = 21;
                inline constexpr auto marginBot    = 15;
                inline constexpr auto width        = style::listview::item_width_with_without_scroll;
                inline constexpr auto height       = 80;
                inline constexpr auto marginTop    = 32;
                inline constexpr auto marginBot    = 20;
                inline constexpr auto separator    = 30;
                inline constexpr auto timeInput12h = 120;
                inline constexpr auto timeInput24h = 195;
            } // namespace time

            namespace options
            {
                inline constexpr auto height    = 63;
                inline constexpr auto label_h   = 30;
                inline constexpr auto arrow_w_h = 20;
                inline constexpr auto spinner_h = 40;
                inline constexpr auto label_h   = 27;
                inline constexpr auto h         = spinner_h + label_h;
            } // namespace options

            namespace checkBox
            {
                inline constexpr auto height        = 44;
                inline constexpr auto marginTop     = 18;
                inline constexpr auto inputBox_w    = style::window::label::big_h;
                inline constexpr auto description_w = 280;
            } // namespace checkBox
        } // namespace item

    } // namespace window

M module-apps/application-alarm-clock/widgets/AlarmItem.cpp => module-apps/application-alarm-clock/widgets/AlarmItem.cpp +12 -13
@@ 10,7 10,8 @@

namespace gui
{
    AlarmItem::AlarmItem(app::alarmClock::AlarmPresenter p) : AlarmEventItem(p)
    AlarmItem::AlarmItem(std::shared_ptr<app::alarmClock::AlarmRRulePresenter> presenter)
        : AlarmRRuleItem(std::move(presenter))
    {
        setMinimumSize(style::window::default_body_width, style::alarmClock::window::item::height);
        setMargins(gui::Margins(0, style::margins::small, 0, style::alarmClock::window::item::botMargin));


@@ 20,26 21,24 @@ namespace gui

        vBox = new gui::VBox(hBox, 0, 0, 0, 0);
        vBox->setEdges(gui::RectangleEdge::None);
        vBox->setMinimumSize(style::alarmClock::window::item::vBoxWidth, style::alarmClock::window::item::height);
        vBox->setMargins(gui::Margins(style::alarmClock::window::item::vBoxLeftMargin, 0, 0, 0));
        vBox->setMaximumSize(style::window::default_body_width, style::alarmClock::window::item::height);
        vBox->setMargins(gui::Margins(style::widgets::leftMargin, 0, 0, 0));

        timeLabel = new gui::Label(vBox, 0, 0, 0, 0);
        timeLabel->setEdges(gui::RectangleEdge::None);
        timeLabel->setMinimumSize(style::alarmClock::window::item::vBoxWidth,
                                  style::alarmClock::window::item::timeHeight);
        timeLabel->setMaximumSize(style::window::default_body_width, style::alarmClock::window::item::timeHeight);
        timeLabel->setMargins(gui::Margins(0, style::margins::small, 0, 0));
        timeLabel->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center});
        timeLabel->setFont(style::window::font::largelight);

        periodLabel = new gui::Label(vBox, 0, 0, 0, 0);
        periodLabel->setEdges(gui::RectangleEdge::None);
        periodLabel->setMinimumSize(style::alarmClock::window::item::vBoxWidth,
                                    style::alarmClock::window::item::periodHeight);
        periodLabel->setMaximumSize(style::window::default_body_width, style::alarmClock::window::item::periodHeight);
        periodLabel->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Top});
        periodLabel->setFont(style::window::font::small);

        onOffImage = new gui::ButtonOnOff(hBox, ButtonState::On);
        onOffImage->setMargins(gui::Margins(style::alarmClock::window::item::imageMargin, 0, 0, 0));
        onOffImage->setMargins(gui::Margins(0, 0, style::widgets::rightMargin, 0));

        setAlarm();



@@ 51,16 50,16 @@ namespace gui

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

        if (presenter().hasRecurrence()) {
            periodLabel->setText(presenter().getDescription());
        if (getPresenter()->hasRecurrence()) {
            periodLabel->setText(getPresenter()->getDescription());
        }

        if (periodLabel->getText().empty()) {
            periodLabel->setMaximumSize(0, 0);
            timeLabel->setMinimumSize(style::alarmClock::window::item::vBoxWidth,
            timeLabel->setMaximumSize(style::window::default_body_width,
                                      style::alarmClock::window::item::timeHeight +
                                          style::alarmClock::window::item::periodHeight);
            vBox->resizeItems();

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

namespace gui
{
    class AlarmItem : public ListItem, public app::alarmClock::AlarmEventItem
    class AlarmItem : public ListItem, public app::alarmClock::AlarmRRuleItem
    {
        gui::HBox *hBox              = nullptr;
        gui::VBox *vBox              = nullptr;


@@ 23,6 23,6 @@ namespace gui
        void setAlarm();

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

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

#include "AlarmMusicOptionsItem.hpp"

#include <service-audio/AudioServiceAPI.hpp>
#include <purefs/filesystem_paths.hpp>

namespace gui
{
    AlarmMusicOptionsItem::AlarmMusicOptionsItem(app::ApplicationCommon *app,
                                                 const std::string &description,
                                                 std::function<void(const UTF8 &text)> bottomBarTemporaryMode,
                                                 std::function<void()> bottomBarRestoreFromTemporaryMode)
        : AlarmOptionsItem(description), bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode))
    {
        assert(app != nullptr);

        // create audioOperations to allow sounds preview
        audioOperations = std::make_unique<app::AsyncAudioOperations>(app);

        alarmSoundList = getMusicFilesList();
        std::vector<UTF8> printOptions;
        for (const auto &musicFile : getMusicFilesList()) {
            printOptions.push_back(musicFile.title);
        }
        optionSpinner->setData({printOptions});

        inputCallback = [&](gui::Item &item, const gui::InputEvent &event) {
            if (event.isShortRelease(gui::KeyCode::KEY_LF)) {
                if (getFilePath(optionSpinner->getCurrentValue()) != currentlyPreviewedPath) {
                    playAudioPreview(getFilePath(optionSpinner->getCurrentValue()));
                }
                else if (musicStatus == MusicStatus::Stop) {
                    resumeAudioPreview();
                }
                else {
                    pauseAudioPreview();
                }
            }

            // stop preview playback when we go back
            if (musicStatus == MusicStatus::Play && event.isShortRelease(gui::KeyCode::KEY_RF)) {
                stopAudioPreview();
            }

            return optionSpinner->onInput(event);
        };

        focusChangedCallback = [&](Item &item) {
            setFocusItem(focus ? optionSpinner : nullptr);

            if (focus) {
                this->bottomBarTemporaryMode(utils::translate(style::strings::common::play_pause));
            }
            else {
                this->bottomBarRestoreFromTemporaryMode();
            }

            // stop preview playback when we loose focus
            if (musicStatus == MusicStatus::Play) {
                stopAudioPreview();
            }

            return true;
        };

        onSaveCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
            // stop preview playback if it is played
            if (musicStatus == MusicStatus::Play) {
                stopAudioPreview();
            }

            alarm->musicTone = getFilePath(optionSpinner->getCurrentValue());
        };

        onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
            optionSpinner->setCurrentValue(getTitle(alarm->musicTone));
        };
    }

    std::vector<tags::fetcher::Tags> AlarmMusicOptionsItem::getMusicFilesList()
    {
        const auto musicFolder = (purefs::dir::getCurrentOSPath() / "assets/audio/alarm").string();
        std::vector<tags::fetcher::Tags> musicFiles;
        LOG_INFO("Scanning music folder: %s", musicFolder.c_str());
        for (const auto &ent : std::filesystem::directory_iterator(musicFolder)) {
            if (!ent.is_directory()) {
                const auto filePath = std::string(musicFolder) + "/" + ent.path().filename().c_str();
                auto fileTags       = tags::fetcher::fetchTags(filePath);
                musicFiles.push_back(fileTags);
                LOG_DEBUG("file: %s found", ent.path().filename().c_str());
            }
        }
        LOG_INFO("Total number of music files found: %u", static_cast<unsigned int>(musicFiles.size()));
        return musicFiles;
    }

    bool AlarmMusicOptionsItem::playAudioPreview(const std::string &path)
    {
        return audioOperations->play(path, [this, path](audio::RetCode retCode, audio::Token token) {
            if (retCode != audio::RetCode::Success || !token.IsValid()) {
                LOG_ERROR("Audio preview callback failed with retcode = %s. Token validity: %d",
                          str(retCode).c_str(),
                          token.IsValid());
                return;
            }
            musicStatus             = MusicStatus::Play;
            currentlyPreviewedToken = token;
            currentlyPreviewedPath  = path;
        });
    }

    bool AlarmMusicOptionsItem::pauseAudioPreview()
    {
        return audioOperations->pause(currentlyPreviewedToken, [this](audio::RetCode retCode, audio::Token token) {
            if (token != currentlyPreviewedToken) {
                LOG_ERROR("Audio preview pause failed: wrong token");
                return;
            }
            if (retCode != audio::RetCode::Success || !token.IsValid()) {
                LOG_ERROR("Audio preview pause failed with retcode = %s. Token validity: %d",
                          str(retCode).c_str(),
                          token.IsValid());
                return;
            }
            musicStatus = MusicStatus::Stop;
        });
    }

    bool AlarmMusicOptionsItem::resumeAudioPreview()
    {
        return audioOperations->resume(currentlyPreviewedToken, [this](audio::RetCode retCode, audio::Token token) {
            if (token != currentlyPreviewedToken) {
                LOG_ERROR("Audio preview resume failed: wrong token");
                return;
            }

            if (retCode != audio::RetCode::Success || !token.IsValid()) {
                LOG_ERROR("Audio preview pause failed with retcode = %s. Token validity: %d",
                          str(retCode).c_str(),
                          token.IsValid());
                return;
            }
            musicStatus = MusicStatus::Play;
        });
    }

    bool AlarmMusicOptionsItem::stopAudioPreview()
    {
        if (currentlyPreviewedToken.IsValid()) {
            musicStatus            = MusicStatus::Stop;
            currentlyPreviewedPath = "";
            return audioOperations->stop(currentlyPreviewedToken, [](audio::RetCode, audio::Token) {});
        }
        return false;
    }

    std::string AlarmMusicOptionsItem::getTitle(const std::string &filePath)
    {
        for (const auto &musicFile : alarmSoundList) {
            if (musicFile.filePath == filePath) {
                return musicFile.title;
            }
        }
        return std::string();
    }

    std::string AlarmMusicOptionsItem::getFilePath(const std::string &title)
    {
        for (const auto &musicFile : alarmSoundList) {
            if (musicFile.title == title) {
                return musicFile.filePath;
            }
        }
        return std::string();
    }
} /* namespace gui */

A module-apps/application-alarm-clock/widgets/AlarmMusicOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmMusicOptionsItem.hpp +51 -0
@@ 0,0 1,51 @@
// 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 "AlarmOptionsItem.hpp"

#include <application-alarm-clock/data/AlarmsData.hpp>
#include <apps-common/ApplicationCommon.hpp>
#include <apps-common/AudioOperations.hpp>
#include <tags_fetcher/TagsFetcher.hpp>

namespace gui
{
    class AlarmMusicOptionsItem : public AlarmOptionsItem
    {
      private:
        enum class MusicStatus
        {
            Stop,
            Play
        };

        std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;

        /// pointer to audio operations which allows to make audio preview
        std::unique_ptr<app::AbstractAudioOperations> audioOperations;
        std::vector<tags::fetcher::Tags> alarmSoundList;
        MusicStatus musicStatus = MusicStatus::Stop;
        audio::Token currentlyPreviewedToken;
        std::string currentlyPreviewedPath;

        std::vector<tags::fetcher::Tags> getMusicFilesList();

      public:
        explicit AlarmMusicOptionsItem(app::ApplicationCommon *app,
                                       const std::string &description,
                                       std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                                       std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);

      private:
        [[nodiscard]] std::string getTitle(const std::string &filePath);
        [[nodiscard]] std::string getFilePath(const std::string &title);
        bool playAudioPreview(const std::string &path);
        bool pauseAudioPreview();
        bool resumeAudioPreview();
        bool stopAudioPreview();
    };

} /* namespace gui */
\ No newline at end of file

M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp +13 -315
@@ 2,34 2,19 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "AlarmOptionsItem.hpp"

#include "AlarmClockStyle.hpp"
#include <InputEvent.hpp>
#include <Style.hpp>
#include <Utils.hpp>
#include <service-audio/AudioServiceAPI.hpp>
#include <purefs/filesystem_paths.hpp>

namespace gui
{

    AlarmOptionsItem::AlarmOptionsItem(app::ApplicationCommon *app,
                                       AlarmOptionItemName itemName,
                                       std::function<void(const UTF8 &)> bottomBarTemporaryMode,
                                       std::function<void()> bottomBarRestoreFromTemporaryMode)
        : itemName(itemName), bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode))
    AlarmOptionsItem::AlarmOptionsItem(const std::string &description, const std::vector<UTF8> &options)
    {
        application = app;
        assert(app != nullptr);

        // create audioOperations to allow shounds preview
        audioOperations = std::make_unique<app::AsyncAudioOperations>(app);
        setMinimumSize(style::listview::item_width_with_without_scroll, style::alarmClock::window::item::options::h);

        setMinimumSize(style::window::default_body_width, style::alarmClock::window::item::options::height);

        setEdges(RectangleEdge::Bottom);
        setPenWidth(style::window::default_border_rect_no_focus);
        setMargins(gui::Margins(style::margins::small, style::margins::huge / 2, 0, style::margins::huge / 2));
        setEdges(RectangleEdge::None);
        setMargins(gui::Margins(style::widgets::leftMargin, style::margins::large, 0, 0));

        vBox = new gui::VBox(this, 0, 0, 0, 0);
        vBox->setEdges(gui::RectangleEdge::None);


@@ 39,312 24,25 @@ namespace gui
        descriptionLabel->setMinimumSize(style::window::default_body_width,
                                         style::alarmClock::window::item::options::label_h);
        descriptionLabel->setEdges(gui::RectangleEdge::None);
        descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Top));
        descriptionLabel->setFont(style::window::font::small);
        descriptionLabel->activeItem = false;
        descriptionLabel->setText(description);

        hBox = new gui::HBox(vBox, 0, 0, 0, 0);
        hBox->setMinimumSize(style::window::default_body_width,
                             style::alarmClock::window::item::options::height -
                                 style::alarmClock::window::item::options::label_h);
        hBox->setEdges(gui::RectangleEdge::None);
        hBox->activeItem = false;

        leftArrow = new gui::Image(hBox, 0, 0, 0, 0);
        leftArrow->setMinimumSize(style::alarmClock::window::item::options::arrow_w_h,
                                  style::alarmClock::window::item::options::arrow_w_h);
        leftArrow->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        leftArrow->activeItem = false;
        leftArrow->set("arrow_left");
        leftArrow->setVisible(false);

        optionLabel = new gui::Label(hBox, 0, 0, 0, 0);
        optionLabel->setMinimumSize(
            style::window::default_body_width - 2 * style::alarmClock::window::item::options::arrow_w_h,
            style::alarmClock::window::item::options::height - style::alarmClock::window::item::options::label_h);
        optionLabel->setMargins(gui::Margins(style::alarmClock::window::item::options::arrow_w_h / 2, 0, 0, 0));
        optionLabel->setEdges(gui::RectangleEdge::None);
        optionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        optionLabel->setFont(style::window::font::medium);
        optionLabel->activeItem = false;

        rightArrow = new gui::Image(hBox, 0, 0, 0, 0);
        rightArrow->setMinimumSize(style::alarmClock::window::item::options::arrow_w_h,
                                   style::alarmClock::window::item::options::arrow_w_h);
        rightArrow->setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
        rightArrow->activeItem = false;
        rightArrow->set("arrow_right");
        rightArrow->setVisible(false);

        prepareOptionsNames();
        applyCallbacks();
    }

    void AlarmOptionsItem::prepareOptionsNames()
    {
        songsList = getMusicFilesList();
        optionsNames.clear();
        switch (itemName) {
        case AlarmOptionItemName::Sound:
            descriptionLabel->setText(utils::translate("app_alarm_clock_sound"));
            break;
        case AlarmOptionItemName::Snooze:
            descriptionLabel->setText(utils::translate("app_alarm_clock_snooze"));
            break;
        case AlarmOptionItemName::Repeat:
            descriptionLabel->setText(utils::translate("app_alarm_clock_repeat"));
            break;
        }
        if (itemName == AlarmOptionItemName::Sound) {
            for (const auto &musicFile : songsList) {
                optionsNames.push_back(musicFile.title);
            }
        }
        else if (itemName == AlarmOptionItemName::Snooze) {
            optionsNames.push_back(utils::translate("app_alarm_clock_snooze_5_min"));
            optionsNames.push_back(utils::translate("app_alarm_clock_snooze_10_min"));
            optionsNames.push_back(utils::translate("app_alarm_clock_snooze_15_min"));
            optionsNames.push_back(utils::translate("app_alarm_clock_snooze_30_min"));
        }
        else if (itemName == AlarmOptionItemName::Repeat) {
            optionsNames.push_back(utils::translate("app_alarm_clock_repeat_never"));
            optionsNames.push_back(utils::translate("app_alarm_clock_repeat_everyday"));
            optionsNames.push_back(utils::translate("app_alarm_clock_repeat_week_days"));
            optionsNames.push_back(utils::translate("app_alarm_clock_repeat_custom"));
        }
    }
        optionSpinner = new gui::TextSpinnerBox(vBox, options, Boundaries::Continuous);
        optionSpinner->setMinimumSize(style::window::default_body_width,
                                      style::alarmClock::window::item::options::spinner_h);

    void AlarmOptionsItem::applyCallbacks()
    {
        focusChangedCallback = [&](Item &item) {
            if (item.focus) {
                optionLabel->setFont(style::window::font::mediumbold);
                optionLabel->setMargins(gui::Margins(0, 0, 0, 0));
                leftArrow->setVisible(true);
                rightArrow->setVisible(true);
                hBox->resizeItems();
                if (itemName == AlarmOptionItemName::Sound) {
                    bottomBarTemporaryMode(utils::translate(style::strings::common::play_pause));
                }
                if (itemName == AlarmOptionItemName::Repeat && actualVectorIndex == optionsNames.size() - 1) {
                    bottomBarTemporaryMode(utils::translate("app_alarm_clock_edit"));
                }
            }
            else {
                optionLabel->setFont(style::window::font::medium);
                optionLabel->setMargins(gui::Margins(style::alarmClock::window::item::options::arrow_w_h / 2, 0, 0, 0));
                leftArrow->setVisible(false);
                rightArrow->setVisible(false);
                hBox->resizeItems();
                bottomBarRestoreFromTemporaryMode();
            }

            // stop preview playback when we loose focus
            if (itemName == AlarmOptionItemName::Sound && musicStatus == MusicStatus::Play) {
                stopAudioPreview();
            }
        focusChangedCallback = [&](gui::Item &item) {
            setFocusItem(focus ? optionSpinner : nullptr);
            return true;
        };

        inputCallback = [&](gui::Item &item, const gui::InputEvent &event) {
            if (!event.isShortRelease()) {
                return false;
            }
            if (event.is(gui::KeyCode::KEY_RF)) {
                setFocusItem(nullptr);
            }

            if (event.is(gui::KeyCode::KEY_LEFT)) {
                actualVectorIndex--;
                if (actualVectorIndex >= optionsNames.size()) {
                    actualVectorIndex = optionsNames.size() - 1;
                    if (itemName == AlarmOptionItemName::Repeat) {
                        bottomBarTemporaryMode(utils::translate("app_alarm_clock_edit"));
                    }
                }
                else if (itemName == AlarmOptionItemName::Repeat) {
                    bottomBarRestoreFromTemporaryMode();
                }
                optionLabel->setText(optionsNames[actualVectorIndex]);
                return true;
            }

            if (event.is(gui::KeyCode::KEY_RIGHT)) {
                actualVectorIndex++;
                if (actualVectorIndex >= optionsNames.size()) {
                    actualVectorIndex = 0;
                }
                if (actualVectorIndex == optionsNames.size() - 1 && itemName == AlarmOptionItemName::Repeat) {
                    bottomBarTemporaryMode(utils::translate("app_alarm_clock_edit"));
                }
                else if (itemName == AlarmOptionItemName::Repeat) {
                    bottomBarRestoreFromTemporaryMode();
                }
                optionLabel->setText(optionsNames[actualVectorIndex]);
                return true;
            }

            if (event.is(gui::KeyCode::KEY_LF) && itemName == AlarmOptionItemName::Repeat &&
                actualVectorIndex == optionsNames.size() - 1) {
                OptionParser parser;
                auto weekDayRepeatData = std::make_unique<WeekDaysRepeatData>();
                auto weekDayData       = parser.setWeekDayOptions(repeatOptionValue, std::move(weekDayRepeatData));
                application->switchWindow(style::alarmClock::window::name::customRepeat, std::move(weekDayData));
            }

            if (event.is(gui::KeyCode::KEY_LF) && itemName == AlarmOptionItemName::Sound) {
                if (songsList[actualVectorIndex].filePath != currentlyPreviewedPath) {
                    playAudioPreview(songsList[actualVectorIndex].filePath);
                }
                else if (musicStatus == MusicStatus::Stop) {
                    resumeAudioPreview();
                }
                else {
                    pauseAudioPreview();
                }
            }

            // stop preview playback when we go back
            if (itemName == AlarmOptionItemName::Sound && musicStatus == MusicStatus::Play &&
                event.is(gui::KeyCode::KEY_RF)) {
                stopAudioPreview();
            }
            return false;
        };

        onSaveCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
            switch (itemName) {
            case AlarmOptionItemName::Sound: {
                // stop preview playback if it is played
                if (musicStatus == MusicStatus::Play) {
                    stopAudioPreview();
                }
                alarm->musicTone = songsList[actualVectorIndex].filePath;
                break;
            }
            case AlarmOptionItemName::Snooze: {
                alarm->snoozeDuration = static_cast<uint32_t>(snoozeOptions[actualVectorIndex]);
                break;
            }
            case AlarmOptionItemName::Repeat:
                // repead handled in class
                break;
            }
        };

        onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
            switch (itemName) {
            case AlarmOptionItemName::Sound: {
                auto it = std::find_if(songsList.begin(), songsList.end(), [alarm](const tags::fetcher::Tags &tag) {
                    return tag.filePath == alarm->musicTone.c_str();
                });
                if (it == songsList.end()) {
                    LOG_DEBUG("No such song in the list");
                    actualVectorIndex = 0;
                }
                else {
                    actualVectorIndex = std::distance(songsList.begin(), it);
                }
                break;
            }
            case AlarmOptionItemName::Snooze: {
                auto it = std::find(
                    snoozeOptions.begin(), snoozeOptions.end(), static_cast<AlarmSnooze>(alarm->snoozeDuration));
                if (it == snoozeOptions.end()) {
                    actualVectorIndex = 0;
                }
                else {
                    actualVectorIndex = std::distance(snoozeOptions.begin(), it);
                }
                break;
            }
            case AlarmOptionItemName::Repeat:
                // repead handled in class
                break;
            }
            optionLabel->setText(optionsNames[actualVectorIndex]);
        };
        inputCallback = [&](Item &item, const InputEvent &event) { return optionSpinner->onInput(event); };

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

    std::vector<tags::fetcher::Tags> AlarmOptionsItem::getMusicFilesList()
    {
        const auto musicFolder = (purefs::dir::getUserDiskPath() / "music").string();
        std::vector<tags::fetcher::Tags> musicFiles;
        LOG_INFO("Scanning music folder: %s", musicFolder.c_str());
        for (const auto &ent : std::filesystem::directory_iterator(musicFolder)) {
            if (!ent.is_directory()) {
                const auto filePath = std::string(musicFolder) + "/" + ent.path().filename().c_str();
                auto fileTags       = tags::fetcher::fetchTags(filePath);
                musicFiles.push_back(fileTags);
                LOG_DEBUG("file: %s found", ent.path().filename().c_str());
            }
        }
        LOG_INFO("Total number of music files found: %u", static_cast<unsigned int>(musicFiles.size()));
        return musicFiles;
    }

    bool AlarmOptionsItem::playAudioPreview(const std::string &path)
    {
        return audioOperations->play(path, [this, path](audio::RetCode retCode, audio::Token token) {
            if (retCode != audio::RetCode::Success || !token.IsValid()) {
                LOG_ERROR("Audio preview callback failed with retcode = %s. Token validity: %d",
                          str(retCode).c_str(),
                          token.IsValid());
                return;
            }
            musicStatus             = MusicStatus::Play;
            currentlyPreviewedToken = token;
            currentlyPreviewedPath  = path;
        });
    }

    bool AlarmOptionsItem::pauseAudioPreview()
    {
        return audioOperations->pause(currentlyPreviewedToken, [this](audio::RetCode retCode, audio::Token token) {
            if (token != currentlyPreviewedToken) {
                LOG_ERROR("Audio preview pause failed: wrong token");
                return;
            }
            if (retCode != audio::RetCode::Success || !token.IsValid()) {
                LOG_ERROR("Audio preview pause failed with retcode = %s. Token validity: %d",
                          str(retCode).c_str(),
                          token.IsValid());
                return;
            }
            musicStatus = MusicStatus::Stop;
        });
    }

    bool AlarmOptionsItem::resumeAudioPreview()
    {
        return audioOperations->resume(currentlyPreviewedToken, [this](audio::RetCode retCode, audio::Token token) {
            if (token != currentlyPreviewedToken) {
                LOG_ERROR("Audio preview resume failed: wrong token");
                return;
            }

            if (retCode != audio::RetCode::Success || !token.IsValid()) {
                LOG_ERROR("Audio preview pause failed with retcode = %s. Token validity: %d",
                          str(retCode).c_str(),
                          token.IsValid());
                return;
            }
            musicStatus = MusicStatus::Play;
        });
    }

    bool AlarmOptionsItem::stopAudioPreview()
    {
        if (currentlyPreviewedToken.IsValid()) {
            musicStatus            = MusicStatus::Stop;
            currentlyPreviewedPath = "";
            return audioOperations->stop(currentlyPreviewedToken, [](audio::RetCode, audio::Token) {});
        }
        return false;
    }
} /* namespace gui */

M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp +8 -48
@@ 3,65 3,25 @@

#pragma once

#include <apps-common/ApplicationCommon.hpp>
#include "AlarmInternalListItem.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include <apps-common/AudioOperations.hpp>

#include <Label.hpp>
#include <Image.hpp>
#include <BoxLayout.hpp>
#include <tags_fetcher/TagsFetcher.hpp>
#include <widgets/TextSpinnerBox.hpp>

namespace gui
{
    const std::array<AlarmSnooze, 4> snoozeOptions = {
        AlarmSnooze::FiveMinutes, AlarmSnooze::TenMinutes, AlarmSnooze::FifteenMinutes, AlarmSnooze::ThirtyMinutes};

    class AlarmOptionsItem : public AlarmInternalListItem
    {
      protected:
        enum class MusicStatus
        {
            Stop,
            Play
        };
        app::ApplicationCommon *application = nullptr;
        gui::VBox *vBox               = nullptr;
        gui::HBox *hBox               = nullptr;
        gui::Label *optionLabel       = nullptr;
        gui::Label *descriptionLabel  = nullptr;
        gui::Image *leftArrow         = nullptr;
        gui::Image *rightArrow        = nullptr;
        AlarmOptionItemName itemName;
        std::vector<std::string> optionsNames;
        /// pointer to audio operations which allows to make audio preview
        std::unique_ptr<app::AbstractAudioOperations> audioOperations;

        std::vector<tags::fetcher::Tags> songsList;
        MusicStatus musicStatus = MusicStatus::Stop;
        audio::Token currentlyPreviewedToken;
        std::string currentlyPreviewedPath;

        unsigned int actualVectorIndex = 0;
        uint32_t repeatOptionValue     = 0;

        std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;
        void prepareOptionsNames();
        void applyCallbacks();
        std::vector<tags::fetcher::Tags> getMusicFilesList();

      public:
        AlarmOptionsItem(app::ApplicationCommon *app,
                         AlarmOptionItemName itemName,
                         std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                         std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);
        explicit AlarmOptionsItem(const std::string &description, const std::vector<UTF8> &options = {});

      protected:
        bool playAudioPreview(const std::string &path);
        bool pauseAudioPreview();
        bool resumeAudioPreview();
        bool stopAudioPreview();
        gui::TextSpinnerBox *optionSpinner = nullptr;

      private:
        gui::VBox *vBox              = nullptr;
        gui::Label *descriptionLabel = nullptr;
    };

} /* namespace gui */

A module-apps/application-alarm-clock/widgets/AlarmRRuleOptionsItem.cpp => module-apps/application-alarm-clock/widgets/AlarmRRuleOptionsItem.cpp +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

#include "AlarmRRuleOptionsItem.hpp"

#include <application-alarm-clock/widgets/AlarmClockStyle.hpp>

namespace gui
{
    AlarmRRuleOptionsItem::AlarmRRuleOptionsItem(app::ApplicationCommon *app,
                                                 const std::string &description,
                                                 std::shared_ptr<app::alarmClock::AlarmRRulePresenter> presenter,
                                                 std::function<void(const UTF8 &text)> bottomBarTemporaryMode,
                                                 std::function<void()> bottomBarRestoreFromTemporaryMode)
        : AlarmOptionsItem(description), AlarmRRuleItem(std::move(presenter)), app(app),
          bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode))
    {
        printOptions();

        inputCallback = [&](gui::Item &item, const gui::InputEvent &event) {
            auto ret = optionSpinner->onInput(event);

            if (getRRuleOption(optionSpinner->getCurrentValue()) == RRule::Custom) {
                this->bottomBarTemporaryMode(utils::translate(style::strings::common::edit));
                if (event.isShortRelease(gui::KeyCode::KEY_LF)) {
                    this->app->switchWindow(style::alarmClock::window::name::customRepeat);
                }
            }
            else {
                this->bottomBarRestoreFromTemporaryMode();
            }

            return ret;
        };

        focusChangedCallback = [&](Item &item) {
            setFocusItem(focus ? optionSpinner : nullptr);

            if (getRRuleOption(optionSpinner->getCurrentValue()) == RRule::Custom) {
                this->bottomBarTemporaryMode(utils::translate(style::strings::common::edit));
            }
            else {
                this->bottomBarRestoreFromTemporaryMode();
            }

            return true;
        };

        onSaveCallback = [&]([[maybe_unused]] std::shared_ptr<AlarmEventRecord> alarm) {
            if (getRRuleOption(optionSpinner->getCurrentValue()) != RRule::Custom) {
                getPresenter()->setOption(getRRuleOption(optionSpinner->getCurrentValue()));
            }
        };

        onLoadCallback = [&]([[maybe_unused]] std::shared_ptr<AlarmEventRecord> alarm) {
            checkCustomOption(getPresenter()->getDescription());
            optionSpinner->setCurrentValue(getPresenter()->getDescription());
        };
    }

    AlarmRRuleOptionsItem::RRule AlarmRRuleOptionsItem::getRRuleOption(const std::string &selectedOption)
    {
        for (auto const &option : rRuleOptions) {
            if (option.second == selectedOption) {
                return option.first;
            }
        }

        return AlarmRRuleOptionsItem::RRule::Never;
    }

    void AlarmRRuleOptionsItem::checkCustomOption(const std::string &selectedOption)
    {
        for (auto const &option : rRuleOptions) {
            if (selectedOption.empty() || option.second == selectedOption) {
                return;
            }
        }

        // replace Custom with days options
        rRuleOptions.back().second = selectedOption;
        printOptions();
    }

    void AlarmRRuleOptionsItem::printOptions()
    {
        std::vector<UTF8> printOptions;
        for (auto const &option : rRuleOptions) {
            printOptions.push_back(option.second);
        }
        optionSpinner->setData({printOptions});
    }
} /* namespace gui */

A module-apps/application-alarm-clock/widgets/AlarmRRuleOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmRRuleOptionsItem.hpp +39 -0
@@ 0,0 1,39 @@
// 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 "AlarmOptionsItem.hpp"

#include <application-alarm-clock/data/AlarmsData.hpp>
#include <application-alarm-clock/presenter/AlarmRRulePresenter.hpp>
#include <ApplicationCommon.hpp>

namespace gui
{
    class AlarmRRuleOptionsItem : public AlarmOptionsItem, public app::alarmClock::AlarmRRuleItem
    {
      private:
        app::ApplicationCommon *app                                  = nullptr;
        std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;

        using RRule                                             = app::alarmClock::AlarmRRulePresenter::RRuleOptions;
        std::vector<std::pair<RRule, std::string>> rRuleOptions = {
            {RRule::Never, utils::translate("app_alarm_clock_repeat_never")},
            {RRule::Everyday, utils::translate("app_alarm_clock_repeat_everyday")},
            {RRule::Weekdays, utils::translate("app_alarm_clock_repeat_week_days")},
            {RRule::Custom, utils::translate("app_alarm_clock_repeat_custom")}};

        [[nodiscard]] RRule getRRuleOption(const std::string &selectedOption);
        void checkCustomOption(const std::string &selectedOption);
        void printOptions();

      public:
        explicit AlarmRRuleOptionsItem(app::ApplicationCommon *app,
                                       const std::string &description,
                                       std::shared_ptr<app::alarmClock::AlarmRRulePresenter> presenter,
                                       std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                                       std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);
    };
} /* namespace gui */

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

#include "AlarmSnoozeOptionsItem.hpp"
#include <i18n/i18n.hpp>

namespace gui
{
    AlarmSnoozeOptionsItem::AlarmSnoozeOptionsItem(const std::string &description) : AlarmOptionsItem(description)
    {
        std::vector<UTF8> printOptions;

        for (auto const &option : snoozeOptions) {
            printOptions.push_back(option.second);
        }

        optionSpinner->setData({printOptions});

        onSaveCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
            for (auto const &option : snoozeOptions) {
                if (option.second == optionSpinner->getCurrentValue().c_str()) {
                    alarm->snoozeDuration = option.first.count();
                }
            }
        };

        onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
            auto valueToLoad = alarm->snoozeDuration;
            for (auto const &option : snoozeOptions) {
                if (option.first.count() == valueToLoad) {
                    optionSpinner->setCurrentValue(option.second);
                }
            }
        };
    }
} /* namespace gui */

A module-apps/application-alarm-clock/widgets/AlarmSnoozeOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmSnoozeOptionsItem.hpp +23 -0
@@ 0,0 1,23 @@
// 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 "AlarmOptionsItem.hpp"

namespace gui
{
    class AlarmSnoozeOptionsItem : public AlarmOptionsItem
    {
      public:
        explicit AlarmSnoozeOptionsItem(const std::string &description);

      private:
        std::vector<std::pair<std::chrono::minutes, const std::string>> snoozeOptions = {
            {std::chrono::minutes{0}, utils::translate("app_alarm_clock_no_snooze")},
            {std::chrono::minutes{5}, utils::translate("app_alarm_clock_snooze_5_min")},
            {std::chrono::minutes{10}, utils::translate("app_alarm_clock_snooze_10_min")},
            {std::chrono::minutes{15}, utils::translate("app_alarm_clock_snooze_15_min")},
            {std::chrono::minutes{30}, utils::translate("app_alarm_clock_snooze_30_min")}};
    };
} /* namespace gui */

M module-apps/application-alarm-clock/widgets/AlarmTimeItem.cpp => module-apps/application-alarm-clock/widgets/AlarmTimeItem.cpp +11 -8
@@ 18,10 18,11 @@ namespace gui
        : mode24H{mode24H}, bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode))
    {
        setMinimumSize(style::window::default_body_width, timeItem::height);
        setMinimumSize(timeItem::width, timeItem::height);

        setEdges(RectangleEdge::None);
        setMargins(gui::Margins(style::margins::small, timeItem::margin, 0, timeItem::marginBot));
        setMargins(gui::Margins(
            style::widgets::leftMargin, timeItem::marginTop, style::widgets::rightMargin, timeItem::marginBot));

        hBox = new gui::HBox(this, 0, 0, 0, 0);
        hBox->setEdges(gui::RectangleEdge::None);


@@ 31,7 32,7 @@ namespace gui
        applyItemSpecificProperties(hourInput);

        colonLabel = new gui::Label(hBox, 0, 0, 0, 0);
        colonLabel->setMinimumSize(timeItem::separator, timeItem::height - timeItem::separator);
        colonLabel->setMinimumSize(timeItem::separator, timeItem::height);
        colonLabel->setEdges(gui::RectangleEdge::None);
        colonLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        colonLabel->setFont(style::window::font::medium);


@@ 181,10 182,11 @@ namespace gui
                return true;
            };

            mode12hInput->setMinimumSize(timeItem::timeInput12h, timeItem::height - timeItem::separator);
            auto elemWidth = (timeItem::width - (2 * timeItem::separator)) / 3;
            mode12hInput->setMinimumSize(elemWidth, timeItem::height);
            mode12hInput->setMargins(gui::Margins(timeItem::separator, 0, 0, 0));
            hourInput->setMinimumSize(timeItem::timeInput12h, timeItem::height - timeItem::separator);
            minuteInput->setMinimumSize(timeItem::timeInput12h, timeItem::height - timeItem::separator);
            hourInput->setMinimumSize(elemWidth, timeItem::height);
            minuteInput->setMinimumSize(elemWidth, timeItem::height);

            onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
                hourInput->setText(TimePointToHourString12H(alarm->startDate));


@@ 198,8 200,9 @@ namespace gui
            };
        }
        else {
            hourInput->setMinimumSize(timeItem::timeInput24h, timeItem::height - timeItem::separator);
            minuteInput->setMinimumSize(timeItem::timeInput24h, timeItem::height - timeItem::separator);
            auto elemWidth = (timeItem::width - timeItem::separator) / 2;
            hourInput->setMinimumSize(elemWidth, timeItem::height);
            minuteInput->setMinimumSize(elemWidth, timeItem::height);

            onLoadCallback = [&](std::shared_ptr<AlarmEventRecord> alarm) {
                hourInput->setText(TimePointToHourString24H(alarm->startDate));

M module-apps/application-alarm-clock/widgets/CustomCheckBoxWithLabel.cpp => module-apps/application-alarm-clock/widgets/CustomCheckBoxWithLabel.cpp +30 -74
@@ 3,97 3,53 @@

#include "CustomCheckBoxWithLabel.hpp"
#include "AlarmClockStyle.hpp"

#include <application-alarm-clock/data/AlarmsData.hpp>

#include <InputEvent.hpp>

namespace gui
{
    const std::map<WeekDayIso, std::string> CustomCheckBoxWithLabel::weekDays = {
        {WeekDayIso::Monday, style::strings::common::Monday},
        {WeekDayIso::Tuesday, style::strings::common::Tuesday},
        {WeekDayIso::Wednesday, style::strings::common::Wednesday},
        {WeekDayIso::Thursday, style::strings::common::Thursday},
        {WeekDayIso::Friday, style::strings::common::Friday},
        {WeekDayIso::Saturday, style::strings::common::Saturday},
        {WeekDayIso::Sunday, style::strings::common::Sunday}};

    CustomCheckBoxWithLabel::CustomCheckBoxWithLabel(app::ApplicationCommon *app,
                                                     const std::string &description,
                                                     const WeekDaysRepeatData &data)
        : application(app), checkBoxData(data)
    CustomCheckBoxWithLabel::CustomCheckBoxWithLabel(
        const std::string &description,
        bool initialState,
        const std::function<void(const UTF8 &text)> &bottomBarTemporaryMode,
        const std::function<void()> &bottomBarRestoreFromTemporaryMode)
        : initialState(initialState)
    {
        assert(application != nullptr);

        setMinimumSize(style::window::default_body_width, style::alarmClock::window::item::checkBox::height);
        setMargins(gui::Margins(style::margins::small, style::alarmClock::window::item::checkBox::marginTop, 0, 0));
        setEdges(RectangleEdge::None);
        setMinimumSize(style::window::default_body_width, style::window::label::big_h);
        setMargins(gui::Margins(style::widgets::leftMargin, style::margins::big, 0, 0));

        checkBoxWithLabel = new gui::CheckBoxWithLabel(this,
                                                       0,
                                                       0,
                                                       0,
                                                       0,
                                                       description,
                                                       bottomBarTemporaryMode,
                                                       bottomBarRestoreFromTemporaryMode,
                                                       BottomBar::Side::LEFT);

        inputCallback = [&]([[maybe_unused]] Item &item, const InputEvent &event) {
            return checkBoxWithLabel->onInput(event);
        };

        hBox = new gui::HBox(this, 0, 0, 0, 0);
        hBox->setEdges(gui::RectangleEdge::None);

        checkBox = new gui::CheckBox(
            hBox,
            0,
            0,
            0,
            0,
            [=](const UTF8 &text) { application->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [=]() { application->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });
        checkBox->setMinimumSize(style::alarmClock::window::item::checkBox::inputBox_w,
                                 style::alarmClock::window::item::checkBox::height);

        descriptionLabel = new gui::Label(hBox, 0, 0, 0, 0);
        descriptionLabel->setMinimumSize(style::alarmClock::window::item::checkBox::description_w,
                                         style::alarmClock::window::item::checkBox::height);
        descriptionLabel->setMargins(gui::Margins(style::margins::very_big, 0, 0, 0));
        descriptionLabel->setEdges(gui::RectangleEdge::None);
        descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        descriptionLabel->setFont(style::window::font::medium);
        descriptionLabel->setText(description);

        applyCallbacks();
        setCheckBoxes();
    }

    void CustomCheckBoxWithLabel::applyCallbacks()
    {
        focusChangedCallback = [&](Item &item) {
            if (focus) {
                descriptionLabel->setFont(style::window::font::mediumbold);
                setFocusItem(checkBox);
            }
            else {
                descriptionLabel->setFont(style::window::font::medium);
                setFocusItem(nullptr);
            }
            setFocusItem(focus ? checkBoxWithLabel : nullptr);
            return true;
        };

        inputCallback = [&](gui::Item &item, const gui::InputEvent &event) {
            if (event.is(gui::KeyCode::KEY_RF) || event.is(gui::KeyCode::KEY_ENTER)) {
                setFocusItem(nullptr);
                return false;
            }
            if (checkBox->onInput(event)) {
                checkBox->resizeItems();
                return true;
            }
            return false;
        };
        onContentChangedCallback = [&]() { return checkBox->isChecked(); };

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

    void CustomCheckBoxWithLabel::setCheckBoxes()
    bool CustomCheckBoxWithLabel::isChecked()
    {
        for (auto const &[key, dayName] : weekDays) {
            if (descriptionLabel->getText() == utils::translate(dayName)) {
                checkBox->setCheck(checkBoxData.getData(static_cast<uint32_t>(key)));
            }
        }
        return checkBoxWithLabel->isChecked();
    }

} // namespace gui

M module-apps/application-alarm-clock/widgets/CustomCheckBoxWithLabel.hpp => module-apps/application-alarm-clock/widgets/CustomCheckBoxWithLabel.hpp +14 -21
@@ 1,33 1,26 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// 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 "AlarmInternalListItem.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "ApplicationCommon.hpp"
#include "application-calendar/data/CalendarData.hpp"
#include <BoxLayout.hpp>
#include <CheckBox.hpp>
#include <ListItem.hpp>
#include <CheckBoxWithLabel.hpp>

#include <application-calendar/data/CalendarData.hpp>

namespace gui
{
    class CustomCheckBoxWithLabel : public AlarmInternalListItem
    class CustomCheckBoxWithLabel : public ListItem
    {
        gui::HBox *hBox               = nullptr;
        app::ApplicationCommon *application = nullptr;
        gui::Label *descriptionLabel  = nullptr;
        gui::CheckBox *checkBox       = nullptr;
        WeekDaysRepeatData checkBoxData;

        void setCheckBoxes();
        void applyCallbacks();
      private:
        gui::CheckBoxWithLabel *checkBoxWithLabel = nullptr;
        bool initialState                         = false;

      public:
        CustomCheckBoxWithLabel(app::ApplicationCommon *app,
                                const std::string &description,
                                const WeekDaysRepeatData &data);

        static const std::map<WeekDayIso, std::string> weekDays;
        CustomCheckBoxWithLabel(const std::string &description,
                                bool initialState,
                                const std::function<void(const UTF8 &text)> &bottomBarTemporaryMode = nullptr,
                                const std::function<void()> &bottomBarRestoreFromTemporaryMode      = nullptr);
        bool isChecked();
    };
} // namespace gui

D module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.cpp => module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.cpp +0 -48
@@ 1,48 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 "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

D module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.hpp => module-apps/application-alarm-clock/widgets/addedit/AlarmOptionRepeat.hpp +0 -29
@@ 1,29 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 "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-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp => module-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp +1 -0
@@ 85,6 85,7 @@ namespace app::alarmClock
        if (inputEvent.isShortRelease(gui::KeyCode::KEY_LEFT)) {
            auto rec                              = new AlarmEventRecord();
            rec->startDate                        = TimePointNow();
            rec->snoozeDuration                   = 10;
            auto event                            = std::make_shared<AlarmEventRecord>(*rec);
            std::unique_ptr<AlarmRecordData> data = std::make_unique<AlarmRecordData>(event);
            data->setDescription(style::alarmClock::newAlarm);

M module-apps/application-alarm-clock/windows/CustomRepeatWindow.cpp => module-apps/application-alarm-clock/windows/CustomRepeatWindow.cpp +15 -11
@@ 3,6 3,11 @@

#include "CustomRepeatWindow.hpp"

#include <application-alarm-clock/data/AlarmsData.hpp>
#include <application-alarm-clock/widgets/AlarmClockStyle.hpp>

#include <InputEvent.hpp>

namespace app::alarmClock
{



@@ 34,15 39,16 @@ namespace app::alarmClock
        setFocusItem(list);
    }

    void CustomRepeatWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    void CustomRepeatWindow::onClose(gui::Window::CloseReason reason)
    {
        if (auto receivedData = dynamic_cast<WeekDaysRepeatData *>(data); receivedData != nullptr) {
            weekDaysOptData = *receivedData;
        }
        else {
            weekDaysOptData = WeekDaysRepeatData();
        if (reason != CloseReason::PhoneLock) {
            presenter->eraseProviderData();
        }
        presenter->loadData(weekDaysOptData);
    }

    void CustomRepeatWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        presenter->loadData();
    }

    bool CustomRepeatWindow::onInput(const gui::InputEvent &inputEvent)


@@ 52,10 58,8 @@ namespace app::alarmClock
        }

        if (inputEvent.isShortRelease(gui::KeyCode::KEY_ENTER)) {
            weekDaysOptData = presenter->getWeekDaysRepeatData();
            application->switchWindow(style::alarmClock::window::name::newEditAlarm,
                                      gui::ShowMode::GUI_SHOW_RETURN,
                                      std::make_unique<WeekDaysRepeatData>(weekDaysOptData));
            presenter->saveData();
            application->switchWindow(style::alarmClock::window::name::newEditAlarm);
            return true;
        }
        return false;

M module-apps/application-alarm-clock/windows/CustomRepeatWindow.hpp => module-apps/application-alarm-clock/windows/CustomRepeatWindow.hpp +5 -7
@@ 1,13 1,11 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// 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 "application-alarm-clock/widgets/AlarmClockStyle.hpp"
#include "application-alarm-clock/presenter/CustomRepeatWindowPresenter.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "Application.hpp"
#include <InputEvent.hpp>
#include <application-alarm-clock/presenter/CustomRepeatWindowPresenter.hpp>

#include <Application.hpp>
#include <ListView.hpp>

namespace app::alarmClock


@@ 16,13 14,13 @@ namespace app::alarmClock
    {
        gui::ListView *list = nullptr;
        std::unique_ptr<CustomRepeatWindowContract::Presenter> presenter;
        WeekDaysRepeatData weekDaysOptData;

      public:
        CustomRepeatWindow(app::ApplicationCommon *app,
                           std::unique_ptr<CustomRepeatWindowContract::Presenter> &&windowPresenter);

        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;
        void onClose(CloseReason reason) override;
        bool onInput(const gui::InputEvent &inputEvent) override;
        void buildInterface() override;
    };

M module-apps/application-alarm-clock/windows/NewEditAlarmWindow.cpp => module-apps/application-alarm-clock/windows/NewEditAlarmWindow.cpp +0 -10
@@ 2,9 2,6 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NewEditAlarmWindow.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "application-calendar/data/OptionParser.hpp"
#include <module-db/Interface/AlarmEventRecord.hpp>

namespace app::alarmClock
{


@@ 54,13 51,6 @@ namespace app::alarmClock
            }
            presenter->loadData(alarmRecord);
        }

        if (mode == gui::ShowMode::GUI_SHOW_RETURN) {
            if (auto receivedData = dynamic_cast<WeekDaysRepeatData *>(data); receivedData != nullptr) {
                presenter->updateRepeat(alarmRecord, *receivedData);
                presenter->loadRepeat(alarmRecord);
            }
        }
    }

    bool NewEditAlarmWindow::onInput(const gui::InputEvent &inputEvent)

M module-apps/application-calendar/models/MonthModel.cpp => module-apps/application-calendar/models/MonthModel.cpp +1 -1
@@ 38,7 38,7 @@ uint32_t MonthModel::getFirstWeekOffset()
        return 6;
    }
    else {
        return this->firstWeekDayNumb - 1;
        return this->firstWeekDayNumb;
    }
}


M module-apps/apps-common/CMakeLists.txt => module-apps/apps-common/CMakeLists.txt +1 -0
@@ 40,6 40,7 @@ target_sources(apps-common
        widgets/InputBox.cpp
        widgets/ModesBox.cpp
        widgets/SpinBox.cpp
        widgets/TextSpinnerBox.cpp
        widgets/TextWithIconsWidget.cpp
        widgets/TimeSetSpinner.cpp
        widgets/AlarmSetSpinner.cpp

M module-apps/apps-common/widgets/DateWidget.cpp => module-apps/apps-common/widgets/DateWidget.cpp +0 -5
@@ 125,11 125,6 @@ namespace gui
        setOnInputCallback(*dayInput);
        setOnInputCallback(*monthInput);
        setOnInputCallback(*yearInput);

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

    date::year_month_day DateWidget::validateDate()

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

#include "TextSpinnerBox.hpp"

namespace gui
{
    TextSpinnerBox::TextSpinnerBox(Item *parent, const std::vector<UTF8> &data, Boundaries boundaries) : HBox(parent)
    {
        setEdges(RectangleEdge::Bottom);

        leftArrow = new gui::ImageBox(this, new Image("arrow_left_24px_W_G"));
        leftArrow->setMinimumSizeToFitImage();
        leftArrow->setVisible(false);

        spinner = new UTF8Spinner(data, Boundaries::Continuous, Orientation::Horizontal);
        spinner->setFont(style::window::font::medium);
        spinner->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        spinner->setEdges(RectangleEdge::All);
        spinner->setVisible(true);
        addWidget(spinner);

        rightArrow = new gui::ImageBox(this, new Image("arrow_right_24px_W_G"));
        rightArrow->setMinimumSizeToFitImage();
        rightArrow->setVisible(false);

        inputCallback = [&](Item &item, const InputEvent &event) { return spinner->onInput(event); };

        focusChangedCallback = [&](gui::Item &item) {
            if (focus) {
                leftArrow->setVisible(true);
                rightArrow->setVisible(true);
                spinner->setFont(style::window::font::mediumbold);
                setPenWidth(style::window::default_border_focus_w);
            }
            else {
                leftArrow->setVisible(false);
                rightArrow->setVisible(false);
                spinner->setFont(style::window::font::medium);
                setPenWidth(style::window::default_border_rect_no_focus);
            }
            resizeItems();
            return true;
        };

        dimensionChangedCallback = [&](gui::Item &, const BoundingBox &newDim) -> bool {
            spinner->setMaximumSize(widgetArea.w, widgetArea.h);
            resizeItems();
            return true;
        };
    }

    void TextSpinnerBox::setData(const std::vector<UTF8> &data)
    {
        spinner->setRange(data);
    }

    UTF8 TextSpinnerBox::getCurrentValue() const noexcept
    {
        return spinner->getCurrentValue();
    }

    void TextSpinnerBox::setCurrentValue(UTF8 val)
    {
        spinner->setCurrentValue(val);
    }
} // namespace gui

A module-apps/apps-common/widgets/TextSpinnerBox.hpp => module-apps/apps-common/widgets/TextSpinnerBox.hpp +27 -0
@@ 0,0 1,27 @@
// 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 <widgets/spinners/Spinners.hpp>
#include <BoxLayout.hpp>
#include <ImageBox.hpp>

#include <vector>

namespace gui
{
    class TextSpinnerBox : public HBox
    {
      public:
        TextSpinnerBox(Item *parent, const std::vector<UTF8> &data, Boundaries boundaries);
        void setData(const std::vector<UTF8> &data);
        [[nodiscard]] UTF8 getCurrentValue() const noexcept;
        void setCurrentValue(UTF8 val);

      private:
        UTF8Spinner *spinner = nullptr;
        ImageBox *leftArrow  = nullptr;
        ImageBox *rightArrow = nullptr;
    };
} // namespace gui

M module-apps/apps-common/widgets/spinners/GenericSpinner.hpp => module-apps/apps-common/widgets/spinners/GenericSpinner.hpp +42 -24
@@ 14,32 14,35 @@ namespace gui
        using Range = typename Policy::Range;
        using Type  = typename Policy::Type;

        explicit GenericSpinner(Range range, Boundaries boundaries = Boundaries::Continuous);

        void setFocusEdges(RectangleEdge edges);
        explicit GenericSpinner(Range range,
                                Boundaries boundaries   = Boundaries::Continuous,
                                Orientation orientation = Orientation::Vertical);

        [[nodiscard]] Type getCurrentValue() const noexcept;

        void setCurrentValue(Type val);
        void setRange(Range range);

      private:
        void setFocusEdges(RectangleEdge edges);
        bool onInput(const InputEvent &inputEvent) override;

        bool onFocus(bool state) override;

      private:
        void stepNext();

        void stepPrevious();

        bool isPreviousEvent(const InputEvent &inputEvent);
        bool isNextEvent(const InputEvent &inputEvent);
        void update();

        Policy policy;
        RectangleEdge focusEdges = RectangleEdge::Bottom;
        Orientation orientation  = Orientation::Vertical;
    };

    template <typename ValuePolicy>
    GenericSpinner<ValuePolicy>::GenericSpinner(GenericSpinner::Range range, Boundaries boundaries)
        : policy{range, boundaries}
    GenericSpinner<ValuePolicy>::GenericSpinner(GenericSpinner::Range range,
                                                Boundaries boundaries,
                                                Orientation orientation)
        : policy{range, boundaries}, orientation(orientation)
    {
        setEditMode(EditMode::Browse);
        drawUnderline(false);


@@ 51,24 54,46 @@ namespace gui
        focusEdges = edges;
    }

    template <typename Policy> void GenericSpinner<Policy>::setRange(Range range)
    {
        policy.updateRange(range);
        update();
    }

    template <typename ValuePolicy> void GenericSpinner<ValuePolicy>::setCurrentValue(const Type val)
    {
        policy.set(val);
        update();
    }

    template <typename Policy>
    typename GenericSpinner<Policy>::Type GenericSpinner<Policy>::getCurrentValue() const noexcept
    {
        return policy.get();
    }

    template <typename ValuePolicy> bool GenericSpinner<ValuePolicy>::isPreviousEvent(const InputEvent &inputEvent)
    {
        return (orientation == Orientation::Vertical && inputEvent.is(KeyCode::KEY_UP)) ||
               (orientation == Orientation::Horizontal && inputEvent.is(KeyCode::KEY_LEFT));
    }

    template <typename ValuePolicy> bool GenericSpinner<ValuePolicy>::isNextEvent(const InputEvent &inputEvent)
    {
        return (orientation == Orientation::Vertical && inputEvent.is(KeyCode::KEY_DOWN)) ||
               (orientation == Orientation::Horizontal && inputEvent.is(KeyCode::KEY_RIGHT));
    }

    template <typename ValuePolicy> bool GenericSpinner<ValuePolicy>::onInput(const InputEvent &inputEvent)
    {
        if (inputEvent.isShortRelease()) {
            switch (inputEvent.getKeyCode()) {
            case KeyCode::KEY_UP:
                stepNext();
                return true;
            case KeyCode::KEY_DOWN:
            if (isPreviousEvent(inputEvent)) {
                stepPrevious();
                return true;
            default:
                break;
            }
            else if (isNextEvent(inputEvent)) {
                stepNext();
                return true;
            }
        }
        return false;


@@ 102,11 127,4 @@ namespace gui
    {
        setText(policy.str());
    }

    template <typename Policy>
    typename GenericSpinner<Policy>::Type GenericSpinner<Policy>::getCurrentValue() const noexcept
    {
        return policy.get();
    }

} // namespace gui

M module-apps/apps-common/widgets/spinners/SpinnerContents.hpp => module-apps/apps-common/widgets/spinners/SpinnerContents.hpp +2 -0
@@ 10,6 10,8 @@ namespace gui
    template <typename ValueT, typename StringT> class NumWithString
    {
      public:
        NumWithString() = default;

        NumWithString(ValueT value, StringT string) : value{value}, string{string}
        {}


M module-apps/apps-common/widgets/spinners/SpinnerPolicies.hpp => module-apps/apps-common/widgets/spinners/SpinnerPolicies.hpp +22 -7
@@ 5,10 5,9 @@

#include <gui/Common.hpp>

#include <utf8/UTF8.hpp>

#include <cstdint>
#include <vector>
#include <cstdint>
#include <utf8/UTF8.hpp>

namespace gui
{


@@ 23,12 22,12 @@ namespace gui

        ValType get() const
        {
            return range[pos];
            return pos < range.size() ? range[pos] : ValType{};
        }

        UTF8 str() const
        {
            return range[pos];
            return pos < range.size() ? range[pos] : UTF8{};
        }

        void set(ValType val)


@@ 71,13 70,21 @@ namespace gui
            }
        }

        void updateRange(Range newRange)
        {
            if (range != newRange) {
                range = newRange;
                pos   = 0;
            }
        }

      private:
        std::uint32_t upRange() const
        {
            return range.size() - 1;
        }

        const Range range;
        Range range;
        std::uint32_t pos{};
        const Boundaries boundaries{};
    };


@@ 141,8 148,16 @@ namespace gui
            }
        }

        void updateRange(Range newRange)
        {
            if (range != newRange) {
                range        = newRange;
                currentValue = 0;
            }
        }

      private:
        const Range range;
        Range range;
        ValType currentValue{};
        const Boundaries boundaries{};
    };

M module-gui/gui/Common.hpp => module-gui/gui/Common.hpp +4 -13
@@ 72,20 72,10 @@ namespace gui
        GUI_SHOW_RETURN
    };

    enum class AlignementFlags
    {
        GUI_ALIGN_VERTICAL_CENTER   = 0x01,
        GUI_ALIGN_VERTICAL_TOP      = 0x02,
        GUI_ALIGN_VERTICAL_BOTTOM   = 0x04,
        GUI_ALIGN_HORIZONTAL_CENTER = 0x08,
        GUI_ALIGN_HORIZONTAL_LEFT   = 0x10,
        GUI_ALIGN_HORIZONTAL_RIGHT  = 0x20
    };

    enum class OrientationFlags
    enum class Orientation
    {
        GUI_ORIENTATION_HORIZONTAL = 0x00,
        GUI_ORIENTATION_VERTICAL   = 0x01
        Vertical,
        Horizontal
    };

    template <class T> bool operator&(const T &lhs, const T &rhs)


@@ 139,6 129,7 @@ namespace gui
        BottomLeft  = 0x40,
        BottomRight = 0x80,
    };

    enum class Boundaries
    {
        Fixed,     ///< Fixed - will stop scrolling on first or last elements on appropriate top or bottom

M module-gui/gui/widgets/BoxLayout.cpp => module-gui/gui/widgets/BoxLayout.cpp +1 -1
@@ 569,7 569,7 @@ namespace gui
        resizeItems();
        setNavigation();

        return true;
        return Item::onDimensionChanged(oldDim, newDim);
    }

    HBox::HBox() : BoxLayout()

M module-gui/gui/widgets/ImageBox.cpp => module-gui/gui/widgets/ImageBox.cpp +4 -2
@@ 5,8 5,7 @@

using namespace gui;

ImageBox::ImageBox(
    Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h, Image *image)
ImageBox::ImageBox(Item *parent, const Position &x, const Position &y, const Length &w, const Length &h, Image *image)
    : HBox(parent, x, y, w, h), image(image)
{
    setEdges(RectangleEdge::None);


@@ 14,6 13,9 @@ ImageBox::ImageBox(
    addWidget(image);
}

ImageBox::ImageBox(Item *parent, Image *image) : ImageBox(parent, 0, 0, 0, 0, image)
{}

void ImageBox::showImage(bool show)
{
    image->setVisible(show);

M module-gui/gui/widgets/ImageBox.hpp => module-gui/gui/widgets/ImageBox.hpp +2 -2
@@ 11,8 11,8 @@ namespace gui
    class ImageBox : public HBox
    {
      public:
        ImageBox(
            Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h, Image *image);
        ImageBox(Item *parent, const Position &x, const Position &y, const Length &w, const Length &h, Image *image);
        ImageBox(Item *parent, Image *image);

        ~ImageBox() override = default;


M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +3 -0
@@ 165,6 165,7 @@ namespace style
            inline constexpr auto call           = "common_call";
            inline constexpr auto send           = "common_send";
            inline constexpr auto save           = "common_save";
            inline constexpr auto edit           = "common_edit";
            inline constexpr auto import         = "common_import";
            inline constexpr auto confirm        = "common_confirm";
            inline constexpr auto select         = "common_select";


@@ 237,6 238,8 @@ namespace style

        inline constexpr auto item_width_with_scroll =
            style::window::default_body_width - style::listview::scroll::item_margin;
        inline constexpr auto item_width_with_without_scroll =
            style::window::default_body_width - 2 * style::listview::scroll::item_margin;
        inline constexpr auto body_width_with_scroll =
            style::window::default_body_width + style::listview::scroll::margin;


M module-services/service-time/ServiceTime.hpp => module-services/service-time/ServiceTime.hpp +1 -1
@@ 30,7 30,7 @@ namespace stm
    class ServiceTime : public sys::Service
    {
      private:
        static constexpr auto StackDepth = 2048;
        static constexpr auto StackDepth = 2048 * 4;

        std::unique_ptr<TimeManager> timeManager;


M module-utils/time/time/time_locale.hpp => module-utils/time/time/time_locale.hpp +11 -9
@@ 28,7 28,7 @@ namespace utils
            // imo it would be nicer to have datetime locales in different json with thiny bit nicer and more effective
            // getters
            const std::array<std::string, num_days> daysShort = {
                "common_mo", "common_tu", "common_we", "common_th", "common_fr", "common_sa", "common_su"};
                "common_sun", "common_mon", "common_tue", "common_wed", "common_thu", "common_fri", "common_sat"};

            const std::array<std::string, num_days> days = {"common_sunday",
                                                            "common_monday",


@@ 158,9 158,15 @@ namespace utils
                    LOG_ERROR("Bad value: %d", day);
                    return "";
                }
                else {
                    return translate(tlocale.days[day]);
                return translate(tlocale.days[day]);
            }

            static const UTF8 get_day(const uint32_t &day)
            {
                if (day >= num_days) {
                    return "";
                }
                return translate(tlocale.days[day]);
            }

            static const UTF8 get_short_day(const uint32_t &day)


@@ 169,9 175,7 @@ namespace utils
                    LOG_ERROR("Bad value");
                    return "";
                }
                else {
                    return translate(tlocale.daysShort[day]);
                }
                return translate(tlocale.daysShort[day]);
            }

            static const UTF8 get_month(enum Month mon)


@@ 180,9 184,7 @@ namespace utils
                    LOG_ERROR("Bad value %d", mon);
                    return "";
                }
                else {
                    return translate(tlocale.months[mon]);
                }
                return translate(tlocale.months[mon]);
            }

            static const UTF8 yesterday()