~aleteoryx/muditaos

537ab85f465a6d62e76e5c16c30d720bb3d1275b — KacperLewandowski 5 years ago c58bb1a
[EGD-4072] Add new/edit window in alarm clock app (#1159)

Introduced the possibility to add a new alarm and edit an existing alarm
M changelog.md => changelog.md +1 -0
@@ 13,6 13,7 @@
* `[settings]` Nightshift window - GUI.
* `[bluetooth][settings]` Add Bluetooth settings to database.
* `[PowerManagement]` Added CPU load measurement.
* `[alarms]` Added new/edit alarm window

### Changed


M image/assets/lang/English.json => image/assets/lang/English.json +12 -0
@@ 74,6 74,7 @@
  "home_modes_notdisturb": "DO NOT DISTURB",
  "home_modes_offline": "OFFLINE",
  "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_custom" : "Custom",


@@ 83,6 84,17 @@
  "app_alarm_clock_options_delete": "Delete",
  "app_alarm_clock_options_turn_off_all_alarms": "Turn off all alarms",
  "app_alarm_clock_delete_confirmation": "Delete this alarm?",
  "app_alarm_clock_new_alarm_title" : "New alarm",
  "app_alarm_clock_edit_alarm_title" : "Edit alarm",
  "app_alarm_clock_sound" : "Sound",
  "app_alarm_clock_snooze" : "Snooze",
  "app_alarm_clock_repeat" :"Repeat",
  "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_play_pause" : "PLAY/PAUSE",
  "app_alarm_clock_edit" : "EDIT",
  "app_calendar_title_main": "Calendar",
  "app_calendar_options_title": "Options",
  "app_calendar_options_edit": "Edit",

M module-apps/application-alarm-clock/ApplicationAlarmClock.cpp => module-apps/application-alarm-clock/ApplicationAlarmClock.cpp +8 -0
@@ 3,6 3,7 @@

#include "ApplicationAlarmClock.hpp"
#include "application-alarm-clock/windows/AlarmClockMainWindow.hpp"
#include "application-alarm-clock/windows/NewEditAlarmWindow.hpp"
#include "application-alarm-clock/widgets/AlarmClockStyle.hpp"
#include "application-alarm-clock/presenter/AlarmClockMainWindowPresenter.hpp"
#include "windows/Dialog.hpp"


@@ 93,6 94,13 @@ namespace app
            return std::make_unique<alarmClock::AlarmClockMainWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(
            style::alarmClock::window::name::newEditAlarm, [](Application *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(
            utils::localize.get("app_alarm_clock_options_title"),
            [](Application *app, const std::string &name) { return std::make_unique<gui::OptionWindow>(app, name); });


M module-apps/application-alarm-clock/CMakeLists.txt => module-apps/application-alarm-clock/CMakeLists.txt +8 -2
@@ 1,15 1,21 @@
target_sources( ${PROJECT_NAME}

        PRIVATE
    PRIVATE
        "${CMAKE_CURRENT_LIST_DIR}/ApplicationAlarmClock.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/windows/AlarmClockMainWindow.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/windows/AlarmClockOptions.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/windows/NewEditAlarmWindow.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/models/AlarmsModel.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/models/AlarmsRepository.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/models/NewEditAlarmModel.cpp"
        "${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}/presenter/AlarmClockMainWindowPresenter.cpp"
        PUBLIC
        "${CMAKE_CURRENT_LIST_DIR}/presenter/AlarmClockEditWindowPresenter.cpp"
    PUBLIC
        "${CMAKE_CURRENT_LIST_DIR}/ApplicationAlarmClock.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmClockStyle.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/widgets/AlarmInternalListItem.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/data/AlarmsData.hpp"
        )

M module-apps/application-alarm-clock/data/AlarmsData.hpp => module-apps/application-alarm-clock/data/AlarmsData.hpp +21 -0
@@ 12,6 12,27 @@ enum class AlarmRepeat
    weekDays
};

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

enum class AlarmOptionItemName
{
    Sound,
    Snooze,
    Repeat
};

enum class AlarmAction
{
    Add,
    Edit
};

class AlarmRecordData : public gui::SwitchData
{
  protected:

A module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp => module-apps/application-alarm-clock/models/NewEditAlarmModel.cpp +105 -0
@@ 0,0 1,105 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// 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/AlarmClockStyle.hpp"
#include <ListView.hpp>

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

    unsigned int NewEditAlarmModel::requestRecordsCount()
    {
        return internalData.size();
    }

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

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

    gui::ListItem *NewEditAlarmModel::getItem(gui::Order order)
    {
        return getRecord(order);
    }

    void NewEditAlarmModel::createData()
    {
        auto app = application;
        assert(app != nullptr);

        internalData.push_back(new gui::AlarmTimeItem(
            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(
            application,
            AlarmOptionItemName::Snooze,
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));

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

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

    void NewEditAlarmModel::loadData(std::shared_ptr<AlarmsRecord> record)
    {
        list->clear();
        eraseInternalData();

        createData();

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

        list->rebuildList();
    }

    void NewEditAlarmModel::saveData(std::shared_ptr<AlarmsRecord> alarm, AlarmAction action)
    {
        for (auto &item : internalData) {
            if (item->onSaveCallback) {
                item->onSaveCallback(alarm);
            }
        }

        if (action == AlarmAction::Edit) {
            alarmsRepository->update(*alarm,
                                     [this](bool) { application->switchWindow(gui::name::window::main_window); });
        }
        else {
            alarmsRepository->add(*alarm, [this](bool) { application->returnToPreviousWindow(); });
        }
        list->clear();
        eraseInternalData();
    }
} // namespace app::alarmClock

A module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp => module-apps/application-alarm-clock/models/NewEditAlarmModel.hpp +46 -0
@@ 0,0 1,46 @@
// Copyright (c) 2017-2020, 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/AlarmInternalListItem.hpp"
#include "application-alarm-clock/models/AlarmsRepository.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "Application.hpp"
#include "InternalModel.hpp"
#include <ListItemProvider.hpp>
#include <module-db/Interface/AlarmsRecord.hpp>

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

        virtual void loadData(std::shared_ptr<AlarmsRecord> record)                     = 0;
        virtual void saveData(std::shared_ptr<AlarmsRecord> record, AlarmAction action) = 0;
    };

    class NewEditAlarmModel : public AlarmsInternalListItemProvider
    {
        app::Application *application = nullptr;
        std::shared_ptr<AbstractAlarmsRepository> alarmsRepository;
        bool mode24H = false;

      public:
        NewEditAlarmModel(app::Application *app,
                          std::shared_ptr<AbstractAlarmsRepository> alarmsRepository,
                          bool mode24H = false);

        void loadData(std::shared_ptr<AlarmsRecord> record) override;
        void saveData(std::shared_ptr<AlarmsRecord> alarm, AlarmAction action) override;
        void createData();

        [[nodiscard]] unsigned int getMinimalItemHeight() const override;
        [[nodiscard]] unsigned int requestRecordsCount() override;
        [[nodiscard]] gui::ListItem *getItem(gui::Order order) override;
        void requestRecords(uint32_t offset, uint32_t limit) override;
    };
} // namespace app::alarmClock

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

#include "AlarmClockEditWindowPresenter.hpp"

namespace app::alarmClock
{
    AlarmClockEditWindowPresenter::AlarmClockEditWindowPresenter(
        std::shared_ptr<AlarmsInternalListItemProvider> itemProvider)
        : alarmFieldsProvider{std::move(itemProvider)}
    {}

    std::shared_ptr<gui::ListItemProvider> AlarmClockEditWindowPresenter::getAlarmsItemProvider() const
    {
        return alarmFieldsProvider;
    }

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

    void AlarmClockEditWindowPresenter::saveData(std::shared_ptr<AlarmsRecord> record, AlarmAction action)
    {
        alarmFieldsProvider->saveData(std::move(record), action);
    }
} // namespace app::alarmClock

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

#pragma once

#include "BasePresenter.hpp"
#include "application-alarm-clock/models/NewEditAlarmModel.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"

namespace app::alarmClock
{
    class AlarmClockEditWindowContract
    {
      public:
        class View
        {
          public:
            virtual ~View() noexcept = default;
        };
        class Presenter : public BasePresenter<AlarmClockEditWindowContract::View>
        {
          public:
            virtual ~Presenter() noexcept = default;

            [[nodiscard]] virtual std::shared_ptr<gui::ListItemProvider> getAlarmsItemProvider() const = 0;
            virtual void loadData(std::shared_ptr<AlarmsRecord> record)                                = 0;
            virtual void saveData(std::shared_ptr<AlarmsRecord> record, AlarmAction action)            = 0;
        };
    };

    class AlarmClockEditWindowPresenter : public AlarmClockEditWindowContract::Presenter
    {
      public:
        explicit AlarmClockEditWindowPresenter(std::shared_ptr<AlarmsInternalListItemProvider> itemProvider);

        [[nodiscard]] std::shared_ptr<gui::ListItemProvider> getAlarmsItemProvider() const override;
        void loadData(std::shared_ptr<AlarmsRecord> record) override;
        void saveData(std::shared_ptr<AlarmsRecord> record, AlarmAction action) override;

      private:
        std::shared_ptr<AlarmsInternalListItemProvider> alarmFieldsProvider;
    };
} // namespace app::alarmClock

M module-apps/application-alarm-clock/widgets/AlarmClockStyle.hpp => module-apps/application-alarm-clock/widgets/AlarmClockStyle.hpp +22 -0
@@ 7,6 7,9 @@

namespace style::alarmClock
{
    inline constexpr auto editAlarm = "Edit";
    inline constexpr auto newAlarm  = "New";

    namespace window
    {
        inline constexpr auto cross_x    = 48;


@@ 20,6 23,8 @@ namespace style::alarmClock

        namespace name
        {
            inline constexpr auto newEditAlarm = "NewEditWindow";
            inline constexpr auto customRepeat = "CustomRepeat";
            inline constexpr auto dialogYesNo = "DialogYesNo";
        }



@@ 32,6 37,23 @@ namespace style::alarmClock
            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 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;
            } // namespace options
        } // namespace item

    } // namespace window

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

#pragma once
#include <module-db/Interface/AlarmsRecord.hpp>
#include <ListItem.hpp>

namespace gui
{
    class AlarmInternalListItem : public ListItem
    {
      public:
        std::function<void(std::shared_ptr<AlarmsRecord> event)> onSaveCallback = nullptr;
        std::function<void(std::shared_ptr<AlarmsRecord> event)> onLoadCallback = nullptr;
    };

} /* namespace gui */

A module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.cpp +263 -0
@@ 0,0 1,263 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// 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 <module-services/service-audio/service-audio/AudioServiceAPI.hpp>

namespace gui
{

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

        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));

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

        descriptionLabel = new gui::Label(vBox, 0, 0, 0, 0);
        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->setFont(style::window::font::small);
        descriptionLabel->activeItem = false;

        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::localize.get("app_alarm_clock_sound"));
            break;
        case AlarmOptionItemName::Snooze:
            descriptionLabel->setText(utils::localize.get("app_alarm_clock_snooze"));
            break;
        case AlarmOptionItemName::Repeat:
            descriptionLabel->setText(utils::localize.get("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::localize.get("app_alarm_clock_snooze_5_min"));
            optionsNames.push_back(utils::localize.get("app_alarm_clock_snooze_10_min"));
            optionsNames.push_back(utils::localize.get("app_alarm_clock_snooze_15_min"));
            optionsNames.push_back(utils::localize.get("app_alarm_clock_snooze_30_min"));
        }
        else if (itemName == AlarmOptionItemName::Repeat) {
            optionsNames.push_back(utils::localize.get("app_alarm_clock_repeat_never"));
            optionsNames.push_back(utils::localize.get("app_alarm_clock_repeat_everyday"));
            optionsNames.push_back(utils::localize.get("app_alarm_clock_repeat_week_days"));
            optionsNames.push_back(utils::localize.get("app_alarm_clock_repeat_custom"));
        }
    }

    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::localize.get("app_alarm_clock_play_pause"));
                }
                if (itemName == AlarmOptionItemName::Repeat && actualVectorIndex == optionsNames.size() - 1) {
                    bottomBarTemporaryMode(utils::localize.get("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();
            }
            return true;
        };

        inputCallback = [&](gui::Item &item, const gui::InputEvent &event) {
            if (event.state != gui::InputEvent::State::keyReleasedShort) {
                return false;
            }

            if (event.is(gui::KeyCode::KEY_LEFT)) {
                actualVectorIndex--;
                if (actualVectorIndex >= optionsNames.size()) {
                    actualVectorIndex = optionsNames.size() - 1;
                }
                optionLabel->setText(optionsNames[actualVectorIndex]);
                return true;
            }

            if (event.is(gui::KeyCode::KEY_RIGHT)) {
                actualVectorIndex++;
                if (actualVectorIndex >= optionsNames.size()) {
                    actualVectorIndex = 0;
                }
                optionLabel->setText(optionsNames[actualVectorIndex]);
                return true;
            }

            if (event.is(gui::KeyCode::KEY_LF) && itemName == AlarmOptionItemName::Sound) {
                if (musicStatus == MusicStatus::Stop) {
                    musicStatus = MusicStatus::Play;
                    AudioServiceAPI::PlaybackStart(
                        application, audio::PlaybackType::Multimedia, songsList[actualVectorIndex].filePath);
                }
                else if (musicStatus == MusicStatus::Play) {
                    musicStatus = MusicStatus::Stop;
                    AudioServiceAPI::StopAll(application);
                }
            }
            return false;
        };

        onSaveCallback = [&](std::shared_ptr<AlarmsRecord> alarm) {
            switch (itemName) {
            case AlarmOptionItemName::Sound: {
                alarm->path = songsList[actualVectorIndex].filePath;
                break;
            }
            case AlarmOptionItemName::Snooze: {
                alarm->snooze = static_cast<uint32_t>(snoozeOptions[actualVectorIndex]);
                break;
            }
            case AlarmOptionItemName::Repeat: {
                alarm->repeat = actualVectorIndex;
                break;
            }
            }
        };

        onLoadCallback = [&](std::shared_ptr<AlarmsRecord> alarm) {
            switch (itemName) {
            case AlarmOptionItemName::Sound: {
                auto it = std::find_if(songsList.begin(), songsList.end(), [alarm](const audio::Tags &tag) {
                    return tag.filePath == alarm->path.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->snooze));
                if (it == snoozeOptions.end()) {
                    actualVectorIndex = 0;
                }
                else {
                    actualVectorIndex = std::distance(snoozeOptions.begin(), it);
                }
                break;
            }
            case AlarmOptionItemName::Repeat: {
                if (alarm->repeat < optionsNames.size() - 1) {
                    actualVectorIndex = alarm->repeat;
                }
                else {
                    actualVectorIndex = optionsNames.size() - 1;
                }
                break;
            }
            }
            optionLabel->setText(optionsNames[actualVectorIndex]);
        };
    }

    bool AlarmOptionsItem::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
    {
        vBox->setPosition(0, 0);
        vBox->setSize(newDim.w, newDim.h);
        return true;
    }

    std::vector<audio::Tags> AlarmOptionsItem::getMusicFilesList()
    {
        const char *musicFolder = USER_PATH("music");
        std::vector<audio::Tags> musicFiles;
        LOG_INFO("Scanning music folder: %s", musicFolder);
        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       = AudioServiceAPI::GetFileTags(application, filePath);
                if (fileTags) {
                    musicFiles.push_back(*fileTags);
                    LOG_DEBUG("file: %s found", ent.path().filename().c_str());
                }
                else {
                    LOG_ERROR("Not an audio file %s", ent.path().filename().c_str());
                }
            }
        }
        LOG_INFO("Total number of music files found: %u", static_cast<unsigned int>(musicFiles.size()));
        return musicFiles;
    }

} /* namespace gui */

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

