A art/phone/application_musicplayer/now_playing_icon_list.png => art/phone/application_musicplayer/now_playing_icon_list.png +0 -0
A art/phone/application_musicplayer/now_playing_icon_pause_list.png => art/phone/application_musicplayer/now_playing_icon_pause_list.png +0 -0
A image/assets/images/now_playing_icon_list.vpi => image/assets/images/now_playing_icon_list.vpi +0 -0
A image/assets/images/now_playing_icon_pause_list.vpi => image/assets/images/now_playing_icon_pause_list.vpi +0 -0
M image/assets/lang/English.json => image/assets/lang/English.json +1 -0
@@ 513,6 513,7 @@
"app_meditation_minutes": "MINUTES",
"app_music_player_all_songs": "All songs",
"app_music_player_play": "PLAY",
+ "app_music_player_music_library_window_name": "Music Library",
"app_music_player_music_library": "MUSIC LIBRARY",
"app_music_player_quit": "QUIT",
"app_music_player_music_empty_window_notification": "Press Music Library to choose\n a song from the library",
M module-apps/application-music-player/ApplicationMusicPlayer.cpp => module-apps/application-music-player/ApplicationMusicPlayer.cpp +16 -8
@@ 2,8 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <application-music-player/ApplicationMusicPlayer.hpp>
+
+#include "AudioNotificationsHandler.hpp"
+
#include <windows/MusicPlayerAllSongsWindow.hpp>
-#include <windows/MusicPlayerEmptyWindow.hpp>
#include <presenters/AudioOperations.hpp>
#include <presenters/SongsPresenter.hpp>
#include <models/SongsRepository.hpp>
@@ 40,14 42,17 @@ namespace app
{
LOG_INFO("ApplicationMusicPlayer::create");
- auto songsRepository = std::make_unique<app::music_player::SongsRepository>(this);
+ bus.channels.push_back(sys::BusChannel::ServiceAudioNotifications);
+
+ auto tagsFetcher = std::make_unique<app::music_player::ServiceAudioTagsFetcher>(this);
+ auto songsRepository = std::make_unique<app::music_player::SongsRepository>(std::move(tagsFetcher));
priv->songsModel = std::make_unique<app::music_player::SongsModel>(std::move(songsRepository));
auto audioOperations = std::make_unique<app::music_player::AudioOperations>(this);
priv->songsPresenter =
std::make_unique<app::music_player::SongsPresenter>(priv->songsModel, std::move(audioOperations));
// callback used when playing state is changed
- using SongState = music_player::SongsModelInterface::SongState;
+ using SongState = app::music_player::SongState;
std::function<void(SongState)> autolockCallback = [this](SongState isPlaying) {
if (isPlaying == SongState::Playing) {
LOG_DEBUG("Preventing autolock while playing track.");
@@ 64,6 69,12 @@ namespace app
// callback used when track is not played and we are in DetermineByAppState
std::function<bool()> stateLockCallback = []() -> bool { return true; };
lockPolicyHandler.setPreventsAutoLockByStateCallback(std::move(stateLockCallback));
+
+ connect(typeid(AudioStopNotification), [&](sys::Message *msg) -> sys::MessagePointer {
+ auto notification = static_cast<AudioStopNotification *>(msg);
+ music_player::AudioNotificationsHandler audioNotificationHandler{priv->songsPresenter};
+ return audioNotificationHandler.handleAudioStopNotification(notification);
+ });
}
ApplicationMusicPlayer::~ApplicationMusicPlayer() = default;
@@ 96,24 107,21 @@ namespace app
}
createUserInterface();
-
return ret;
}
sys::ReturnCodes ApplicationMusicPlayer::DeinitHandler()
{
+ priv->songsPresenter->getMusicPlayerItemProvider()->clearData();
priv->songsPresenter->stop();
return Application::DeinitHandler();
}
void ApplicationMusicPlayer::createUserInterface()
{
- windowsFactory.attach(gui::name::window::all_songs_window, [&](Application *app, const std::string &name) {
+ windowsFactory.attach(gui::name::window::main_window, [&](Application *app, const std::string &name) {
return std::make_unique<gui::MusicPlayerAllSongsWindow>(app, priv->songsPresenter);
});
- windowsFactory.attach(gui::name::window::main_window, [](Application *app, const std::string &name) {
- return std::make_unique<gui::MusicPlayerEmptyWindow>(app);
- });
attachPopups(
{gui::popup::ID::Volume, gui::popup::ID::Tethering, gui::popup::ID::PhoneModes, gui::popup::ID::PhoneLock});
A module-apps/application-music-player/AudioNotificationsHandler.cpp => module-apps/application-music-player/AudioNotificationsHandler.cpp +26 -0
@@ 0,0 1,26 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "AudioNotificationsHandler.hpp"
+
+#include <service-audio/AudioMessage.hpp>
+
+namespace app::music_player
+{
+
+ AudioNotificationsHandler::AudioNotificationsHandler(
+ std::shared_ptr<app::music_player::SongsContract::Presenter> presenter)
+ : presenter(presenter)
+ {}
+
+ sys::MessagePointer AudioNotificationsHandler::handleAudioStopNotification(
+ const AudioStopNotification *notification)
+ {
+ if (notification == nullptr) {
+ return sys::msgNotHandled();
+ }
+
+ return presenter->handleAudioStopNotifiaction(notification->token) ? sys::msgNotHandled() : sys::msgHandled();
+ }
+
+} // namespace app::music_player
A module-apps/application-music-player/AudioNotificationsHandler.hpp => module-apps/application-music-player/AudioNotificationsHandler.hpp +21 -0
@@ 0,0 1,21 @@
+// 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 <presenters/SongsPresenter.hpp>
+
+class AudioStopNotification;
+namespace app::music_player
+{
+ class AudioNotificationsHandler
+ {
+ public:
+ explicit AudioNotificationsHandler(std::shared_ptr<app::music_player::SongsContract::Presenter> presenter);
+
+ sys::MessagePointer handleAudioStopNotification(const AudioStopNotification *notification);
+
+ private:
+ std::shared_ptr<app::music_player::SongsContract::Presenter> presenter;
+ };
+} // namespace app::music_player
M module-apps/application-music-player/CMakeLists.txt => module-apps/application-music-player/CMakeLists.txt +8 -2
@@ 13,6 13,8 @@ target_include_directories(application-music-player
target_sources(application-music-player
PRIVATE
ApplicationMusicPlayer.cpp
+ AudioNotificationsHandler.cpp
+ models/SongContext.cpp
models/SongsModel.cpp
models/SongsRepository.cpp
presenters/AudioOperations.cpp
@@ 20,9 22,10 @@ target_sources(application-music-player
widgets/Action.cpp
widgets/SongItem.cpp
windows/MusicPlayerAllSongsWindow.cpp
- windows/MusicPlayerEmptyWindow.cpp
PRIVATE
+ AudioNotificationsHandler.hpp
data/MusicPlayerStyle.hpp
+ models/SongContext.hpp
models/SongsModel.hpp
models/SongsRepository.hpp
models/SongsModelInterface.hpp
@@ 31,7 34,6 @@ target_sources(application-music-player
widgets/Action.hpp
widgets/SongItem.hpp
windows/MusicPlayerAllSongsWindow.hpp
- windows/MusicPlayerEmptyWindow.hpp
PUBLIC
include/application-music-player/ApplicationMusicPlayer.hpp
)
@@ 54,3 56,7 @@ target_link_libraries(application-music-player
apps-common
module-audio
)
+
+if (${ENABLE_TESTS})
+ add_subdirectory(tests)
+endif()
M module-apps/application-music-player/data/MusicPlayerStyle.hpp => module-apps/application-music-player/data/MusicPlayerStyle.hpp +4 -4
@@ 101,13 101,13 @@ namespace musicPlayerStyle
constexpr uint32_t w = style::window::default_body_width;
constexpr uint32_t h = 100;
- constexpr uint32_t bold_text_h = 24;
- constexpr uint32_t text_h = 22;
+ constexpr uint32_t bold_text_h = 33;
+ constexpr uint32_t text_h = 33;
constexpr uint32_t duration_w = 50;
- constexpr int32_t topMargin = 18;
+ constexpr int32_t topMargin = 16;
constexpr int32_t leftMargin = 10;
- constexpr int32_t rightMargin = 10;
+ constexpr int32_t rightMargin = 4;
} // namespace songItem
A module-apps/application-music-player/models/SongContext.cpp => module-apps/application-music-player/models/SongContext.cpp +30 -0
@@ 0,0 1,30 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "SongContext.hpp"
+#include <optional>
+
+namespace app::music_player
+{
+ void SongContext::clear()
+ {
+ currentSongState = SongState::NotPlaying;
+ currentFileToken = std::nullopt;
+ filePath = "";
+ }
+
+ bool SongContext::isValid() const
+ {
+ return (currentFileToken && currentFileToken->IsValid() && !filePath.empty());
+ }
+
+ bool SongContext::isPlaying() const
+ {
+ return isValid() && currentSongState == SongState::Playing;
+ }
+
+ bool SongContext::isPaused() const
+ {
+ return isValid() && currentSongState == SongState::NotPlaying;
+ }
+} // namespace app::music_player
A module-apps/application-music-player/models/SongContext.hpp => module-apps/application-music-player/models/SongContext.hpp +30 -0
@@ 0,0 1,30 @@
+// 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 <Audio/decoder/Decoder.hpp>
+
+namespace app::music_player
+{
+
+ enum class SongState
+ {
+ Playing,
+ NotPlaying
+ };
+
+ struct SongContext
+ {
+ public:
+ SongState currentSongState = SongState::NotPlaying;
+ std::optional<audio::Token> currentFileToken;
+ std::string filePath;
+
+ void clear();
+
+ bool isPlaying() const;
+ bool isPaused() const;
+ bool isValid() const;
+ };
+
+} // namespace app::music_player
M module-apps/application-music-player/models/SongsModel.cpp => module-apps/application-music-player/models/SongsModel.cpp +81 -16
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "SongsModel.hpp"
+#include "Style.hpp"
#include "application-music-player/widgets/SongItem.hpp"
#include <ListView.hpp>
@@ 22,7 23,7 @@ namespace app::music_player
auto SongsModel::getMinimalItemSpaceRequired() const -> unsigned int
{
- return musicPlayerStyle::songItem::h;
+ return musicPlayerStyle::songItem::h + style::margins::small * 2;
}
void SongsModel::requestRecords(const uint32_t offset, const uint32_t limit)
@@ 36,51 37,115 @@ namespace app::music_player
return getRecord(order);
}
- void SongsModel::createData(std::function<bool(const std::string &fileName)> func)
+ void SongsModel::createData(OnShortReleaseCallback shortReleaseCallback,
+ OnLongPressCallback longPressCallback,
+ OnSetBottomBarTemporaryCallback bottomBarTemporaryMode,
+ OnRestoreBottomBarTemporaryCallback bottomBarRestoreFromTemporaryMode)
{
+ songsRepository->scanMusicFilesList();
auto songsList = songsRepository->getMusicFilesList();
for (const auto &song : songsList) {
- auto item = new gui::SongItem(song.artist, song.title, utils::time::Duration(song.total_duration_s).str());
+ auto item = new gui::SongItem(song.artist,
+ song.title,
+ utils::time::Duration(song.total_duration_s).str(),
+ bottomBarTemporaryMode,
+ bottomBarRestoreFromTemporaryMode);
item->activatedCallback = [=](gui::Item &) {
- func(song.filePath);
+ shortReleaseCallback(song.filePath);
return true;
};
+ item->inputCallback = [longPressCallback](gui::Item &, const gui::InputEvent &event) {
+ if (event.isLongRelease(gui::KeyCode::KEY_ENTER)) {
+ longPressCallback();
+ return true;
+ }
+ return false;
+ };
+
internalData.push_back(item);
}
for (auto &item : internalData) {
item->deleteByList = false;
}
+ }
- list->rebuildList();
+ bool SongsModel::isSongPlaying() const noexcept
+ {
+ return songContext.currentSongState == SongState::Playing;
}
- void SongsModel::clearData()
+ void SongsModel::setCurrentSongState(SongState songState) noexcept
{
- list->reset();
+ songContext.currentSongState = songState;
+ updateCurrentItemState();
+ }
- list->rebuildList();
+ std::optional<audio::Token> SongsModel::getCurrentFileToken() const noexcept
+ {
+ return songContext.currentFileToken;
}
- bool SongsModel::isSongPlaying() const noexcept
+ size_t SongsModel::getCurrentIndex() const
{
- return currentSongState == SongState::Playing;
+ auto index = songsRepository->getFileIndex(songContext.filePath);
+ return index == std::numeric_limits<size_t>::max() ? 0 : index;
}
- void SongsModel::setCurrentSongState(SongState songState) noexcept
+ SongContext SongsModel::getCurrentSongContext() const noexcept
{
- currentSongState = songState;
+ return songContext;
}
- std::optional<audio::Token> SongsModel::getCurrentFileToken() const noexcept
+ void SongsModel::setCurrentSongContext(SongContext context)
{
- return currentFileToken;
+ using namespace gui;
+ clearCurrentItemState();
+
+ songContext = context;
+
+ updateCurrentItemState();
}
- void SongsModel::setCurrentFileToken(std::optional<audio::Token> token) noexcept
+ void SongsModel::clearCurrentSongContext()
{
- currentFileToken = token;
+ clearCurrentItemState();
+ songContext.clear();
+ }
+
+ void SongsModel::clearCurrentItemState()
+ {
+ using namespace gui;
+ const auto songIndex = getCurrentIndex();
+ if (songIndex < internalData.size()) {
+ internalData[songIndex]->setState(SongItem::ItemState::None);
+ }
+ }
+
+ void SongsModel::updateCurrentItemState()
+ {
+ using namespace gui;
+ const auto songIndex = getCurrentIndex();
+ if (songIndex >= internalData.size()) {
+ return;
+ }
+
+ if (songContext.isPlaying()) {
+ internalData[songIndex]->setState(SongItem::ItemState::Playing);
+ }
+ else if (songContext.isPaused()) {
+ internalData[songIndex]->setState(SongItem::ItemState::Paused);
+ }
+ else {
+ internalData[songIndex]->setState(SongItem::ItemState::None);
+ }
+ }
+
+ void SongsModel::clearData()
+ {
+ list->reset();
+ eraseInternalData();
}
} // namespace app::music_player
M module-apps/application-music-player/models/SongsModel.hpp => module-apps/application-music-player/models/SongsModel.hpp +18 -7
@@ 5,6 5,7 @@
#include "module-apps/application-music-player/data/MusicPlayerStyle.hpp"
+#include "SongContext.hpp"
#include "SongsRepository.hpp"
#include "SongsModelInterface.hpp"
@@ 17,8 18,10 @@ namespace app::music_player
public:
explicit SongsModel(std::shared_ptr<AbstractSongsRepository> songsRepository);
- void clearData();
- void createData(std::function<bool(const std::string &fileName)>) override;
+ void createData(OnShortReleaseCallback shortReleaseCallback,
+ OnLongPressCallback longPressCallback,
+ OnSetBottomBarTemporaryCallback bottomBarTemporaryMode,
+ OnRestoreBottomBarTemporaryCallback bottomBarRestoreFromTemporaryMode) override;
[[nodiscard]] auto requestRecordsCount() -> unsigned int override;
@@ 26,17 29,25 @@ namespace app::music_player
auto getItem(gui::Order order) -> gui::ListItem * override;
- void requestRecords(const uint32_t offset, const uint32_t limit) override;
+ void requestRecords(uint32_t offset, uint32_t limit) override;
+
+ size_t getCurrentIndex() const override;
bool isSongPlaying() const noexcept override;
void setCurrentSongState(SongState songState) noexcept override;
std::optional<audio::Token> getCurrentFileToken() const noexcept override;
- void setCurrentFileToken(std::optional<audio::Token> token) noexcept override;
- protected:
- SongState currentSongState = SongState::NotPlaying;
+ SongContext getCurrentSongContext() const noexcept override;
+ void setCurrentSongContext(SongContext context) override;
+ void clearCurrentSongContext() override;
+
+ void clearData() override;
+
+ private:
+ void clearCurrentItemState();
+ void updateCurrentItemState();
+ SongContext songContext;
- std::optional<audio::Token> currentFileToken;
std::shared_ptr<AbstractSongsRepository> songsRepository;
};
} // namespace app::music_player
M module-apps/application-music-player/models/SongsModelInterface.hpp => module-apps/application-music-player/models/SongsModelInterface.hpp +17 -9
@@ 3,18 3,30 @@
#pragma once
+#include "SongContext.hpp"
+#include <widgets/SongItem.hpp>
#include <InternalModel.hpp>
#include <ListItemProvider.hpp>
#include <apps-common/Application.hpp>
namespace app::music_player
{
- class SongsListItemProvider : public app::InternalModel<gui::ListItem *>, public gui::ListItemProvider
+ class SongsListItemProvider : public app::InternalModel<gui::SongItem *>, public gui::ListItemProvider
{
public:
+ using OnShortReleaseCallback = std::function<bool(const std::string &fileName)>;
+ using OnLongPressCallback = std::function<void()>;
+ using OnSetBottomBarTemporaryCallback = std::function<void(const UTF8 &)>;
+ using OnRestoreBottomBarTemporaryCallback = std::function<void()>;
+
virtual ~SongsListItemProvider() noexcept = default;
- virtual void createData(std::function<bool(const std::string &fileName)>) = 0;
+ virtual void createData(OnShortReleaseCallback shortReleaseCallback,
+ OnLongPressCallback longPressCallback,
+ OnSetBottomBarTemporaryCallback bottomBarTemporaryMode,
+ OnRestoreBottomBarTemporaryCallback bottomBarRestoreFromTemporaryMode) = 0;
+ virtual size_t getCurrentIndex() const = 0;
+ virtual void clearData() = 0;
};
class SongsModelInterface : public SongsListItemProvider
@@ 22,15 34,11 @@ namespace app::music_player
public:
virtual ~SongsModelInterface() noexcept = default;
- enum class SongState
- {
- Playing,
- NotPlaying
- };
-
virtual bool isSongPlaying() const noexcept = 0;
virtual void setCurrentSongState(SongState songState) noexcept = 0;
virtual std::optional<audio::Token> getCurrentFileToken() const noexcept = 0;
- virtual void setCurrentFileToken(std::optional<audio::Token>) noexcept = 0;
+ virtual SongContext getCurrentSongContext() const noexcept = 0;
+ virtual void setCurrentSongContext(SongContext context) = 0;
+ virtual void clearCurrentSongContext() = 0;
};
} // namespace app::music_player
M module-apps/application-music-player/models/SongsRepository.cpp => module-apps/application-music-player/models/SongsRepository.cpp +32 -12
@@ 3,47 3,67 @@
#include "SongsRepository.hpp"
+#include <algorithm>
#include <log.hpp>
#include <service-audio/AudioServiceAPI.hpp>
#include <service-audio/AudioServiceName.hpp>
#include <time/ScopedTime.hpp>
-#include <purefs/filesystem_paths.hpp>
#include <service-audio/AudioMessage.hpp>
#include <filesystem>
namespace app::music_player
{
- SongsRepository::SongsRepository(Application *application) : application(application)
+ ServiceAudioTagsFetcher::ServiceAudioTagsFetcher(Application *application) : application(application)
{}
- std::vector<audio::Tags> SongsRepository::getMusicFilesList()
+ std::optional<audio::Tags> ServiceAudioTagsFetcher::getFileTags(const std::string &filePath) const
{
- const auto musicFolder = purefs::dir::getUserDiskPath() / "music";
- std::vector<audio::Tags> musicFiles;
- LOG_INFO("Scanning music folder: %s", musicFolder.c_str());
+ return AudioServiceAPI::GetFileTags(application, filePath);
+ }
+
+ SongsRepository::SongsRepository(std::unique_ptr<AbstractTagsFetcher> tagsFetcher, std::string musicFolderName)
+ : tagsFetcher(std::move(tagsFetcher)), musicFolderName(std::move(musicFolderName))
+ {}
+
+ void SongsRepository::scanMusicFilesList()
+ {
+ musicFiles.clear();
+
+ LOG_INFO("Scanning music folder: %s", musicFolderName.c_str());
{
auto time = utils::time::Scoped("fetch tags time");
- for (const auto &entry : std::filesystem::directory_iterator(musicFolder)) {
+ for (const auto &entry : std::filesystem::directory_iterator(musicFolderName)) {
if (!std::filesystem::is_directory(entry)) {
const auto &filePath = entry.path();
- const auto fileTags = getFileTags(filePath);
+ const auto fileTags = tagsFetcher->getFileTags(filePath);
if (fileTags) {
musicFiles.push_back(*fileTags);
- LOG_DEBUG(" - file %s found", entry.path().c_str());
}
else {
- LOG_ERROR("Not an audio file %s", entry.path().c_str());
+ LOG_ERROR("Scanned not an audio file, skipped");
}
}
}
}
LOG_INFO("Total number of music files found: %u", static_cast<unsigned int>(musicFiles.size()));
+ }
+
+ std::vector<audio::Tags> SongsRepository::getMusicFilesList() const
+ {
return musicFiles;
}
- std::optional<audio::Tags> SongsRepository::getFileTags(const std::string &filePath)
+ std::size_t SongsRepository::getFileIndex(const std::string &filePath) const
{
- return AudioServiceAPI::GetFileTags(application, filePath);
+ auto it = std::find_if(musicFiles.begin(), musicFiles.end(), [filePath](const auto &musicFile) {
+ return musicFile.filePath == filePath;
+ });
+
+ if (it != musicFiles.end()) {
+ return std::distance(musicFiles.begin(), it);
+ }
+
+ return std::numeric_limits<size_t>::max();
}
} // namespace app::music_player
M module-apps/application-music-player/models/SongsRepository.hpp => module-apps/application-music-player/models/SongsRepository.hpp +40 -6
@@ 5,27 5,61 @@
#include <apps-common/Application.hpp>
#include <Audio/decoder/Decoder.hpp>
+#include <purefs/filesystem_paths.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <cstddef>
namespace app::music_player
{
+ class AbstractTagsFetcher
+ {
+ public:
+ virtual ~AbstractTagsFetcher() noexcept = default;
+
+ virtual std::optional<audio::Tags> getFileTags(const std::string &filePath) const = 0;
+ };
+
+ class ServiceAudioTagsFetcher : public AbstractTagsFetcher
+ {
+ public:
+ explicit ServiceAudioTagsFetcher(Application *application);
+
+ std::optional<audio::Tags> getFileTags(const std::string &filePath) const final;
+
+ private:
+ Application *application = nullptr;
+ };
+
class AbstractSongsRepository
{
public:
virtual ~AbstractSongsRepository() noexcept = default;
- virtual std::vector<audio::Tags> getMusicFilesList() = 0;
- virtual std::optional<audio::Tags> getFileTags(const std::string &filePath) = 0;
+ virtual void scanMusicFilesList() = 0;
+ virtual std::vector<audio::Tags> getMusicFilesList() const = 0;
+ virtual std::size_t getFileIndex(const std::string &filePath) const = 0;
};
class SongsRepository : public AbstractSongsRepository
{
+ static constexpr auto musicSubfolderName = "music";
+
public:
- explicit SongsRepository(Application *application);
+ explicit SongsRepository(std::unique_ptr<AbstractTagsFetcher> tagsFetcher,
+ std::string musicFolderName = purefs::dir::getUserDiskPath() / musicSubfolderName);
- std::vector<audio::Tags> getMusicFilesList() override;
- std::optional<audio::Tags> getFileTags(const std::string &filePath) override;
+ void scanMusicFilesList() override;
+ std::vector<audio::Tags> getMusicFilesList() const override;
+ std::size_t getFileIndex(const std::string &filePath) const override;
private:
- Application *application = nullptr;
+ std::unique_ptr<AbstractTagsFetcher> tagsFetcher;
+ std::string musicFolderName;
+ std::vector<audio::Tags> musicFiles;
};
} // namespace app::music_player
M module-apps/application-music-player/presenters/AudioOperations.cpp => module-apps/application-music-player/presenters/AudioOperations.cpp +33 -10
@@ 28,7 28,7 @@ namespace app::music_player
return false;
}
if (callback) {
- callback(result->token);
+ callback(result->retCode, result->token);
}
return true;
};
@@ 36,26 36,49 @@ namespace app::music_player
return true;
}
- bool AudioOperations::pause(const audio::Token &token)
+ bool AudioOperations::pause(const audio::Token &token, const OnPauseCallback &callback)
{
- return AudioServiceAPI::Pause(application, token);
+ auto msg = std::make_unique<AudioPauseRequest>(token);
+ auto task = app::AsyncRequest::createFromMessage(std::move(msg), service::name::audio);
+ auto cb = [callback](auto response) {
+ auto result = dynamic_cast<AudioPauseResponse *>(response);
+ if (result == nullptr) {
+ return false;
+ }
+ if (callback) {
+ callback(result->retCode, result->token);
+ }
+ return true;
+ };
+ task->execute(application, this, cb);
+ return true;
}
- bool AudioOperations::resume(const audio::Token &token)
+ bool AudioOperations::resume(const audio::Token &token, const OnResumeCallback &callback)
{
- return AudioServiceAPI::Resume(application, token);
+ auto msg = std::make_unique<AudioResumeRequest>(token);
+ auto task = app::AsyncRequest::createFromMessage(std::move(msg), service::name::audio);
+ auto cb = [callback](auto response) {
+ auto result = dynamic_cast<AudioResumeResponse *>(response);
+ if (result == nullptr) {
+ return false;
+ }
+ if (callback) {
+ callback(result->retCode, result->token);
+ }
+ return true;
+ };
+ task->execute(application, this, cb);
+ return true;
}
- bool AudioOperations::stop(const audio::Token &token, const OnStopCallback &callback)
+ bool AudioOperations::stop(const audio::Token &token, [[maybe_unused]] const OnStopCallback &callback)
{
auto msg = std::make_unique<AudioStopRequest>(token);
auto task = app::AsyncRequest::createFromMessage(std::move(msg), service::name::audio);
- auto cb = [callback](auto response) {
+ auto cb = [](auto response) {
auto result = dynamic_cast<AudioStopResponse *>(response);
if (result == nullptr) {
return false;
}
- if (callback) {
- callback(result->token);
- }
return true;
};
task->execute(application, this, cb);
M module-apps/application-music-player/presenters/AudioOperations.hpp => module-apps/application-music-player/presenters/AudioOperations.hpp +10 -8
@@ 13,15 13,17 @@ namespace app::music_player
class AbstractAudioOperations
{
public:
- using OnPlayCallback = std::function<void(audio::Token token)>;
- using OnStopCallback = OnPlayCallback;
+ using OnPlayCallback = std::function<void(audio::RetCode retCode, audio::Token token)>;
+ using OnStopCallback = OnPlayCallback;
+ using OnPauseCallback = OnPlayCallback;
+ using OnResumeCallback = OnPlayCallback;
virtual ~AbstractAudioOperations() noexcept = default;
- virtual bool play(const std::string &filePath, const OnPlayCallback &callback) = 0;
- virtual bool pause(const audio::Token &token) = 0;
- virtual bool resume(const audio::Token &token) = 0;
- virtual bool stop(const audio::Token &token, const OnStopCallback &callback) = 0;
+ virtual bool play(const std::string &filePath, const OnPlayCallback &callback) = 0;
+ virtual bool pause(const audio::Token &token, const OnPauseCallback &callback) = 0;
+ virtual bool resume(const audio::Token &token, const OnResumeCallback &callback) = 0;
+ virtual bool stop(const audio::Token &token, const OnStopCallback &callback) = 0;
};
class AudioOperations : public AbstractAudioOperations, public app::AsyncCallbackReceiver
@@ 30,8 32,8 @@ namespace app::music_player
explicit AudioOperations(Application *application);
bool play(const std::string &filePath, const OnPlayCallback &callback) override;
- bool pause(const audio::Token &token) override;
- bool resume(const audio::Token &token) override;
+ bool pause(const audio::Token &token, const OnPauseCallback &callback) override;
+ bool resume(const audio::Token &token, const OnResumeCallback &callback) override;
bool stop(const audio::Token &token, const OnStopCallback &callback) override;
private:
M module-apps/application-music-player/presenters/SongsPresenter.cpp => module-apps/application-music-player/presenters/SongsPresenter.cpp +105 -36
@@ 3,10 3,10 @@
#include "SongsPresenter.hpp"
+#include <service-audio/AudioMessage.hpp>
+
namespace app::music_player
{
- using SongState = SongsModelInterface::SongState;
-
SongsPresenter::SongsPresenter(std::shared_ptr<app::music_player::SongsModelInterface> songsModelInterface,
std::unique_ptr<AbstractAudioOperations> &&audioOperations)
: songsModelInterface{std::move(songsModelInterface)}, audioOperations{std::move(audioOperations)}
@@ 17,31 17,54 @@ namespace app::music_player
return songsModelInterface;
}
- void SongsPresenter::createData(std::function<bool(const std::string &fileName)> fn)
+ void SongsPresenter::createData()
{
- songsModelInterface->createData(fn);
+ songsModelInterface->createData([this](const std::string &fileName) { return requestAudioOperation(fileName); },
+ [this]() { stop(); },
+ [this](const UTF8 &text) { setViewBottomBarTemporaryMode(text); },
+ [this]() { restoreViewBottomBarFromTemporaryMode(); });
+ updateViewSongState();
}
bool SongsPresenter::play(const std::string &filePath)
{
- songsModelInterface->setCurrentSongState(SongState::Playing);
- if (changePlayingStateCallback != nullptr) {
- changePlayingStateCallback(SongState::Playing);
- }
-
- return audioOperations->play(filePath,
- [this](audio::Token token) { songsModelInterface->setCurrentFileToken(token); });
+ return audioOperations->play(filePath, [this, filePath](audio::RetCode retCode, audio::Token token) {
+ if (retCode != audio::RetCode::Success || !token.IsValid()) {
+ LOG_ERROR("Playback audio operation failed, retcode = %s, token validity = %d",
+ str(retCode).c_str(),
+ token.IsValid());
+ return;
+ }
+ SongContext songToken{SongState::Playing, token, filePath};
+ songsModelInterface->setCurrentSongContext(songToken);
+ if (changePlayingStateCallback != nullptr) {
+ changePlayingStateCallback(SongState::Playing);
+ }
+ updateViewSongState();
+ });
}
bool SongsPresenter::pause()
{
auto currentFileToken = songsModelInterface->getCurrentFileToken();
if (currentFileToken) {
- songsModelInterface->setCurrentSongState(SongState::NotPlaying);
- if (changePlayingStateCallback != nullptr) {
- changePlayingStateCallback(SongState::NotPlaying);
- }
- return audioOperations->pause(currentFileToken.value());
+ return audioOperations->pause(currentFileToken.value(), [this](audio::RetCode retCode, audio::Token token) {
+ if (retCode != audio::RetCode::Success || !token.IsValid()) {
+ LOG_ERROR("Pause audio operation failed, retcode = %s, token validity = %d",
+ str(retCode).c_str(),
+ token.IsValid());
+ return;
+ }
+ if (token != songsModelInterface->getCurrentFileToken()) {
+ LOG_ERROR("Pause audio operation failed, wrong token");
+ return;
+ }
+ songsModelInterface->setCurrentSongState(SongState::NotPlaying);
+ if (changePlayingStateCallback != nullptr) {
+ changePlayingStateCallback(SongState::NotPlaying);
+ }
+ updateViewSongState();
+ });
}
return false;
}
@@ 50,11 73,24 @@ namespace app::music_player
{
auto currentFileToken = songsModelInterface->getCurrentFileToken();
if (currentFileToken) {
- songsModelInterface->setCurrentSongState(SongState::Playing);
- if (changePlayingStateCallback != nullptr) {
- changePlayingStateCallback(SongState::Playing);
- }
- return audioOperations->resume(currentFileToken.value());
+ return audioOperations->resume(
+ currentFileToken.value(), [this](audio::RetCode retCode, audio::Token token) {
+ if (retCode != audio::RetCode::Success || !token.IsValid()) {
+ LOG_ERROR("Resume audio operation failed, retcode = %s, token validity = %d",
+ str(retCode).c_str(),
+ token.IsValid());
+ return;
+ }
+ if (token != songsModelInterface->getCurrentFileToken()) {
+ LOG_ERROR("Resume audio operation failed, wrong token");
+ return;
+ }
+ songsModelInterface->setCurrentSongState(SongState::Playing);
+ if (changePlayingStateCallback != nullptr) {
+ changePlayingStateCallback(SongState::Playing);
+ }
+ updateViewSongState();
+ });
}
return false;
}
@@ 63,34 99,67 @@ namespace app::music_player
{
auto currentFileToken = songsModelInterface->getCurrentFileToken();
if (currentFileToken) {
- songsModelInterface->setCurrentSongState(SongState::NotPlaying);
+ return audioOperations->stop(currentFileToken.value(), [](audio::RetCode, audio::Token) {
+ // The answer will come via multicast and will be handled in the application
+ });
+ }
+ return false;
+ }
+
+ void SongsPresenter::setPlayingStateCallback(std::function<void(SongState)> cb)
+ {
+ changePlayingStateCallback = std::move(cb);
+ }
+
+ bool SongsPresenter::handleAudioStopNotifiaction(audio::Token token)
+ {
+ if (token == songsModelInterface->getCurrentFileToken()) {
+ songsModelInterface->clearCurrentSongContext();
if (changePlayingStateCallback != nullptr) {
changePlayingStateCallback(SongState::NotPlaying);
}
-
- return audioOperations->stop(currentFileToken.value(), [this](audio::Token token) {
- if (token == songsModelInterface->getCurrentFileToken()) {
- songsModelInterface->setCurrentFileToken(std::nullopt);
- songsModelInterface->setCurrentSongState(SongState::NotPlaying);
- }
- });
+ updateViewSongState();
+ refreshView();
+ return true;
}
return false;
}
- void SongsPresenter::togglePlaying()
+ bool SongsPresenter::requestAudioOperation(const std::string &filePath)
{
- if (songsModelInterface->isSongPlaying()) {
- pause();
+ auto currentSongContext = songsModelInterface->getCurrentSongContext();
+
+ if (currentSongContext.isValid() && (filePath.empty() || currentSongContext.filePath == filePath)) {
+ return currentSongContext.isPlaying() ? pause() : resume();
}
- else {
- resume();
+ return play(filePath);
+ }
+
+ void SongsPresenter::setViewBottomBarTemporaryMode(const std::string &text)
+ {
+ if (auto view = getView(); view != nullptr) {
+ view->setBottomBarTemporaryMode(text);
}
}
- void SongsPresenter::setPlayingStateCallback(std::function<void(SongState)> cb)
+ void SongsPresenter::restoreViewBottomBarFromTemporaryMode()
{
- changePlayingStateCallback = std::move(cb);
+ if (auto view = getView(); view != nullptr) {
+ view->restoreFromBottomBarTemporaryMode();
+ }
}
+ void SongsPresenter::updateViewSongState()
+ {
+ if (auto view = getView(); view != nullptr) {
+ view->updateSongsState();
+ }
+ }
+
+ void SongsPresenter::refreshView()
+ {
+ if (auto view = getView(); view != nullptr) {
+ view->refreshWindow();
+ }
+ }
} // namespace app::music_player
M module-apps/application-music-player/presenters/SongsPresenter.hpp => module-apps/application-music-player/presenters/SongsPresenter.hpp +22 -8
@@ 17,23 17,29 @@ namespace app::music_player
class View
{
public:
- virtual ~View() noexcept = default;
+ virtual ~View() noexcept = default;
+ virtual void updateSongsState() = 0;
+ virtual void refreshWindow() = 0;
+ virtual void setBottomBarTemporaryMode(const std::string &text) = 0;
+ virtual void restoreFromBottomBarTemporaryMode() = 0;
};
class Presenter : public BasePresenter<SongsContract::View>
{
public:
+ using OnPlayingStateChangeCallback = std::function<void(SongState)>;
+
virtual ~Presenter() noexcept = default;
virtual std::shared_ptr<SongsListItemProvider> getMusicPlayerItemProvider() const = 0;
- virtual void createData(std::function<bool(const std::string &fileName)>) = 0;
+ virtual void createData() = 0;
virtual bool play(const std::string &filePath) = 0;
virtual bool pause() = 0;
virtual bool resume() = 0;
virtual bool stop() = 0;
- virtual void togglePlaying() = 0;
- virtual void setPlayingStateCallback(std::function<void(SongsModelInterface::SongState)> cb) = 0;
+ virtual void setPlayingStateCallback(OnPlayingStateChangeCallback cb) = 0;
+ virtual bool handleAudioStopNotifiaction(audio::Token token) = 0;
};
};
@@ 45,18 51,26 @@ namespace app::music_player
std::shared_ptr<SongsListItemProvider> getMusicPlayerItemProvider() const override;
- void createData(std::function<bool(const std::string &fileName)>) override;
+ void createData() override;
bool play(const std::string &filePath) override;
bool pause() override;
bool resume() override;
bool stop() override;
- void togglePlaying() override;
- void setPlayingStateCallback(std::function<void(SongsModelInterface::SongState)> cb) override;
+
+ void setPlayingStateCallback(std::function<void(SongState)> cb) override;
+ bool handleAudioStopNotifiaction(audio::Token token) override;
private:
+ void updateViewSongState();
+ void refreshView();
+
+ /// Request state dependant audio operation
+ bool requestAudioOperation(const std::string &filePath = "");
+ void setViewBottomBarTemporaryMode(const std::string &text);
+ void restoreViewBottomBarFromTemporaryMode();
std::shared_ptr<SongsModelInterface> songsModelInterface;
std::unique_ptr<AbstractAudioOperations> audioOperations;
- std::function<void(SongsModelInterface::SongState)> changePlayingStateCallback = nullptr;
+ std::function<void(SongState)> changePlayingStateCallback = nullptr;
};
} // namespace app::music_player
A module-apps/application-music-player/tests/CMakeLists.txt => module-apps/application-music-player/tests/CMakeLists.txt +12 -0
@@ 0,0 1,12 @@
+add_gtest_executable(
+ NAME
+ app-music-player
+ SRCS
+ unittest.cpp
+ unittest_songrepository.cpp
+ unittest_songsmodel.cpp
+ LIBS
+ application-music-player
+ INCLUDE
+ ${CMAKE_CURRENT_LIST_DIR}/..
+)
A module-apps/application-music-player/tests/MockSongsRepository.hpp => module-apps/application-music-player/tests/MockSongsRepository.hpp +24 -0
@@ 0,0 1,24 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include <models/SongsRepository.hpp>
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace testing::app::music_player
+{
+ class MockSongsRepository : public ::app::music_player::AbstractSongsRepository
+ {
+ public:
+ MOCK_METHOD(void, scanMusicFilesList, (), (override));
+ MOCK_METHOD(std::vector<audio::Tags>, getMusicFilesList, (), (const override));
+ MOCK_METHOD(std::size_t, getFileIndex, (const std::string &filePath), (const override));
+ };
+}; // namespace testing::app::music_player
A module-apps/application-music-player/tests/MockTagsFetcher.hpp => module-apps/application-music-player/tests/MockTagsFetcher.hpp +20 -0
@@ 0,0 1,20 @@
+// 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 <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include <models/SongsRepository.hpp>
+
+#include <optional>
+
+namespace testing::app::music_player
+{
+ class MockTagsFetcher : public ::app::music_player::AbstractTagsFetcher
+ {
+ public:
+ MOCK_METHOD(std::optional<audio::Tags>, getFileTags, (const std::string &filePath), (const override));
+ };
+}; // namespace testing::app::music_player
A module-apps/application-music-player/tests/unittest.cpp => module-apps/application-music-player/tests/unittest.cpp +10 -0
@@ 0,0 1,10 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
A module-apps/application-music-player/tests/unittest_songrepository.cpp => module-apps/application-music-player/tests/unittest_songrepository.cpp +153 -0
@@ 0,0 1,153 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "MockTagsFetcher.hpp"
+
+#include <models/SongsRepository.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <stdexcept>
+
+using ::testing::Return;
+using ::testing::app::music_player::MockTagsFetcher;
+namespace fs = std::filesystem;
+
+constexpr auto testDir = "appmusic-test";
+constexpr auto emptyDir = "empty";
+constexpr auto musicDir = "music";
+constexpr auto bazDir = "bazdir";
+auto testDirPath = fs::path(testDir);
+auto emptyDirPath = testDirPath / emptyDir;
+auto musicDirPath = testDirPath / musicDir;
+auto bazDirPath = musicDirPath / bazDir;
+
+class SongsRepositoryFixture : public ::testing::Test
+{
+ protected:
+ static void SetUpTestSuite()
+ {
+ if (fs::exists(testDirPath)) {
+ TearDownTestSuite();
+ }
+
+ fs::create_directory(testDirPath);
+ fs::create_directory(emptyDirPath);
+ fs::create_directory(musicDirPath);
+
+ createFile(musicDirPath / "foo");
+ createFile(musicDirPath / "bar");
+
+ fs::create_directory(bazDirPath);
+
+ createFile(bazDirPath / "baz");
+
+ fs::create_directory(musicDirPath / "bazzinga");
+ }
+
+ static void createFile(const std::string &path)
+ {
+ std::ofstream file(path);
+ file << "app music test file";
+ }
+
+ static void TearDownTestSuite()
+ {
+ fs::remove_all(testDir);
+ }
+
+ auto getMockedRepository(const std::string &directoryToScan)
+ {
+ return std::make_unique<app::music_player::SongsRepository>(std::make_unique<MockTagsFetcher>(),
+ directoryToScan);
+ }
+};
+
+TEST_F(SongsRepositoryFixture, LazyInit)
+{
+ auto repo = getMockedRepository(testDir);
+ auto musicFiles = repo->getMusicFilesList();
+ EXPECT_EQ(musicFiles.size(), 0);
+}
+
+TEST_F(SongsRepositoryFixture, Empty)
+{
+ auto repo = getMockedRepository(testDirPath / emptyDir);
+ repo->scanMusicFilesList();
+
+ auto musicFiles = repo->getMusicFilesList();
+ EXPECT_EQ(musicFiles.size(), 0);
+}
+
+TEST_F(SongsRepositoryFixture, ScanEmptyFiles)
+{
+ auto tagsFetcherMock = std::make_unique<MockTagsFetcher>();
+ auto rawMock = tagsFetcherMock.get();
+ auto repo = std::make_unique<app::music_player::SongsRepository>(std::move(tagsFetcherMock), musicDirPath);
+
+ EXPECT_CALL(*rawMock, getFileTags).Times(2);
+ repo->scanMusicFilesList();
+
+ auto musicFiles = repo->getMusicFilesList();
+ EXPECT_EQ(musicFiles.size(), 0);
+}
+
+TEST_F(SongsRepositoryFixture, ScanWithTagsReturn)
+{
+ auto tagsFetcherMock = std::make_unique<MockTagsFetcher>();
+ auto rawMock = tagsFetcherMock.get();
+ auto repo = std::make_unique<app::music_player::SongsRepository>(std::move(tagsFetcherMock), musicDirPath);
+
+ auto fooTags = ::audio::Tags();
+ auto barTags = ::audio::Tags();
+
+ fooTags.title = "foo";
+ barTags.title = "bar";
+
+ ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "foo").c_str())).WillByDefault(Return(fooTags));
+ ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "bar").c_str())).WillByDefault(Return(barTags));
+ EXPECT_CALL(*rawMock, getFileTags).Times(2);
+ repo->scanMusicFilesList();
+
+ auto musicFiles = repo->getMusicFilesList();
+ EXPECT_EQ(musicFiles.size(), 2);
+}
+
+TEST_F(SongsRepositoryFixture, FileIndex)
+{
+ auto tagsFetcherMock = std::make_unique<MockTagsFetcher>();
+ auto rawMock = tagsFetcherMock.get();
+ auto repo = std::make_unique<app::music_player::SongsRepository>(std::move(tagsFetcherMock), musicDirPath);
+
+ auto fooPath = musicDirPath / "foo";
+ auto barPath = musicDirPath / "bar";
+
+ auto fooTags = ::audio::Tags();
+ auto barTags = ::audio::Tags();
+
+ fooTags.title = "foo";
+ fooTags.filePath = fooPath.c_str();
+
+ barTags.title = "bar";
+ barTags.filePath = barPath.c_str();
+
+ ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "foo").c_str())).WillByDefault(Return(fooTags));
+ ON_CALL(*rawMock, getFileTags(fs::path(musicDirPath / "bar").c_str())).WillByDefault(Return(barTags));
+ EXPECT_CALL(*rawMock, getFileTags).Times(2);
+ repo->scanMusicFilesList();
+
+ auto fooIndex = repo->getFileIndex(fooPath);
+ auto barIndex = repo->getFileIndex(barPath);
+
+ EXPECT_NE(fooIndex, static_cast<std::size_t>(-1));
+ EXPECT_LT(fooIndex, 2);
+
+ EXPECT_NE(barIndex, static_cast<std::size_t>(-1));
+ EXPECT_LT(barIndex, 2);
+
+ auto bazIndex = repo->getFileIndex("baz");
+ EXPECT_EQ(bazIndex, static_cast<std::size_t>(-1));
+}
A module-apps/application-music-player/tests/unittest_songsmodel.cpp => module-apps/application-music-player/tests/unittest_songsmodel.cpp +46 -0
@@ 0,0 1,46 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <gtest/gtest.h>
+
+#include "MockSongsRepository.hpp"
+
+#include <models/SongsModel.hpp>
+
+#include <memory>
+#include <optional>
+
+using ::app::music_player::SongsModel;
+using ::testing::Return;
+using ::testing::app::music_player::MockSongsRepository;
+
+TEST(SongsModel, Init)
+{
+ auto mockRepo = std::make_shared<MockSongsRepository>();
+ auto model = SongsModel(mockRepo);
+
+ EXPECT_EQ(model.requestRecordsCount(), 0);
+ EXPECT_FALSE(model.isSongPlaying());
+}
+
+TEST(SongsModel, EmptyContext)
+{
+ auto mockRepo = std::make_shared<MockSongsRepository>();
+ auto model = SongsModel(mockRepo);
+
+ auto ctx = model.getCurrentSongContext();
+
+ EXPECT_EQ(ctx.currentFileToken, std::nullopt);
+ EXPECT_TRUE(ctx.filePath.empty());
+ EXPECT_EQ(ctx.currentSongState, app::music_player::SongState::NotPlaying);
+}
+
+TEST(SongsModel, createDataNoSongs)
+{
+ auto mockRepo = std::make_shared<MockSongsRepository>();
+ auto model = SongsModel(mockRepo);
+
+ EXPECT_CALL(*mockRepo, scanMusicFilesList);
+ EXPECT_CALL(*mockRepo, getMusicFilesList).WillRepeatedly(Return(std::vector<audio::Tags>()));
+ model.createData([](const std::string &) { return true; }, []() {}, [](const UTF8 &) {}, []() {});
+}
M module-apps/application-music-player/widgets/SongItem.cpp => module-apps/application-music-player/widgets/SongItem.cpp +59 -6
@@ 2,13 2,19 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "module-apps/application-music-player/widgets/SongItem.hpp"
+#include <i18n/i18n.hpp>
namespace gui
{
using namespace musicPlayerStyle;
- SongItem::SongItem(const std::string &authorName, const std::string &songName, const std::string &duration)
+ SongItem::SongItem(const std::string &authorName,
+ const std::string &songName,
+ const std::string &duration,
+ std::function<void(const UTF8 &)> setBtBarCallback,
+ std::function<void()> restoreBtBarCallback)
+ : bottomBarTemporaryMode(setBtBarCallback), bottomBarRestoreFromTemporaryMode(restoreBtBarCallback)
{
setMinimumSize(songItem::w, songItem::h);
setMargins(Margins(0, style::margins::small, 0, style::margins::small));
@@ 44,15 50,14 @@ namespace gui
songText->setMargins(Margins(songItem::leftMargin, 0, 0, 0));
songText->setEdges(RectangleEdge::None);
songText->setUnderline(false);
- songText->setFont(style::window::font::verysmallbold);
+ songText->setFont(style::window::font::bigbold);
songText->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
songText->setEditMode(EditMode::Browse);
songText->setText(songName);
- playedSong = new ImageBox(secondHBox, 0, 0, 0, 0, new Image("messages_error_W_M"));
- playedSong->setMinimumSize(songItem::duration_w, songItem::text_h);
+ playedSong = new Image(secondHBox, 0, 0, "");
+ playedSong->setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
playedSong->setVisible(false);
- playedSong->setEdges(RectangleEdge::None);
authorText = new TextFixedSize(secondHBox, 0, 0, 0, 0);
authorText->setMinimumHeight(songItem::text_h);
@@ 60,7 65,7 @@ namespace gui
authorText->setMargins(Margins(songItem::leftMargin, 0, 0, 0));
authorText->setEdges(RectangleEdge::None);
authorText->setUnderline(false);
- authorText->setFont(style::window::font::verysmall);
+ authorText->setFont(style::window::font::medium);
authorText->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
authorText->setEditMode(EditMode::Browse);
authorText->setText(authorName);
@@ 69,5 74,53 @@ namespace gui
vBox->setArea({0, 0, newDim.w, newDim.h});
return true;
};
+
+ focusChangedCallback = [&](gui::Item &item) {
+ if (item.focus) {
+ std::string bottorBarText;
+ switch (itemState) {
+ case ItemState::Playing:
+ bottorBarText = utils::translate("common_pause");
+ break;
+ case ItemState::Paused:
+ bottorBarText = utils::translate("common_resume");
+ break;
+ case ItemState::None:
+ bottorBarText = utils::translate("app_music_player_play");
+ ;
+ break;
+ }
+ if (bottomBarTemporaryMode != nullptr) {
+ bottomBarTemporaryMode(bottorBarText);
+ }
+ }
+ else {
+ setFocusItem(nullptr);
+ if (bottomBarRestoreFromTemporaryMode != nullptr) {
+ bottomBarRestoreFromTemporaryMode();
+ }
+ }
+ return true;
+ };
+ }
+
+ void SongItem::setState(ItemState state)
+ {
+ itemState = state;
+ switch (state) {
+ case ItemState::Paused:
+ playedSong->set("now_playing_icon_pause_list");
+ playedSong->setVisible(true);
+ break;
+ case ItemState::Playing:
+ playedSong->set("now_playing_icon_list");
+ playedSong->setVisible(true);
+ break;
+ case ItemState::None:
+ playedSong->set("");
+ playedSong->setVisible(false);
+ break;
+ }
+ secondHBox->resizeItems();
}
} /* namespace gui */
M module-apps/application-music-player/widgets/SongItem.hpp => module-apps/application-music-player/widgets/SongItem.hpp +25 -10
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 8,7 8,7 @@
#include <ListItem.hpp>
#include <Text.hpp>
#include <TextFixedSize.hpp>
-#include <ImageBox.hpp>
+#include <Image.hpp>
namespace gui
{
@@ 16,16 16,31 @@ namespace gui
{
public:
- SongItem(const std::string &authorName, const std::string &songName, const std::string &duration);
+ SongItem(const std::string &authorName,
+ const std::string &songName,
+ const std::string &duration,
+ std::function<void(const UTF8 &)> bottomBarTemporaryMode,
+ std::function<void()> bottomBarRestoreFromTemporaryMode);
+
+ enum class ItemState
+ {
+ None,
+ Playing,
+ Paused
+ };
+ void setState(ItemState state);
private:
- VBox *vBox = nullptr;
- HBox *firstHBox = nullptr;
- HBox *secondHBox = nullptr;
- TextFixedSize *authorText = nullptr;
- TextFixedSize *songText = nullptr;
- TextFixedSize *durationText = nullptr;
- ImageBox *playedSong = nullptr;
+ VBox *vBox = nullptr;
+ HBox *firstHBox = nullptr;
+ HBox *secondHBox = nullptr;
+ TextFixedSize *authorText = nullptr;
+ TextFixedSize *songText = nullptr;
+ TextFixedSize *durationText = nullptr;
+ Image *playedSong = nullptr;
+ ItemState itemState = ItemState::None;
+ std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
+ std::function<void()> bottomBarRestoreFromTemporaryMode = nullptr;
};
} /* namespace gui */
M module-apps/application-music-player/windows/MusicPlayerAllSongsWindow.cpp => module-apps/application-music-player/windows/MusicPlayerAllSongsWindow.cpp +54 -5
@@ 10,6 10,7 @@
#include <i18n/i18n.hpp>
#include <service-audio/AudioServiceAPI.hpp>
#include <gui/widgets/ListView.hpp>
+#include <gui/widgets/Icon.hpp>
namespace gui
{
@@ 32,10 33,8 @@ namespace gui
{
AppWindow::buildInterface();
- setTitle(utils::translate("app_music_player_all_songs"));
-
- bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_music_player_play"));
- bottomBar->setText(BottomBar::Side::RIGHT, utils::translate(style::strings::common::back));
+ bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_music_player_music_library"));
+ bottomBar->setText(BottomBar::Side::RIGHT, utils::translate("app_music_player_quit"));
songsList = new gui::ListView(this,
musicPlayerStyle::allSongsWindow::x,
@@ 45,6 44,20 @@ namespace gui
presenter->getMusicPlayerItemProvider(),
listview::ScrollBarType::Fixed);
+ emptyListIcon = new gui::Icon(this,
+ ::style::window::default_left_margin,
+ ::style::window::default_vertical_pos,
+ ::style::window::default_body_width,
+ ::style::window::default_body_height,
+ "note",
+ utils::translate("app_music_player_music_empty_window_notification"));
+
+ emptyListIcon->setAlignment(Alignment::Horizontal::Center);
+ songsList->emptyListCallback = [this]() { emptyListIcon->setVisible(true); };
+ songsList->notEmptyListCallback = [this]() {
+ emptyListIcon->setVisible(false);
+ setTitle(utils::translate("app_music_player_music_library_window_name"));
+ };
setFocusItem(songsList);
}
@@ 55,7 68,43 @@ namespace gui
void MusicPlayerAllSongsWindow::onBeforeShow([[maybe_unused]] ShowMode mode, [[maybe_unused]] SwitchData *data)
{
- presenter->createData([this](const std::string &fileName) { return presenter->play(fileName); });
+ presenter->attach(this);
+ auto index = presenter->getMusicPlayerItemProvider()->getCurrentIndex();
+
+ songsList->rebuildList(listview::RebuildType::OnPageElement, index);
+ }
+
+ void MusicPlayerAllSongsWindow::updateSongsState()
+ {
+ songsList->rebuildList(gui::listview::RebuildType::InPlace);
+ }
+
+ void MusicPlayerAllSongsWindow::refreshWindow()
+ {
+ application->refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
}
+ void MusicPlayerAllSongsWindow::setBottomBarTemporaryMode(const std::string &text)
+ {
+ bottomBarTemporaryMode(text, BottomBar::Side::CENTER, false);
+ }
+
+ void MusicPlayerAllSongsWindow::restoreFromBottomBarTemporaryMode()
+ {
+ bottomBarRestoreFromTemporaryMode();
+ }
+
+ bool MusicPlayerAllSongsWindow::onInput(const InputEvent &inputEvent)
+ {
+ if (AppWindow::onInput(inputEvent)) {
+ return true;
+ }
+
+ if (inputEvent.isShortRelease(gui::KeyCode::KEY_ENTER)) {
+ presenter->createData();
+ return true;
+ }
+
+ return false;
+ }
} /* namespace gui */
M module-apps/application-music-player/windows/MusicPlayerAllSongsWindow.hpp => module-apps/application-music-player/windows/MusicPlayerAllSongsWindow.hpp +8 -1
@@ 10,21 10,28 @@
namespace gui
{
class ListView;
+ class Icon;
class MusicPlayerAllSongsWindow : public AppWindow, public app::music_player::SongsContract::View
{
std::shared_ptr<app::music_player::SongsContract::Presenter> presenter;
ListView *songsList = nullptr;
+ Icon *emptyListIcon = nullptr;
public:
explicit MusicPlayerAllSongsWindow(app::Application *app,
std::shared_ptr<app::music_player::SongsContract::Presenter> presenter);
- // virtual methods
void onBeforeShow([[maybe_unused]] ShowMode mode, [[maybe_unused]] SwitchData *data) override;
void rebuild() override;
void buildInterface() override;
void destroyInterface() override;
+ bool onInput(const InputEvent &inputEvent) override;
+
+ void updateSongsState() override;
+ void refreshWindow() override;
+ void setBottomBarTemporaryMode(const std::string &text) override;
+ void restoreFromBottomBarTemporaryMode() override;
};
} /* namespace gui */
D module-apps/application-music-player/windows/MusicPlayerEmptyWindow.cpp => module-apps/application-music-player/windows/MusicPlayerEmptyWindow.cpp +0 -80
@@ 1,80 0,0 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#include "MusicPlayerEmptyWindow.hpp"
-#include "application-music-player/ApplicationMusicPlayer.hpp"
-#include "application-music-player/data/MusicPlayerStyle.hpp"
-
-#include <Style.hpp>
-#include <i18n/i18n.hpp>
-#include <log.hpp>
-#include <service-audio/AudioServiceAPI.hpp>
-
-namespace gui
-{
- using namespace musicPlayerStyle::emptyWindow;
-
- MusicPlayerEmptyWindow::MusicPlayerEmptyWindow(app::Application *app)
- : AppWindow(app, gui::name::window::main_window)
- {
- buildInterface();
- }
-
- void MusicPlayerEmptyWindow::rebuild()
- {
- destroyInterface();
- buildInterface();
- }
-
- void MusicPlayerEmptyWindow::buildInterface()
- {
- AppWindow::buildInterface();
-
- bottomBar->setText(BottomBar::Side::LEFT, utils::translate("app_music_player_music_library"));
- bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_music_player_play"));
- bottomBar->setText(BottomBar::Side::RIGHT, utils::translate("app_music_player_quit"));
-
- img = new gui::Image(this, noteImg::x, noteImg::y, "note");
-
- text = new Text(this, infoText::x, infoText::y, infoText::w, infoText::h);
- text->setText(utils::translate("app_music_player_music_empty_window_notification"));
- text->setTextType(TextType::MultiLine);
- text->setEditMode(EditMode::Browse);
- text->setEdges(RectangleEdge::None);
- text->setFont(style::window::font::medium);
- text->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
-
- placeHolder = new gui::Image(this, placeHolderImg::x, placeHolderImg::y, "placeholder_player");
- }
-
- void MusicPlayerEmptyWindow::destroyInterface()
- {
- erase();
- }
-
- void MusicPlayerEmptyWindow::onBeforeShow(ShowMode mode, SwitchData *data)
- {}
-
- bool MusicPlayerEmptyWindow::onDatabaseMessage(sys::Message *msgl)
- {
- return false;
- }
-
- bool MusicPlayerEmptyWindow::onInput(const InputEvent &inputEvent)
- {
- if (AppWindow::onInput(inputEvent)) {
- return true;
- }
-
- if (inputEvent.isShortRelease(gui::KeyCode::KEY_LF)) {
- application->switchWindow(gui::name::window::all_songs_window);
- return true;
- }
-
- if (inputEvent.is(gui::KeyCode::KEY_ENTER) || inputEvent.is(gui::KeyCode::HEADSET_OK)) {
- }
-
- return false;
- }
-
-} /* namespace gui */
D module-apps/application-music-player/windows/MusicPlayerEmptyWindow.hpp => module-apps/application-music-player/windows/MusicPlayerEmptyWindow.hpp +0 -35
@@ 1,35 0,0 @@
-// 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 "AppWindow.hpp"
-#include <Text.hpp>
-#include <Image.hpp>
-
-#include <vector>
-#include <string>
-
-namespace gui
-{
-
- class MusicPlayerEmptyWindow : public AppWindow
- {
- Image *img = nullptr;
- Text *text = nullptr;
- Image *placeHolder = nullptr;
-
- public:
- MusicPlayerEmptyWindow(app::Application *app);
-
- // virtual methods
- void onBeforeShow(ShowMode mode, SwitchData *data) override;
-
- void rebuild() override;
- void buildInterface() override;
- void destroyInterface() override;
- bool onDatabaseMessage(sys::Message *msg) override;
- bool onInput(const InputEvent &inputEvent) final;
- };
-
-} /* namespace gui */
M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +0 -2
@@ 288,8 288,6 @@ std::unique_ptr<AudioResponseMessage> ServiceAudio::HandlePause(std::optional<Au
retCode = audioInput->audio->Pause();
retToken = audioInput->token;
audioInput->DisableVibration();
- bus.sendMulticast(std::make_shared<AudioPausedNotification>(audioInput->token),
- sys::BusChannel::ServiceAudioNotifications);
}
else {
retCode = StopInput(audioInput);
M module-services/service-audio/include/service-audio/AudioMessage.hpp => module-services/service-audio/include/service-audio/AudioMessage.hpp +0 -7
@@ 56,13 56,6 @@ class AudioNotificationMessage : public AudioMessage
const audio::Token token;
};
-class AudioPausedNotification : public AudioNotificationMessage
-{
- public:
- explicit AudioPausedNotification(audio::Token token) : AudioNotificationMessage{token}
- {}
-};
-
class AudioStopNotification : public AudioNotificationMessage
{
public: