From 40afdb710da7907af6faf2843a2fd2182b3272d0 Mon Sep 17 00:00:00 2001 From: Dawid Wojtas Date: Wed, 20 Mar 2024 17:19:08 +0100 Subject: [PATCH] [BH-1920] Labels with correct page and song This is reimplementation ListViewWithLabels which is able to set correct page with focused item. --- module-db/Tables/MultimediaFilesTable.cpp | 23 +- module-gui/gui/widgets/ListViewEngine.cpp | 2 +- .../BellHybrid/apps/common/CMakeLists.txt | 4 +- .../include/common/SoundsRepository.hpp | 2 + .../include/common/models/SongsModel.hpp | 7 +- ...{LabelListItem.hpp => LabelMarkerItem.hpp} | 11 - .../common/widgets/LabelOptionWithTick.hpp | 4 +- .../common/widgets/ListViewWithLabels.hpp | 23 +- .../apps/common/src/SoundsRepository.cpp | 10 + .../apps/common/src/models/SongsModel.cpp | 16 +- ...{LabelListItem.cpp => LabelMarkerItem.cpp} | 10 +- .../src/widgets/LabelOptionWithTick.cpp | 6 +- .../common/src/widgets/ListViewWithLabels.cpp | 226 +++++++++++------- 13 files changed, 191 insertions(+), 153 deletions(-) rename products/BellHybrid/apps/common/include/common/widgets/{LabelListItem.hpp => LabelMarkerItem.hpp} (66%) rename products/BellHybrid/apps/common/src/widgets/{LabelListItem.cpp => LabelMarkerItem.cpp} (89%) diff --git a/module-db/Tables/MultimediaFilesTable.cpp b/module-db/Tables/MultimediaFilesTable.cpp index e40705266bbbaebd6788808dd2ddb4f028bee66b..4940ae3474116a93ab914ac553a1eb02c2f9b5ed 100644 --- a/module-db/Tables/MultimediaFilesTable.cpp +++ b/module-db/Tables/MultimediaFilesTable.cpp @@ -391,23 +391,12 @@ namespace db::multimedia_files const std::string &recordPath, SortingBy sorting) -> SortedRecord { - const std::string query = "SELECT * FROM (" - " SELECT" - " ROW_NUMBER () OVER (" - " ORDER BY " + - getSorting(sorting) + - " ) offset," - " path" - " FROM" - " files" - " WHERE path LIKE '" + - folderPath + "%%'" + - " )" - " WHERE" - " path='" + - recordPath + "'"; - - std::unique_ptr retQuery = db->query(query.c_str()); + std::unique_ptr retQuery = + db->query("SELECT * FROM ( SELECT ROW_NUMBER () OVER ( ORDER BY %q ) offset, " + " path FROM files WHERE path LIKE '%q%%' ) WHERE path='%q'", + getSorting(sorting).c_str(), + folderPath.c_str(), + recordPath.c_str()); if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) { return SortedRecord{.path = recordPath, .offset = 0}; } diff --git a/module-gui/gui/widgets/ListViewEngine.cpp b/module-gui/gui/widgets/ListViewEngine.cpp index a5d50c9aee076dbb7ed2aa226a41b3a7eaee9f30..f6c86b762e211b02483cb38338583d81b170a508 100644 --- a/module-gui/gui/widgets/ListViewEngine.cpp +++ b/module-gui/gui/widgets/ListViewEngine.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ListViewEngine.hpp" diff --git a/products/BellHybrid/apps/common/CMakeLists.txt b/products/BellHybrid/apps/common/CMakeLists.txt index c783f76f5112bf9c4337ae70f8a76103e3ea6dcd..a661ae689f57fa41c54e31802f6b0d0921a238d7 100644 --- a/products/BellHybrid/apps/common/CMakeLists.txt +++ b/products/BellHybrid/apps/common/CMakeLists.txt @@ -65,7 +65,7 @@ target_sources(application-bell-common src/widgets/LayoutVertical.cpp src/widgets/ClockVertical.cpp src/widgets/ListViewWithLabels.cpp - src/widgets/LabelListItem.cpp + src/widgets/LabelMarkerItem.cpp src/widgets/LabelOptionWithTick.cpp src/options/BellOptionWindow.cpp @@ -151,7 +151,7 @@ target_sources(application-bell-common include/common/widgets/LayoutVertical.hpp include/common/widgets/ClockVertical.hpp include/common/widgets/ListViewWithLabels.hpp - include/common/widgets/LabelListItem.hpp + include/common/widgets/LabelMarkerItem.hpp include/common/widgets/LabelOptionWithTick.hpp include/common/options/BellOptionWindow.hpp diff --git a/products/BellHybrid/apps/common/include/common/SoundsRepository.hpp b/products/BellHybrid/apps/common/include/common/SoundsRepository.hpp index c25d3bed84d6cc32afcb38934539ef8b85c02fd6..984a19a2df0eaf68ae1cb4624b460b0b597241fb 100644 --- a/products/BellHybrid/apps/common/include/common/SoundsRepository.hpp +++ b/products/BellHybrid/apps/common/include/common/SoundsRepository.hpp @@ -50,6 +50,7 @@ class AbstractSoundsRepository const OnGetMusicFilesListCallback &callback) = 0; virtual std::uint32_t getFilesCount() = 0; + virtual std::uint32_t getFilesCountFromPath(const std::string &filesPath) = 0; virtual void updateFilesCount() = 0; }; @@ -75,6 +76,7 @@ class SoundsRepository : public AbstractSoundsRepository, public app::AsyncCallb const OnGetMusicFilesListCallback &viewUpdateCallback) override; std::uint32_t getFilesCount() override; + std::uint32_t getFilesCountFromPath(const std::string &filesPath) override; void updateFilesCount() override; private: diff --git a/products/BellHybrid/apps/common/include/common/models/SongsModel.hpp b/products/BellHybrid/apps/common/include/common/models/SongsModel.hpp index ec3b191219e42e57af0742faecd9530b858c3849..2e2eeb4d14dfff0c30119b8916b1d211fa9127be 100644 --- a/products/BellHybrid/apps/common/include/common/models/SongsModel.hpp +++ b/products/BellHybrid/apps/common/include/common/models/SongsModel.hpp @@ -4,13 +4,13 @@ #pragma once #include -#include +#include #include #include namespace app { - using LabelsWithPaths = std::map; + using LabelsWithPaths = std::vector>; class SongsProvider : public app::DatabaseModel, public gui::ListItemProvider @@ -55,6 +55,7 @@ namespace app auto nextRecordExists(gui::Order order) -> bool; auto updateCurrentlyChosenRecordPath(const std::string &path) -> void; + auto getLabelsFilesCount() -> std::vector>; [[nodiscard]] auto getCurrentlyChosenRecordPath() const -> std::string; private: @@ -62,7 +63,7 @@ namespace app unsigned repoRecordsCount) -> bool; [[nodiscard]] auto updateRecords(std::vector records) -> bool override; - auto getLabelFromPath(const std::string &path) -> gui::ListLabel; + auto getLabelFromPath(const std::string &path) -> std::string; ApplicationCommon *application{nullptr}; std::unique_ptr songsRepository; diff --git a/products/BellHybrid/apps/common/include/common/widgets/LabelListItem.hpp b/products/BellHybrid/apps/common/include/common/widgets/LabelMarkerItem.hpp similarity index 66% rename from products/BellHybrid/apps/common/include/common/widgets/LabelListItem.hpp rename to products/BellHybrid/apps/common/include/common/widgets/LabelMarkerItem.hpp index 05ee9e05acd8bfd3ced8632300f44e562da567f1..3a118b0ab32651a8e2667ba926c68b9de96b5140 100644 --- a/products/BellHybrid/apps/common/include/common/widgets/LabelListItem.hpp +++ b/products/BellHybrid/apps/common/include/common/widgets/LabelMarkerItem.hpp @@ -9,17 +9,6 @@ namespace gui { - class LabelListItem : public ListItem - { - private: - ListLabel label{}; - - public: - explicit LabelListItem(ListLabel label); - virtual ~LabelListItem() = default; - ListLabel getLabel(); - }; - class LabelMarkerItem : public ListItem { public: diff --git a/products/BellHybrid/apps/common/include/common/widgets/LabelOptionWithTick.hpp b/products/BellHybrid/apps/common/include/common/widgets/LabelOptionWithTick.hpp index c0089410d78abf2cf809fa14d88392b52e8b8ccd..33757b749a9a345cf8d4d3f26423ed47ad898047 100644 --- a/products/BellHybrid/apps/common/include/common/widgets/LabelOptionWithTick.hpp +++ b/products/BellHybrid/apps/common/include/common/widgets/LabelOptionWithTick.hpp @@ -17,7 +17,7 @@ namespace gui::option Hide }; - LabelOptionWithTick(ListLabel label, + LabelOptionWithTick(const std::string &label, const UTF8 &text, TickState tickState, std::function activatedCallback, @@ -30,7 +30,7 @@ namespace gui::option auto prepareLabelOption(ListItem *item) const -> void; auto getAdjustedText(TextFixedSize *textItem) const -> UTF8; - ListLabel label; + std::string label; TickState tickState; }; } // namespace gui::option diff --git a/products/BellHybrid/apps/common/include/common/widgets/ListViewWithLabels.hpp b/products/BellHybrid/apps/common/include/common/widgets/ListViewWithLabels.hpp index 8e4583e6a3ffba4ed3bf09207b2ab0bc9e56809a..0a94de3a92034cdbdc11e2c6092c34886dd7fe67 100644 --- a/products/BellHybrid/apps/common/include/common/widgets/ListViewWithLabels.hpp +++ b/products/BellHybrid/apps/common/include/common/widgets/ListViewWithLabels.hpp @@ -13,8 +13,6 @@ namespace gui class ListItemProvider; class LabelMarkerItem; - using ListLabel = std::optional; - class ListViewWithLabels : public ListViewWithArrows { public: @@ -25,19 +23,20 @@ namespace gui unsigned int h, std::shared_ptr prov); - void reset() override; - private: [[nodiscard]] std::size_t getSlotsLeft() const; void addItemsOnPage() override; - void addLabelMarker(ListItem *item); - void updateState(ListLabel newMarker); - LabelMarkerItem *createMarkerItem(ListLabel label); - - ListLabel current{std::nullopt}; - ListLabel previous{std::nullopt}; - ListLabel currentMarker{std::nullopt}; + void addItems(); + void addLabelItem(); + void getLabels(); + std::uint32_t getLabelsCount(unsigned int index); + LabelMarkerItem *createMarkerItem(const std::string &label); + + std::vector> labelFiles; + unsigned int currentFocusIndex{0}; + unsigned int hiddenItemIndex{0}; std::uint32_t itemsOnPage{0}; - bool labelAdded{false}; + std::uint32_t labelsCount{0}; + bool wasSetFocus{false}; }; } // namespace gui diff --git a/products/BellHybrid/apps/common/src/SoundsRepository.cpp b/products/BellHybrid/apps/common/src/SoundsRepository.cpp index 0e2adc80b713005bca94a39682e293c74bb9c4af..245aa9a701e1d862843fa006d4dec8c32700d94f 100644 --- a/products/BellHybrid/apps/common/src/SoundsRepository.cpp +++ b/products/BellHybrid/apps/common/src/SoundsRepository.cpp @@ -185,6 +185,16 @@ std::uint32_t SoundsRepository::getFilesCount() paths.begin(), paths.end(), 0, [](const std::uint32_t sum, const auto &record) { return sum + record.count; }); } +std::uint32_t SoundsRepository::getFilesCountFromPath(const std::string &filesPath) +{ + for (const auto &path : paths) { + if (filesPath == path.prefix) { + return path.count; + } + } + return 0; +} + void SoundsRepository::updateFilesCount() { for (const auto &path : paths) { diff --git a/products/BellHybrid/apps/common/src/models/SongsModel.cpp b/products/BellHybrid/apps/common/src/models/SongsModel.cpp index 9a6d4fdfb691ad906ad6baadb17d3f8fb286fd14..a7636cf39d58419a414099676ab051b6f18f37c6 100644 --- a/products/BellHybrid/apps/common/src/models/SongsModel.cpp +++ b/products/BellHybrid/apps/common/src/models/SongsModel.cpp @@ -23,7 +23,7 @@ namespace app auto SongsModel::getMinimalItemSpaceRequired() const -> unsigned { - return style::bell_options::h + 2 * style::bell_options::option_margin; + return style::bell_options::h + 3 * style::bell_options::option_margin; } auto SongsModel::getItem(gui::Order order) -> gui::ListItem * @@ -104,14 +104,24 @@ namespace app return true; } - auto SongsModel::getLabelFromPath(const std::string &path) -> gui::ListLabel + auto SongsModel::getLabelFromPath(const std::string &path) -> std::string { for (const auto &[label, pathPrefix] : pathPrefixes) { if (path.find(pathPrefix) != std::string::npos) { return label; } } - return std::nullopt; + return {}; + } + + auto SongsModel::getLabelsFilesCount() -> std::vector> + { + std::vector> labelWithFilesCount; + for (const auto &[label, pathPrefix] : pathPrefixes) { + const auto count = songsRepository->getFilesCountFromPath(pathPrefix); + labelWithFilesCount.push_back({label, count}); + } + return labelWithFilesCount; } auto SongsModel::updateCurrentlyChosenRecordPath(const std::string &path) -> void diff --git a/products/BellHybrid/apps/common/src/widgets/LabelListItem.cpp b/products/BellHybrid/apps/common/src/widgets/LabelMarkerItem.cpp similarity index 89% rename from products/BellHybrid/apps/common/src/widgets/LabelListItem.cpp rename to products/BellHybrid/apps/common/src/widgets/LabelMarkerItem.cpp index 2d0e68e557046631a42cfb08ebc2cf6d8cadf2eb..0fe00b1bf8976b2e8c45592139289c68f528d4e6 100644 --- a/products/BellHybrid/apps/common/src/widgets/LabelListItem.cpp +++ b/products/BellHybrid/apps/common/src/widgets/LabelMarkerItem.cpp @@ -1,19 +1,11 @@ // Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md -#include +#include #include "common/options/OptionBellMenu.hpp" namespace gui { - LabelListItem::LabelListItem(ListLabel label) : label{std::move(label)} - {} - - ListLabel LabelListItem::getLabel() - { - return label; - } - LabelMarkerItem::LabelMarkerItem(const UTF8 &labelText) { constexpr auto linesMaxNumber{1U}; diff --git a/products/BellHybrid/apps/common/src/widgets/LabelOptionWithTick.cpp b/products/BellHybrid/apps/common/src/widgets/LabelOptionWithTick.cpp index d84682e04899dd38f754f87066a5a5565b6183bb..e459d49ae0734fd04c08d733f9d7735d3a286ae2 100644 --- a/products/BellHybrid/apps/common/src/widgets/LabelOptionWithTick.cpp +++ b/products/BellHybrid/apps/common/src/widgets/LabelOptionWithTick.cpp @@ -2,11 +2,11 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include -#include +#include namespace gui::option { - LabelOptionWithTick::LabelOptionWithTick(ListLabel label, + LabelOptionWithTick::LabelOptionWithTick(const std::string &label, const UTF8 &text, TickState tickState, std::function activatedCallback, @@ -18,7 +18,7 @@ namespace gui::option auto LabelOptionWithTick::build() const -> ListItem * { - auto labelOption = new LabelListItem(label); + auto labelOption = new ListItem(); prepareLabelOption(labelOption); return labelOption; } diff --git a/products/BellHybrid/apps/common/src/widgets/ListViewWithLabels.cpp b/products/BellHybrid/apps/common/src/widgets/ListViewWithLabels.cpp index 05205c6f0df9bb81870a71e771673179911122fb..6128e49939ac4f82b32b0f29de34f851115b6ac9 100644 --- a/products/BellHybrid/apps/common/src/widgets/ListViewWithLabels.cpp +++ b/products/BellHybrid/apps/common/src/widgets/ListViewWithLabels.cpp @@ -2,9 +2,14 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include -#include +#include +#include #include +namespace +{ + constexpr auto maxItemsOnPage{4U}; +} namespace gui { ListViewWithLabels::ListViewWithLabels(Item *parent, @@ -16,127 +21,168 @@ namespace gui : ListViewWithArrows(parent, x, y, w, h, std::move(prov)) { body->dimensionChangedCallback = [&](gui::Item &, const BoundingBox &newDim) -> bool { return true; }; - } - void ListViewWithLabels::addItemsOnPage() - { - currentPageSize = 0; - itemsOnPage = 0; - labelAdded = false; + updateScrollCallback = [this](ListViewScrollUpdateData data) { + if (currentPageSize + data.startIndex < data.elementsCount) { + listOverlay->lastBox->setVisible(true); + } + else { + listOverlay->lastBox->setVisible(false); + } - ListItem *item; - while ((item = provider->getItem(getOrderFromDirection())) != nullptr) { - /* If direction is top-to-bottom, add label mark before adding relaxation item. */ - if (direction == listview::Direction::Bottom) { - addLabelMarker(item); + if (data.startIndex == 0 && titleBody) { + titleBody->setVisible(true); + arrowTop->setVisible(false); + listOverlay->firstBox->setVisible(true); } - /* Check if new item fits, if it does - add it, if not - handle label insertion - * case for bottom-to-top navigation direction. */ - if (getSlotsLeft() > 0) { - body->addWidget(item); - itemsOnPage++; + else if (data.startIndex > 1) { + if (titleBody) { + titleBody->setVisible(false); + } + arrowTop->setVisible(true); + listOverlay->firstBox->setVisible(true); } else { - /* Add invisible item to list to avoid memory leak */ - item->setVisible(false); - body->addWidget(item); - break; + listOverlay->firstBox->setVisible(false); } - /* If direction is bottom-to-top, add label mark after adding relaxation item. */ - if (direction == listview::Direction::Top) { - addLabelMarker(item); - } - currentPageSize++; + + listOverlay->resizeItems(); + // Second resize is needed as we need to first apply max size for center box and next extra margins. + listOverlay->resizeItems(); + }; + } + + void ListViewWithLabels::addLabelItem() + { + if (!labelsCount) { + return; } - recalculateStartIndex(); + const std::int32_t size = direction == listview::Direction::Top ? -currentPageSize : currentPageSize; - if (!labelAdded) { - currentMarker.reset(); + auto position = 0U; + for (auto i = 0U; i < labelsCount; ++i) { + // First label always starts from 0 + if (i == 0) { + position = 0; + } + else { + position += labelFiles[i - 1].second; + } + const auto &[labelName, filesCount] = labelFiles[i]; + const auto isSpace = (getSlotsLeft() > 0); + if (((startIndex + size) == position) && (filesCount > 0) && isSpace) { + // Make sure that hidden item doesn't allow + // to display label on the next page + if ((hiddenItemIndex == position) && (position > 0)) { + hiddenItemIndex = 0; + break; + } + body->addWidget(createMarkerItem(labelName)); + itemsOnPage++; + } } } - LabelMarkerItem *ListViewWithLabels::createMarkerItem(ListLabel label) + void ListViewWithLabels::getLabels() { - if (label.has_value()) { - const auto &labelString = UTF8(utils::translate(label.value())); - return new LabelMarkerItem(labelString); + const auto songsProvider = std::dynamic_pointer_cast(provider); + if (songsProvider) { + labelFiles = songsProvider->getLabelsFilesCount(); + labelsCount = labelFiles.size(); } - return new LabelMarkerItem(UTF8("")); } - void ListViewWithLabels::addLabelMarker(ListItem *item) + std::uint32_t ListViewWithLabels::getLabelsCount(unsigned int index) { - const auto labelListItem = dynamic_cast(item); - if (labelListItem == nullptr) { - return; - }; - previous = current; - current = labelListItem->getLabel(); - - switch (direction) { - case listview::Direction::Bottom: - if (current != previous && current != currentMarker) { - body->addWidget(createMarkerItem(*current)); - updateState(current); + auto total = 0U; + auto offset = 0U; + for (const auto &[_, files] : labelFiles) { + if (index > offset) { + total++; } - break; - - case listview::Direction::Top: - if (current != previous && previous != currentMarker) { - const auto initialSlotsLeft = getSlotsLeft(); - - body->removeWidget(labelListItem); - body->addWidget(createMarkerItem(*previous)); - updateState(previous); - - /* Add item to body even if it won't fit to avoid manual memory - * management for item, but apply correction to currentPageSize - * if it is not visible. */ - body->addWidget(labelListItem); + offset += files; + } + return total; + } - if (initialSlotsLeft == 0) { - currentPageSize--; - itemsOnPage--; - } + void ListViewWithLabels::addItemsOnPage() + { + getLabels(); + + const auto rebuildType = lastRebuildRequest.first; + if ((storedFocusIndex != listview::nPos) && (rebuildType != listview::RebuildType::InPlace)) { + const auto totalLabels = getLabelsCount(startIndex + storedFocusIndex); + const auto nextPage = (totalLabels + storedFocusIndex) / maxItemsOnPage; + currentFocusIndex = storedFocusIndex + totalLabels; + if (nextPage) { + startIndex += maxItemsOnPage - totalLabels; + currentFocusIndex %= maxItemsOnPage; + } + else if (startIndex) { + startIndex -= totalLabels; } else { - /* This is bad, as it limits usage of this widget to just SongsModel */ - const auto songsProvider = std::dynamic_pointer_cast(provider); - if (songsProvider == nullptr) { - break; - } - const auto nextItemExists = songsProvider->nextRecordExists(getOrderFromDirection()); - if (!nextItemExists && getSlotsLeft() == 1) { - body->addWidget(createMarkerItem(current)); - updateState(current); - } + currentFocusIndex -= totalLabels; + } + requestNextPage(); + wasSetFocus = true; + } + else { + currentPageSize = 0; + itemsOnPage = 0; + if (wasSetFocus) { + storedFocusIndex = currentFocusIndex; + wasSetFocus = false; } - break; + addItems(); } } - std::size_t ListViewWithLabels::getSlotsLeft() const + void ListViewWithLabels::addItems() { - constexpr auto maxItemDisplayed{4U}; - if (itemsOnPage > maxItemDisplayed) { - return 0; + ListItem *item; + while (true) { + item = provider->getItem(getOrderFromDirection()); + if (item == nullptr) { + // Add label if the direction is Top and we are on the first page + // So we don't get more songs items but we need to add a label + addLabelItem(); + break; + } + addLabelItem(); + // Check if new item fits, if it does - add it + if (getSlotsLeft() > 0) { + body->addWidget(item); + itemsOnPage++; + } + else { + // Add invisible item to list to avoid memory leak + item->setVisible(false); + body->addWidget(item); + // Save the hidden index for calculating label position + hiddenItemIndex = startIndex; + if (direction == listview::Direction::Bottom) { + hiddenItemIndex += currentPageSize; + } + break; + } + currentPageSize++; } - return maxItemDisplayed - itemsOnPage; + recalculateStartIndex(); } - void ListViewWithLabels::reset() + LabelMarkerItem *ListViewWithLabels::createMarkerItem(const std::string &label) { - currentMarker.reset(); - previous.reset(); - current.reset(); - ListViewEngine::reset(); + const auto &labelString = UTF8(utils::translate(label)); + return new LabelMarkerItem(labelString); } - void ListViewWithLabels::updateState(ListLabel marker) + std::size_t ListViewWithLabels::getSlotsLeft() const { - currentMarker = std::move(marker); - itemsOnPage++; - labelAdded = true; + if (itemsOnPage > maxItemsOnPage) { + return 0; + } + return maxItemsOnPage - itemsOnPage; } } // namespace gui