#pragma once
#include "Application.hpp"
#include "AlarmInternalListItem.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include <Label.hpp>
#include <Image.hpp>
#include <BoxLayout.hpp>
#include <module-audio/Audio/decoder/decoder.hpp>

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

    class AlarmOptionsItem : public AlarmInternalListItem
    {
        enum class MusicStatus
        {
            Stop,
            Play
        };
        app::Application *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;
        std::vector<audio::Tags> songsList;
        MusicStatus musicStatus        = MusicStatus::Stop;
        unsigned int actualVectorIndex = 0;

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

      public:
        AlarmOptionsItem(app::Application *app,
                         AlarmOptionItemName itemName,
                         std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                         std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);

        bool onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) override;
    };

} /* namespace gui */

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

#include "AlarmTimeItem.hpp"
#include "AlarmClockStyle.hpp"
#include <ListView.hpp>
#include <Style.hpp>
#include <time/time_conversion.hpp>
#include <time/time_date_validation.hpp>

namespace gui
{
    namespace timeItem = style::alarmClock::window::item::time;

    AlarmTimeItem::AlarmTimeItem(bool mode24H,
                                 std::function<void(const UTF8 &text)> bottomBarTemporaryMode,
                                 std::function<void()> bottomBarRestoreFromTemporaryMode)
        : mode24H{mode24H}, bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode))
    {
        setMinimumSize(style::window::default_body_width, timeItem::height);

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

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

        hourInput = new gui::Text(hBox, 0, 0, 0, 0);
        applyItemSpecificProperties(hourInput);

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

        minuteInput = new gui::Text(hBox, 0, 0, 0, 0);
        applyItemSpecificProperties(minuteInput);

        focusChangedCallback = [&](Item &item) {
            setFocusItem(focus ? hBox : nullptr);
            if (!item.focus) {
                validateHour();
            }
            return true;
        };

        applyInputCallbacks();
        prepareForTimeMode();
    }

    void AlarmTimeItem::applyItemSpecificProperties(gui::Text *item)
    {
        item->setEdges(gui::RectangleEdge::Bottom);
        item->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        item->setFont(style::window::font::largelight);
        item->setInputMode(new InputMode({InputMode::digit}));
        item->setPenFocusWidth(style::window::default_border_focus_w);
        item->setPenWidth(style::window::default_border_rect_no_focus);
        item->setEditMode(gui::EditMode::Edit);
    }

    void AlarmTimeItem::applyInputCallbacks()
    {
        inputCallback = [&](Item &item, const InputEvent &event) {
            auto focusedItem = getFocusItem();
            if (!event.isShortPress()) {
                return false;
            }
            if (event.is(gui::KeyCode::KEY_ENTER) || event.is(gui::KeyCode::KEY_RF)) {
                return false;
            }

            if (focusedItem->onInput(event)) {
                uint32_t hours;
                try {
                    hours = std::stoi(hourInput->getText().c_str());
                }
                catch (const std::exception &e) {
                    LOG_INFO("AlarmTimeItem hours not valid: %s", e.what());
                    hours = 0;
                }
                uint32_t minutes;
                try {
                    minutes = std::stoi(minuteInput->getText().c_str());
                }
                catch (const std::exception &e) {
                    LOG_INFO("AlarmTimeItem minutes not valid: %s", e.what());
                    minutes = 0;
                }

                if (mode24H && hours > utils::time::hoursInday - 1) {
                    hourInput->setText("0");
                }
                else if (!mode24H && hours > utils::time::hoursInday / 2) {
                    hourInput->setText("12");
                }
                if (minutes > utils::time::minutesInHour - 1) {
                    minuteInput->setText("00");
                }
                return true;
            }
            else if (hBox->onInput(event)) {
                return true;
            }

            return false;
        };

        onSaveCallback = [&](std::shared_ptr<AlarmsRecord> record) {
            validateHour();
            auto hours   = std::chrono::hours(std::stoi(hourInput->getText().c_str()));
            auto minutes = std::chrono::minutes(std::stoi(minuteInput->getText().c_str()));
            if (!mode24H) {
                hours = date::make24(hours, isPm(mode12hInput->getText()));
            }
            record->time = TimePointFromYearMonthDay(TimePointToYearMonthDay(TimePointNow())) + hours + minutes;
        };

        onInputCallback(*hourInput);
        onInputCallback(*minuteInput);
    }

    void AlarmTimeItem::onInputCallback(gui::Text &textItem)
    {
        textItem.inputCallback = [&](Item &item, const InputEvent &event) {
            if (!event.isShortPress()) {
                return false;
            }
            if (textItem.getText().length() > 1 && !event.is(gui::KeyCode::KEY_LEFT) &&
                !event.is(gui::KeyCode::KEY_RIGHT) && !event.is(gui::KeyCode::KEY_PND) &&
                !event.is(gui::KeyCode::KEY_UP) && !event.is(gui::KeyCode::KEY_DOWN)) {
                return true;
            }
            return false;
        };
    }

    void AlarmTimeItem::prepareForTimeMode()
    {
        if (!mode24H) {
            mode12hInput = new gui::Label(hBox, 0, 0, 0, 0);
            mode12hInput->setEdges(gui::RectangleEdge::Bottom);
            mode12hInput->setAlignment(
                gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
            mode12hInput->setFont(style::window::font::largelight);
            mode12hInput->setPenFocusWidth(style::window::default_border_focus_w);
            mode12hInput->setPenWidth(style::window::default_border_rect_no_focus);
            mode12hInput->setText(utils::localize.get(utils::time::Locale::getAM()));
            mode12hInput->inputCallback = [&](Item &item, const InputEvent &event) {
                if (event.state != gui::InputEvent::State::keyReleasedShort) {
                    return false;
                }
                if (event.keyCode == gui::KeyCode::KEY_LF) {
                    if (mode12hInput->getText() == utils::localize.get(utils::time::Locale::getAM())) {
                        mode12hInput->setText(utils::localize.get(utils::time::Locale::getPM()));
                    }
                    else {
                        mode12hInput->setText(utils::localize.get(utils::time::Locale::getAM()));
                    }
                    return true;
                }
                return false;
            };
            mode12hInput->focusChangedCallback = [&](Item &item) {
                if (item.focus) {
                    bottomBarTemporaryMode(utils::localize.get("common_switch"));
                }
                else {
                    bottomBarRestoreFromTemporaryMode();
                }
                return true;
            };

            mode12hInput->setMinimumSize(timeItem::timeInput12h, timeItem::height - timeItem::separator);
            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);

            onLoadCallback = [&](std::shared_ptr<AlarmsRecord> alarm) {
                hourInput->setText(TimePointToHourString12H(alarm->time));
                minuteInput->setText(TimePointToMinutesString(alarm->time));
                if (date::is_am(TimePointToHourMinSec(alarm->time).hours())) {
                    mode12hInput->setText(utils::localize.get(utils::time::Locale::getAM()));
                }
                else {
                    mode12hInput->setText(utils::localize.get(utils::time::Locale::getPM()));
                }
            };
        }
        else {
            hourInput->setMinimumSize(timeItem::timeInput24h, timeItem::height - timeItem::separator);
            minuteInput->setMinimumSize(timeItem::timeInput24h, timeItem::height - timeItem::separator);

            onLoadCallback = [&](std::shared_ptr<AlarmsRecord> alarm) {
                hourInput->setText(TimePointToHourString24H(alarm->time));
                minuteInput->setText(TimePointToMinutesString(alarm->time));
            };
        }
    }

    bool AlarmTimeItem::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
    {
        hBox->setPosition(0, 0);
        hBox->setSize(newDim.w, newDim.h);
        return true;
    }

    bool AlarmTimeItem::isPm(const std::string &text) const
    {
        return !(text == utils::localize.get(utils::time::Locale::getAM()));
    }

    void AlarmTimeItem::validateHour()
    {
        if (!utils::time::validateTime(hourInput->getText(), minuteInput->getText(), !mode24H)) {
            hourInput->setText("0");
            minuteInput->setText("00");
        }
    }

} /* namespace gui */

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

