diff --git a/approot/templates.xml b/approot/templates.xml index 1e9d1148..b0808347 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -444,4 +444,80 @@ + + + + + ${contents} + + + + + + + + ${cover} + ${name}${artist} + + + + + + + + ${contents} + + + + + + + + ${gif} + ${name} + + + + + + ${title} + ${release-container} + ${show-more} + + + + + + + + ${cover} + + ${release-name} ${artist-name} - ${year} + + + + + ${track-container} + + + + + + + ${track-pos}. ${track-name} ${btn}${time} + + diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 1bd8c8f4..70e05de6 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -117,3 +117,20 @@ div.contents { height: 48px; } +.release_res_shadow { + box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5) +} + +.release_truncated-name { + white-space: nowrap; + overflow: hidden; + overflow-x: hidden; + overflow-y: hidden; + text-overflow: ellipsis; +} + +.release_img-responsive { + display: block; + max-width: 100%; + height: auto; +} diff --git a/src/Makefile.am b/src/Makefile.am index ce6a0c12..e8304d03 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,6 @@ lms_SOURCES = \ $(srcdir)/image/Image.cpp \ $(srcdir)/logger/Logger.cpp \ $(srcdir)/metadata/AvFormat.cpp \ - $(srcdir)/metadata/Utils.cpp \ $(srcdir)/service/ServiceManager.cpp \ $(srcdir)/service/DatabaseUpdateService.cpp \ $(srcdir)/ui/LmsApplication.cpp \ @@ -34,7 +33,7 @@ lms_SOURCES = \ $(srcdir)/ui/audio/desktop/TrackView.cpp \ $(srcdir)/ui/audio/mobile/ArtistSearch.cpp \ $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ - $(srcdir)/ui/audio/mobile/MobileAudioMediaPlayer.cpp \ + $(srcdir)/ui/audio/mobile/MobileAudioMediaPlayer.cpp \ $(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \ $(srcdir)/ui/audio/mobile/TrackSearch.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ @@ -48,8 +47,9 @@ lms_SOURCES = \ $(srcdir)/ui/settings/SettingsFirstConnectionFormView.cpp \ $(srcdir)/ui/settings/SettingsMediaDirectories.cpp \ $(srcdir)/ui/settings/SettingsMediaDirectoryFormView.cpp \ - $(srcdir)/ui/settings/SettingsUserFormView.cpp \ - $(srcdir)/ui/settings/SettingsUsers.cpp + $(srcdir)/ui/settings/SettingsUserFormView.cpp \ + $(srcdir)/ui/settings/SettingsUsers.cpp \ + $(srcdir)/utils/Utils.cpp if VIDEO lms_SOURCES += \ diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 738d04f3..1cfe38ef 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -170,23 +170,25 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re { using namespace Database; - boost::filesystem::path firstTrackPath; - bool embeddedCover = false; + Wt::Dbo::Transaction transaction(session); + + // If the release does not exist or is the special release "None", do nothing + Release::pointer release = Release::getById(session, releaseId); + if (!release || release->isNone()) + return std::vector(); // Get the first track of the release - { - Wt::Dbo::Transaction transaction(session); + std::vector tracks = Track::getByFilter(session, + SearchFilter::ById(SearchFilter::Field::Release, releaseId), + -1, 1 /* limit result size */); - std::vector tracks = Track::getByFilter(session, - SearchFilter::IdMatch({{SearchFilter::Field::Release, {releaseId}}}), - -1, 1 /* limit result size */); + if (tracks.empty()) + return std::vector(); - if (tracks.empty()) - return std::vector(); + boost::filesystem::path firstTrackPath = tracks.front()->getPath(); + bool embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded); - firstTrackPath = tracks.front()->getPath(); - embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded); - } + transaction.commit(); // First, try to get covers from the directory of the release std::vector res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 5ef5415a..cb683b25 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -44,6 +44,11 @@ Release::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) return session.find().where("mbid = ?").bind(mbid); } +Release::pointer +Release::getById(Wt::Dbo::Session& session, Release::id_type id) +{ + return session.find().where("id = ?").bind(id); +} Release::pointer Release::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) @@ -51,6 +56,12 @@ Release::create(Wt::Dbo::Session& session, const std::string& name, const std::s return session.add(new Release(name, MBID)); } +bool +Release::isNone() const +{ + return _name == ""; +} + Release::pointer Release::getNone(Wt::Dbo::Session& session) { @@ -129,16 +140,4 @@ Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, return std::vector(res.begin(), res.end()); } -std::vector< Wt::Dbo::ptr > -Release::getArtists() const -{ - assert(self()); - assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); - assert(session()); - - Wt::Dbo::collection< Wt::Dbo::ptr > res = session()->query >("SELECT a FROM artist a INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.release_id = r.id").where("r.id = ?").bind(id()); - - return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); -} - } // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp index 0bb779d0..d9a4d43f 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -65,8 +65,6 @@ class Release : public Wt::Dbo::Dbo std::string getMBID() const { return _MBID; } bool isNone(void) const; boost::posix_time::time_duration getDuration(void) const; - std::vector > getArtists() const; // Get the artists of this release - std::vector > getTracks() const; // Get the tracks of this release void setMBID(std::string mbid) { _MBID = mbid; } diff --git a/src/database/SearchFilter.hpp b/src/database/SearchFilter.hpp index 2d0d397c..00b3f8fc 100644 --- a/src/database/SearchFilter.hpp +++ b/src/database/SearchFilter.hpp @@ -58,12 +58,18 @@ class SearchFilter return SearchFilter(_nameLikeMatch); } - // Single ID match + // Single Field ID match static SearchFilter ById(Field field, Wt::Dbo::dbo_default_traits::IdType id) { return SearchFilter({{field, {id} }}); } + // Single Field Name match + static SearchFilter ByName(Field field, std::vector keywords) + { + return NameLikeMatch({{{field, keywords}}}); + } + // The filter is a AND of the following conditions: // ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ... diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index 36bc6c4f..6d751cee 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -19,15 +19,12 @@ #include "AvFormat.hpp" -#include +#include "logger/Logger.hpp" +#include "utils/Utils.hpp" #include -#include #include "av/AvInfo.hpp" -#include "logger/Logger.hpp" - -#include "Utils.hpp" namespace MetaData { @@ -112,11 +109,11 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) for (it = metadata.begin(); it != metadata.end(); ++it) { if (boost::iequals(it->first, "artist")) - items.insert( std::make_pair(MetaData::Type::Artist, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(it->second)) )); else if (boost::iequals(it->first, "album")) - items.insert( std::make_pair(MetaData::Type::Album, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(it->second)) )); else if (boost::iequals(it->first, "title")) - items.insert( std::make_pair(MetaData::Type::Title, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(it->second)) )); else if (boost::iequals(it->first, "track")) { std::size_t number; if (readAs(it->second, number)) @@ -145,6 +142,7 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) } else if (boost::iequals(it->first, "genre")) { + // TODO use splitStrings std::list genres; if (readList(it->second, ";,", genres)) items.insert( std::make_pair(MetaData::Type::Genres, genres)); @@ -153,12 +151,12 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) else if (boost::iequals(it->first, "MusicBrainz Artist Id") || boost::iequals(it->first, "MUSICBRAINZ_ARTISTID")) { - items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( stringToUTF8(it->second)) )); } else if (boost::iequals(it->first, "MusicBrainz Album Id") || boost::iequals(it->first, "MUSICBRAINZ_ALBUMID")) { - items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(it->second)) )); } } diff --git a/src/metadata/AvFormat.hpp b/src/metadata/AvFormat.hpp index da5981b7..4c08d943 100644 --- a/src/metadata/AvFormat.hpp +++ b/src/metadata/AvFormat.hpp @@ -25,7 +25,7 @@ namespace MetaData { -// Implements AVFORMAT library +// Parse that makes use of AvFormat class AvFormat : public Parser { public: diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp index ebf2a432..d01a48ef 100644 --- a/src/ui/audio/desktop/TrackView.cpp +++ b/src/ui/audio/desktop/TrackView.cpp @@ -23,6 +23,7 @@ #include #include "logger/Logger.hpp" +#include "utils/Utils.hpp" #include "LmsApplication.hpp" #include "TrackView.hpp" @@ -41,18 +42,10 @@ class DurationItemDelegate : public Wt::WItemDelegate { boost::posix_time::time_duration duration = boost::any_cast(index.data(Wt::DisplayRole)); - boost::posix_time::time_facet* facet = new boost::posix_time::time_facet(); + // TODO bug when redrawn + std::string format = duration.total_seconds() < 3600 ? "%M:%S" : "%H:%M:%S"; - if (duration.total_seconds() < 3600) - facet->time_duration_format("%M:%S"); - else - facet->time_duration_format("%H:%M:%S"); - - std::ostringstream oss; - oss.imbue(std::locale(oss.getloc(), facet)); - oss << duration; - - return new Wt::WText(oss.str(), Wt::PlainText); + return new Wt::WText(durationToString(duration, format), Wt::PlainText); } }; diff --git a/src/ui/audio/mobile/ArtistSearch.cpp b/src/ui/audio/mobile/ArtistSearch.cpp index f2dc41fb..35033792 100644 --- a/src/ui/audio/mobile/ArtistSearch.cpp +++ b/src/ui/audio/mobile/ArtistSearch.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "LmsApplication.hpp" @@ -38,6 +39,11 @@ _resCount(0) title->setTemplateText(Wt::WString::tr("mobile-search-title")); title->bindWidget("text", new Wt::WText("Artists", Wt::PlainText)); + + Wt::WTemplate* artistWrapper = new Wt::WTemplate(this); + artistWrapper->setTemplateText(Wt::WString::tr("wa-artist-wrapper")); + _contents = new Wt::WContainerWidget(); + artistWrapper->bindWidget("contents", _contents ); } void @@ -75,7 +81,13 @@ ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb) for (Artist::pointer artist : artists) { Wt::WTemplate* res = new Wt::WTemplate(this); - res->setTemplateText(Wt::WString::tr("mobile-artist-res")); + res->setTemplateText(Wt::WString::tr("wa-artist-res")); + + Wt::WImage *artistImg = new Wt::WImage(); + artistImg->setStyleClass("center-block"); // TODO move in css? + artistImg->setStyleClass("release_res_shadow release_img-responsive"); // TODO move in css? + + res->bindWidget("gif", artistImg); Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist->getName()), Wt::PlainText); res->bindWidget("name", text); diff --git a/src/ui/audio/mobile/ArtistSearch.hpp b/src/ui/audio/mobile/ArtistSearch.hpp index dbe361ca..f1218c9c 100644 --- a/src/ui/audio/mobile/ArtistSearch.hpp +++ b/src/ui/audio/mobile/ArtistSearch.hpp @@ -20,7 +20,6 @@ #ifndef UI_MOBILE_ARTIST_SEARCH_HPP #define UI_MOBILE_ARTIST_SEARCH_HPP -#include #include #include "database/Types.hpp" @@ -48,6 +47,7 @@ class ArtistSearch : public Wt::WContainerWidget void clear(void); void addResults(Database::SearchFilter filter, size_t nb); + Wt::WContainerWidget* _contents; std::size_t _resCount; }; diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp index 5d910dd8..67a2b995 100644 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ b/src/ui/audio/mobile/MobileAudio.cpp @@ -17,9 +17,6 @@ * along with LMS. If not, see . */ -#include -#include -#include #include #include @@ -27,16 +24,17 @@ #include #include -#include "LmsApplication.hpp" +#include "logger/Logger.hpp" +#include "utils/Utils.hpp" -#include "MobileAudio.hpp" +#include "LmsApplication.hpp" #include "ArtistSearch.hpp" #include "ReleaseSearch.hpp" #include "TrackSearch.hpp" #include "MobileAudioMediaPlayer.hpp" -#include "logger/Logger.hpp" +#include "MobileAudio.hpp" namespace UserInterface { namespace Mobile { @@ -87,26 +85,17 @@ Audio::Audio(Wt::WContainerWidget *parent) AudioMediaPlayer* mediaPlayer = new AudioMediaPlayer(encoding); footer->bindWidget("player", mediaPlayer); - edit->changed().connect(std::bind([=] () { - std::string text = edit->text().toUTF8(); - + edit->changed().connect(std::bind([=] () + { // When a new search is done, output some results from: // Artist // Release // Song - std::vector keywords; - boost::algorithm::split(keywords, text, boost::is_any_of(" "), boost::token_compress_on); + std::vector keywords = splitStrings(edit->text().toUTF8(), " ");; - releaseSearch->search(SearchFilter::NameLikeMatch( {{ - { SearchFilter::Field::Artist, keywords }, - { SearchFilter::Field::Release, keywords }}}), - 3); - - artistSearch->search(SearchFilter::NameLikeMatch({{{SearchFilter::Field::Artist, keywords}}}), - 3); - - trackSearch->search(SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, keywords}}}), - 3); + releaseSearch->search(SearchFilter::ByName(SearchFilter::Field::Release, keywords), 4); + artistSearch->search(SearchFilter::ByName(SearchFilter::Field::Artist, keywords), 4); + trackSearch->search(SearchFilter::ByName(SearchFilter::Field::Track, keywords), 4); artistSearch->show(); releaseSearch->show(); diff --git a/src/ui/audio/mobile/ReleaseSearch.cpp b/src/ui/audio/mobile/ReleaseSearch.cpp index d9af0cab..fc9092e1 100644 --- a/src/ui/audio/mobile/ReleaseSearch.cpp +++ b/src/ui/audio/mobile/ReleaseSearch.cpp @@ -40,6 +40,11 @@ _resCount(0) title->setTemplateText(Wt::WString::tr("mobile-search-title")); title->bindWidget("text", new Wt::WText("Releases", Wt::PlainText)); + + Wt::WTemplate* releaseWrapper = new Wt::WTemplate(this); + releaseWrapper->setTemplateText(Wt::WString::tr("wa-release-wrapper")); + _contents = new Wt::WContainerWidget(); + releaseWrapper->bindWidget("contents", _contents ); } void @@ -58,9 +63,23 @@ ReleaseSearch::search(Database::SearchFilter filter, size_t max) addResults(filter, max); } +static Wt::WString +getArtistFromRelease(Release::pointer release) +{ + auto artists = Artist::getByFilter(DboSession(), + SearchFilter::ById(SearchFilter::Field::Release, release.id()), -1, 2); + + if (artists.size() > 1) + return Wt::WString::fromUTF8("Various artists", Wt::PlainText); + else + return Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText); +} + void ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb) { + using namespace Database; + Wt::Dbo::Transaction transaction(DboSession()); std::vector releases = Release::getByFilter(DboSession(), filter, _resCount, nb + 1); @@ -77,14 +96,17 @@ ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb) for (Release::pointer release : releases) { Wt::WTemplate* releaseWidget = new Wt::WTemplate(this); - releaseWidget->setTemplateText(Wt::WString::tr("mobile-release-res")); + releaseWidget->setTemplateText(Wt::WString::tr("wa-release-res")); Wt::WImage *cover = new Wt::WImage(); cover->setStyleClass("center-block"); - cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 56))); - releaseWidget->bindWidget("cover", cover); + cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 512))); + cover->setStyleClass("release_res_shadow release_img-responsive"); // TODO move? + releaseWidget->bindWidget("cover", cover); releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText)); + releaseWidget->bindString("release_name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); + releaseWidget->bindString("artist", getArtistFromRelease(release)); releaseWidget->clicked().connect(std::bind([=] { _sigReleaseSelected(release.id()); diff --git a/src/ui/audio/mobile/ReleaseSearch.hpp b/src/ui/audio/mobile/ReleaseSearch.hpp index 876c1492..7cbd0058 100644 --- a/src/ui/audio/mobile/ReleaseSearch.hpp +++ b/src/ui/audio/mobile/ReleaseSearch.hpp @@ -48,6 +48,7 @@ class ReleaseSearch : public Wt::WContainerWidget void clear(void); void addResults(Database::SearchFilter filter, size_t nb); + Wt::WContainerWidget* _contents; std::size_t _resCount; }; diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp index 0c1b9b94..10e97742 100644 --- a/src/ui/audio/mobile/TrackSearch.cpp +++ b/src/ui/audio/mobile/TrackSearch.cpp @@ -23,6 +23,7 @@ #include #include "logger/Logger.hpp" +#include "utils/Utils.hpp" #include "LmsApplication.hpp" #include "TrackSearch.hpp" @@ -33,101 +34,146 @@ namespace Mobile { using namespace Database; TrackSearch::TrackSearch(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent), -_resCount(0) +: Wt::WContainerWidget(parent) { + Wt::WTemplate* wrapper = new Wt::WTemplate(this); + wrapper->setTemplateText(Wt::WString::tr("wa-trackview-wrapper")); + Wt::WTemplate* title = new Wt::WTemplate(this); + wrapper->bindWidget("title", title); + title->setTemplateText(Wt::WString::tr("mobile-search-title")); + title->bindString("text", "Releases", Wt::PlainText); - title->bindWidget("text", new Wt::WText("Tracks", Wt::PlainText)); + _releaseContainer = new Wt::WContainerWidget(); + wrapper->bindWidget("release-container", _releaseContainer); + _showMore = new Wt::WTemplate(); + wrapper->bindWidget("show-more", _showMore); + + _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); + _showMore->bindString("text", "Tap to show more results..."); + _showMore->hide(); } void TrackSearch::clear() { - while (count() > 1) - removeWidget(this->widget(1)); + // Flush the release container + _releaseContainer->clear(); - _resCount = 0; + // Flush the current context + _currentTrackContainer = nullptr; + _nbTracks = 0; + _showMore->hide(); } void -TrackSearch::search(Database::SearchFilter filter, size_t max) +TrackSearch::search(SearchFilter filter, size_t nb) { + _filter = filter; + clear(); - addResults(filter, max); + addResults(nb); } -void -TrackSearch::addResults(Database::SearchFilter filter, size_t nb) +static +std::vector +getTracks(SearchFilter filter, size_t offset, size_t nb, bool &moreResults) { - Wt::Dbo::Transaction transaction(DboSession()); + std::vector tracks = Track::getByFilter(DboSession(), filter, offset, nb + 1); - std::vector tracks = Track::getByFilter(DboSession(), filter, _resCount, nb + 1); - - bool expectMoreResults; if (tracks.size() == nb + 1) { - expectMoreResults = true; + moreResults = true; tracks.pop_back(); } else - expectMoreResults = false; + moreResults = false; + + return tracks; +} + +static Wt::WString +getArtistNameFromRelease(Release::pointer release) +{ + auto artists = Artist::getByFilter(DboSession(), + SearchFilter::ById(SearchFilter::Field::Release, release.id()), -1, 2); + + if (artists.size() > 1) + return Wt::WString::fromUTF8("Various artists", Wt::PlainText); + else + return Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText); +} + +void +TrackSearch::addResults(size_t nb) +{ + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + std::vector tracks = getTracks(_filter, _nbTracks, nb, moreResults); for (Track::pointer track : tracks) { - Wt::WTemplate* trackWidget = new Wt::WTemplate(this); - trackWidget->setTemplateText(Wt::WString::tr("mobile-track-res")); + // First check if we need to create a new track container - Wt::WImage *cover = new Wt::WImage(); - cover->setStyleClass("center-block"); - cover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 56)) ); - trackWidget->bindWidget("cover", cover); - - // Track Name (bold) - // Artist - Album (italic) - Wt::WContainerWidget *container = new Wt::WContainerWidget(); - Wt::WText *title = new Wt::WText(Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - title->setStyleClass("mobile-track"); - container->addWidget(title); - - if (!track->getArtist()->getName().empty() - || !track->getRelease()->getName().empty()) + // New container if it is the first one or if the released has changed + if (!_currentTrackContainer + || _currentReleaseId != track->getRelease().id()) { - title->setInline(false); - Wt::WText *artistRelease = new Wt::WText(Wt::WString::fromUTF8(track->getArtist()->getName() + " - " + track->getRelease()->getName()), Wt::PlainText); - artistRelease->setStyleClass("mobile-artist"); - container->addWidget(artistRelease); - } - trackWidget->bindWidget("name", container); + Release::pointer release = track->getRelease(); - Wt::WPushButton *playBtn = new Wt::WPushButton("Play"); - playBtn->setStyleClass("btn-primary center-block"); + Wt::WTemplate *releaseContainer = new Wt::WTemplate(_releaseContainer); + releaseContainer->setTemplateText(Wt::WString::tr("wa-trackview-release-container")); + _currentReleaseId = release.id(); + + Wt::WImage *cover = new Wt::WImage(); + cover->setStyleClass ("center-block img-responsive"); // TODO move to CSS? + cover->setImageLink(Wt::WLink (LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 512))); + + releaseContainer->bindWidget("cover", cover); + releaseContainer->bindString("artist-name", getArtistNameFromRelease(release)); + releaseContainer->bindString("release-name", release->getName(), Wt::PlainText); + releaseContainer->bindInt("year", 2001); + + _currentTrackContainer = new Wt::WContainerWidget(); + releaseContainer->bindWidget("track-container", _currentTrackContainer); + } + + assert(_currentTrackContainer != nullptr); + + Wt::WTemplate* trackRes = new Wt::WTemplate(_currentTrackContainer); + trackRes->setTemplateText(Wt::WString::tr("wa-trackview-track")); + + // TODO, do not display track pos for tracks that are not part of a release + trackRes->bindInt("track-pos", track->getTrackNumber()); + trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + // TODO, display artist name for compilation releases? + + // TODO handle large duration (> 1 hour) + trackRes->bindString("time", durationToString(track->getDuration(), "%M:%S"), Wt::PlainText); + + Wt::WText *playBtn = new Wt::WText("Play", Wt::PlainText); + playBtn->setStyleClass("center-block"); // TODO move to CSS? playBtn->clicked().connect(std::bind([=] { _sigTrackPlay.emit(track.id()); })); - trackWidget->bindWidget("btn", playBtn); - + trackRes->bindWidget("btn", playBtn); + _nbTracks++; } - _resCount += tracks.size(); - - if (expectMoreResults) + if (moreResults) { - Wt::WTemplate* moreRes = new Wt::WTemplate(this); - moreRes->setTemplateText(Wt::WString::tr("mobile-search-more")); - - moreRes->bindWidget("text", new Wt::WText("Tap to show more results...")); - - moreRes->clicked().connect(std::bind([=] { + _showMore->clicked().connect(std::bind([=] { _sigMoreTracksSelected(); - removeWidget(moreRes); - - addResults(filter, 20); + addResults(20); })); + _showMore->show(); } + else + _showMore->hide(); } } // namespace Mobile diff --git a/src/ui/audio/mobile/TrackSearch.hpp b/src/ui/audio/mobile/TrackSearch.hpp index 6cf3b317..adc80189 100644 --- a/src/ui/audio/mobile/TrackSearch.hpp +++ b/src/ui/audio/mobile/TrackSearch.hpp @@ -48,9 +48,19 @@ class TrackSearch : public Wt::WContainerWidget Wt::Signal _sigMoreTracksSelected; void clear(void); - void addResults(Database::SearchFilter filter, size_t nb); + void addResults(size_t nb); - std::size_t _resCount; + Wt::WTemplate* _showMore; + + Database::SearchFilter _filter; + std::size_t _nbTracks; + + // Main container that holds the releases + Wt::WContainerWidget* _releaseContainer; + + // Used to add more results on the fly + Database::Release::id_type _currentReleaseId; + Wt::WContainerWidget* _currentTrackContainer; }; } // namespace Mobile diff --git a/src/ui/resource/CoverResource.hpp b/src/ui/resource/CoverResource.hpp index 82fad5d4..abd14cd0 100644 --- a/src/ui/resource/CoverResource.hpp +++ b/src/ui/resource/CoverResource.hpp @@ -41,6 +41,7 @@ class CoverResource : public Wt::WResource std::string getReleaseUrl(Database::Release::id_type releaseId, size_t size) const; std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const; + std::string getArtistUrl(Database::Artist::id_type artistId, size_t size) const; std::string getUnknownTrackUrl(size_t size) const; void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response); diff --git a/src/metadata/Utils.cpp b/src/utils/Utils.cpp similarity index 65% rename from src/metadata/Utils.cpp rename to src/utils/Utils.cpp index ff926363..e95ea8d0 100644 --- a/src/metadata/Utils.cpp +++ b/src/utils/Utils.cpp @@ -21,12 +21,11 @@ #include #include -#include +#include +#include #include "Utils.hpp" -namespace MetaData -{ bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time) { @@ -56,11 +55,11 @@ bool readList(const std::string& str, const std::string& separators, std::listtime_duration_format(format.c_str()); + std::ostringstream oss; + oss.imbue(std::locale(oss.getloc(), facet)); + oss << duration; + return oss.str(); } +std::vector +splitStrings(std::string string, std::string separators) +{ + std::vector res; + + boost::algorithm::split(res, string, boost::is_any_of(separators), boost::token_compress_on); + + return res; +} + +std::string +stringTrim(const std::string& str, const std::string& whitespace) +{ + const auto strBegin = str.find_first_not_of(whitespace); + if (strBegin == std::string::npos) + return ""; // no content + + const auto strEnd = str.find_last_not_of(whitespace); + const auto strRange = strEnd - strBegin + 1; + + return str.substr(strBegin, strRange); +} + +std::string +stringToUTF8(const std::string& str) +{ + return boost::locale::conv::to_utf(str, "UTF-8"); +} + + diff --git a/src/metadata/Utils.hpp b/src/utils/Utils.hpp similarity index 53% rename from src/metadata/Utils.hpp rename to src/utils/Utils.hpp index 786e1a2b..f59b840c 100644 --- a/src/metadata/Utils.hpp +++ b/src/utils/Utils.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2015 Emeric Poupon * * This file is part of LMS. * @@ -17,20 +17,32 @@ * along with LMS. If not, see . */ -#ifndef METADATA_UTILS_HPP -#define METADATA_UTILS_HPP +#pragma once #include +#include #include #include +#include -namespace MetaData -{ +bool +readAsPosixTime(const std::string& str, boost::posix_time::ptime& time); -bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time); +bool +readList(const std::string& str, const std::string& separators, std::list& results); -bool readList(const std::string& str, const std::string& separators, std::list& results); +std::string +durationToString(boost::posix_time::time_duration duration, std::string format); + +std::vector +splitStrings(std::string string, std::string separators); + +std::string +stringTrim(const std::string& str, const std::string& whitespace = " \t"); + +std::string +stringToUTF8(const std::string& str); template static inline bool readAs(const std::string& str, T& data) @@ -40,30 +52,3 @@ static inline bool readAs(const std::string& str, T& data) return !iss.fail(); } - - -std::string -static inline string_trim(const std::string& str, - const std::string& whitespace = " \t") -{ - const auto strBegin = str.find_first_not_of(whitespace); - if (strBegin == std::string::npos) - return ""; // no content - - const auto strEnd = str.find_last_not_of(whitespace); - const auto strRange = strEnd - strBegin + 1; - - return str.substr(strBegin, strRange); -} - - -std::string -static inline string_to_utf8(const std::string& str) -{ - return boost::locale::conv::to_utf(str, "UTF-8"); -} - -} // namespace MetaData - -#endif -