~aleteoryx/muditaos

71b612f8052be18aac53061cf14dcb519d502130 — Lukasz Mastalerz 2 years ago ed208de
[BH-1815] Optimalize loading music files in relaxation

Relaxation is loading smooth with any number of files.
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 24,6 24,7 @@

### Changed / Improved
* Disabled USB MTP protocol
* Optimize the way Relaxation is loading music files

## [2.2.1 2023-10-30]


M module-db/Interface/MultimediaFilesRecord.cpp => module-db/Interface/MultimediaFilesRecord.cpp +13 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "MultimediaFilesRecord.hpp"


@@ 81,6 81,9 @@ namespace db::multimedia_files
        if (typeid(*query) == typeid(query::GetByPath)) {
            return runQueryImplGetByPath(std::static_pointer_cast<query::GetByPath>(query));
        }
        if (typeid(*query) == typeid(query::GetCountForPath)) {
            return runQueryImplGetCountForPath(std::static_pointer_cast<query::GetCountForPath>(query));
        }
        return nullptr;
    }



@@ 269,4 272,13 @@ namespace db::multimedia_files
        response->setRequestQuery(query);
        return response;
    }

    std::unique_ptr<db::multimedia_files::query::GetCountResult> MultimediaFilesRecordInterface::
        runQueryImplGetCountForPath(const std::shared_ptr<db::multimedia_files::query::GetCountForPath> &query)
    {
        const auto ret = database->files.count(std::vector{query->path});
        auto response  = std::make_unique<query::GetCountResult>(ret);
        response->setRequestQuery(query);
        return response;
    }
} // namespace db::multimedia_files

M module-db/Interface/MultimediaFilesRecord.hpp => module-db/Interface/MultimediaFilesRecord.hpp +4 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 44,6 44,7 @@ namespace db::multimedia_files::query
    class RemoveAll;
    class RemoveByPath;
    class RemoveResult;
    class GetCountForPath;
} // namespace db::multimedia_files::query

namespace db::multimedia_files


@@ 99,6 100,8 @@ namespace db::multimedia_files
            const std::shared_ptr<db::multimedia_files::query::GetByPath> &query);
        std::unique_ptr<db::multimedia_files::query::RemoveResult> runQueryImplRemoveByPath(
            const std::shared_ptr<db::multimedia_files::query::RemoveByPath> &query);
        std::unique_ptr<db::multimedia_files::query::GetCountResult> runQueryImplGetCountForPath(
            const std::shared_ptr<db::multimedia_files::query::GetCountForPath> &query);

        MultimediaFilesDB *database = nullptr;
    };

M module-db/queries/multimedia_files/QueryMultimediaFilesCount.cpp => module-db/queries/multimedia_files/QueryMultimediaFilesCount.cpp +9 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryMultimediaFilesCount.hpp"


@@ 61,4 61,12 @@ namespace db::multimedia_files::query
        return "GetCountForAlbum";
    }

    GetCountForPath::GetCountForPath(const std::string &path) : Query(Query::Type::Read), path(path)
    {}

    [[nodiscard]] auto GetCountForPath::debugInfo() const -> std::string
    {
        return "GetCountForPath";
    }

} // namespace db::multimedia_files::query