#pragma once

#include "AlarmInternalListItem.hpp"
#include <Text.hpp>

namespace gui
{
    class AlarmTimeItem : public AlarmInternalListItem
    {
        gui::HBox *hBox          = nullptr;
        gui::Label *colonLabel   = nullptr;
        gui::Text *hourInput     = nullptr;
        gui::Text *minuteInput   = nullptr;
        gui::Label *mode12hInput = nullptr;
        bool mode24H             = false;

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

        void applyItemSpecificProperties(gui::Text *item);
        void applyInputCallbacks();
        void onInputCallback(gui::Text &textItem);
        void prepareForTimeMode();
        bool isPm(const std::string &text) const;
        void validateHour();

      public:
        AlarmTimeItem(bool mode24H,
                      std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                      std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);

        // virtual methods from Item
        bool onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) override;
    };

} /* namespace gui */

M module-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp => module-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp +7 -1
@@ 3,6 3,7 @@

#include "AlarmClockMainWindow.hpp"
#include "application-alarm-clock/widgets/AlarmClockStyle.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "windows/DialogMetadata.hpp"
#include "messages/DialogMetadataMessage.hpp"
#include <InputEvent.hpp>


@@ 97,7 98,12 @@ namespace app::alarmClock
        }

        if (inputEvent.isShortPress() && inputEvent.is(gui::KeyCode::KEY_LEFT)) {
            LOG_DEBUG("Not implemented yet");
            auto rec                              = new AlarmsRecord();
            rec->time                             = TimePointNow();
            auto event                            = std::make_shared<AlarmsRecord>(*rec);
            std::unique_ptr<AlarmRecordData> data = std::make_unique<AlarmRecordData>(event);
            data->setDescription(style::alarmClock::newAlarm);
            application->switchWindow(style::alarmClock::window::name::newEditAlarm, std::move(data));
            return true;
        }


