From 4e034df4ed893ce3475319e1cb47e6f2d509e5be Mon Sep 17 00:00:00 2001 From: Przemyslaw Brudny Date: Tue, 12 Jan 2021 18:05:24 +0100 Subject: [PATCH] [EDG-4743] Add new/options windows to quotes Add windows for add/edit/delete quotes, use ListView for quote display. --- image/assets/lang/English.json | 11 +- .../data/applications/settings/quotes.json | 27 +++ .../ApplicationSettings.cpp | 18 +- .../ApplicationSettings.hpp | 14 +- .../application-settings-new/CMakeLists.txt | 3 +- .../data/QuoteSwitchData.hpp | 45 +++++ .../model/QuotesModel.cpp | 100 ++++++++++ .../model/QuotesModel.hpp | 42 +++++ .../model/QuotesRepository.cpp | 103 +++++++++++ .../model/QuotesRepository.hpp | 58 ++++++ .../widgets/QuoteWidget.cpp | 117 ++++++++++++ .../widgets/QuoteWidget.hpp | 42 +++++ .../windows/QuotesAddWindow.cpp | 175 +++++++++++++++++- .../windows/QuotesAddWindow.hpp | 20 +- .../windows/QuotesMainWindow.cpp | 126 ++++++------- .../windows/QuotesMainWindow.hpp | 27 +-- .../windows/QuotesOptionsWindow.cpp | 83 +++++++++ .../windows/QuotesOptionsWindow.hpp | 24 +++ module-gui/gui/widgets/Style.hpp | 1 + 19 files changed, 915 insertions(+), 121 deletions(-) create mode 100644 image/user/data/applications/settings/quotes.json create mode 100644 module-apps/application-settings-new/data/QuoteSwitchData.hpp create mode 100644 module-apps/application-settings-new/model/QuotesModel.cpp create mode 100644 module-apps/application-settings-new/model/QuotesModel.hpp create mode 100644 module-apps/application-settings-new/model/QuotesRepository.cpp create mode 100644 module-apps/application-settings-new/model/QuotesRepository.hpp create mode 100644 module-apps/application-settings-new/widgets/QuoteWidget.cpp create mode 100644 module-apps/application-settings-new/widgets/QuoteWidget.hpp create mode 100644 module-apps/application-settings-new/windows/QuotesOptionsWindow.cpp create mode 100644 module-apps/application-settings-new/windows/QuotesOptionsWindow.hpp diff --git a/image/assets/lang/English.json b/image/assets/lang/English.json index 85c39e47d77f62b5b8114331b9281ad5bd0b60ee..7c61dea6416bdad5a36d85a1b63cb1b164a2f6b7 100644 --- a/image/assets/lang/English.json +++ b/image/assets/lang/English.json @@ -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", diff --git a/image/user/data/applications/settings/quotes.json b/image/user/data/applications/settings/quotes.json new file mode 100644 index 0000000000000000000000000000000000000000..cdf18534f36b6c82c032ff312ad9fad1f3d61f4a --- /dev/null +++ b/image/user/data/applications/settings/quotes.json @@ -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" + } +] diff --git a/module-apps/application-settings-new/ApplicationSettings.cpp b/module-apps/application-settings-new/ApplicationSettings.cpp index 71a35cfbbe3a57983275054a08fcdb557879e797..e119f269e5c120454eb782d4904a84d8fe6b77db 100644 --- a/module-apps/application-settings-new/ApplicationSettings.cpp +++ b/module-apps/application-settings-new/ApplicationSettings.cpp @@ -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(app); }); - windowsFactory.attach(gui::window::name::quotes, [](Application *app, const std::string &name) { - return std::make_unique(app); - }); - windowsFactory.attach(gui::window::name::new_quote, [](Application *app, const std::string &name) { - return std::make_unique(app); + windowsFactory.attach(gui::window::name::quotes_dialog_yes_no, [](Application *app, const std::string &name) { + return std::make_unique(app, name); }); windowsFactory.attach(gui::window::name::security, [](Application *app, const std::string &name) { return std::make_unique(app); }); - windowsFactory.attach(gui::window::name::change_passcode, [](Application *app, const std::string &name) { - return std::make_unique(app); - }); - windowsFactory.attach(gui::window::name::dialog_confirm, [](Application *app, const std::string &name) { - return std::make_unique(app, gui::window::name::dialog_confirm); - }); } void ApplicationSettingsNew::destroyUserInterface() diff --git a/module-apps/application-settings-new/ApplicationSettings.hpp b/module-apps/application-settings-new/ApplicationSettings.hpp index 9aa536e9066afb799f217baf5f96bb006b927ace..734f0b0f9bc65e538545027b5767e38282ffce4f 100644 --- a/module-apps/application-settings-new/ApplicationSettings.hpp +++ b/module-apps/application-settings-new/ApplicationSettings.hpp @@ -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; diff --git a/module-apps/application-settings-new/CMakeLists.txt b/module-apps/application-settings-new/CMakeLists.txt index 456332f6237bf696a78ab5b3436a9b6360164ffc..4ec1ada36f8f4d37715a28c86a7f3f10531b22d8 100644 --- a/module-apps/application-settings-new/CMakeLists.txt +++ b/module-apps/application-settings-new/CMakeLists.txt @@ -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) diff --git a/module-apps/application-settings-new/data/QuoteSwitchData.hpp b/module-apps/application-settings-new/data/QuoteSwitchData.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8a6b9dcc44034adbe64848770082cbbeb0f457d1 --- /dev/null +++ b/module-apps/application-settings-new/data/QuoteSwitchData.hpp @@ -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 +#include +#include + +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 diff --git a/module-apps/application-settings-new/model/QuotesModel.cpp b/module-apps/application-settings-new/model/QuotesModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1310ab97d98afd2cfd183355bd6895135ce1f802 --- /dev/null +++ b/module-apps/application-settings-new/model/QuotesModel.cpp @@ -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 +#include +#include +#include +#include +#include + +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 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(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::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 "es, unsigned int) { + auto app = application; + for (const auto "e : 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 "e) + { + repository->remove(quote); + } + + void QuotesModel::save(const app::QuoteRecord "e) + { + repository->save(quote); + } + +} // namespace app diff --git a/module-apps/application-settings-new/model/QuotesModel.hpp b/module-apps/application-settings-new/model/QuotesModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cf47216a72fd7d172d9be9db290478fc66d76c5b --- /dev/null +++ b/module-apps/application-settings-new/model/QuotesModel.hpp @@ -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 +#include +#include + +namespace gui +{ + class QuoteWidget; +} + +namespace app +{ + class QuotesModel : public app::InternalModel, public gui::ListItemProvider + { + public: + QuotesModel(app::Application *app, std::unique_ptr 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 "e); + void save(const app::QuoteRecord "e); + + private: + void createData(); + + app::Application *application = nullptr; + std::unique_ptr repository = nullptr; + }; + +} // namespace app diff --git a/module-apps/application-settings-new/model/QuotesRepository.cpp b/module-apps/application-settings-new/model/QuotesRepository.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a75ea44dc4dde81795b37e9632def0322f375af9 --- /dev/null +++ b/module-apps/application-settings-new/model/QuotesRepository.cpp @@ -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 +#include +#include + +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 result{start, end}; + callback(result, result.size()); + } + } + + void QuotesJsonRepository::save(const QuoteRecord "e) + { + 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 "e) + { + quotes.remove_if([quote](auto &&d) { return d.id == quote.id; }); + writeQuotes(repositoryPath); + } + + void QuotesJsonRepository::writeQuotes(const fs::path "esFilename) + { + 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 "esFilename) + { + 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(length + 1); + std::fread(buffer.get(), 1, length, file); + return std::string(buffer.get()); + } +} // namespace app diff --git a/module-apps/application-settings-new/model/QuotesRepository.hpp b/module-apps/application-settings-new/model/QuotesRepository.hpp new file mode 100644 index 0000000000000000000000000000000000000000..15641b7dee6f4bd2af8410dd07c9fd1640321cbe --- /dev/null +++ b/module-apps/application-settings-new/model/QuotesRepository.hpp @@ -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 + +#include +#include +#include + +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 &, 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 "e) = 0; + virtual void remove(const QuoteRecord "e) = 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 "e) override; + void remove(const QuoteRecord "e) override; + + private: + void writeQuotes(const fs::path &path); + void readQuotes(const fs::path &fn); + std::string readFileToString(const fs::path &fn); + + std::list quotes; + std::string repositoryPath; + }; +} // namespace app diff --git a/module-apps/application-settings-new/widgets/QuoteWidget.cpp b/module-apps/application-settings-new/widgets/QuoteWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2e4586cd523132095878323ffdd6dba648630b36 --- /dev/null +++ b/module-apps/application-settings-new/widgets/QuoteWidget.cpp @@ -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 +#include +#include +#include + +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 "e, + std::function bottomBarTemporaryMode, + std::function 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 */ diff --git a/module-apps/application-settings-new/widgets/QuoteWidget.hpp b/module-apps/application-settings-new/widgets/QuoteWidget.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c2ed8eafacb2723aa7bf6bb831e0c8b03262b2c5 --- /dev/null +++ b/module-apps/application-settings-new/widgets/QuoteWidget.hpp @@ -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 +#include +#include +#include +#include + +namespace gui +{ + class QuoteWidget : public ListItem + { + public: + QuoteWidget(const app::QuoteRecord "e, + std::function bottomBarTemporaryMode = nullptr, + std::function 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 bottomBarTemporaryMode = nullptr; + std::function bottomBarRestoreFromTemporaryMode = nullptr; + + app::QuoteRecord quote; + }; + +} /* namespace gui */ diff --git a/module-apps/application-settings-new/windows/QuotesAddWindow.cpp b/module-apps/application-settings-new/windows/QuotesAddWindow.cpp index 32c1dca7802c7f1138259652e73dd45bcaf8aa39..208cf93b17eda29cbf83d197ac700d7532b6f6cd 100644 --- a/module-apps/application-settings-new/windows/QuotesAddWindow.cpp +++ b/module-apps/application-settings-new/windows/QuotesAddWindow.cpp @@ -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 #include +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 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(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 diff --git a/module-apps/application-settings-new/windows/QuotesAddWindow.hpp b/module-apps/application-settings-new/windows/QuotesAddWindow.hpp index 14181e39babde9bab0e00ceb6070e4a8810fbb2d..bada202ad7240c6adb4a192af62bcd3c8c0df320 100644 --- a/module-apps/application-settings-new/windows/QuotesAddWindow.hpp +++ b/module-apps/application-settings-new/windows/QuotesAddWindow.hpp @@ -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 model); void buildInterface() override; private: - std::vector 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 quoteModel; }; } // namespace gui diff --git a/module-apps/application-settings-new/windows/QuotesMainWindow.cpp b/module-apps/application-settings-new/windows/QuotesMainWindow.cpp index b9d30bd7f8fd58c2e1832efd6cbe7ca2ac4938fb..d8ce0e052492a84b0130f15322b763b3d53fbb15 100644 --- a/module-apps/application-settings-new/windows/QuotesMainWindow.cpp +++ b/module-apps/application-settings-new/windows/QuotesMainWindow.cpp @@ -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 #include #include -#include -#include -#include +#include + +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 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(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 + void QuotesMainWindow::onBeforeShow(ShowMode mode, SwitchData *data) { - std::list optionsList; - - for (auto "e : quotes) { - optionsList.emplace_back(std::make_unique( - utils::translateI18(quote.first), - ["e, 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(length + 1); - std::fread(buffer.get(), 1, length, file); - std::fclose(file); - return std::string(buffer.get()); - } - } // namespace gui diff --git a/module-apps/application-settings-new/windows/QuotesMainWindow.hpp b/module-apps/application-settings-new/windows/QuotesMainWindow.hpp index cfbcf237a72585699c93788e94bff54050091848..775da661be1dc17661ef3d13bf700b68b4b3babc 100644 --- a/module-apps/application-settings-new/windows/QuotesMainWindow.hpp +++ b/module-apps/application-settings-new/windows/QuotesMainWindow.hpp @@ -4,27 +4,28 @@ #pragma once #include "BaseSettingsWindow.hpp" +#include "application-settings-new/widgets/QuoteWidget.hpp" +#include "application-settings-new/model/QuotesModel.hpp" +#include +#include +#include +#include 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