~aleteoryx/muditaos

4e034df4ed893ce3475319e1cb47e6f2d509e5be — Przemyslaw Brudny 5 years ago 9645fbc
[EDG-4743] Add new/options windows to quotes

Add windows for add/edit/delete quotes, use ListView for quote display.
M image/assets/lang/English.json => image/assets/lang/English.json +8 -3
@@ 299,12 299,17 @@
  "app_settings_display_input_language": "Input language",
  "app_settings_display_locked_screen_autolock": "Autolock",
  "app_settings_display_locked_screen_wallpaper": "Wallpaper",
  "app_settings_display_locked_screen_quotes": "Quotes",
  "app_settings_display_locked_screen_new_quote": "New quote",
  "app_settings_display_wallpaper_logo": "Mudita logo",
  "app_settings_display_wallpaper_clock": "Clock",
  "app_settings_display_wallpaper_quotes": "Quotes",
  "app_settings_display_wallpaper_select_quotes": "Select Quotes",
  "app_settings_display_wallpaper_quotes_options": "Options",
  "app_settings_display_wallpaper_quotes_edit": "Edit quote",
  "app_settings_display_wallpaper_quotes_delete": "Delete quote",
  "app_settings_display_wallpaper_quotes_new": "New quote",
  "app_settings_display_wallpaper_quotes_select": "Select quotes",
  "app_settings_display_wallpaper_quotes_delete_confirmation": "Delete quote?",
  "app_settings_display_wallpaper_quotes_quote": "Quote text",
  "app_settings_display_wallpaper_quotes_author": "Quote author",
  "app_settings_system": "System",
  "app_settings_apps_tools": "Apps and tools",
  "app_settings_apps_messages": "Messages",

A image/user/data/applications/settings/quotes.json => image/user/data/applications/settings/quotes.json +27 -0
@@ 0,0 1,27 @@
[
  {
    "lang":"english",
    "author": "Buddha",
    "quote": "Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment."
  },
  {
    "lang":"english",
    "author": "Tara Brach",
    "quote": "The only way to live is by accepting each minute as an unrepeatable miracle."
  },
  {
    "lang":"english",
    "author": "Richar Bach",
    "quote": "The simplest things are often the truest"
  },
  {
    "lang":"english",
    "author": "Eckhart Tolle",
    "quote": "Always say yes to the present moment. Say yes to life."
  },
  {
    "lang":"english",
    "author": "Naomi Judd",
    "quote": "Slow down, simplify and be kind"
  }
]

M module-apps/application-settings-new/ApplicationSettings.cpp => module-apps/application-settings-new/ApplicationSettings.cpp +6 -12
@@ 25,6 25,7 @@
#include "windows/QuotesMainWindow.hpp"
#include "windows/QuotesAddWindow.hpp"
#include "windows/SecurityMainWindow.hpp"
#include "windows/QuotesOptionsWindow.hpp"
#include "windows/ChangePasscodeWindow.hpp"

#include "Dialog.hpp"


@@ 51,7 52,9 @@ namespace app
    namespace settings
    {
        constexpr inline auto operators_on = "operators_on";
    }
        const std::string quotesPath =
            purefs::createPath(purefs::dir::getUserDiskPath(), "data/applications/settings/quotes.json");
    } // namespace settings

    ApplicationSettingsNew::ApplicationSettingsNew(std::string name,
                                                   std::string parent,


@@ 223,21 226,12 @@ namespace app
        windowsFactory.attach(gui::window::name::wallpaper, [](Application *app, const std::string &name) {
            return std::make_unique<gui::WallpaperWindow>(app);
        });
        windowsFactory.attach(gui::window::name::quotes, [](Application *app, const std::string &name) {
            return std::make_unique<gui::QuotesMainWindow>(app);
        });
        windowsFactory.attach(gui::window::name::new_quote, [](Application *app, const std::string &name) {
            return std::make_unique<gui::QuotesAddWindow>(app);
        windowsFactory.attach(gui::window::name::quotes_dialog_yes_no, [](Application *app, const std::string &name) {
            return std::make_unique<gui::DialogYesNo>(app, name);
        });
        windowsFactory.attach(gui::window::name::security, [](Application *app, const std::string &name) {
            return std::make_unique<gui::SecurityMainWindow>(app);
        });
        windowsFactory.attach(gui::window::name::change_passcode, [](Application *app, const std::string &name) {
            return std::make_unique<gui::ChangePasscodeWindow>(app);
        });
        windowsFactory.attach(gui::window::name::dialog_confirm, [](Application *app, const std::string &name) {
            return std::make_unique<gui::DialogConfirm>(app, gui::window::name::dialog_confirm);
        });
    }

    void ApplicationSettingsNew::destroyUserInterface()

M module-apps/application-settings-new/ApplicationSettings.hpp => module-apps/application-settings-new/ApplicationSettings.hpp +10 -4
@@ 33,10 33,14 @@ namespace gui::window::name
    inline constexpr auto nightshift = "Nightshift";
    inline constexpr auto templates  = "Templates";

    inline constexpr auto autolock  = "Autolock";
    inline constexpr auto wallpaper = "Wallpaper";
    inline constexpr auto quotes    = "Quotes";
    inline constexpr auto new_quote = "NewQuote";
    inline constexpr auto autolock             = "Autolock";
    inline constexpr auto wallpaper            = "Wallpaper";
    inline constexpr auto quotes               = "Quotes";
    inline constexpr auto new_quote            = "NewQuote";
    inline constexpr auto edit_quote           = "EditQuote";
    inline constexpr auto options_quote        = "OptionsQuote";
    inline constexpr auto delete_quote         = "DeleteQuote";
    inline constexpr auto quotes_dialog_yes_no = "DialogYesNo";

    inline constexpr auto display_and_keypad = "DisplayAndKeypad";
    inline constexpr auto change_settings    = "ChangeSettings";


@@ 130,6 134,8 @@ namespace app
        void setStatus(bool isDisplayLightSwitchOn) override;

      private:
        void attachQuotesWindows();

        Store::GSM::SIM selectedSim   = Store::GSM::get()->selected;
        std::string selectedSimNumber = {};
        bsp::Board board              = bsp::Board::none;

M module-apps/application-settings-new/CMakeLists.txt => module-apps/application-settings-new/CMakeLists.txt +1 -2
@@ 17,6 17,7 @@ target_sources( ${PROJECT_NAME}
        ApplicationSettings.cpp
        widgets/timeWidget.cpp
        widgets/ChangePasscodeLockHandler.cpp
        widgets/QuoteWidget.cpp
        windows/SettingsMainWindow.cpp
        windows/AddDeviceWindow.cpp
        windows/AllDevicesWindow.cpp


@@ 46,7 47,6 @@ target_sources( ${PROJECT_NAME}

    PUBLIC
        ApplicationSettings.hpp
        widgets/ChangePasscodeLockHandler.hpp
        windows/SettingsMainWindow.hpp
        windows/BaseSettingsWindow.hpp
        windows/FontSizeWindow.hpp


@@ 60,7 60,6 @@ target_sources( ${PROJECT_NAME}
        widgets/SettingsStyle.hpp
        windows/AutolockWindow.hpp
        windows/WallpaperWindow.hpp
        windows/ChangePasscodeWindow.hpp
)

add_dependencies(${PROJECT_NAME} version)

A module-apps/application-settings-new/data/QuoteSwitchData.hpp => module-apps/application-settings-new/data/QuoteSwitchData.hpp +45 -0
@@ 0,0 1,45 @@
// 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-settings-new/model/QuotesModel.hpp"

#include <module-gui/gui/SwitchData.hpp>
#include <json/json11.hpp>
#include <utility>

namespace app
{
    class QuotesModel;
};

namespace gui
{
    enum class QuoteAction
    {
        None,
        Add,
        Edit
    };

    class QuoteSwitchData : public gui::SwitchData
    {
      public:
        QuoteSwitchData(QuoteAction action, app::QuoteRecord quote = {}) : action(action), quote(std::move(quote))
        {}

        [[nodiscard]] auto getQuote() const
        {
            return quote;
        }
        [[nodiscard]] auto getAction() const
        {
            return action;
        }

      private:
        QuoteAction action;
        app::QuoteRecord quote;
    };
} // namespace gui

A module-apps/application-settings-new/model/QuotesModel.cpp => module-apps/application-settings-new/model/QuotesModel.cpp +100 -0
@@ 0,0 1,100 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "application-settings-new/windows/QuotesMainWindow.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/model/QuotesRepository.hpp"
#include "application-settings-new/model/QuotesModel.hpp"
#include "application-settings-new/model/QuotesModel.hpp"

#include <InputEvent.hpp>
#include <i18n/i18n.hpp>
#include <json/json11.hpp>
#include <Utils.hpp>
#include <string>
#include <utility>

namespace style::quotes::list
{
    constexpr auto item_height = 63;
    constexpr auto max_quotes  = 100;
} // namespace style::quotes::list

namespace app
{
    QuotesModel::QuotesModel(app::Application *app, std::unique_ptr<QuotesRepository> repository)
        : application(app), repository(std::move(repository))
    {}

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

    auto QuotesModel::getMinimalItemHeight() const -> unsigned int
    {
        return style::quotes::list::item_height;
    }

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

    auto QuotesModel::getItem(gui::Order order) -> gui::ListItem *
    {
        auto app   = application;
        auto *item = dynamic_cast<gui::QuoteWidget *>(getRecord(order));

        if (item != nullptr) {
            item->inputCallback = [app, item](gui::Item &, const gui::InputEvent &event) {
                if (event.isShortPress() && event.is(gui::KeyCode::KEY_LF)) {
                    app->switchWindow(
                        gui::window::name::options_quote,
                        std::make_unique<gui::QuoteSwitchData>(gui::QuoteAction::None, item->getQuoteData()));
                }
                return false;
            };
        }
        return item;
    }

    void QuotesModel::rebuild()
    {
        list->clear();
        eraseInternalData();
        createData();
        list->rebuildList();
    }

    void QuotesModel::createData()
    {
        repository->get(0, style::quotes::list::max_quotes, [this](const std::list<QuoteRecord> &quotes, unsigned int) {
            auto app = application;
            for (const auto &quote : quotes) {
                auto item = new gui::QuoteWidget(
                    quote,
                    [app](const UTF8 &text) {
                        app->getCurrentWindow()->bottomBarTemporaryMode(text, gui::BottomBar::Side::CENTER, false);
                    },
                    [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });

                item->deleteByList = false;
                internalData.push_back(item);
            }
            return true;
        });
    }

    void QuotesModel::remove(const app::QuoteRecord &quote)
    {
        repository->remove(quote);
    }

    void QuotesModel::save(const app::QuoteRecord &quote)
    {
        repository->save(quote);
    }

} // namespace app

A module-apps/application-settings-new/model/QuotesModel.hpp => module-apps/application-settings-new/model/QuotesModel.hpp +42 -0
@@ 0,0 1,42 @@
// 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 "QuotesRepository.hpp"

#include <purefs/filesystem_paths.hpp>
#include <module-gui/gui/widgets/ListView.hpp>
#include <module-apps/InternalModel.hpp>

namespace gui
{
    class QuoteWidget;
}

namespace app
{
    class QuotesModel : public app::InternalModel<gui::QuoteWidget *>, public gui::ListItemProvider
    {
      public:
        QuotesModel(app::Application *app, std::unique_ptr<QuotesRepository> repository);

        [[nodiscard]] auto requestRecordsCount() -> unsigned int final;
        [[nodiscard]] auto getMinimalItemHeight() const -> unsigned int final;

        auto getItem(gui::Order order) -> gui::ListItem * final;
        void requestRecords(const uint32_t offset, const uint32_t limit) final;

        void rebuild();

        void remove(const app::QuoteRecord &quote);
        void save(const app::QuoteRecord &quote);

      private:
        void createData();

        app::Application *application                     = nullptr;
        std::unique_ptr<app::QuotesRepository> repository = nullptr;
    };

} // namespace app

A module-apps/application-settings-new/model/QuotesRepository.cpp => module-apps/application-settings-new/model/QuotesRepository.cpp +103 -0
@@ 0,0 1,103 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesRepository.hpp"

#include <module-utils/gsl/gsl_util>
#include <algorithm>
#include <utility>

namespace app
{
    QuotesJsonRepository::QuotesJsonRepository(const std::string &path) : repositoryPath{std::move(path)}
    {}

    void QuotesJsonRepository::get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback)
    {
        if (quotes.empty()) {
            readQuotes(repositoryPath);
        }

        if (callback) {
            std::uint32_t size = quotes.size();
            auto start         = std::next(quotes.begin(), std::min(offset, size));
            auto end           = std::next(quotes.begin(), std::min(offset + limit, size));
            std::list<QuoteRecord> result{start, end};
            callback(result, result.size());
        }
    }

    void QuotesJsonRepository::save(const QuoteRecord &quote)
    {
        auto toEdit = std::find_if(quotes.begin(), quotes.end(), [quote](auto &&d) { return d.id == quote.id; });

        if (toEdit != quotes.end()) {
            toEdit->quote  = quote.quote;
            toEdit->author = quote.author;
        }
        else if (quote.id == 0) {
            quotes.push_back(quote);
        }

        writeQuotes(repositoryPath);
    }

    void QuotesJsonRepository::remove(const QuoteRecord &quote)
    {
        quotes.remove_if([quote](auto &&d) { return d.id == quote.id; });
        writeQuotes(repositoryPath);
    }

    void QuotesJsonRepository::writeQuotes(const fs::path &quotesFilename)
    {
        if (auto file = std::fopen(repositoryPath.c_str(), "w"); file != nullptr) {
            auto _    = gsl::finally([file] { std::fclose(file); });
            auto body = json11::Json{quotes};
            auto text = body.dump();
            std::fwrite(text.c_str(), 1, text.length(), file);
        }
    }

    void QuotesJsonRepository::readQuotes(const fs::path &quotesFilename)
    {
        std::string err;

        const auto fileContents = readFileToString(quotesFilename);
        auto obj                = json11::Json::parse(fileContents.c_str(), err).array_items();

        if (!err.empty()) {
            LOG_ERROR("Error while parsing quotes from file: %s error: %s ", quotesFilename.c_str(), err.c_str());
            return;
        }

        quotes.clear();

        auto id = 1;
        std::transform(obj.begin(), obj.end(), std::back_inserter(quotes), [&id](auto item) {
            return QuoteRecord{id++, item["quote"].string_value(), item["author"].string_value()};
        });
    }

    auto QuotesJsonRepository::readFileToString(const fs::path &filename) -> std::string
    {
        constexpr auto tar_buf = 8192 * 4;
        auto file              = std::fopen(filename.c_str(), "r");
        if (file == nullptr) {
            return {};
        }
        auto _            = gsl::finally([file] { std::fclose(file); });
        const auto length = utils::filesystem::filelength(file);

        if (length >= tar_buf) {
            LOG_ERROR("File %s length is too high!", filename.c_str());
            return {};
        }
        LOG_INFO("file length: %ld", length);
        auto buffer = std::make_unique<char[]>(length + 1);
        std::fread(buffer.get(), 1, length, file);
        return std::string(buffer.get());
    }
} // namespace app

A module-apps/application-settings-new/model/QuotesRepository.hpp => module-apps/application-settings-new/model/QuotesRepository.hpp +58 -0
@@ 0,0 1,58 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

// 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 <json/json11.hpp>

#include <list>
#include <functional>
#include <module-apps/Application.hpp>

namespace app
{
    struct QuoteRecord
    {
        int id = 0;
        std::string quote;
        std::string author;

        [[nodiscard]] auto to_json() const -> json11::Json
        {
            return json11::Json::object{{"quote", quote}, {"author", author}};
        }
    };

    class QuotesRepository
    {
      public:
        using OnGetCallback = std::function<bool(const std::list<QuoteRecord> &, unsigned int)>;

        virtual ~QuotesRepository() noexcept = default;

        virtual void get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback) = 0;
        virtual void save(const QuoteRecord &quote)                                                = 0;
        virtual void remove(const QuoteRecord &quote)                                              = 0;
    };

    class QuotesJsonRepository : public QuotesRepository
    {
      public:
        QuotesJsonRepository(const std::string &path);

        void get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback) override;
        void save(const QuoteRecord &quote) override;
        void remove(const QuoteRecord &quote) override;

      private:
        void writeQuotes(const fs::path &path);
        void readQuotes(const fs::path &fn);
        std::string readFileToString(const fs::path &fn);

        std::list<QuoteRecord> quotes;
        std::string repositoryPath;
    };
} // namespace app

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

#include "QuoteWidget.hpp"

#include <BottomBar.hpp>
#include <InputEvent.hpp>
#include <utility>
#include <i18n/i18n.hpp>

namespace style::quotes
{
    namespace widget
    {
        inline constexpr uint32_t w = style::window::default_body_width;
        inline constexpr uint32_t h = 50;

        inline constexpr uint32_t input_box_w           = 55;
        inline constexpr uint32_t input_box_h           = h;
        inline constexpr int32_t input_box_right_margin = 20;

        inline constexpr uint32_t description_label_w           = 280;
        inline constexpr uint32_t description_label_h           = 33;
        inline constexpr int32_t description_label_right_margin = 40;

        inline constexpr int32_t tick_image_left_margin  = -64;
        inline constexpr int32_t tick_image_right_margin = 32;

    } // namespace widget

} // namespace style::quotes

namespace gui
{

    QuoteWidget::QuoteWidget(const app::QuoteRecord &quote,
                             std::function<void(const UTF8 &)> bottomBarTemporaryMode,
                             std::function<void()> bottomBarRestoreFromTemporaryMode)
        : bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode)), quote(quote)
    {

        setMinimumSize(style::quotes::widget::w, style::quotes::widget::h);

        setMargins(gui::Margins(0, style::margins::big, 0, 0));

        hBox = new gui::HBox(this, 0, 0, this->getWidth(), 80);
        hBox->setEdges(gui::RectangleEdge::None);
        hBox->setPenFocusWidth(style::window::default_border_focus_w);
        hBox->setPenWidth(style::window::default_border_rect_no_focus);

        inputBoxLabel = new gui::Label(hBox, 0, 0, 0, 0);
        inputBoxLabel->setMinimumSize(style::quotes::widget::input_box_w, style::quotes::widget::input_box_h);

        inputBoxLabel->setMargins(gui::Margins(0, 0, style::quotes::widget::input_box_right_margin, 0));
        inputBoxLabel->setEdges(gui::RectangleEdge::Bottom);
        inputBoxLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        inputBoxLabel->setFont(style::window::font::medium);
        inputBoxLabel->activeItem = false;

        tickImage = new gui::Image(hBox, 0, 0, 0, 0);
        tickImage->setAlignment(Alignment(gui::Alignment::Vertical::Center));
        // Not ideal -> best solution would be to create separate widget with image inside box.
        tickImage->setMargins(gui::Margins(
            style::quotes::widget::tick_image_left_margin, 0, style::quotes::widget::tick_image_right_margin, 0));
        tickImage->set("small_tick_W_M");
        tickImage->setVisible(true);
        tickImage->activeItem = false;

        descriptionLabel = new gui::Label(hBox, 0, 0, 0, 0);
        descriptionLabel->setMinimumSize(style::quotes::widget::description_label_w,
                                         style::quotes::widget::description_label_h);
        descriptionLabel->setMargins(gui::Margins(0, 0, style::quotes::widget::description_label_right_margin, 0));
        descriptionLabel->setEdges(gui::RectangleEdge::None);
        descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        descriptionLabel->setFont(style::window::font::medium);
        descriptionLabel->activeItem = false;

        descriptionLabel->setText(quote.quote);

        focusChangedCallback = [&](gui::Item &item) {
            if (item.focus) {
                descriptionLabel->setFont(style::footer::font::bold);
                setFocusItem(inputBoxLabel);
                auto bottorBarText =
                    tickImage->visible ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
                this->bottomBarTemporaryMode(bottorBarText);
            }
            else {
                descriptionLabel->setFont(style::footer::font::medium);
                setFocusItem(nullptr);
                this->bottomBarRestoreFromTemporaryMode();
            }
            return true;
        };

        activatedCallback = [&](gui::Item &item) {
            tickImage->setVisible(!tickImage->visible);
            auto bottorBarText =
                tickImage->visible ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
            this->bottomBarTemporaryMode(bottorBarText);
            hBox->resizeItems();
            return true;
        };

        setEdges(gui::RectangleEdge::None);
    }

    auto QuoteWidget::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) -> bool
    {
        hBox->setPosition(0, 0);
        hBox->setSize(newDim.w, newDim.h);

        return true;
    }

} /* namespace gui */