M module-apps/application-alarm-clock/windows/AlarmClockOptions.cpp => module-apps/application-alarm-clock/windows/AlarmClockOptions.cpp +7 -4
@@ 3,6 3,7 @@

#include "AlarmClockOptions.hpp"
#include "application-alarm-clock/widgets/AlarmClockStyle.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "windows/Options.hpp"
#include "windows/DialogMetadata.hpp"
#include "messages/DialogMetadataMessage.hpp"


@@ 44,8 45,11 @@ namespace app::alarmClock
        std::list<gui::Option> options;
        addOption(
            {"app_alarm_clock_options_edit"},
            [](gui::Item &) {
                LOG_DEBUG("Not implemented yet - switch to edit window");
            [application, record](gui::Item &) {
                auto rec  = std::make_unique<AlarmsRecord>(record);
                auto data = std::make_unique<AlarmRecordData>(std::move(rec));
                data->setDescription(style::alarmClock::editAlarm);
                application->switchWindow(style::alarmClock::window::name::newEditAlarm, std::move(data));
                return true;
            },
            options);


@@ 59,8 63,7 @@ namespace app::alarmClock
        addOption(
            {"app_alarm_clock_options_turn_off_all_alarms"},
            [application, &alarmsRepository](gui::Item &) {
                alarmsRepository.turnOffAll(
                    [application](bool) { application->switchWindow(gui::name::window::main_window); });
                alarmsRepository.turnOffAll([application](bool) { application->returnToPreviousWindow(); });
                return true;
            },
            options);

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

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

namespace app::alarmClock
{

    NewEditAlarmWindow::NewEditAlarmWindow(app::Application *app,
                                           std::unique_ptr<AlarmClockEditWindowContract::Presenter> &&windowPresenter)
        : AppWindow(app, style::alarmClock::window::name::newEditAlarm), presenter{std::move(windowPresenter)}
    {
        presenter->attach(this);
        buildInterface();
    }

    void NewEditAlarmWindow::buildInterface()
    {
        AppWindow::buildInterface();

        topBar->setActive(gui::TopBar::Elements::TIME, true);
        bottomBar->setActive(gui::BottomBar::Side::RIGHT, true);
        bottomBar->setActive(gui::BottomBar::Side::CENTER, true);
        bottomBar->setText(gui::BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::localize.get(style::strings::common::save));

        list = new gui::ListView(this,
                                 style::alarmClock::window::listView_x,
                                 style::alarmClock::window::listView_y,
                                 style::alarmClock::window::listView_w,
                                 style::alarmClock::window::listView_h,
                                 presenter->getAlarmsItemProvider());
        setFocusItem(list);
    }

    void NewEditAlarmWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        switch (alarmAction) {
        case AlarmAction::Add:
            setTitle(utils::localize.get("app_alarm_clock_new_alarm_title"));
            break;
        case AlarmAction::Edit:
            setTitle(utils::localize.get("app_alarm_clock_edit_alarm_title"));
            break;
        }

        if (mode == gui::ShowMode::GUI_SHOW_INIT) {
            auto rec = dynamic_cast<AlarmRecordData *>(data);
            if (rec != nullptr) {
                alarmRecord = rec->getData();
            }
            presenter->loadData(alarmRecord);
        }
    }

    bool NewEditAlarmWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (AppWindow::onInput(inputEvent)) {
            return true;
        }

        if (inputEvent.isShortPress() && inputEvent.is(gui::KeyCode::KEY_ENTER)) {
            presenter->saveData(alarmRecord, alarmAction);
            return true;
        }

        return false;
    }

    bool NewEditAlarmWindow::handleSwitchData(gui::SwitchData *data)
    {
        if (data == nullptr) {
            return false;
        }

        if (data->getDescription() == style::alarmClock::editAlarm) {
            alarmAction = AlarmAction::Edit;
        }
        else {
            alarmAction = AlarmAction::Add;
        }
        return true;
    }
} // namespace app::alarmClock

A module-apps/application-alarm-clock/windows/NewEditAlarmWindow.hpp => module-apps/application-alarm-clock/windows/NewEditAlarmWindow.hpp +35 -0
@@ 0,0 1,35 @@
// Copyright (c) 2017-2020, 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/models/NewEditAlarmModel.hpp"
#include "application-alarm-clock/presenter/AlarmClockEditWindowPresenter.hpp"
#include "application-alarm-clock/data/AlarmsData.hpp"
#include "windows/AppWindow.hpp"
#include "Application.hpp"
#include <InputEvent.hpp>
#include <ListView.hpp>
#include <Utils.hpp>

namespace app::alarmClock
{
    class NewEditAlarmWindow : public gui::AppWindow, public AlarmClockEditWindowContract::View
    {
        std::shared_ptr<AlarmsRecord> alarmRecord;
        gui::ListView *list     = nullptr;
        AlarmAction alarmAction = AlarmAction::Add;
        std::unique_ptr<AlarmClockEditWindowContract::Presenter> presenter;

      public:
        NewEditAlarmWindow(app::Application *app,
                           std::unique_ptr<AlarmClockEditWindowContract::Presenter> &&windowPresenter);

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

} // namespace app::alarmClock