M module-db/Tables/MultimediaFilesTable.cpp => module-db/Tables/MultimediaFilesTable.cpp +6 -17
@@ 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<QueryResult> retQuery = db->query(query.c_str());
+ std::unique_ptr<QueryResult> 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};
}
M module-gui/gui/widgets/ListViewEngine.cpp => module-gui/gui/widgets/ListViewEngine.cpp +1 -1
@@ 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"
M products/BellHybrid/apps/common/CMakeLists.txt => products/BellHybrid/apps/common/CMakeLists.txt +2 -2
@@ 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
M products/BellHybrid/apps/common/include/common/SoundsRepository.hpp => products/BellHybrid/apps/common/include/common/SoundsRepository.hpp +2 -0
@@ 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:
M products/BellHybrid/apps/common/include/common/models/SongsModel.hpp => products/BellHybrid/apps/common/include/common/models/SongsModel.hpp +4 -3
@@ 4,13 4,13 @@
#pragma once
#include <common/SoundsRepository.hpp>
-#include <common/widgets/LabelListItem.hpp>
+#include <common/widgets/LabelMarkerItem.hpp>
#include <apps-common/models/SongsModelInterface.hpp>
#include <gui/widgets/ListItemProvider.hpp>
namespace app
{
- using LabelsWithPaths = std::map<std::string, std::string>;
+ using LabelsWithPaths = std::vector<std::pair<std::string, std::string>>;
class SongsProvider : public app::DatabaseModel<db::multimedia_files::MultimediaFilesRecord>,
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<std::pair<std::string, std::uint32_t>>;
[[nodiscard]] auto getCurrentlyChosenRecordPath() const -> std::string;
private:
@@ 62,7 63,7 @@ namespace app
unsigned repoRecordsCount) -> bool;
[[nodiscard]] auto updateRecords(std::vector<db::multimedia_files::MultimediaFilesRecord> 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<AbstractSoundsRepository> songsRepository;
R products/BellHybrid/apps/common/include/common/widgets/LabelListItem.hpp => products/BellHybrid/apps/common/include/common/widgets/LabelMarkerItem.hpp +0 -11
@@ 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:
M products/BellHybrid/apps/common/include/common/widgets/LabelOptionWithTick.hpp => products/BellHybrid/apps/common/include/common/widgets/LabelOptionWithTick.hpp +2 -2
@@ 17,7 17,7 @@ namespace gui::option
Hide
};
- LabelOptionWithTick(ListLabel label,
+ LabelOptionWithTick(const std::string &label,
const UTF8 &text,
TickState tickState,
std::function<bool(Item &)> 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
M products/BellHybrid/apps/common/include/common/widgets/ListViewWithLabels.hpp => products/BellHybrid/apps/common/include/common/widgets/ListViewWithLabels.hpp +11 -12
@@ 13,8 13,6 @@ namespace gui
class ListItemProvider;
class LabelMarkerItem;
- using ListLabel = std::optional<std::string>;
-
class ListViewWithLabels : public ListViewWithArrows
{
public:
@@ 25,19 23,20 @@ namespace gui
unsigned int h,
std::shared_ptr<ListItemProvider> 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<std::pair<std::string, std::uint32_t>> 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
M products/BellHybrid/apps/common/src/SoundsRepository.cpp => products/BellHybrid/apps/common/src/SoundsRepository.cpp +10 -0
@@ 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) {
M products/BellHybrid/apps/common/src/models/SongsModel.cpp => products/BellHybrid/apps/common/src/models/SongsModel.cpp +13 -3
@@ 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::pair<std::string, std::uint32_t>>
+ {
+ std::vector<std::pair<std::string, std::uint32_t>> 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
R products/BellHybrid/apps/common/src/widgets/LabelListItem.cpp => products/BellHybrid/apps/common/src/widgets/LabelMarkerItem.cpp +1 -9
@@ 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 <common/widgets/LabelListItem.hpp>
+#include <common/widgets/LabelMarkerItem.hpp>
#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};
M products/BellHybrid/apps/common/src/widgets/LabelOptionWithTick.cpp => products/BellHybrid/apps/common/src/widgets/LabelOptionWithTick.cpp +3 -3
@@ 2,11 2,11 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <common/widgets/LabelOptionWithTick.hpp>
-#include <common/widgets/LabelListItem.hpp>
+#include <common/widgets/LabelMarkerItem.hpp>
namespace gui::option
{
- LabelOptionWithTick::LabelOptionWithTick(ListLabel label,
+ LabelOptionWithTick::LabelOptionWithTick(const std::string &label,
const UTF8 &text,
TickState tickState,
std::function<bool(Item &)> 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;
}
M products/BellHybrid/apps/common/src/widgets/ListViewWithLabels.cpp => products/BellHybrid/apps/common/src/widgets/ListViewWithLabels.cpp +136 -90
@@ 2,9 2,14 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <common/widgets/ListViewWithLabels.hpp>
-#include <common/widgets/LabelListItem.hpp>
+#include <common/widgets/LabelMarkerItem.hpp>
+#include <common/widgets/LabelOptionWithTick.hpp>
#include <common/models/SongsModel.hpp>
+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<app::SongsModel>(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<gui::LabelListItem *>(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<app::SongsModel>(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