A module-apps/application-settings-new/widgets/QuoteWidget.hpp => module-apps/application-settings-new/widgets/QuoteWidget.hpp +42 -0
@@ 0,0 1,42 @@
// 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-settings-new/data/QuoteSwitchData.hpp"

#include <BoxLayout.hpp>
#include <Image.hpp>
#include <Label.hpp>
#include <ListItem.hpp>
#include <json/json11.hpp>

namespace gui
{
    class QuoteWidget : public ListItem
    {
      public:
        QuoteWidget(const app::QuoteRecord &quote,
                    std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                    std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);

        auto onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) -> bool override;

        [[nodiscard]] auto getQuoteData() const -> app::QuoteRecord
        {
            return quote;
        }

      private:
        gui::HBox *hBox              = nullptr;
        gui::Label *inputBoxLabel    = nullptr;
        gui::Label *descriptionLabel = nullptr;
        gui::Image *tickImage        = nullptr;

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

        app::QuoteRecord quote;
    };

} /* namespace gui */

M module-apps/application-settings-new/windows/QuotesAddWindow.cpp => module-apps/application-settings-new/windows/QuotesAddWindow.cpp +165 -10
@@ 2,32 2,187 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesAddWindow.hpp"
#include "QuotesMainWindow.hpp"

#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/data/QuoteSwitchData.hpp"
#include "application-settings-new/model/QuotesRepository.hpp"