M module-db/queries/multimedia_files/QueryMultimediaFilesCount.hpp => module-db/queries/multimedia_files/QueryMultimediaFilesCount.hpp +10 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 35,6 35,15 @@ namespace db::multimedia_files::query
        const Album album;
    };

    class GetCountForPath : public Query
    {
      public:
        explicit GetCountForPath(const std::string &path);
        [[nodiscard]] auto debugInfo() const -> std::string override;

        const std::string path;
    };

    class GetCountResult : public QueryResult
    {
        unsigned count;

M products/BellHybrid/apps/application-bell-relaxation/ApplicationBellRelaxation.cpp => products/BellHybrid/apps/application-bell-relaxation/ApplicationBellRelaxation.cpp +7 -5
@@ 63,11 63,13 @@ namespace app
    void ApplicationBellRelaxation::createUserInterface()
    {
        windowsFactory.attach(gui::name::window::main_window, [](ApplicationCommon *app, const std::string &name) {
            auto tagsFetcher      = std::make_unique<app::music::ServiceAudioTagsFetcher>(app);
            const auto paths      = std::vector<std::string>{paths::audio::proprietary() / paths::audio::relaxation(),
                                                        paths::audio::userApp() / paths::audio::relaxation()};
            auto soundsRepository = std::make_unique<app::music::SongsRepository>(app, std::move(tagsFetcher), paths);
            auto presenter = std::make_unique<relaxation::RelaxationMainWindowPresenter>(std::move(soundsRepository));
            const auto paths = std::map<relaxation::MusicType, std::string>{
                {relaxation::MusicType::Relaxation, paths::audio::proprietary() / paths::audio::relaxation()},
                {relaxation::MusicType::User, paths::audio::userApp() / paths::audio::relaxation()}};

            auto soundsRepository = std::make_unique<relaxation::RelaxationSongsRepository>(app, paths);
            auto songsModel = std::make_unique<relaxation::RelaxationSongsModel>(app, std::move(soundsRepository));
            auto presenter  = std::make_unique<relaxation::RelaxationMainWindowPresenter>(std::move(songsModel));
            return std::make_unique<gui::RelaxationMainWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(

M products/BellHybrid/apps/application-bell-relaxation/CMakeLists.txt => products/BellHybrid/apps/application-bell-relaxation/CMakeLists.txt +4 -0
@@ 58,6 58,10 @@ target_sources(application-bell-relaxation
        windows/RelaxationEndedWindow.hpp
        windows/RelaxationLowBatteryWindow.hpp
        windows/RelaxationErrorWindow.hpp
        model/RelaxationSongsModel.hpp
        model/RelaxationSongsModel.cpp
        model/RelaxationSongsRepository.hpp
        model/RelaxationSongsRepository.cpp

        PUBLIC
        include/application-bell-relaxation/ApplicationBellRelaxation.hpp

A products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsModel.cpp => products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsModel.cpp +75 -0
@@ 0,0 1,75 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "RelaxationSongsModel.hpp"
#include "common/options/OptionBellMenu.hpp"

namespace app::relaxation
{

    RelaxationSongsProvider::RelaxationSongsProvider(ApplicationCommon *app) : DatabaseModel(app)
    {}

    RelaxationSongsModel::RelaxationSongsModel(
        ApplicationCommon *application, std::unique_ptr<app::relaxation::RelaxationSongsRepository> soundsRepository)
        : RelaxationSongsProvider(application), application(application), songsRepository{std::move(soundsRepository)}
    {}

    void RelaxationSongsModel::createData(OnActivateCallback callback)
    {
        activateCallback = callback;

        songsRepository->init();
    }

    unsigned int RelaxationSongsModel::requestRecordsCount()
    {
        return songsRepository->getRecordsCount();
    }
    unsigned int RelaxationSongsModel::getMinimalItemSpaceRequired() const
    {
        return style::bell_options::h + 2 * style::bell_options::option_margin;
    }
    gui::ListItem *RelaxationSongsModel::getItem(gui::Order order)
    {
        const auto sound = getRecord(order);
        if (!sound) {
            return nullptr;
        }

        auto item = new gui::option::OptionBellMenu(
            sound->tags.title,
            [=](gui::Item &item) {
                activateCallback(*sound);
                return true;
            },
            [=](gui::Item &item) { return true; },
            nullptr);

        return item->build();
    }
    void RelaxationSongsModel::requestRecords(std::uint32_t offset, std::uint32_t limit)
    {
        songsRepository->getMusicFiles(
            offset,
            limit,
            [this](const std::vector<db::multimedia_files::MultimediaFilesRecord> &records,
                   unsigned int repoRecordsCount) { return onMusicListRetrieved(records, repoRecordsCount); });
    }
    bool RelaxationSongsModel::onMusicListRetrieved(
        const std::vector<db::multimedia_files::MultimediaFilesRecord> &records, unsigned int repoRecordsCount)
    {
        if (list != nullptr && recordsCount != repoRecordsCount) {
            recordsCount = repoRecordsCount;
            list->reSendLastRebuildRequest();
            return false;
        }
        return updateRecords(records);
    }
    bool RelaxationSongsModel::updateRecords(std::vector<db::multimedia_files::MultimediaFilesRecord> records)
    {
        DatabaseModel::updateRecords(std::move(records));
        list->onProviderDataUpdate();
        return true;
    }
} // namespace app::relaxation

A products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsModel.hpp => products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsModel.hpp +48 -0
@@ 0,0 1,48 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "RelaxationSongsRepository.hpp"
#include <apps-common/models/SongsModelInterface.hpp>
#include <gui/widgets/ListItemProvider.hpp>

namespace app::relaxation
{

    class RelaxationSongsProvider : public app::DatabaseModel<db::multimedia_files::MultimediaFilesRecord>,
                                    public gui::ListItemProvider
    {
      public:
        using OnActivateCallback =
            std::function<bool(const db::multimedia_files::MultimediaFilesRecord &selectedSound)>;
        explicit RelaxationSongsProvider(ApplicationCommon *application);
        virtual void createData(OnActivateCallback activateCallback) = 0;
    };

    class RelaxationSongsModel : public RelaxationSongsProvider
    {
      private:
        ApplicationCommon *application;
        std::unique_ptr<RelaxationSongsRepository> songsRepository;
        OnActivateCallback activateCallback{nullptr};

        bool onMusicListRetrieved(const std::vector<db::multimedia_files::MultimediaFilesRecord> &records,
                                  unsigned int repoRecordsCount);
        [[nodiscard]] bool updateRecords(std::vector<db::multimedia_files::MultimediaFilesRecord> records) override;

      public:
        explicit RelaxationSongsModel(ApplicationCommon *application,
                                      std::unique_ptr<RelaxationSongsRepository> soundsRepository);

        unsigned int requestRecordsCount() override;

        [[nodiscard]] unsigned int getMinimalItemSpaceRequired() const override;

        gui::ListItem *getItem(gui::Order order) override;

        void requestRecords(std::uint32_t offset, std::uint32_t limit) override;

        void createData(OnActivateCallback activateCallback) override;
    };
} // namespace app::relaxation

A products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.cpp => products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.cpp +135 -0
@@ 0,0 1,135 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "RelaxationSongsRepository.hpp"
#include <module-db/queries/multimedia_files/QueryMultimediaFilesGetLimited.hpp>
#include <module-db/queries/multimedia_files/QueryMultimediaFilesCount.hpp>
#include <AsyncTask.hpp>
#include <vector>

namespace
{
    std::uint32_t calculateOffset(std::uint32_t offset, std::uint32_t filesCount)
    {
        if (offset >= filesCount) {
            return offset - filesCount;
        }
        return offset;
    }
    std::pair<std::uint32_t, std::uint32_t> calculateNewOffsetAndLimit(std::uint32_t offset,
                                                                       std::uint32_t limit,
                                                                       std::uint32_t loadedFiles)
    {
        const auto newLimit  = limit - loadedFiles;
        const auto newOffset = offset + loadedFiles;
        return {newOffset, newLimit};
    }
} // namespace
namespace app::relaxation
{

    RelaxationSongsRepository::RelaxationSongsRepository(ApplicationCommon *application,
                                                         const std::map<MusicType, std::string> &pathPrefixes)
        : app::AsyncCallbackReceiver{application}, application{application}, pathPrefixes{pathPrefixes}
    {}

    void RelaxationSongsRepository::init()
    {
        for (const auto &[musicType, musicPath] : pathPrefixes) {
            updateFilesCount(musicType, musicPath);
        }
    }

    void RelaxationSongsRepository::updateFilesCount(const MusicType &type, const std::string &path)
    {
        auto query = std::make_unique<db::multimedia_files::query::GetCountForPath>(path);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::MultimediaFiles);
        task->setCallback([this, &type](auto response) {
            auto result = dynamic_cast<db::multimedia_files::query::GetCountResult *>(response);
            if (result == nullptr) {
                return false;
            }
            return updateCountCallback(type, result->getCount());
        });
        task->execute(application, this);
    }

    bool RelaxationSongsRepository::updateCountCallback(const MusicType &type, std::uint32_t count)
    {
        filesPerType[type] = count;
        return true;
    }

    void RelaxationSongsRepository::getMusicFiles(
        const MusicType &musicType,
        std::uint32_t offset,
        std::uint32_t limit,
        const RelaxationSongsRepository::OnGetMusicFilesListCallback &viewUpdateCallback)
    {
        auto taskCallback = [this, viewUpdateCallback, offset, musicType](auto response) {
            auto result = dynamic_cast<db::multimedia_files::query::GetLimitedResult *>(response);
            if (result == nullptr) {
                return false;
            }
            for (auto &record : result->getResult()) {
                musicFilesViewCache.records.push_back(record);
            }
            musicFilesViewCache.recordsOffset = offset;
            musicFilesViewCache.recordsCount  = getRecordsCount();

            if (viewUpdateCallback) {
                viewUpdateCallback(musicFilesViewCache.records, musicFilesViewCache.recordsCount);
            }
            updateCountCallback(musicType, result->getCount());
            return true;
        };

        auto query = std::make_unique<db::multimedia_files::query::GetLimitedByPaths>(
            std::vector<std::string>{pathPrefixes[musicType]}, offset, limit);
        auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::MultimediaFiles);
        task->setCallback(taskCallback);
        task->execute(application, this);
    }

    std::uint32_t RelaxationSongsRepository::getRecordsCount()
    {
        return std::accumulate(filesPerType.begin(),
                               filesPerType.end(),
                               0,
                               [](const std::uint32_t sum, const auto &record) { return sum + record.second; });
    }

    void RelaxationSongsRepository::getMusicFiles(
        std::uint32_t offset,
        std::uint32_t limit,
        const RelaxationSongsRepository::OnGetMusicFilesListCallback &callback)
    {
        const auto skipViewUpdate = []([[maybe_unused]] const auto &sound, [[maybe_unused]] auto count) {
            return true;
        };
        musicFilesViewCache.records.clear();
        std::uint32_t totalFilesCount{0};
        const auto lastFileIndex = offset + limit;

        if (filesPerType.empty()) {
            getMusicFiles(MusicType::Relaxation, offset, limit, callback);
        }

        for (const auto &[type, filesCount] : filesPerType) {
            totalFilesCount += filesCount;
            if (const auto newOffset = calculateOffset(offset, filesCount); newOffset != offset) {
                offset = newOffset;
            }
            else if (lastFileIndex <= totalFilesCount) {
                getMusicFiles(type, offset, limit, callback);
                break;
            }
            else {
                const auto filesToLoad = filesCount - offset;
                getMusicFiles(type, offset, filesToLoad, skipViewUpdate);
                std::tie(offset, limit) = calculateNewOffsetAndLimit(offset, limit, filesToLoad);
                offset                  = calculateOffset(offset, filesCount);
            }
        }
    }

} // namespace app::relaxation

A products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.hpp => products/BellHybrid/apps/application-bell-relaxation/model/RelaxationSongsRepository.hpp +43 -0
@@ 0,0 1,43 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <apps-common/models/SongsRepository.hpp>
#include <memory>
namespace app::relaxation
{
    enum class MusicType
    {
        Relaxation,
        User
    };

    class RelaxationSongsRepository : public app::AsyncCallbackReceiver
    {
      public:
        using OnGetMusicFilesListCallback =
            std::function<bool(const std::vector<db::multimedia_files::MultimediaFilesRecord> &, std::uint32_t)>;

        explicit RelaxationSongsRepository(ApplicationCommon *application,
                                           const std::map<MusicType, std::string> &pathPrefixes);
        void init();
        void getMusicFiles(std::uint32_t offset,
                           std::uint32_t limit,
                           const OnGetMusicFilesListCallback &viewUpdateCallback);

        std::uint32_t getRecordsCount();

      private:
        ApplicationCommon *application;
        std::map<MusicType, std::string> pathPrefixes;
        std::map<MusicType, std::uint32_t> filesPerType;
        music::FilesCache musicFilesViewCache;
        void updateFilesCount(const MusicType &type, const std::string &path);
        bool updateCountCallback(const MusicType &type, std::uint32_t count);
        void getMusicFiles(const MusicType &musicType,
                           std::uint32_t offset,
                           std::uint32_t limit,
                           const OnGetMusicFilesListCallback &viewUpdateCallback);
    };
} // namespace app::relaxation

M products/BellHybrid/apps/application-bell-relaxation/presenter/RelaxationMainWindowPresenter.cpp => products/BellHybrid/apps/application-bell-relaxation/presenter/RelaxationMainWindowPresenter.cpp +17 -21
@@ 4,31 4,27 @@
#include "RelaxationMainWindowPresenter.hpp"
#include <apps-common/models/SongsRepository.hpp>

namespace
{
    constexpr auto offset            = 0;
    constexpr auto filesLimitPerPath = 100;
} // namespace

namespace app::relaxation
{
    RelaxationMainWindowPresenter::RelaxationMainWindowPresenter(
        std::unique_ptr<app::music::AbstractSongsRepository> soundsRepository)
        : soundsRepository{std::move(soundsRepository)}
    RelaxationMainWindowPresenter::RelaxationMainWindowPresenter(std::unique_ptr<RelaxationSongsModel> songsModel)
        : songsModel{std::move(songsModel)}
    {}

    void RelaxationMainWindowPresenter::loadAudioRecords()
    void RelaxationMainWindowPresenter::createData(RelaxationSongsModel::OnActivateCallback activateCallback)
    {
        songsModel->createData(activateCallback);
        updateViewState();
    }
    void RelaxationMainWindowPresenter::updateViewState()
    {
        auto view = getView();
        if (view != nullptr) {
            view->updateViewState();
        }
    }

    std::shared_ptr<RelaxationSongsModel> RelaxationMainWindowPresenter::getSongsModel()
    {
        soundsRepository->getMusicFilesListByPaths(
            offset,
            filesLimitPerPath,
            [this](const std::vector<db::multimedia_files::MultimediaFilesRecord> &records,
                   unsigned int repoRecordsCount) {
                getView()->setSoundsList(records);
                if (repoRecordsCount > filesLimitPerPath) {
                    getView()->handleError();
                }
                return true;
            });
        return songsModel;
    }
} // namespace app::relaxation

M products/BellHybrid/apps/application-bell-relaxation/presenter/RelaxationMainWindowPresenter.hpp => products/BellHybrid/apps/application-bell-relaxation/presenter/RelaxationMainWindowPresenter.hpp +13 -5
@@ 3,6 3,7 @@

#pragma once

#include "model/RelaxationSongsModel.hpp"
#include <apps-common/BasePresenter.hpp>
#include <module-db/Interface/MultimediaFilesRecord.hpp>
#include <vector>


@@ 13,6 14,7 @@ namespace app::music
}
namespace app::relaxation
{

    class RelaxationMainWindowContract
    {
      public:


@@ 21,24 23,30 @@ namespace app::relaxation
          public:
            virtual ~View() = default;

            virtual void setSoundsList(std::vector<db::multimedia_files::MultimediaFilesRecord> songs) = 0;
            virtual void updateViewState()                                                             = 0;
            virtual void handleError()                                                                 = 0;
        };

        class Presenter : public BasePresenter<RelaxationMainWindowContract::View>
        {

          public:
            virtual void loadAudioRecords() = 0;
            virtual void createData(RelaxationSongsModel::OnActivateCallback activateCallback) = 0;
            virtual void updateViewState()                                                     = 0;
            virtual std::shared_ptr<RelaxationSongsModel> getSongsModel()                      = 0;
        };
    };

    class RelaxationMainWindowPresenter : public RelaxationMainWindowContract::Presenter
    {
        std::unique_ptr<app::music::AbstractSongsRepository> soundsRepository;
        void loadAudioRecords() override;
      private:
        std::shared_ptr<RelaxationSongsModel> songsModel;
        void createData(RelaxationSongsModel::OnActivateCallback activateCallback) override;
        void updateViewState() override;
        std::shared_ptr<RelaxationSongsModel> getSongsModel() override;

      public:
        explicit RelaxationMainWindowPresenter(std::unique_ptr<app::music::AbstractSongsRepository> soundsRepository);
        explicit RelaxationMainWindowPresenter(std::unique_ptr<RelaxationSongsModel> songsModel);
    };

} // namespace app::relaxation

M products/BellHybrid/apps/application-bell-relaxation/windows/RelaxationMainWindow.cpp => products/BellHybrid/apps/application-bell-relaxation/windows/RelaxationMainWindow.cpp +29 -30
@@ 5,54 5,53 @@
#include <data/RelaxationAudioData.hpp>
#include <data/RelaxationErrorData.hpp>
#include <ApplicationBellRelaxation.hpp>
#include "common/options/BellOptionsNavigation.hpp"

#include <common/options/OptionBellMenu.hpp>
#include <i18n/i18n.hpp>

namespace
{
    constexpr auto maxDisplayedTitleLength    = 30U;
    constexpr auto incompleteSequenceCharCode = '\342';
} // namespace

namespace gui
{
    RelaxationMainWindow::RelaxationMainWindow(
        app::ApplicationCommon *app,
        std::unique_ptr<app::relaxation::RelaxationMainWindowContract::Presenter> &&presenter)
        : BellOptionWindow(app, gui::name::window::main_window), presenter{std::move(presenter)}
        : AppWindow(app, gui::name::window::main_window), presenter{std::move(presenter)}
    {
        this->presenter->attach(this);
        buildInterface();
        setListTitle(utils::translate("app_bellmain_relaxation"));
    }

    void RelaxationMainWindow::setSoundsList(std::vector<db::multimedia_files::MultimediaFilesRecord> sounds)
    void RelaxationMainWindow::buildInterface()
    {
        std::list<gui::Option> menuOptionList;
        auto addRecord = [&](const db::multimedia_files::MultimediaFilesRecord &sound) {
            menuOptionList.emplace_back(std::make_unique<gui::option::OptionBellMenu>(
                sound.tags.title,
                [=](gui::Item &item) {
                    onActivated(sound);
                    return true;
                },
                [=](gui::Item &item) { return true; },
                this));
        AppWindow::buildInterface();

        statusBar->setVisible(false);
        header->setTitleVisibility(false);
        navBar->setVisible(false);

        songList = new gui::ListViewWithArrows(
            this, 0, 0, style::window_width, style::window_height, presenter->getSongsModel());
        songList->applySizeRestrictions(style::bell_options_list::w,
                                        style::bell_options_list::h,
                                        style::bell_options_list::outer_layouts_h,
                                        style::bell_options_list::outer_layouts_margin);

        songList->setListTitle(utils::translate("app_bellmain_relaxation"));

        auto storedCallback     = songList->inputCallback;
        songList->inputCallback = [&, storedCallback](Item &item, const InputEvent &event) {
            return storedCallback(item, invertNavigationDirection(event));
        };
        for (const auto &sound : sounds) {
            addRecord(sound);
        }
        addOptions(std::move(menuOptionList));
    }
        setFocusItem(songList);

    void RelaxationMainWindow::buildInterface()
    {
        BellOptionWindow::buildInterface();
        presenter->loadAudioRecords();
        presenter->createData([this](const db::multimedia_files::MultimediaFilesRecord &selectedSound) {
            activate(selectedSound);
            return true;
        });
    }

    void RelaxationMainWindow::onActivated(const db::multimedia_files::MultimediaFilesRecord &selectedSound)
    void RelaxationMainWindow::activate(const db::multimedia_files::MultimediaFilesRecord &selectedSound)
    {
        auto audioContext = std::make_unique<RelaxationAudioContext>(selectedSound);
        auto switchData   = std::make_unique<RelaxationSwitchData>(std::move(audioContext));


@@ 65,9 64,9 @@ namespace gui
        application->switchWindow(gui::window::name::relaxationError, std::move(switchData));
    }

    void RelaxationMainWindow::rebuild()
    void RelaxationMainWindow::updateViewState()
    {
        presenter->loadAudioRecords();
        songList->rebuildList(gui::listview::RebuildType::InPlace);
    }

} // namespace gui

M products/BellHybrid/apps/application-bell-relaxation/windows/RelaxationMainWindow.hpp => products/BellHybrid/apps/application-bell-relaxation/windows/RelaxationMainWindow.hpp +5 -4
@@ 9,7 9,7 @@

namespace gui
{
    class RelaxationMainWindow : public BellOptionWindow, public app::relaxation::RelaxationMainWindowContract::View
    class RelaxationMainWindow : public AppWindow, public app::relaxation::RelaxationMainWindowContract::View
    {
      public:
        RelaxationMainWindow(app::ApplicationCommon *app,


@@ 18,10 18,11 @@ namespace gui
      private:
        std::unique_ptr<app::relaxation::RelaxationMainWindowContract::Presenter> presenter;

        gui::ListViewWithArrows *songList = nullptr;

        void buildInterface() override;
        void handleError() override;
        void rebuild() override;
        void setSoundsList(std::vector<db::multimedia_files::MultimediaFilesRecord> soundsTags);
        void onActivated(const db::multimedia_files::MultimediaFilesRecord &selectedSound);
        void updateViewState() override;
        void activate(const db::multimedia_files::MultimediaFilesRecord &selectedSound);
    };
} // namespace gui

M products/BellHybrid/apps/application-bell-relaxation/windows/RelaxationRunningProgressWindow.cpp => products/BellHybrid/apps/application-bell-relaxation/windows/RelaxationRunningProgressWindow.cpp +0 -1
@@ 7,7 7,6 @@
#include <data/RelaxationErrorData.hpp>

#include <ApplicationBellRelaxation.hpp>
#include <audio/AudioMessage.hpp>
#include <apps-common/widgets/BellBaseLayout.hpp>
#include <apps-common/widgets/ProgressTimerWithBarGraphAndCounter.hpp>


M products/BellHybrid/apps/common/src/options/BellOptionWindow.cpp => products/BellHybrid/apps/common/src/options/BellOptionWindow.cpp +2 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common/options/BellOptionWindow.hpp"


@@ 66,4 66,5 @@ namespace gui

        optionsList->rebuildList(listview::RebuildType::InPlace);
    }

} /* namespace gui */

M products/BellHybrid/paths/Paths.cpp => products/BellHybrid/paths/Paths.cpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "Paths.hpp"

M products/BellHybrid/paths/Paths.hpp => products/BellHybrid/paths/Paths.hpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once