[UI] Added a playqueue on mobile version, minor visual changes on desktop version

This commit is contained in:
emeric
2016-04-07 13:02:51 +02:00
parent 0d8516231a
commit 4c1dcbe9de
15 changed files with 485 additions and 65 deletions
+2 -2
View File
@@ -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.
+28 -14
View File
@@ -356,15 +356,6 @@
</div>
</message>
<message id="mediaplayer-controls">
<div class="btn-group" role="group" aria-label="...">
${prev}
${play}
${pause}
${next}
</div>
</message>
<message id="mobile-search-title">
<div class="row">
<div class="col-xs-12">
@@ -426,7 +417,7 @@
</message>
<!--
wa-track_search
wa-track-search
-> wa-track-search-res
-> wa-track-search-res
-> ...
@@ -445,7 +436,7 @@
<div class="vertical-align">
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
<div class="col-xs-8">${artist-name}<br/>${track-name}</div>
<div class="col-xs-2" style="margin-top:3px"><span class="badge">${btn}</span></div>
<div class="col-xs-2">${play-btn}${add-btn}</div>
</div>
</div>
</div>
@@ -490,13 +481,36 @@
</message>
<message id="wa-trackview-track">
<div class="menu-item list-group-item ">${<if-has-disc-num>}${disc-num}-${</if-has-disc-num>}${<if-has-track-num>}${track-num}. ${</if-has-track-num>}${track-name} <span class="badge">${play-btn}</span></div>
<div class="menu-item list-group-item ">${<if-has-disc-num>}${disc-num}-${</if-has-disc-num>}${<if-has-track-num>}${track-num}. ${</if-has-track-num>}${track-name} ${play-btn}${add-btn}</div>
</message>
<!-- Playqueue view
!-->
<message id="wa-playqueue-view">
${controls}
<div class="menu-category list-group">
${contents}
</div>
</message>
<message id="wa-playqueue-controls">
${load}${save}${clear}${shuffle}${repeat}
</message>
<message id="wa-playqueue-track">
<div class="row">
<div class="vertical-align">
<div class="col-xs-2" style="margin-top:3px">${cover}</div>
<div class="col-xs-8">${artist-name}<br/>${track-name}</div>
<div class="col-xs-2" style="margin-top:3px">${play-btn}${del-btn}</div>
</div>
</div>
</message>
<message id="wa-audio-player">
${audio}
${prev}${play-pause}${next}
<div class="mediaplayer-volume-container visible-lg">
<div class="mediaplayer-volume-container hidden-xs">
${volume}
</div>
<div class="mediaplayer-cover hidden-xs">
@@ -510,7 +524,7 @@
${curtime}${seekbar}${duration}
</div>
</div>
${shuffle}${repeat}
${<if-has-shuffle>}${shuffle}${</if-has-shuffle>}${<if-has-repeat>}${repeat}${</if-has-repeat>}${<if-has-playqueue>}${playqueue}${</if-has-playqueue>}
</message>
</messages>
+23
View File
@@ -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;
}
+1
View File
@@ -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 \
+58 -30
View File
@@ -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("<i class=\"fa fa-repeat fa-2x\"></i>", 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("<i class=\"fa fa-repeat fa-2x\"></i>", 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("<i class=\"fa fa-random fa-2x\"></i>", 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("<i class=\"fa fa-list-ul fa-2x\"></i>", 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("<i class=\"fa fa-random fa-2x\"></i>", 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("<i class=\"fa fa-step-backward fa-2x\"></i>", Wt::XHTMLText);
prevBtn->addStyleClass("mediaplayer-btn hidden-xs");
prevBtn->addStyleClass("mediaplayer-btn");
bindWidget("prev", prevBtn);
prevBtn->clicked().connect(std::bind([=] ()
{
+14 -1
View File
@@ -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<void>& playPrevious() {return _playPrevious;}
Wt::Signal<bool>& shuffle() {return _shuffle;}
Wt::Signal<bool>& loop() {return _loop;}
Wt::Signal<bool>& showPlayQueue() {return _playQueue;}
private:
@@ -52,6 +61,7 @@ class AudioPlayer : public Wt::WTemplate
Wt::Signal<void> _playPrevious;
Wt::Signal<bool> _shuffle;
Wt::Signal<bool> _loop;
Wt::Signal<bool> _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
+5 -5
View File
@@ -149,16 +149,16 @@ _playQueue(nullptr)
playlistBtn->setMenu(popupMain);
}
Wt::WPushButton *upBtn = new Wt::WPushButton("UP");
Wt::WPushButton *upBtn = new Wt::WPushButton("<i class =\"fa fa-arrow-up fa-lg\"></i>", Wt::XHTMLText);
upBtn->setStyleClass("btn-sm");
playlistControls->addWidget(upBtn);
Wt::WPushButton *downBtn = new Wt::WPushButton("DO");
Wt::WPushButton *downBtn = new Wt::WPushButton("<i class =\"fa fa-arrow-down fa-lg\"></i>", Wt::XHTMLText);
downBtn->setStyleClass("btn-sm");
playlistControls->addWidget(downBtn);
Wt::WPushButton *delBtn = new Wt::WPushButton("DEL");
Wt::WPushButton *delBtn = new Wt::WPushButton("<i class =\"fa fa-remove fa-lg\"></i>", Wt::XHTMLText);
delBtn->setStyleClass("btn-sm btn-warning");
playlistControls->addWidget(delBtn);
Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR");
Wt::WPushButton *clearBtn = new Wt::WPushButton("<i class =\"fa fa-trash fa-lg\"></i>", 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);
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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);
+43 -2
View File
@@ -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
+206
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/WTemplate>
#include <Wt/WImage>
#include <Wt/WText>
#include <Wt/WPushButton>
#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("<i class=\"fa fa-upload fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("load", loadBtn);
loadBtn->setStyleClass("mobile-btn");
Wt::WText *saveBtn = new Wt::WText("<i class=\"fa fa-download fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("save", saveBtn);
saveBtn->setStyleClass("mobile-btn");
Wt::WText *clearBtn = new Wt::WText("<i class=\"fa fa-remove fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("clear", clearBtn);
clearBtn->setStyleClass("mobile-btn");
clearBtn->clicked().connect(std::bind([=]
{
clear();
}));
_shuffleBtn = new Wt::WText("<i class=\"fa fa-random fa-2x\"></i>", Wt::XHTMLText);
controls->bindWidget("shuffle", _shuffleBtn);
_shuffleBtn->setStyleClass("mobile-btn");
_shuffleBtn->clicked().connect(std::bind([=] ()
{
setShuffle(!_shuffle);
}));
_loopBtn = new Wt::WText("<i class=\"fa fa-repeat fa-2x\"></i>", 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("<i class=\"fa fa-play fa-lg\"></i>", 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("<i class=\"fa fa-remove fa-lg\"></i>", 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
+75
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#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<Database::Track::id_type>& 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<Database::Track::id_type> _sigTrackPlay;
std::vector<Database::Track::id_type> _trackIds;
Wt::WContainerWidget* _trackContainer;
};
} // namespace Mobile
} // namespace UserInterface
@@ -28,6 +28,7 @@ namespace Mobile {
struct PlayQueueEvents
{
Wt::Signal<Database::Track::id_type> trackPlay;
Wt::Signal<Database::Track::id_type> trackAdd;
};
+13 -3
View File
@@ -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("<i class=\"fa fa-play fa-lg\"></i>", 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("<i class=\"fa fa-plus fa-lg\"></i>", Wt::XHTMLText);
trackRes->bindWidget("add-btn", addBtn);
addBtn->setStyleClass("mobile-btn");
addBtn->clicked().connect(std::bind([=] {
_events.trackAdd.emit(track.id());
}));
_nbTracks++;
}
+12 -4
View File
@@ -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("<i class=\"fa fa-play fa-lg\"></i>", 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("<i class=\"fa fa-plus fa-lg\"></i>", Wt::XHTMLText);
res->bindWidget("add-btn", addBtn);
addBtn->setStyleClass("mobile-btn");
addBtn->clicked().connect(std::bind([=] {
_events.trackAdd.emit(track.id());
}));
}
if (moreResults)