#include <i18n/i18n.hpp>
#include <widgets/Text.hpp>

namespace style
{
    constexpr auto counterWidth = 70;
    constexpr auto headerWidth  = style::window::default_body_width - counterWidth;
} // namespace style

namespace gui
{
    QuotesAddWindow::QuotesAddWindow(app::Application *app) : AppWindow(app, gui::window::name::quotes)
    namespace
    {
        constexpr auto maxQuoteCharactersCount  = 150U;
        constexpr auto maxQuoteLinesCount       = 4;
        constexpr auto maxAuthorCharactersCount = 30U;

        auto formatCounterText(uint32_t counter, uint32_t maxValue) -> std::string
        {
            std::ostringstream counterText;
            counterText << counter << '/' << maxValue;
            return counterText.str();
        }
    } // namespace

    QuoteAddEditWindow::QuoteAddEditWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model)
        : AppWindow(app, gui::window::name::quotes), quoteModel(std::move(model))
    {
        setTitle(utils::localize.get("app_settings_display_locked_screen_new_quote"));
        buildInterface();
    }

    void QuotesAddWindow::buildInterface()
    void QuoteAddEditWindow::buildInterface()
    {
        AppWindow::buildInterface();

        bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::save));
        bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));

        auto text = new gui::Text(nullptr,
                                  style::window::default_left_margin,
                                  title->offset_h() + style::margins::big,
                                  style::window::default_body_width,
                                  style::window::default_body_height);
        auto vBox = new VBox(this,
                             style::window::default_left_margin,
                             style::header::height + style::margins::very_big,
                             style::window::default_body_width,
                             style::window::default_body_height);

        vBox->setEdges(RectangleEdge::None);
        vBox->setPenFocusWidth(::style::window::default_border_focus_w);
        vBox->setPenWidth(::style::window::default_border_rect_no_focus);

        auto quoteHeader = new HBox(vBox, 0, 0, 0, 0);
        quoteHeader->setMinimumSize(style::window::default_body_width, style::window::label::default_h);
        quoteHeader->activeItem = false;
        quoteHeader->setEdges(gui::RectangleEdge::None);

        auto quoteLabel = new Label(quoteHeader, 0, 0, 0, 0);
        quoteLabel->setMinimumSize(style::headerWidth, style::window::label::default_h);
        quoteLabel->setEdges(RectangleEdge::None);
        quoteLabel->setPenFocusWidth(::style::window::default_border_focus_w);
        quoteLabel->setText(utils::localize.get("app_settings_display_wallpaper_quotes_quote"));
        quoteLabel->setFont(::style::window::font::verysmall);
        quoteLabel->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left});
        quoteLabel->activeItem = false;

        quoteCharCounter = new gui::Label(quoteHeader, 0, 0, 0, 0);
        quoteCharCounter->setMinimumSize(style::counterWidth, style::window::label::default_h);
        quoteCharCounter->setEdges(gui::RectangleEdge::None);
        quoteCharCounter->setFont(::style::window::font::verysmall);
        quoteCharCounter->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Right});

        quoteText = new gui::Text(vBox, 0, 0, 0, 0);
        quoteText->setAlignment(gui::Alignment{gui::Alignment::Vertical::Top});
        quoteText->setMinimumSize(style::window::default_body_width,
                                  style::window::label::default_h * maxQuoteLinesCount);
        quoteText->setPenFocusWidth(::style::window::default_border_focus_w);
        quoteText->setPenWidth(::style::window::default_border_rect_no_focus);
        quoteText->setEdges(gui::RectangleEdge::None);
        quoteText->setFont(::style::window::font::medium);
        quoteText->setInputMode(new InputMode(
            {InputMode::ABC, InputMode::abc, InputMode::digit},
            [=](const UTF8 &text) { bottomBarTemporaryMode(text); },
            [=]() { bottomBarRestoreFromTemporaryMode(); },
            [=]() { selectSpecialCharacter(); }));
        quoteText->setTextLimitType(gui::TextLimitType::MaxSignsCount, maxQuoteCharactersCount);
        quoteText->setTextChangedCallback([this](Item &, const UTF8 &text) { setQuoteCharactersCount(text.length()); });

        auto authorHeader = new HBox(vBox, 0, 0, 0, 0);
        authorHeader->setMinimumSize(style::window::default_body_width, style::window::label::default_h);
        authorHeader->setEdges(gui::RectangleEdge::None);
        authorHeader->activeItem = false;

        auto authorLabel = new Label(authorHeader, 0, 0, 0, 0);
        authorLabel->setMinimumSize(style::headerWidth, style::window::label::default_h);
        authorLabel->setEdges(RectangleEdge::None);
        authorLabel->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left});
        authorLabel->setText(utils::localize.get("app_settings_display_wallpaper_quotes_author"));
        authorLabel->setPenFocusWidth(::style::window::default_border_focus_w);
        authorLabel->setFont(::style::window::font::verysmall);
        authorLabel->setPadding(gui::Padding(0, 0, 0, 0));
        authorLabel->activeItem = false;

        authorCharCounter = new gui::Label(authorHeader, 0, 0, 0, 0);
        authorCharCounter->setMinimumSize(style::counterWidth, style::window::label::default_h);
        authorCharCounter->setEdges(gui::RectangleEdge::None);
        authorCharCounter->setFont(::style::window::font::verysmall);
        authorCharCounter->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Right});

        authorText = new gui::Text(vBox, 0, 0, 0, 0);
        authorText->setMinimumSize(style::window::default_body_width, style::window::label::default_h);
        authorText->setAlignment(gui::Alignment{gui::Alignment::Vertical::Top});
        authorText->setPenFocusWidth(::style::window::default_border_focus_w);
        authorText->setPenWidth(::style::window::default_border_rect_no_focus);
        authorText->setEdges(gui::RectangleEdge::None);
        authorText->setFont(::style::window::font::medium);
        authorText->setInputMode(new InputMode(
            {InputMode::ABC, InputMode::abc, InputMode::digit},
            [=](const UTF8 &text) { bottomBarTemporaryMode(text); },
            [=]() { bottomBarRestoreFromTemporaryMode(); },
            [=]() { selectSpecialCharacter(); }));
        authorText->setTextLimitType(gui::TextLimitType::MaxSignsCount, maxAuthorCharactersCount);
        authorText->setTextChangedCallback(
            [this](Item &, const UTF8 &text) { setAuthorCharactersCount(text.length()); });

        setTitle(utils::localize.get("app_settings_display_wallpaper_quotes_new"));
        vBox->resizeItems();
        setFocusItem(quoteText);
    }

        addWidget(text);
        setFocusItem(text);
    void QuoteAddEditWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        auto *quotedata = dynamic_cast<QuoteSwitchData *>(data);
        if (quotedata == nullptr) {
            return;
        }

        quoteAction = quotedata->getAction();
        quoteData   = quotedata->getQuote();

        if (quoteAction == QuoteAction::Edit) {
            setTitle(utils::localize.get("app_settings_display_wallpaper_quotes_edit"));
            quoteText->setText(quoteData.quote);
            authorText->setText(quoteData.author);
        }
        else {
            setTitle(utils::localize.get("app_settings_display_wallpaper_quotes_new"));
        }

        setAuthorCharactersCount(authorText->getText().length());
        setQuoteCharactersCount(quoteText->getText().length());
    }

    bool QuoteAddEditWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (inputEvent.isShortPress() && inputEvent.is(gui::KeyCode::KEY_ENTER)) {
            LOG_DEBUG("Save Quote: %s", quoteText->getText().c_str());
            quoteData.quote  = quoteText->getText();
            quoteData.author = authorText->getText();
            quoteModel->save(quoteData);

            auto backToOptionWindow = 1;
            auto backToMainWindow   = 2;

            auto windowToBack = quoteAction == QuoteAction::Add ? backToOptionWindow : backToMainWindow;
            application->returnToPreviousWindow(windowToBack);
        }

        return AppWindow::onInput(inputEvent);
    }

    void QuoteAddEditWindow::setAuthorCharactersCount(std::uint32_t count)
    {
        authorCharCounter->setText(formatCounterText(count, maxAuthorCharactersCount));
    }

    void QuoteAddEditWindow::setQuoteCharactersCount(std::uint32_t count)
    {
        quoteCharCounter->setText(formatCounterText(count, maxQuoteCharactersCount));
    }

} // namespace gui

M module-apps/application-settings-new/windows/QuotesAddWindow.hpp => module-apps/application-settings-new/windows/QuotesAddWindow.hpp +17 -3
@@ 4,18 4,32 @@
#pragma once

#include "BaseSettingsWindow.hpp"
#include "QuotesMainWindow.hpp"
#include "application-settings-new/model/QuotesModel.hpp"

namespace gui
{
    class CheckBoxWithLabel;

    class QuotesAddWindow : public AppWindow
    class QuoteAddEditWindow : public AppWindow
    {
      public:
        QuotesAddWindow(app::Application *app);
        QuoteAddEditWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model);
        void buildInterface() override;

      private:
        std::vector<CheckBoxWithLabel *> boxes;
        auto onInput(const InputEvent &inputEvent) -> bool override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;
        void setAuthorCharactersCount(uint32_t count);
        void setQuoteCharactersCount(uint32_t count);

        gui::Text *quoteText          = nullptr;
        gui::Text *authorText         = nullptr;
        gui::Label *authorCharCounter = nullptr;
        gui::Label *quoteCharCounter  = nullptr;

        QuoteAction quoteAction;
        app::QuoteRecord quoteData;
        std::shared_ptr<app::QuotesModel> quoteModel;
    };
} // namespace gui

M module-apps/application-settings-new/windows/QuotesMainWindow.cpp => module-apps/application-settings-new/windows/QuotesMainWindow.cpp +52 -74
@@ 2,7 2,8 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesMainWindow.hpp"

