From 4c1dcbe9de2ad1ed7dc0de732d90aebe237f2d7e Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 7 Apr 2016 13:02:51 +0200 Subject: [PATCH] [UI] Added a playqueue on mobile version, minor visual changes on desktop version --- README.md | 4 +- approot/templates.xml | 42 ++-- docroot/css/lms.css | 23 ++ src/Makefile.am | 1 + src/ui/audio/AudioPlayer.cpp | 88 +++++--- src/ui/audio/AudioPlayer.hpp | 15 +- src/ui/audio/desktop/DesktopAudio.cpp | 10 +- src/ui/audio/desktop/TableFilter.cpp | 6 +- src/ui/audio/desktop/TrackView.cpp | 2 +- src/ui/audio/mobile/MobileAudio.cpp | 45 +++- src/ui/audio/mobile/MobilePlayQueue.cpp | 206 ++++++++++++++++++ src/ui/audio/mobile/MobilePlayQueue.hpp | 75 +++++++ src/ui/audio/mobile/MobilePlayQueueEvents.hpp | 1 + src/ui/audio/mobile/ReleaseView.cpp | 16 +- src/ui/audio/mobile/TrackSearch.cpp | 16 +- 15 files changed, 485 insertions(+), 65 deletions(-) create mode 100644 src/ui/audio/mobile/MobilePlayQueue.cpp create mode 100644 src/ui/audio/mobile/MobilePlayQueue.hpp diff --git a/README.md b/README.md index 9602a973..20028439 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ This command requires root privileges ## Running Here is a command line example to run on port 5081: ```sh -$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081 +$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images,/favicon.ico' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081 ``` It is highly recommended to run LMS as a non root user. The exectuable needs write accesses to the /var/lms/ directory. @@ -75,7 +75,7 @@ $ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096 ``` Run (on port 5081): ```sh -$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --https-address 0.0.0.0 --https-port 5081 --ssl-certificate cert.pem --ssl-private-key privkey.pem --ssl-tmp-dh dh2048.pem +$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images,/favicon.ico' --approot=/usr/share/lms/approot --https-address 0.0.0.0 --https-port 5081 --ssl-certificate cert.pem --ssl-private-key privkey.pem --ssl-tmp-dh dh2048.pem ``` Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key. diff --git a/approot/templates.xml b/approot/templates.xml index 27fc5b8a..5be05553 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -356,15 +356,6 @@ - -
- ${prev} - ${play} - ${pause} - ${next} -
-
-
@@ -426,7 +417,7 @@ + + ${controls} + + + + + ${load}${save}${clear}${shuffle}${repeat} + + + +
+
+
${cover}
+
${artist-name}
${track-name}
+
${play-btn}${del-btn}
+
+
${audio} ${prev}${play-pause}${next} -
+
- ${shuffle}${repeat} + ${}${shuffle}${}${}${repeat}${}${}${playqueue}${}
diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 6c0bf27d..814622af 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -129,6 +129,19 @@ writing-mode: bt-lr; } +.mobile-btn { + margin: 8px; +} + +.mobile-btn-active { + color: #428bca; +} + +.mobile-btn i { + cursor: pointer; + text-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); +} + .mobile-search-title { font-weight: bold; height: 32px; @@ -198,3 +211,13 @@ max-width: 100%; height: auto; } + +.release-search-cover { + width: 512px; + min-width: 512px; + height: 512px; + min-height: 512px; + display: flex; + justify-content: space-between; + align-items: center; +} diff --git a/src/Makefile.am b/src/Makefile.am index b5c5e7d6..5b9e6480 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,6 +35,7 @@ lms_SOURCES = \ $(srcdir)/ui/audio/mobile/ArtistSearchView.cpp \ $(srcdir)/ui/audio/mobile/ArtistView.cpp \ $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ + $(srcdir)/ui/audio/mobile/MobilePlayQueue.cpp \ $(srcdir)/ui/audio/mobile/PreviewSearchView.cpp \ $(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \ $(srcdir)/ui/audio/mobile/ReleaseSearchView.cpp \ diff --git a/src/ui/audio/AudioPlayer.cpp b/src/ui/audio/AudioPlayer.cpp index 1df7115e..1cad4b2c 100644 --- a/src/ui/audio/AudioPlayer.cpp +++ b/src/ui/audio/AudioPlayer.cpp @@ -42,7 +42,6 @@ AudioPlayer::getBestEncoding() const return Av::Encoding::MP3; } - bool AudioPlayer::loadTrack(Database::Track::id_type trackId) { @@ -121,7 +120,7 @@ AudioPlayer::loadTrack(Database::Track::id_type trackId) return true; } -AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent) +AudioPlayer::AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent) : Wt::WTemplate(parent) { setTemplateText(Wt::WString::tr("wa-audio-player")); @@ -154,42 +153,71 @@ AudioPlayer::AudioPlayer(Wt::WContainerWidget *parent) volumeSlider->setAttributeValue("orient", "vertical"); // firefox bindWidget("volume", volumeSlider); - Wt::WText *repeatBtn = new Wt::WText("", Wt::XHTMLText); - repeatBtn->addStyleClass("mediaplayer-btn"); - bindWidget("repeat", repeatBtn); - repeatBtn->clicked().connect(std::bind([=] () + if (controls & ControlRepeat) { - if (repeatBtn->hasStyleClass("mediaplayer-btn-active")) + setCondition("if-has-repeat", true); + Wt::WText *repeatBtn = new Wt::WText("", Wt::XHTMLText); + repeatBtn->addStyleClass("mediaplayer-btn hidden-xs"); + bindWidget("repeat", repeatBtn); + repeatBtn->clicked().connect(std::bind([=] () { - repeatBtn->removeStyleClass("mediaplayer-btn-active"); - _loop.emit(false); - } - else - { - repeatBtn->addStyleClass("mediaplayer-btn-active"); - _loop.emit(true); - } - })); + if (repeatBtn->hasStyleClass("mediaplayer-btn-active")) + { + repeatBtn->removeStyleClass("mediaplayer-btn-active"); + _loop.emit(false); + } + else + { + repeatBtn->addStyleClass("mediaplayer-btn-active"); + _loop.emit(true); + } + })); + } - Wt::WText *shuffleBtn = new Wt::WText("", Wt::XHTMLText); - shuffleBtn->addStyleClass("mediaplayer-btn"); - bindWidget("shuffle", shuffleBtn); - shuffleBtn->clicked().connect(std::bind([=] () + if (controls & ControlPlayqueue) { - if (shuffleBtn->hasStyleClass("mediaplayer-btn-active")) + setCondition("if-has-playqueue", true); + Wt::WText *playQueueBtn = new Wt::WText("", Wt::XHTMLText); + bindWidget("playqueue", playQueueBtn); + playQueueBtn->addStyleClass("mediaplayer-btn"); + playQueueBtn->clicked().connect(std::bind([=] () { - shuffleBtn->removeStyleClass("mediaplayer-btn-active"); - _shuffle.emit(false); - } - else + if (playQueueBtn->hasStyleClass("mediaplayer-btn-active")) + { + playQueueBtn->removeStyleClass("mediaplayer-btn-active"); + _playQueue.emit(false); + } + else + { + playQueueBtn->addStyleClass("mediaplayer-btn-active"); + _playQueue.emit(true); + } + })); + } + + if (controls & ControlShuffle) + { + setCondition("if-has-shuffle", true); + Wt::WText *shuffleBtn = new Wt::WText("", Wt::XHTMLText); + shuffleBtn->addStyleClass("mediaplayer-btn hidden-xs"); + bindWidget("shuffle", shuffleBtn); + shuffleBtn->clicked().connect(std::bind([=] () { - shuffleBtn->addStyleClass("mediaplayer-btn-active"); - _shuffle.emit(true); - } - })); + if (shuffleBtn->hasStyleClass("mediaplayer-btn-active")) + { + shuffleBtn->removeStyleClass("mediaplayer-btn-active"); + _shuffle.emit(false); + } + else + { + shuffleBtn->addStyleClass("mediaplayer-btn-active"); + _shuffle.emit(true); + } + })); + } Wt::WText *prevBtn = new Wt::WText("", Wt::XHTMLText); - prevBtn->addStyleClass("mediaplayer-btn hidden-xs"); + prevBtn->addStyleClass("mediaplayer-btn"); bindWidget("prev", prevBtn); prevBtn->clicked().connect(std::bind([=] () { diff --git a/src/ui/audio/AudioPlayer.hpp b/src/ui/audio/AudioPlayer.hpp index 371fe2b3..867824a9 100644 --- a/src/ui/audio/AudioPlayer.hpp +++ b/src/ui/audio/AudioPlayer.hpp @@ -32,7 +32,15 @@ class AudioPlayer : public Wt::WTemplate { public: - AudioPlayer(Wt::WContainerWidget *parent = 0); + enum ControlFlags + { + ControlNone = 0, + ControlShuffle = 1 << 0, + ControlRepeat = 1 << 1, + ControlPlayqueue = 1 << 2, + }; + + AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent = 0); Av::Encoding getBestEncoding() const; bool loadTrack(Database::Track::id_type trackId); @@ -43,6 +51,7 @@ class AudioPlayer : public Wt::WTemplate Wt::Signal& playPrevious() {return _playPrevious;} Wt::Signal& shuffle() {return _shuffle;} Wt::Signal& loop() {return _loop;} + Wt::Signal& showPlayQueue() {return _playQueue;} private: @@ -52,6 +61,7 @@ class AudioPlayer : public Wt::WTemplate Wt::Signal _playPrevious; Wt::Signal _shuffle; Wt::Signal _loop; + Wt::Signal _playQueue; Wt::WAudio* _audio; Wt::WText* _trackCurTime; @@ -60,4 +70,7 @@ class AudioPlayer : public Wt::WTemplate Wt::WImage* _cover; }; +constexpr AudioPlayer::ControlFlags operator|(AudioPlayer::ControlFlags f1, AudioPlayer::ControlFlags f2) { return AudioPlayer::ControlFlags(int(f1)|int(f2)); } + + } // namespace UserInterface diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp index efcd13c3..3432957e 100644 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ b/src/ui/audio/desktop/DesktopAudio.cpp @@ -149,16 +149,16 @@ _playQueue(nullptr) playlistBtn->setMenu(popupMain); } - Wt::WPushButton *upBtn = new Wt::WPushButton("UP"); + Wt::WPushButton *upBtn = new Wt::WPushButton("", Wt::XHTMLText); upBtn->setStyleClass("btn-sm"); playlistControls->addWidget(upBtn); - Wt::WPushButton *downBtn = new Wt::WPushButton("DO"); + Wt::WPushButton *downBtn = new Wt::WPushButton("", Wt::XHTMLText); downBtn->setStyleClass("btn-sm"); playlistControls->addWidget(downBtn); - Wt::WPushButton *delBtn = new Wt::WPushButton("DEL"); + Wt::WPushButton *delBtn = new Wt::WPushButton("", Wt::XHTMLText); delBtn->setStyleClass("btn-sm btn-warning"); playlistControls->addWidget(delBtn); - Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR"); + Wt::WPushButton *clearBtn = new Wt::WPushButton("", Wt::XHTMLText); clearBtn->setStyleClass("btn-sm btn-danger"); playlistControls->addWidget(clearBtn); @@ -180,7 +180,7 @@ _playQueue(nullptr) _playQueue->select(CurrentUser()->getCurPlayingTrackPos()); } - _mediaPlayer = new AudioPlayer(); + _mediaPlayer = new AudioPlayer(AudioPlayer::ControlShuffle | AudioPlayer::ControlRepeat); mainLayout->addWidget(_mediaPlayer, 2, 0, 1, 4); mainLayout->setRowStretch(1, 1); diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp index f7197779..6438f713 100644 --- a/src/ui/audio/desktop/TableFilter.cpp +++ b/src/ui/audio/desktop/TableFilter.cpp @@ -55,7 +55,7 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent) this->setLayoutSizeAware(true); - _queryModel.setBatchSize(100); + _queryModel.setBatchSize(150); // If an item is double clicked, select and emit signal this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) @@ -134,7 +134,7 @@ TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent) this->setLayoutSizeAware(true); - _queryModel.setBatchSize(100); + _queryModel.setBatchSize(150); // If an item is double clicked, select and emit signal this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) @@ -218,7 +218,7 @@ TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent) this->setLayoutSizeAware(true); - _queryModel.setBatchSize(100); + _queryModel.setBatchSize(150); // If an item is double clicked, select and emit signal this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp index d01a48ef..3c3e9cc6 100644 --- a/src/ui/audio/desktop/TrackView.cpp +++ b/src/ui/audio/desktop/TrackView.cpp @@ -70,7 +70,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent) Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - _queryModel.setBatchSize(300); + _queryModel.setBatchSize(500); this->setSortingEnabled(true); this->setSelectionMode(Wt::ExtendedSelection); diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp index 9399264f..0f8b036a 100644 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ b/src/ui/audio/mobile/MobileAudio.cpp @@ -37,6 +37,8 @@ #include "ArtistView.hpp" #include "ReleaseView.hpp" +#include "MobilePlayQueue.hpp" + #include "MobileAudio.hpp" @@ -51,6 +53,7 @@ enum WidgetIdx WidgetIdxSearchTrack = 3, WidgetIdxArtist = 4, WidgetIdxRelease = 5, + WidgetIdxPlayQueue = 6, }; using namespace Database; @@ -70,6 +73,8 @@ Audio::Audio(Wt::WContainerWidget *parent) Wt::WStackedWidget *stack = new Wt::WStackedWidget(this); + PlayQueue *playQueue = new PlayQueue(); + // Same order as WidgetIdxXXX stack->addWidget(new PreviewSearchView(_playQueueEvents)); stack->addWidget(new ArtistSearchView()); @@ -77,6 +82,7 @@ Audio::Audio(Wt::WContainerWidget *parent) stack->addWidget(new TrackSearchView(_playQueueEvents)); stack->addWidget(new ArtistView()); stack->addWidget(new ReleaseView(_playQueueEvents)); + stack->addWidget(playQueue); wApp->internalPathChanged().connect(std::bind([=] (std::string path) { @@ -89,6 +95,7 @@ Audio::Audio(Wt::WContainerWidget *parent) { "/audio/search/track", WidgetIdxSearchTrack}, { "/audio/artist", WidgetIdxArtist}, { "/audio/release", WidgetIdxRelease}, + { "/audio/playqueue", WidgetIdxPlayQueue}, }; for (auto index : indexes) @@ -102,14 +109,48 @@ Audio::Audio(Wt::WContainerWidget *parent) Wt::WTemplate* footer = new Wt::WTemplate(this); footer->setTemplateText(Wt::WString::tr("mobile-audio-footer")); - AudioPlayer* audioPlayer = new AudioPlayer(); + AudioPlayer* audioPlayer = new AudioPlayer(AudioPlayer::ControlPlayqueue); footer->bindWidget("player", audioPlayer); + audioPlayer->showPlayQueue().connect(std::bind([=] (bool state) + { + if (state) + wApp->setInternalPath("/audio/playqueue", true); + }, std::placeholders::_1)); + + // Connect the audio player events to the playqueue + playQueue->playTrack().connect(std::bind([=] (Database::Track::id_type id) + { + audioPlayer->loadTrack(id); + }, std::placeholders::_1)); + audioPlayer->playNext().connect(std::bind([=] + { + playQueue->playNext(); + })); + audioPlayer->playPrevious().connect(std::bind([=] + { + playQueue->playPrevious(); + })); + audioPlayer->playbackEnded().connect(std::bind([=] + { + playQueue->playNext(); + })); + audioPlayer->shuffle().connect(std::bind(&PlayQueue::setShuffle, playQueue, std::placeholders::_1)); + audioPlayer->loop().connect(std::bind(&PlayQueue::setLoop, playQueue, std::placeholders::_1)); + // Connect the events to the player _playQueueEvents.trackPlay.connect(std::bind([=] (Database::Track::id_type id) { - audioPlayer->loadTrack(id); + std::size_t pos = playQueue->addTrack(id); + playQueue->play(pos); }, std::placeholders::_1)); + + _playQueueEvents.trackAdd.connect(std::bind([=] (Database::Track::id_type id) + { + playQueue->addTrack(id); + }, std::placeholders::_1)); + + } } // namespace Mobile diff --git a/src/ui/audio/mobile/MobilePlayQueue.cpp b/src/ui/audio/mobile/MobilePlayQueue.cpp new file mode 100644 index 00000000..e74ddb9a --- /dev/null +++ b/src/ui/audio/mobile/MobilePlayQueue.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2016 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include +#include +#include +#include + +#include "logger/Logger.hpp" + +#include "LmsApplication.hpp" + +#include "MobilePlayQueue.hpp" + + +namespace UserInterface { +namespace Mobile { + + +PlayQueue::PlayQueue(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + Wt::WTemplate* t = new Wt::WTemplate(this); + t->setTemplateText(Wt::WString::tr("wa-playqueue-view")); + + _trackContainer = new Wt::WContainerWidget(); + t->bindWidget("contents", _trackContainer); + + Wt::WTemplate* controls = new Wt::WTemplate(); + t->bindWidget("controls", controls); + controls->setTemplateText(Wt::WString::tr("wa-playqueue-controls")); + + Wt::WText *loadBtn = new Wt::WText("", Wt::XHTMLText); + controls->bindWidget("load", loadBtn); + loadBtn->setStyleClass("mobile-btn"); + + Wt::WText *saveBtn = new Wt::WText("", Wt::XHTMLText); + controls->bindWidget("save", saveBtn); + saveBtn->setStyleClass("mobile-btn"); + + Wt::WText *clearBtn = new Wt::WText("", Wt::XHTMLText); + controls->bindWidget("clear", clearBtn); + clearBtn->setStyleClass("mobile-btn"); + clearBtn->clicked().connect(std::bind([=] + { + clear(); + })); + + _shuffleBtn = new Wt::WText("", Wt::XHTMLText); + controls->bindWidget("shuffle", _shuffleBtn); + _shuffleBtn->setStyleClass("mobile-btn"); + _shuffleBtn->clicked().connect(std::bind([=] () + { + setShuffle(!_shuffle); + })); + + _loopBtn = new Wt::WText("", Wt::XHTMLText); + controls->bindWidget("repeat", _loopBtn); + _loopBtn->setStyleClass("mobile-btn"); + _loopBtn->clicked().connect(std::bind([=] () + { + setLoop(!_loop); + })); + +} + +std::size_t +PlayQueue::addTrack(Database::Track::id_type id) +{ + Wt::Dbo::Transaction transaction(DboSession()); + + Database::Track::pointer track = Database::Track::getById(DboSession(), id); + if (!track) + { + LMS_LOG(UI, INFO) << "No track found for id " << id; + return 0; + } + + std::size_t trackPos = _trackIds.size(); + _trackIds.push_back(id); + + Wt::WTemplate* t = new Wt::WTemplate(_trackContainer); + t->setTemplateText(Wt::WString::tr("wa-playqueue-track")); + + Wt::WImage *cover = new Wt::WImage(); + t->bindWidget("cover", cover); + cover->setStyleClass ("center-block img-responsive"); + cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); + t->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + t->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText); + + Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); + t->bindWidget("play-btn", playBtn); + playBtn->addStyleClass("mobile-btn"); + playBtn->clicked().connect(std::bind([=] + { + int pos = _trackContainer->indexOf(t); + play(pos); + })); + + Wt::WText *delBtn = new Wt::WText("", Wt::XHTMLText); + t->bindWidget("del-btn", delBtn); + delBtn->addStyleClass("mobile-btn"); + delBtn->clicked().connect(std::bind([=] + { + int pos = _trackContainer->indexOf(t); + _trackContainer->removeWidget(t); + _trackIds.erase(_trackIds.begin() + pos); + })); + + return trackPos; +} + +void +PlayQueue::clear(void) +{ + _trackIds.clear(); + _trackContainer->clear(); + _currentPos = 0; +} + +void +PlayQueue::play(size_t pos) +{ + if (_trackIds.empty() || pos >= _trackIds.size()) + return; + + _trackContainer->widget(_currentPos)->removeStyleClass("playqueue-playing"); + + _currentPos = pos; + _trackContainer->widget(_currentPos)->addStyleClass("playqueue-playing"); + + _sigTrackPlay.emit(_trackIds[pos]); +} + +void +PlayQueue::playNext(void) +{ + if (_currentPos == (_trackIds.size() - 1) && _loop) + play(0); + else + play(_currentPos + 1); +} + +void +PlayQueue::playPrevious(void) +{ + if (_currentPos == 0) + { + if (_loop) + { + play(_trackIds.size() - 1); + } + else + return; + } + else + play(_currentPos - 1); +} + +void +PlayQueue::handlePlaybackComplete(void) +{ + playNext(); +} + +void +PlayQueue::setShuffle(bool value) +{ + _shuffle = value; + + if (value) + _shuffleBtn->addStyleClass("mobile-btn-active"); + else + _shuffleBtn->removeStyleClass("mobile-btn-active"); +} + +void +PlayQueue::setLoop(bool value) +{ + _loop = value; + + if (value) + _loopBtn->addStyleClass("mobile-btn-active"); + else + _loopBtn->removeStyleClass("mobile-btn-active"); +} + +} // namespace Mobile +} // namespace UserInterface diff --git a/src/ui/audio/mobile/MobilePlayQueue.hpp b/src/ui/audio/mobile/MobilePlayQueue.hpp new file mode 100644 index 00000000..ab8a1dfc --- /dev/null +++ b/src/ui/audio/mobile/MobilePlayQueue.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +#include +#include + +#include "database/Types.hpp" + +namespace UserInterface { +namespace Mobile { + +class PlayQueue : public Wt::WContainerWidget +{ + public: + PlayQueue(Wt::WContainerWidget* parent = 0); + + // Contents + std::size_t addArtist(Database::Artist::id_type id); + std::size_t addRelease(Database::Release::id_type id); + std::size_t addTrack(Database::Track::id_type id); + void clear(void); + + // Controls + void play(size_t pos); // Play the track at a given position + void playNext(void); + void playPrevious(void); + + void setShuffle(bool enable); // true to enable shuffle + void setLoop(bool enable); // true to enable loop + + // Signals + // Emitted when a song has to be played + Wt::Signal& playTrack() { return _sigTrackPlay; } + + // Slots + // Song has finished + void handlePlaybackComplete(void); + + private: + + bool _loop = false; + bool _shuffle = false; + std::size_t _currentPos = 0; + + Wt::WText* _shuffleBtn = nullptr; + Wt::WText* _loopBtn = nullptr; + + Wt::Signal _sigTrackPlay; + + std::vector _trackIds; + Wt::WContainerWidget* _trackContainer; +}; + +} // namespace Mobile +} // namespace UserInterface diff --git a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp b/src/ui/audio/mobile/MobilePlayQueueEvents.hpp index 9fb80bf4..61775642 100644 --- a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp +++ b/src/ui/audio/mobile/MobilePlayQueueEvents.hpp @@ -28,6 +28,7 @@ namespace Mobile { struct PlayQueueEvents { Wt::Signal trackPlay; + Wt::Signal trackAdd; }; diff --git a/src/ui/audio/mobile/ReleaseView.cpp b/src/ui/audio/mobile/ReleaseView.cpp index a7342f8d..27a6350c 100644 --- a/src/ui/audio/mobile/ReleaseView.cpp +++ b/src/ui/audio/mobile/ReleaseView.cpp @@ -177,13 +177,23 @@ ReleaseView::addResults(size_t nb) trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); // TODO, display artist name for compilation releases? - Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText); - playBtn->setStyleClass("center-block"); // TODO move to CSS? + Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); + trackRes->bindWidget("play-btn", playBtn); + playBtn->setStyleClass("mobile-btn"); playBtn->clicked().connect(std::bind([=] { _events.trackPlay.emit(track.id()); })); - trackRes->bindWidget("play-btn", playBtn); + Wt::WText *addBtn = new Wt::WText("", Wt::XHTMLText); + trackRes->bindWidget("add-btn", addBtn); + addBtn->setStyleClass("mobile-btn"); + addBtn->clicked().connect(std::bind([=] { + _events.trackAdd.emit(track.id()); + })); + + + + _nbTracks++; } diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp index 504ca3b9..79a2db33 100644 --- a/src/ui/audio/mobile/TrackSearch.cpp +++ b/src/ui/audio/mobile/TrackSearch.cpp @@ -89,19 +89,27 @@ TrackSearch::addResults(size_t nb) res->setTemplateText(Wt::WString::tr("wa-track-search-res")); Wt::WImage *cover = new Wt::WImage(); + res->bindWidget("cover", cover); cover->setStyleClass ("center-block img-responsive"); cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); - res->bindWidget("cover", cover); res->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); res->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText); - Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText); - playBtn->setStyleClass("center-block"); // TODO move to CSS? + Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); + res->bindWidget("play-btn", playBtn); + playBtn->setStyleClass("mobile-btn"); playBtn->clicked().connect(std::bind([=] { _events.trackPlay.emit(track.id()); })); - res->bindWidget("btn", playBtn); + Wt::WText *addBtn = new Wt::WText("", Wt::XHTMLText); + res->bindWidget("add-btn", addBtn); + addBtn->setStyleClass("mobile-btn"); + addBtn->clicked().connect(std::bind([=] { + _events.trackAdd.emit(track.id()); + })); + + } if (moreResults)