#include "application-settings-new/model/QuotesRepository.hpp"
#include "application-settings-new/model/QuotesModel.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/widgets/SettingsStyle.hpp"
#include "OptionSetting.hpp"


@@ 10,17 11,54 @@
#include <InputEvent.hpp>
#include <i18n/i18n.hpp>
#include <json/json11.hpp>
#include <purefs/filesystem_paths.hpp>
#include <Utils.hpp>
#include <string>
#include <utility>

namespace style::quotes
{
    namespace list
    {
        constexpr auto X      = style::window::default_left_margin;
        constexpr auto Y      = style::header::height;
        constexpr auto Width  = style::listview::body_width_with_scroll;
        constexpr auto Height = style::window_height - Y - style::footer::height;
    } // namespace list

    inline constexpr auto cross_x = 48;
    inline constexpr auto cross_y = 55;
    inline constexpr auto arrow_x = 30;
    inline constexpr auto arrow_y = 62;

} // namespace style::quotes

namespace gui
{
    QuotesMainWindow::QuotesMainWindow(app::Application *app) : BaseSettingsWindow(app, gui::window::name::quotes)
    QuotesMainWindow::QuotesMainWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model)
        : AppWindow(app, gui::window::name::quotes), quotesModel(std::move(model))
    {
        const auto quotesPath = purefs::dir::getCurrentOSPath() / "data/applications/settings/quotes.json";
        setTitle(utils::localize.get("app_settings_display_locked_screen_quotes"));
        readQuotes(quotesPath);
        buildInterface();
    }

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

        setTitle(utils::localize.get("app_settings_display_wallpaper_quotes"));

        bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::check));
        bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));
        bottomBar->setText(BottomBar::Side::LEFT, utils::localize.get(style::strings::common::options));

        new gui::Image(this, style::quotes::arrow_x, style::quotes::arrow_y, 0, 0, "arrow_left");
        new gui::Image(this, style::quotes::cross_x, style::quotes::cross_y, 0, 0, "cross");

        list = new gui::ListView(this,
                                 style::quotes::list::X,
                                 style::quotes::list::Y,
                                 style::quotes::list::Width,
                                 style::quotes::list::Height,
                                 quotesModel);

        setFocusItem(list);
    }

    auto QuotesMainWindow::onInput(const InputEvent &inputEvent) -> bool


@@ 29,10 67,12 @@ namespace gui
        if (AppWindow::onInput(inputEvent)) {
            return true;
        }
        if (inputEvent.state == InputEvent::State::keyReleasedShort) {

        if (inputEvent.isShortPress()) {
            switch (inputEvent.keyCode) {
            case gui::KeyCode::KEY_LEFT:
                application->switchWindow(gui::window::name::new_quote, nullptr);
                application->switchWindow(gui::window::name::new_quote,
                                          std::make_unique<QuoteSwitchData>(QuoteAction::Add));
                return true;
            default:
                break;


@@ 41,70 81,8 @@ namespace gui
        return false;
    }

    void QuotesMainWindow::readQuotes(fs::path fn)
    {
        std::string err;

        const auto fileContents = readFileToString(fn);
        auto obj                = json11::Json::parse(fileContents.c_str(), err).array_items();

        if (!err.empty()) {
            LOG_ERROR("Error while parsing quotes: %s", err.c_str());
        }

        std::transform(obj.begin(), obj.end(), std::back_inserter(quotes), [](auto item) {
            return std::pair{item["quote"].string_value(), false};
        });
    }

    auto QuotesMainWindow::buildOptionsList() -> std::list<gui::Option>
    void QuotesMainWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        std::list<gui::Option> optionsList;

        for (auto &quote : quotes) {
            optionsList.emplace_back(std::make_unique<gui::option::OptionSettings>(
                utils::translateI18(quote.first),
                [&quote, this](gui::Item &item) {
                    switchHandler(quote.second);
                    return true;
                },
                [=](gui::Item &item) {
                    if (item.focus) {
                        this->setBottomBarText(utils::translateI18(style::strings::common::Switch),
                                               BottomBar::Side::CENTER);
                    }
                    return true;
                },
                this,
                quote.second ? gui::option::SettingRightItem::Checked : gui::option::SettingRightItem::Disabled));
        }

        return optionsList;
        quotesModel->rebuild();
    }

    void QuotesMainWindow::switchHandler(bool &optionSwitch)
    {
        optionSwitch = !optionSwitch;
        rebuildOptionList();
    }

    std::string QuotesMainWindow::readFileToString(const fs::path &fn)
    {
        constexpr auto tar_buf = 8192 * 4;
        auto file              = std::fopen(fn.c_str(), "r");
        if (!file) {
            return {};
        }
        const auto length = utils::filesystem::filelength(file);
        if (length >= tar_buf) {
            LOG_ERROR("File %s length is too high!", fn.c_str());
            std::fclose(file);
            return {};
        }
        auto buffer = std::make_unique<char[]>(length + 1);
        std::fread(buffer.get(), 1, length, file);
        std::fclose(file);
        return std::string(buffer.get());
    }

} // namespace gui

M module-apps/application-settings-new/windows/QuotesMainWindow.hpp => module-apps/application-settings-new/windows/QuotesMainWindow.hpp +14 -13
@@ 4,27 4,28 @@
#pragma once

#include "BaseSettingsWindow.hpp"
#include "application-settings-new/widgets/QuoteWidget.hpp"
#include "application-settings-new/model/QuotesModel.hpp"

#include <purefs/filesystem_paths.hpp>
#include <module-gui/gui/widgets/ListView.hpp>
#include <module-apps/InternalModel.hpp>
#include <utility>

namespace gui
{
    class CheckBoxWithLabel;

    class QuotesMainWindow : public BaseSettingsWindow
    class QuotesMainWindow : public AppWindow
    {
      public:
        QuotesMainWindow(app::Application *app);

        auto onInput(const InputEvent &inputEvent) -> bool override;

      protected:
        auto buildOptionsList() -> std::list<Option> override;
        QuotesMainWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model);

      private:
        void readQuotes(fs::path fn);
        void switchHandler(bool &optionSwitch);
        [[nodiscard]] static std::string readFileToString(const fs::path &fn);
        void buildInterface() override;
        auto onInput(const InputEvent &inputEvent) -> bool override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

        std::list<std::pair<std::string, bool>> quotes;
        std::shared_ptr<app::QuotesModel> quotesModel = nullptr;
        gui::ListView *list                           = nullptr;
    };

} // namespace gui

A module-apps/application-settings-new/windows/QuotesOptionsWindow.cpp => module-apps/application-settings-new/windows/QuotesOptionsWindow.cpp +83 -0
@@ 0,0 1,83 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesOptionsWindow.hpp"
#include "OptionSetting.hpp"
#include "DialogMetadataMessage.hpp"
#include "QuotesMainWindow.hpp"
#include "application-settings-new/model/QuotesRepository.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/widgets/QuoteWidget.hpp"

#include <i18n/i18n.hpp>
#include <widgets/Text.hpp>

namespace gui
{

    QuotesOptionsWindow::QuotesOptionsWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model)
        : BaseSettingsWindow(app, gui::window::name::quotes), quotesModel(std::move(model))
    {
        setTitle(utils::localize.get("app_settings_display_wallpaper_quotes_options"));
    }

    auto QuotesOptionsWindow::buildOptionsList() -> std::list<gui::Option>
    {
        std::list<gui::Option> optionsList;

        optionsList.emplace_back(std::make_unique<gui::option::OptionSettings>(
            utils::translateI18("app_settings_display_wallpaper_quotes_edit"),
            [=](gui::Item &item) {
                application->switchWindow(gui::window::name::new_quote,
                                          std::make_unique<QuoteSwitchData>(QuoteAction::Edit, quote));
                return true;
            },
            [=](gui::Item &item) {
                if (item.focus) {
                    this->setBottomBarText(utils::translateI18(style::strings::common::select),
                                           BottomBar::Side::CENTER);
                }
                return true;
            },
            this));

        optionsList.emplace_back(std::make_unique<gui::option::OptionSettings>(
            utils::translateI18("app_settings_display_wallpaper_quotes_delete"),
            [=](gui::Item &item) {
                gui::DialogMetadata meta;
                meta.text   = utils::localize.get("app_settings_display_wallpaper_quotes_delete_confirmation");
                meta.title  = quote.quote;
                meta.icon   = "phonebook_contact_delete_trashcan";
                meta.action = [this]() {
                    auto backToQuotesMainWindow = 2;
                    quotesModel->remove(quote);
                    application->returnToPreviousWindow(backToQuotesMainWindow);
                    return true;
                };

                application->switchWindow(gui::window::name::quotes_dialog_yes_no,
                                          std::make_unique<gui::DialogMetadataMessage>(meta));
                return true;
            },
            [=](gui::Item &item) {
                if (item.focus) {
                    this->setBottomBarText(utils::translateI18(style::strings::common::select),
                                           BottomBar::Side::CENTER);
                }
                return true;
            },
            this));

        return optionsList;
    } // namespace gui

    void QuotesOptionsWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        auto *quoteSwitchData = dynamic_cast<QuoteSwitchData *>(data);
        if (quoteSwitchData != nullptr) {
            quote = quoteSwitchData->getQuote();
        }

        BaseSettingsWindow::onBeforeShow(mode, data);
    }
} // namespace gui

A module-apps/application-settings-new/windows/QuotesOptionsWindow.hpp => module-apps/application-settings-new/windows/QuotesOptionsWindow.hpp +24 -0
@@ 0,0 1,24 @@
// 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 "BaseSettingsWindow.hpp"
#include "QuotesMainWindow.hpp"
#include "application-settings-new/widgets/QuoteWidget.hpp"

namespace gui
{
    class QuotesOptionsWindow : public BaseSettingsWindow
    {
      public:
        QuotesOptionsWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model);

      private:
        std::list<Option> buildOptionsList() override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

        std::shared_ptr<app::QuotesModel> quotesModel;
        app::QuoteRecord quote;
    };
} // namespace gui

M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +1 -0
@@ 138,6 138,7 @@ namespace style
            inline constexpr auto set            = "common_set";
            inline constexpr auto yes            = "common_yes";
            inline constexpr auto no             = "common_no";
            inline constexpr auto check          = "common_check";
            inline constexpr auto Switch         = "common_switch";
            inline constexpr auto options        = "common_options";
            inline constexpr auto information    = "common_information";