diff --git a/db/collectionDB.cpp b/db/collectionDB.cpp index 8a60197..2e154b2 100644 --- a/db/collectionDB.cpp +++ b/db/collectionDB.cpp @@ -1,1098 +1,1098 @@ /* Babe - tiny music player Copyright (C) 2017 Camilo Higuita This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "collectionDB.h" #include #include #include #include "../utils/babeconsole.h" using namespace BAE; CollectionDB::CollectionDB(QObject *parent) : QObject(parent) { // QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() // { // this->m_db.close(); // this->instance->deleteLater(); // this->instance = nullptr; // }); this->name = QUuid::createUuid().toString(); if(!BAE::fileExists(BAE::CollectionDBPath + BAE::DBName)) { QDir collectionDBPath_dir(BAE::CollectionDBPath); if (!collectionDBPath_dir.exists()) collectionDBPath_dir.mkpath("."); this->openDB(this->name); qDebug()<<"Collection doesn't exists, trying to create it" << BAE::CollectionDBPath + BAE::DBName; this->prepareCollectionDB(); }else this->openDB(this->name); } CollectionDB::~CollectionDB() { qDebug()<< "DELETING COLLECTIONDB SINGLETON"; } CollectionDB *CollectionDB::instance = nullptr; CollectionDB *CollectionDB::getInstance() { if(!instance) { instance = new CollectionDB(); qDebug() << "getInstance(): First DBActions instance\n"; return instance; } else { qDebug()<< "getInstance(): previous DBActions instance\n"; return instance; } } void CollectionDB::deleteInstance() { delete this; } void CollectionDB::prepareCollectionDB() { QSqlQuery query(this->m_db); QFile file(":/db/script.sql"); qDebug()<< file.exists(); if (!file.exists()) { QString log = QStringLiteral("Fatal error on build database. The file '"); log.append(file.fileName() + QStringLiteral("' for database and tables creation query cannot be not found!")); qDebug()<getQuery(queryStr); if (!query.exec()) { qDebug()<m_db); query.prepare(sqlQueryString); int k = 0; foreach (const QVariant &value, values) query.bindValue(k++, value); return query.exec(); } bool CollectionDB::update(const QString &tableName, const FMH::MODEL &updateData, const QVariantMap &where) { if (tableName.isEmpty()) { qDebug()<getQuery(sqlQueryString); qDebug()<execQuery(query); } bool CollectionDB::update(const QString &table, const QString &column, const QVariant &newValue, const QVariant &op, const QString &id) { const auto queryStr = QString("UPDATE %1 SET %2 = \"%3\" WHERE %4 = \"%5\"").arg(table, column, newValue.toString().replace("\"","\"\""), op.toString(), id); auto query = this->getQuery(queryStr); return query.exec(); } bool CollectionDB::remove(const QString &table, const QString &column, const QVariantMap &where) { return false;} bool CollectionDB::execQuery(QSqlQuery &query) const { if(query.exec()) return true; qDebug()<<"ERROR ON EXEC QUERY"; qDebug()<getQuery(queryTxt); - return query.exec(); + return this->execQuery(query); } void CollectionDB::openDB(const QString &name) { if(!QSqlDatabase::contains(name)) { this->m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), name); this->m_db.setDatabaseName(BAE::CollectionDBPath + BAE::DBName); } if (!this->m_db.isOpen()) { if(!this->m_db.open()) qDebug()<<"ERROR OPENING DB"<m_db.lastError().text()<execQuery("PRAGMA journal_mode=WAL"); } } } bool CollectionDB::addTrack(const FMH::MODEL &track) { auto url = track[FMH::MODEL_KEY::URL]; auto title = track[FMH::MODEL_KEY::TITLE]; auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; auto genre = track[FMH::MODEL_KEY::GENRE]; auto year = track[FMH::MODEL_KEY::RELEASEDATE]; auto sourceUrl = track[FMH::MODEL_KEY::SOURCE]; auto duration = track[FMH::MODEL_KEY::DURATION]; auto fav = track[FMH::MODEL_KEY::FAV]; auto trackNumber = track[FMH::MODEL_KEY::TRACK]; auto artwork = track[FMH::MODEL_KEY::ARTWORK].isEmpty() ? "" : track[FMH::MODEL_KEY::ARTWORK]; auto artistTrack = track; artistTrack[FMH::MODEL_KEY::ARTWORK] = ""; BAE::artworkCache(artistTrack, FMH::MODEL_KEY::ARTIST); auto artistArtwork = artistTrack[FMH::MODEL_KEY::ARTWORK]; /* first needs to insert the source, album and artist*/ QVariantMap sourceMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], sourceUrl}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCETYPE], sourceType(url)}}; insert(TABLEMAP[BAE::TABLE::SOURCES], sourceMap); QVariantMap artistMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], artistArtwork}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], ""}}; insert(TABLEMAP[TABLE::ARTISTS],artistMap); QVariantMap albumMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], artwork}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI],""}}; insert(TABLEMAP[TABLE::ALBUMS],albumMap); QVariantMap trackMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCE], sourceUrl}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TRACK], trackNumber}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TITLE], title}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album}, {FMH::MODEL_NAME[FMH::MODEL_KEY::DURATION], duration}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], 0}, {FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], fav}, {FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], 0}, {FMH::MODEL_NAME[FMH::MODEL_KEY::RELEASEDATE], year}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()}, {FMH::MODEL_NAME[FMH::MODEL_KEY::LYRICS],""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::GENRE], genre}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], ""}}; return this->insert(BAE::TABLEMAP[BAE::TABLE::TRACKS], trackMap); } bool CollectionDB::updateTrack(const FMH::MODEL &track) { if(this->check_existance(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], track[FMH::MODEL_KEY::URL])) { QVariantMap artistMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], track[FMH::MODEL_KEY::ARTIST]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI],""}}; insert(TABLEMAP[TABLE::ARTISTS],artistMap); QVariantMap albumMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], track[FMH::MODEL_KEY::ALBUM]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], track[FMH::MODEL_KEY::ARTIST]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI],""}}; insert(TABLEMAP[TABLE::ALBUMS],albumMap); QVariantMap condition {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], track[FMH::MODEL_KEY::URL]}}; if(this->update(TABLEMAP[TABLE::TRACKS], track, condition)) if(cleanAlbums()) cleanArtists(); return true; } return false; } bool CollectionDB::rateTrack(const QString &path, const int &value) { if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path)) return true; return false; } bool CollectionDB::colorTagTrack(const QString &path, const QString &value) { if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path)) return true; return false; } bool CollectionDB::lyricsTrack(const FMH::MODEL &track, const QString &value) { if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::LYRICS], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], track[FMH::MODEL_KEY::URL])) return true; return false; } bool CollectionDB::tagsTrack(const FMH::MODEL &track, const QString &value, const QString &context) { auto url = track[FMH::MODEL_KEY::URL]; qDebug()<<"INSERTIN TRACK TAG"<getDBData(queryTxt); if(result.isEmpty()) return false; auto oldAlbum = result.first(); QVariantMap albumMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM],value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], oldAlbum[FMH::MODEL_KEY::ARTIST]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], oldAlbum[FMH::MODEL_KEY::ARTWORK]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], oldAlbum[FMH::MODEL_KEY::WIKI]}}; if (!insert(TABLEMAP[TABLE::ALBUMS],albumMap)) return false; // update albums SET album = "newalbumname" WHERE album = "albumname" NAD artist = "aretist name"; queryTxt = QString("UPDATE %1 SET %2 = %3 AND %4 = %5 WHERE %2 = %6 AND %4 = %5").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM],value, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], oldAlbum[FMH::MODEL_KEY::ARTIST], oldAlbum[FMH::MODEL_KEY::ALBUM]); auto query = this->getQuery(queryTxt); if(!execQuery(query)) return false; queryTxt = QString("DELETE FROM %1 WHERE %2 = %3 AND %4 = %5").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], oldAlbum[FMH::MODEL_KEY::ALBUM], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); query.prepare(queryTxt); if(!execQuery(query)) return false; return true; } bool CollectionDB::playedTrack(const QString &url, const int &increment) { auto queryTxt = QString("UPDATE %1 SET %2 = %2 + %3 WHERE %4 = \"%5\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], QString::number(increment), FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url); auto query = this->getQuery(queryTxt); if(query.exec()) return true; return false; } bool CollectionDB::wikiTrack(const FMH::MODEL &track, const QString &value) { auto url = track[FMH::MODEL_KEY::URL]; if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url)) return true; return false; } bool CollectionDB::wikiArtist(const FMH::MODEL &track, const QString &value) { auto artist = track[FMH::MODEL_KEY::ARTIST]; if(update(TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], value, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist)) return true; return false; } bool CollectionDB::tagsArtist(const FMH::MODEL &track, const QString &value, const QString &context) { auto artist = track[FMH::MODEL_KEY::ARTIST]; QVariantMap tagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT], context}}; QVariantMap artistTagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT], context}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}}; insert(TABLEMAP[TABLE::TAGS],tagMap); insert(TABLEMAP[TABLE::ARTISTS_TAGS],artistTagMap); return true; } bool CollectionDB::wikiAlbum(const FMH::MODEL &track, QString value) { auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; auto queryStr = QString("UPDATE %1 SET %2 = \"%3\" WHERE %4 = \"%5\" AND %6 = \"%7\"").arg( TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], value.replace("\"","\"\""), FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album,FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); qDebug()<getQuery(queryStr); return query.exec(); } bool CollectionDB::tagsAlbum(const FMH::MODEL &track, const QString &value, const QString &context) { auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; QVariantMap tagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG],value},{FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT],context}}; QVariantMap albumsTagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG],value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT],context}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST],artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM],album}}; insert(TABLEMAP[TABLE::TAGS],tagMap); insert(TABLEMAP[TABLE::ALBUMS_TAGS],albumsTagMap); return true; } bool CollectionDB::addPlaylist(const QString &title) { if(!title.isEmpty()) { QVariantMap playlist {{FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], title}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()}}; if(insert(TABLEMAP[TABLE::PLAYLISTS],playlist)) return true; } return false; } bool CollectionDB::trackPlaylist(const QString &url, const QString &playlist) { QVariantMap map {{FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::URL],url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE],QDate::currentDate()}}; if(insert(TABLEMAP[TABLE::TRACKS_PLAYLISTS],map)) return true; return false; } bool CollectionDB::addFolder(const QString &url) { QVariantMap map {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL],url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE],QDateTime::currentDateTime()}}; if(insert(TABLEMAP[TABLE::FOLDERS],map)) return true; return false; } FMH::MODEL_LIST CollectionDB::getDBData(const QStringList &urls) { FMH::MODEL_LIST mapList; for(const auto &url : urls) { const auto queryTxt = QString("SELECT * FROM %1 t INNER JOIN albums a on a.album = t.album and a.artist = t.artist WHERE t.%2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url); mapList << this->getDBData(queryTxt); } return mapList; } FMH::MODEL_LIST CollectionDB::getDBData(const QString &queryTxt, std::function modifier) { FMH::MODEL_LIST mapList; auto query = this->getQuery(queryTxt); if(query.exec()) { while(query.next()) { FMH::MODEL data; for(const auto &key : FMH::MODEL_NAME.keys()) if(query.record().indexOf(FMH::MODEL_NAME[key]) > -1) data.insert(key, query.value(FMH::MODEL_NAME[key]).toString()); if(modifier) modifier(data); mapList << data; } }else qDebug()<< query.lastError()<< query.lastQuery(); return mapList; } QStringList CollectionDB::dataToList(const FMH::MODEL_LIST &list, const FMH::MODEL_KEY &key) { QStringList res; if(list.isEmpty()) return res; for(const auto &item : list) res << item[key]; return res; } FMH::MODEL_LIST CollectionDB::getAlbumTracks(const QString &album, const QString &artist, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt = QString("SELECT * FROM %1 WHERE %2 = \"%3\" AND %4 = \"%5\" ORDER by %6 %7").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist, FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getArtistTracks(const QString &artist, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt = QString("SELECT * FROM %1 WHERE %2 = \"%3\" ORDER by %4 %5, %6 %5").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist, FMH::MODEL_NAME[orderBy], SLANG[order], FMH::MODEL_NAME[FMH::MODEL_KEY::TRACK]); return this->getDBData(queryTxt); } QStringList CollectionDB::getArtistAlbums(const QString &artist) { QStringList albums; const auto queryTxt = QString("SELECT %4 FROM %1 WHERE %2 = \"%3\" ORDER BY %4 ASC").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist, FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM]); auto query = this->getDBData(queryTxt); for(const auto &track : query) albums << track[FMH::MODEL_KEY::ALBUM]; return albums; } FMH::MODEL_LIST CollectionDB::getBabedTracks(const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt = QString("SELECT * FROM %1 WHERE %2 = 1 ORDER by %3 %4").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getSearchedTracks(const FMH::MODEL_KEY &where, const QString &search) { QString queryTxt; if(where == FMH::MODEL_KEY::COUNT || where == FMH::MODEL_KEY::RATE || where == FMH::MODEL_KEY::FAV) queryTxt = QString("SELECT t.*, al.artwork FROM %1 t inner join albums al on al.album = t.album and t.artist = al.artist WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[where], search); else if(where == FMH::MODEL_KEY::WIKI) queryTxt = QString("SELECT DISTINCT t.*, al.artwork FROM %1 t INNER JOIN %2 al ON t.%3 = al.%3 INNER JOIN %4 ar ON t.%5 = ar.%5 WHERE al.%6 LIKE \"%%7%\" OR ar.%6 LIKE \"%%7%\" COLLATE NOCASE").arg(TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], FMH::MODEL_NAME[where], search); else if(where == FMH::MODEL_KEY::PLAYLIST) queryTxt = QString("SELECT t.* FROM %1 t INNER JOIN %2 tp ON t.%3 = tp.%3 WHERE tp.%4 = \"%5\"").arg(TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], FMH::MODEL_NAME[where], search); else if(where == FMH::MODEL_KEY::TAG) queryTxt = QString("SELECT t.* FROM %1 t INNER JOIN %2 tt ON t.%3 = tt.%3 WHERE tt.%4 = \"%5\" COLLATE NOCASE " "UNION " "SELECT t.* FROM %1 t INNER JOIN %6 at ON t.%7 = at.%7 AND t.%8 = at.%8 WHERE at.%4 = \"%5\" COLLATE NOCASE " "UNION " "SELECT t.* FROM %1 t INNER JOIN %9 art ON t.%8 = art.%8 WHERE art.%4 LIKE \"%%5%\" COLLATE NOCASE " "UNION " "SELECT DISTINCT t.* FROM %1 t INNER JOIN %9 at ON t.%8 = at.%4 WHERE at.%8 LIKE \"%%5%\" COLLATE NOCASE").arg( TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::TRACKS_TAGS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], FMH::MODEL_NAME[where], search, TABLEMAP[TABLE::ALBUMS_TAGS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], TABLEMAP[TABLE::ARTISTS_TAGS]); // else if(where == FMH::MODEL_KEY::SQL) // queryTxt = search; else queryTxt = QString("SELECT t.*, al.artwork FROM %1 t inner join albums al on al.album = t.album and t.artist = al.artist WHERE t.%2 LIKE \"%%3%\" ORDER BY strftime(\"%s\", t.addDate) desc LIMIT 1000").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[where], search); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getPlaylistTracks(const QString &playlist, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt= QString("SELECT t.* FROM %1 t INNER JOIN %2 tp ON t.%3 = tp.%3 WHERE tp.%4 = '%5' ORDER BY tp.%6 %7").arg(TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], playlist, FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getFavTracks(const int &stars, const int &limit, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt= QString("SELECT * FROM %1 WHERE %2 >= %3 ORDER BY %4 %5 LIMIT %6" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], QString::number(stars), FMH::MODEL_NAME[orderBy],SLANG[order],QString::number(limit)); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getRecentTracks(const int &limit, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt= QString("SELECT * FROM %1 ORDER BY strftime(\"%s\",%2) %3 LIMIT %4" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[orderBy],SLANG[order],QString::number(limit)); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getOnlineTracks(const FMH::MODEL_KEY &orderBy, const BAE::W &order) { const auto queryTxt= QString("SELECT * FROM %1 WHERE %2 LIKE \"%3%\" ORDER BY %4 %5" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], YoutubeCachePath, FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } QStringList CollectionDB::getSourcesFolders() { const auto data = this->getDBData("select * from folders order by strftime(\"%s\", addDate) desc"); return this->dataToList(data, FMH::MODEL_KEY::URL); } FMH::MODEL_LIST CollectionDB::getMostPlayedTracks(const int &greaterThan, const int &limit, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt = QString("SELECT * FROM %1 WHERE %2 > %3 ORDER BY %4 %5 LIMIT %6" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], QString::number(greaterThan), FMH::MODEL_NAME[orderBy], SLANG[order], QString::number(limit)); return this->getDBData(queryTxt); } QString CollectionDB::trackColorTag(const QString &path) { QString color; auto query = this->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path)); for(auto track : query) color = track[FMH::MODEL_KEY::COLOR]; return color; } QStringList CollectionDB::getTrackTags(const QString &path) { Q_UNUSED(path); return {}; } int CollectionDB::getTrackStars(const QString &path) { int stars = 0; auto query = this->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path)); for(auto track : query) stars = track[FMH::MODEL_KEY::RATE].toInt(); return stars; } //QStringList CollectionDB::getArtistTags(const QString &artist) //{ // QStringList tags; // auto queryStr = QString("SELECT at.%1 FROM %2 at " // "INNER JOIN %3 ta ON ta.%1 = at.%1 " // "WHERE ta.%4 = '%5' " // "AND at.%6 = \"%7\"").arg(FMH::MODEL_NAME[KEY::TAG], // TABLEMAP[TABLE::ARTISTS_TAGS], // TABLEMAP[TABLE::TAGS], // FMH::MODEL_NAME[KEY::CONTEXT], // PULPO::CONTEXT_MAP[PULPO::CONTEXT::ARTIST_SIMILAR], // FMH::MODEL_NAME[KEY::ARTIST],artist); // auto query = this->getDBData(queryStr); // for(auto track : query) // tags << track[KEY::TAG]; // return tags; //} //QStringList CollectionDB::getAlbumTags(const QString &album, const QString &artist) //{ // QStringList tags; // auto queryStr = QString("SELECT at.%1 FROM %2 at " // "INNER JOIN %3 ta ON ta.%1 = at.%1 " // "WHERE ta.%4 = '%5' AND at.%6 = \"%7\" AND at.%8 = \"%9\"").arg(FMH::MODEL_NAME[KEY::TAG], // TABLEMAP[TABLE::ALBUMS_TAGS], // TABLEMAP[TABLE::TAGS], // FMH::MODEL_NAME[KEY::CONTEXT], // PULPO::CONTEXT_MAP[PULPO::CONTEXT::TAG], // FMH::MODEL_NAME[KEY::ALBUM],album, // FMH::MODEL_NAME[KEY::ARTIST],artist); // auto query = this->getDBData(queryStr); // for(auto track : query) // tags << track[KEY::TAG]; // return tags; //} QStringList CollectionDB::getPlaylistsList() { QStringList playlists; auto queryTxt = QString("SELECT %1, %2 FROM %3 ORDER BY %2 DESC").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], TABLEMAP[TABLE::PLAYLISTS]); for(auto data : this->getDBData(queryTxt)) playlists << data[FMH::MODEL_KEY::PLAYLIST]; return playlists; } FMH::MODEL_LIST CollectionDB::getPlaylists() { auto queryTxt = QString("SELECT %1, %2 FROM %3 ORDER BY %2 DESC").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], TABLEMAP[TABLE::PLAYLISTS]); return this->getDBData(queryTxt); } bool CollectionDB::removeTrack(const QString &path) { auto queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path); auto query = this->getQuery(queryTxt); if(query.exec()) { if(cleanAlbums()) cleanArtists(); return true; } return false; } QSqlQuery CollectionDB::getQuery(const QString &queryTxt) { return QSqlQuery(queryTxt, this->m_db); } bool CollectionDB::removeSource(const QString &url) { const auto path = url.endsWith("/") ? url.chopped(1) : url; auto queryTxt = QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg(TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path); qDebug() << queryTxt; auto query = this->getQuery(queryTxt); if(query.exec()) { queryTxt = QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCE], path); query.prepare(queryTxt); if(query.exec()) { queryTxt = QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg(TABLEMAP[TABLE::SOURCES], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path); query.prepare(queryTxt); if(query.exec()) { this->removeFolder(path); if(cleanAlbums()) cleanArtists(); return true; } } } return false; } sourceTypes CollectionDB::sourceType(const QString &url) { /*for now*/ Q_UNUSED(url); return sourceTypes::LOCAL; } /*******************OLD STUFF********************/ void CollectionDB::insertArtwork(const FMH::MODEL &track) { auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album =track[FMH::MODEL_KEY::ALBUM]; auto path = track[FMH::MODEL_KEY::ARTWORK]; switch(BAE::albumType(track)) { case TABLE::ALBUMS : { auto queryStr = QString("UPDATE %1 SET %2 = \"%3\" WHERE %4 = \"%5\" AND %6 = \"%7\"").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], path.isEmpty() ? SLANG[W::NONE] : path, FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); auto query = this->getQuery(queryStr); if(!query.exec())qDebug()<<"COULDNT Artwork[cover] inerted into DB"<getQuery(queryStr); if(!query.exec())qDebug()<<"COULDNT Artwork[head] inerted into DB"<getQuery(queryTxt); return query.exec(); } bool CollectionDB::removePlaylist(const QString &playlist) { QString queryTxt; queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist); auto query = this->getQuery(queryTxt); if(!query.exec()) return false; queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist); query.prepare(queryTxt); return query.exec(); } void CollectionDB::removeMissingTracks() { auto tracks = this->getDBData("select url from tracks"); for(auto track : tracks) if(!BAE::fileExists(track[FMH::MODEL_KEY::URL])) this->removeTrack(track[FMH::MODEL_KEY::URL]); } bool CollectionDB::removeArtwork(const QString &table, const QVariantMap &item) { return this->update(table, {{FMH::MODEL_KEY::ARTWORK, ""}}, item); } bool CollectionDB::removeArtist(const QString &artist) { const auto queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\" ").arg(TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST],artist); auto query = this->getQuery(queryTxt); return query.exec(); } bool CollectionDB::cleanArtists() { // delete from artists where artist in (select artist from artists except select distinct artist from tracks); const auto queryTxt=QString("DELETE FROM %1 WHERE %2 IN (SELECT %2 FROM %1 EXCEPT SELECT DISTINCT %2 FROM %3)").arg( TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], TABLEMAP[TABLE::TRACKS] ); qDebug()<getQuery(queryTxt); emit this->artistsCleaned(query.numRowsAffected()); return query.exec(); } bool CollectionDB::removeFolder(const QString &url) { const auto queryTxt=QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg( TABLEMAP[TABLE::FOLDERS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url); qDebug()<getQuery(queryTxt); return query.exec(); } bool CollectionDB::favTrack(const QString &path, const bool &value) { if(this->update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], value ? 1 : 0, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path)) return true; return false; } bool CollectionDB::removeAlbum(const QString &album, const QString &artist) { const auto queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\" AND %4 = \"%5\"").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); auto query = this->getQuery(queryTxt); return query.exec(); } bool CollectionDB::cleanAlbums() { // delete from albums where (album, artist) in (select a.album, a.artist from albums a except select distinct album, artist from tracks); const auto queryTxt=QString("DELETE FROM %1 WHERE (%2, %3) IN (SELECT %2, %3 FROM %1 EXCEPT SELECT DISTINCT %2, %3 FROM %4)").arg( TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], TABLEMAP[TABLE::TRACKS] ); qDebug()<getQuery(queryTxt); emit albumsCleaned(query.numRowsAffected()); return query.exec(); } diff --git a/main.qml b/main.qml index e190c47..e9f9027 100644 --- a/main.qml +++ b/main.qml @@ -1,983 +1,984 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import QtQuick.Controls.Material 2.1 import "utils" import "widgets" import "widgets/PlaylistsView" import "widgets/MainPlaylist" import "widgets/SettingsView" import "widgets/SearchView" import "widgets/CloudView" import "view_models" import "view_models/BabeTable" import "services/local" import "services/web" //import "services/web/Spotify" import "view_models/BabeGrid" import "widgets/InfoView" import "db/Queries.js" as Q import "utils/Help.js" as H import "utils/Player.js" as Player import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import Player 1.0 import AlbumsList 1.0 import TracksList 1.0 import TracksList 1.0 Maui.ApplicationWindow { id: root title: qsTr("vvave") /***************************************************/ /******************** ALIASES ********************/ /*************************************************/ property alias mainPlaylist: mainPlaylist property alias selectionBar: _selectionBar property alias progressBar: progressBar property alias dialog : _dialogLoader.item Maui.App.iconName: "qrc:/assets/vvave.svg" Maui.App.description: qsTr("VVAVE will handle your whole music collection by retreaving semantic information from the web. Just relax, enjoy and discover your new music ") /***************************************************/ /******************** PLAYBACK ********************/ /*************************************************/ property bool isShuffle: Maui.FM.loadSettings("SHUFFLE","PLAYBACK", false) property var currentTrack: ({ fav: "0", stars: "0" }) property int currentTrackIndex: -1 property int prevTrackIndex: 0 property string currentArtwork: !mainlistEmpty ? mainPlaylist.list.get(0).artwork : "" property bool currentBabe: currentTrack.fav == "0" ? false : true property alias durationTimeLabel: player.duration property string progressTimeLabel: player.transformTime((player.duration/1000) *(player.pos/ 1000)) property alias isPlaying: player.playing property int onQueue: 0 property bool mainlistEmpty: !mainPlaylist.table.count > 0 /***************************************************/ /******************** HANDLERS ********************/ /*************************************************/ readonly property var viewsIndex: ({ tracks: 0, cloud : 1, albums: 2, artists: 3, playlists: 4, folders: 5, youtube: 6, search: 7}) property string syncPlaylist: "" property bool sync: false property string infoMsg: "" property bool infoLabels: Maui.FM.loadSettings("LABELS", "PLAYBACK", false) == "true" ? true : false property bool focusView : false property bool selectionMode : false /***************************************************/ /******************** UI COLORS *******************/ /*************************************************/ readonly property color babeColor: "#f84172" /*SIGNALS*/ signal missingAlert(var track) /*HANDLE EVENTS*/ onClosing: Player.savePlaylist() onMissingAlert: { var message = qsTr("Missing file...") var messageBody = track.title + " by " + track.artist + " is missing.\nDo you want to remove it from your collection?" notify("dialog-question", message, messageBody, function () { mainPlaylist.list.remove(mainPlaylist.table.currentIndex) }) } /*COMPONENTS*/ Player { id: player volume: 100 onFinishedChanged: if (!mainlistEmpty) { if (currentTrack.url) mainPlaylist.list.countUp(currentTrackIndex) Player.nextTrack() } } FloatingDisk { id: _floatingDisk opacity: 1 - _drawer.position } headBar.middleContent : Maui.ActionGroup { id: _actionGroup Layout.fillWidth: true Layout.fillHeight: true Layout.minimumWidth: implicitWidth currentIndex : swipeView.currentIndex hiddenActions: [ Action { text: qsTr("Folders") icon.name: "folder" }, Action { text: qsTr("YouTube") icon.name: "internet-services" } ] Action { icon.name: "view-media-track" text: qsTr("Tracks") } Action { text: qsTr("Cloud") icon.name: "folder-cloud" } Action { text: qsTr("Albums") icon.name: /*"album"*/ "view-media-album-cover" } Action { text: qsTr("Artists") icon.name: "view-media-artist" } Action { text: qsTr("Playlists") icon.name: "view-media-playlist" } } onSearchButtonClicked: { _actionGroup.currentIndex = viewsIndex.search searchView.searchInput.forceActiveFocus() } Loader { id: _dialogLoader } InfoView { id: infoView maxWidth: parent.width * 0.8 maxHeight: parent.height * 0.9 } Loader { id: _focusViewLoader + focus: true source: focusView ? "widgets/FocusView.qml" : "" } Component { id: _shareDialogComponent Maui.ShareDialog {} } Component { id: _fmDialogComponent Maui.FileDialog { } } SourcesDialog { id: sourcesDialog } mainMenu: [ // Maui.MenuItem // { // text: "Vvave Stream" // icon.name: "headphones" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.vvave // } // }, // Maui.MenuItem // { // text: qsTr("Linking") // icon.name: "view-links" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.linking // if(!isLinked) linkingView.linkingConf.open() // } // }, // Maui.MenuItem // { // text: qsTr("Cloud") // icon.name: "folder-cloud" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.cloud // } // }, // Maui.MenuItem // { // text: qsTr("Spotify") // icon.name: "internet-services" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.spotify // } // }, MenuSeparator{}, MenuItem { text: qsTr("Sources") icon.name: "folder-add" onTriggered: sourcesDialog.open() }, MenuItem { text: qsTr("Open") icon.name: "folder-add" onTriggered: { _dialogLoader.sourceComponent = _fmDialogComponent root.dialog.settings.onlyDirs = false root.dialog.settings.filterType = Maui.FMList.AUDIO root.dialog.show(function(paths) { vvave.openUrls(paths) root.dialog.close() }) } }/*, Menu { title: qsTr("Collection") // icon.name: "settings-configure" MenuItem { text: qsTr("Re-Scan") onTriggered: bae.refreshCollection(); } MenuItem { text: qsTr("Refresh...") onTriggered: H.refreshCollection(); } MenuItem { text: qsTr("Clean") onTriggered: bae.removeMissingTracks(); } }*/ // Maui.Menu // { // title: qsTr("Settings...") // // Kirigami.Action // // { // // text: "Brainz" // // Kirigami.Action // // { // // id: brainzToggle // // text: checked ? "Turn OFF" : "Turn ON" // // checked: bae.brainzState() // // checkable: true // // onToggled: // // { // // checked = !checked // // bae.saveSetting("AUTO", checked, "BRAINZ") // //// bae.brainz(checked) // // } // // } // // } // Maui.MenuItem // { // text: "Info label" + checked ? "ON" : "OFF" // checked: infoLabels // checkable: true // onToggled: // { // infoLabels = checked // bae.saveSetting("LABELS", infoLabels ? true : false, "PLAYBACK") // } // } // Maui.MenuItem // { // text: "Autoplay" // checked: autoplay // checkable: true // onToggled: // { // autoplay = checked // bae.saveSetting("AUTOPLAY", autoplay ? true : false, "BABE") // } // } // } ] Item { id: message visible: infoMsg.length && sync anchors.bottom: parent.bottom width: parent.width height: Maui.Style.rowHeight z: 999 Rectangle { id: infoBg anchors.fill: parent z: -999 color: "#333" opacity: 0.8 SequentialAnimation { id: animBg PropertyAnimation { target: infoBg property: "color" easing.type: Easing.InOutQuad to: babeColor duration: 250 } PropertyAnimation { target: infoBg property: "color" easing.type: Easing.InOutQuad to: "#333" duration: 500 } } } Label { id: infoTxt anchors.centerIn: parent anchors.fill: parent height: parent.height width: parent.width font.pointSize: Maui.Style.fontSizes.medium text: infoMsg horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter color: Kirigami.Theme.textColor SequentialAnimation { id: animTxt PropertyAnimation { target: infoTxt property: "color" easing.type: Easing.InOutQuad to: "white" duration: 250 } PropertyAnimation { target: infoTxt property: "color" easing.type: Easing.InOutQuad to: Kirigami.Theme.textColor duration: 500 } } } } PlaylistDialog { id: playlistDialog } sideBar: Maui.AbstractSideBar { id: _drawer width: visible ? Math.min(Kirigami.Units.gridUnit * 18, root.width) : 0 modal: !isWide height: _drawer.modal ? implicitHeight - _mainPage.footer.height : implicitHeight MainPlaylist { id: mainPlaylist anchors.fill: parent Connections { target: mainPlaylist onCoverPressed: Player.appendAll(tracks) onCoverDoubleClicked: Player.playAll(tracks) } } } Maui.Page { id: _mainPage anchors.fill: parent footer: ColumnLayout { id: _footerLayout visible: !mainlistEmpty height: visible ? Maui.Style.toolBarHeight * 1.2 : 0 width: parent.width spacing: 0 Kirigami.Separator { Layout.fillWidth: true } Slider { id: progressBar Layout.preferredHeight: Maui.Style.unit * (Kirigami.Settings.isMobile ? 6 : 8) Layout.fillWidth: true padding: 0 from: 0 to: 1000 value: player.pos spacing: 0 focus: true onMoved: { player.pos = value } background: Rectangle { implicitWidth: progressBar.width implicitHeight: progressBar.height width: progressBar.availableWidth height: implicitHeight color: "transparent" Rectangle { width: progressBar.visualPosition * parent.width height: progressBar.height color: Kirigami.Theme.highlightColor } } handle: Rectangle { x: progressBar.leftPadding + progressBar.visualPosition * (progressBar.availableWidth - width) y: -(progressBar.height * 0.8) implicitWidth: progressBar.pressed ? Maui.Style.iconSizes.medium : 0 implicitHeight: progressBar.pressed ? Maui.Style.iconSizes.medium : 0 radius: progressBar.pressed ? Maui.Style.iconSizes.medium : 0 color: Kirigami.Theme.highlightColor } } Maui.ToolBar { Layout.fillHeight: true Layout.fillWidth: true position: ToolBar.Footer middleContent: [ ToolButton { id: babeBtnIcon icon.name: "love" enabled: currentTrackIndex >= 0 icon.color: currentBabe ? babeColor : Kirigami.Theme.textColor onClicked: if (!mainlistEmpty) { mainPlaylist.list.fav(currentTrackIndex, !(mainPlaylist.list.get(currentTrackIndex).fav == "1")) currentBabe = mainPlaylist.list.get(currentTrackIndex).fav == "1" } }, ToolButton { icon.name: "media-skip-backward" icon.color: Kirigami.Theme.textColor onClicked: Player.previousTrack() onPressAndHold: Player.playAt(prevTrackIndex) }, ToolButton { id: playIcon enabled: currentTrackIndex >= 0 icon.color: Kirigami.Theme.textColor icon.name: isPlaying ? "media-playback-pause" : "media-playback-start" onClicked: player.playing = !player.playing }, ToolButton { id: nextBtn icon.color: Kirigami.Theme.textColor icon.name: "media-skip-forward" onClicked: Player.nextTrack() onPressAndHold: Player.playAt(Player.shuffle()) }, ToolButton { id: shuffleBtn icon.color: babeColor icon.name: isShuffle ? "media-playlist-shuffle" : "media-playlist-normal" onClicked: { isShuffle = !isShuffle Maui.FM.saveSettings("SHUFFLE", isShuffle, "PLAYBACK") } } ] } } ColumnLayout { anchors.fill: parent SwipeView { id: swipeView Layout.fillHeight: true Layout.fillWidth: true interactive: Kirigami.Settings.isMobile currentIndex: _actionGroup.currentIndex onCurrentIndexChanged: _actionGroup.currentIndex = currentIndex clip: true onCurrentItemChanged: currentItem.forceActiveFocus() TracksView { id: tracksView Connections { target: tracksView onRowClicked: Player.quickPlay(tracksView.list.get(index)) onQuickPlayTrack: Player.quickPlay(tracksView.list.get(index)) onAppendTrack: Player.addTrack(tracksView.list.get(index)) onPlayAll: { var query = Q.GET.allTracks mainPlaylist.list.clear() mainPlaylist.list.query = query Player.playAll() } onAppendAll: { mainPlaylist.list.appendQuery(Q.GET.allTracks) mainPlaylist.listView.positionViewAtEnd() } onQueueTrack: Player.queueTracks([tracksView.list.get(index)], index) } } CloudView { id: cloudView } AlbumsView { id: albumsView holder.emoji: "qrc:/assets/MusicBox.png" holder.isMask: false holder.title : "No Albums!" holder.body: "Add new music sources" holder.emojiSize: Maui.Style.iconSizes.huge title: count + qsTr(" albums") list.query: Albums.ALBUMS list.sortBy: Albums.ALBUM Connections { target: albumsView onRowClicked: Player.quickPlay(track) onAppendTrack: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: albumsView.populateTable(album, artist) onAlbumCoverPressedAndHold: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAll() } onPlayAll: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) query = query.arg(data.artist) mainPlaylist.list.clear() mainPlaylist.list.query = query Player.playAll() } onAppendAll: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) mainPlaylist.list.appendQuery(query) mainPlaylist.listView.positionViewAtEnd() } } } AlbumsView { id: artistsView holder.emoji: "qrc:/assets/MusicBox.png" holder.isMask: false holder.title : qsTr("No Artists!") holder.body: qsTr("Add new music sources") holder.emojiSize: Maui.Style.iconSizes.huge title: count + qsTr(" artists") list.query: Albums.ARTISTS list.sortBy: Albums.ARTIST table.list.sortBy: Tracks.NONE Connections { target: artistsView onRowClicked: Player.quickPlay(track) onAppendTrack: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: artistsView.populateTable(undefined, artist) onAlbumCoverPressedAndHold: { var query = Q.GET.artistTracks_.arg(artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAll() } onPlayAll: { var query = Q.GET.artistTracks_.arg(artist) query = query.arg(data.artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAll() } onAppendAll: { var query = Q.GET.artistTracks_.arg(artist) mainPlaylist.list.appendQuery(query) mainPlaylist.listView.positionViewAtEnd() } } } PlaylistsView { id: playlistsView Connections { target: playlistsView onRowClicked: Player.quickPlay(track) onAppendTrack: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onPlayAll: { var query = playlistsView.playlistQuery mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAll() } onAppendAll: { var query = playlistsView.playlistQuery mainPlaylist.list.appendQuery(query) mainPlaylist.listView.positionViewAtEnd() } onPlaySync: { var query = playlistsView.playlistQuery mainPlaylist.list.appendQuery(query) Player.playAll() root.sync = true root.syncPlaylist = playlist root.infoMsg = qsTr("Syncing to ") + playlist } } } FoldersView { id: foldersView Connections { target: foldersView.list onRowClicked: Player.quickPlay(foldersView.list.model.get(index)) onQuickPlayTrack: Player.quickPlay(foldersView.list.model.get(index)) onAppendTrack: Player.addTrack(foldersView.list.model.get(index)) onPlayAll: { mainPlaylist.list.clear() // mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = foldersView.list.list.query Player.playAll() } onAppendAll: { var query = foldersView.list.list.query mainPlaylist.list.appendQuery(query) mainPlaylist.listView.positionViewAtEnd() } onQueueTrack: Player.queueTracks([foldersView.list.model.get(index)], index) } } YouTube { id: youtubeView } SearchTable { id: searchView Connections { target: searchView onRowClicked: Player.quickPlay(searchView.list.get(index)) onQuickPlayTrack: Player.quickPlay(searchView.list.get(index)) onAppendTrack: Player.addTrack(searchView.list.get(index)) onPlayAll: { mainPlaylist.list.clear() var tracks = searchView.list.getAll() for(var i in tracks) Player.appendTrack(tracks[i]) Player.playAll() } onAppendAll: Player.appendAll(searchView.list.getAll()) onArtworkDoubleClicked: { var query = Q.GET.albumTracks_.arg( searchView.list.get( index).album) query = query.arg(searchView.list.get(index).artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAll() } } } } Maui.SelectionBar { id: _selectionBar property alias listView: _selectionBar.selectionList Layout.fillWidth: true Layout.margins: Maui.Style.space.big Layout.topMargin: Maui.Style.space.small Layout.bottomMargin: Maui.Style.space.big onIconClicked: _contextMenu.popup() onExitClicked: { root.selectionMode = false clear() } SelectionBarMenu { id: _contextMenu } } } } /*animations*/ /*FUNCTIONS*/ function infoMsgAnim() { animBg.running = true animTxt.running = true } function toggleMaximized() { if (root.visibility === Window.Maximized) { root.showNormal(); } else { root.showMaximized(); } } /*CONNECTIONS*/ Connections { target: vvave onRefreshTables: H.refreshCollection(size) // onRefreshTracks: H.refreshTracks() // onRefreshAlbums: H.refreshAlbums() // onRefreshArtists: H.refreshArtists() // onCoverReady: // { // root.currentArtwork = path // currentTrack.artwork = currentArtwork // mainPlaylist.list.update(currentTrack, currentTrackIndex); // } // onTrackLyricsReady: // { // console.log(lyrics) // if (url === currentTrack.url) // Player.setLyrics(lyrics) // } // onSkipTrack: Player.nextTrack() // onBabeIt: if (!mainlistEmpty) // { // mainPlaylist.list.fav(currentTrackIndex, !(mainPlaylist.list.get(currentTrackIndex).fav == "1")) // currentBabe = mainPlaylist.list.get(currentTrackIndex).fav == "1" // } onOpenFiles: { Player.appendTracksAt(tracks, 0) Player.playAt(0) } } Component.onCompleted: { if(isAndroid) { Maui.Android.statusbarColor(Kirigami.Theme.backgroundColor, true) Maui.Android.navBarColor(Kirigami.Theme.backgroundColor, true) } } } diff --git a/models/albums/albumsmodel.cpp b/models/albums/albumsmodel.cpp index 6fbab5c..8f72289 100644 --- a/models/albums/albumsmodel.cpp +++ b/models/albums/albumsmodel.cpp @@ -1,287 +1,287 @@ #include "albumsmodel.h" #include "db/collectionDB.h" #include "utils/brain.h" #include AlbumsModel::AlbumsModel(QObject *parent) : MauiList(parent), db(CollectionDB::getInstance()) {} void AlbumsModel::componentComplete() { connect(this, &AlbumsModel::queryChanged, this, &AlbumsModel::setList); } FMH::MODEL_LIST AlbumsModel::items() const { return this->list; } void AlbumsModel::setQuery(const QUERY &query) { if(this->query == query) return; this->query = query; emit this->queryChanged(); } AlbumsModel::QUERY AlbumsModel::getQuery() const { return this->query; } void AlbumsModel::setSortBy(const SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; emit this->preListChanged(); this->sortList(); emit this->postListChanged(); emit this->sortByChanged(); } AlbumsModel::SORTBY AlbumsModel::getSortBy() const { return this->sort; } void AlbumsModel::sortList() { const auto key = static_cast(this->sort); qDebug()<< "SORTING LIST BY"<< this->sort; std::sort(this->list.begin(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { const auto role = key; switch(role) { case FMH::MODEL_KEY::RELEASEDATE: { if(e1[role].toDouble() > e2[role].toDouble()) return true; break; } case FMH::MODEL_KEY::ADDDATE: { const auto date1 = QDateTime::fromString(e1[role], Qt::TextDate); const auto date2 = QDateTime::fromString(e2[role], Qt::TextDate); if(date1.secsTo(QDateTime::currentDateTime()) < date2.secsTo(QDateTime::currentDateTime())) return true; break; } case FMH::MODEL_KEY::ARTIST: case FMH::MODEL_KEY::ALBUM: { const auto str1 = QString(e1[role]).toLower(); const auto str2 = QString(e2[role]).toLower(); if(str1 < str2) return true; break; } default: if(e1[role] < e2[role]) return true; } return false; }); } void AlbumsModel::setList() { emit this->preListChanged(); QString m_Query; if(this->query == AlbumsModel::QUERY::ALBUMS) m_Query = "select * from albums order by album asc"; else if(this->query == AlbumsModel::QUERY::ARTISTS) m_Query = "select * from artists order by artist asc"; //get albums data with modifier for missing images for artworks this->list = this->db->getDBData(m_Query, [&](FMH::MODEL &item) { - if(!item[FMH::MODEL_KEY::ARTWORK].isEmpty()) + if(item[FMH::MODEL_KEY::ARTWORK].isEmpty()) return; - if(QUrl(item[FMH::MODEL_KEY::ARTWORK]).isLocalFile() && !FMH::fileExists(item[FMH::MODEL_KEY::ARTWORK])) + if(!FMH::fileExists(item[FMH::MODEL_KEY::ARTWORK])) { - this->db->removeArtwork(FMH::MODEL_NAME[static_cast(this->query)], FMH::toMap(item)); + const auto table = this->query == AlbumsModel::QUERY::ALBUMS ? "albums" : "artists"; + this->db->removeArtwork(table, FMH::toMap(item)); item[FMH::MODEL_KEY::ARTWORK] = ""; - } -}); + }}); this->sortList(); emit this->postListChanged(); +if(this->query == AlbumsModel::QUERY::ALBUMS) this->fetchInformation(); } void AlbumsModel::fetchInformation() { qDebug() << "RNUNGING BRAIN EFFORRTS"; QFutureWatcher *watcher = new QFutureWatcher; QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater); // QObject::connect(this, &AlbumsModel::destroyed, watcher, &QFutureWatcher::waitForFinished); auto func = [&, stop = bool()]() mutable { stop = false; QList requests; int index = -1; for(auto &album : this->list) { index++; if(!album[FMH::MODEL_KEY::ARTWORK].isEmpty()) continue; if(BAE::artworkCache(album, FMH::MODEL_KEY::ALBUM)) { this->db->insertArtwork(album); emit this->updateModel(index, {FMH::MODEL_KEY::ARTWORK}); continue; } PULPO::REQUEST request; request.track = album; request.ontology = this->query == AlbumsModel::QUERY::ALBUMS ? PULPO::ONTOLOGY::ALBUM : PULPO::ONTOLOGY::ARTIST; request.services = {PULPO::SERVICES::LastFm, PULPO::SERVICES::Spotify, PULPO::SERVICES::MusicBrainz}; request.info = {PULPO::INFO::ARTWORK}; request.callback = [&, index](PULPO::REQUEST request, PULPO::RESPONSES responses) { qDebug() << "DONE WITH " << request.track ; for(const auto &res : responses) { if(res.context == PULPO::CONTEXT::IMAGE && !res.value.toString().isEmpty()) { auto downloader = new FMH::Downloader; QObject::connect(downloader, &FMH::Downloader::fileSaved, [&, index, request, _downloader = std::move(downloader)](QString path) { FMH::MODEL newTrack = request.track; newTrack[FMH::MODEL_KEY::ARTWORK] = QUrl::fromLocalFile(path).toString(); this->db->insertArtwork(newTrack); -// this->updateArtwork(index, path); + // this->updateArtwork(index, path); album[FMH::MODEL_KEY::ARTWORK] = newTrack[FMH::MODEL_KEY::ARTWORK]; emit this->updateModel(index, {FMH::MODEL_KEY::ARTWORK}); _downloader->deleteLater(); }); QStringList filePathList = res.value.toString().split('/'); const auto format = "." + filePathList.at(filePathList.count() - 1).split(".").last(); QString name = !request.track[FMH::MODEL_KEY::ALBUM].isEmpty() ? request.track[FMH::MODEL_KEY::ARTIST] + "_" + request.track[FMH::MODEL_KEY::ALBUM] : request.track[FMH::MODEL_KEY::ARTIST]; name.replace("/", "-"); name.replace("&", "-"); downloader->setFile(res.value.toString(), BAE::CachePath + name + format); qDebug()<<"SAVING ARTWORK FOR: " << request.track[FMH::MODEL_KEY::ALBUM]<< BAE::CachePath + name + format; } } }; requests << request; } Pulpo pulpo; QEventLoop loop; QObject::connect(&pulpo, &Pulpo::finished, &loop, &QEventLoop::quit); - - QObject::connect(this, &AlbumsModel::destroyed, [&stop]() + QObject::connect(this, &AlbumsModel::destroyed, [&pulpo, &loop, &stop]() { qDebug()<< stop << &stop; + pulpo.disconnect(); stop = true; + if(loop.isRunning()) + loop.quit(); qDebug()<< stop << &stop; - }); for(const auto &req : requests) { - pulpo.request(req); if(stop) { qDebug()<< "TRYING EXITING THREAD LOADINFO" << loop.isRunning(); - - if(loop.isRunning()) - loop.quit(); - qDebug()<< "EXITING THREAD LOADINFO" << loop.isRunning(); break; }else { + pulpo.request(req); loop.exec(); - } } + + qDebug()<< "DISCONNET SIGNAL"; + disconnect(this, SIGNAL(destroyed())); }; QFuture t1 = QtConcurrent::run(func); watcher->setFuture(t1); } void AlbumsModel::updateArtwork(const int index, const QString &artwork) { if(index >= this->list.size() || index < 0) return; this->list[index][FMH::MODEL_KEY::ARTWORK] = artwork; emit this->updateModel(index, {FMH::MODEL_KEY::ARTWORK}); } QVariantMap AlbumsModel::get(const int &index) const { if(index >= this->list.size() || index < 0) return QVariantMap(); return FMH::toMap(this->list.at(index)); } void AlbumsModel::append(const QVariantMap &item) { if(item.isEmpty()) return; emit this->preItemAppended(); FMH::MODEL model; for(auto key : item.keys()) model.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); this->list << model; emit this->postItemAppended(); } void AlbumsModel::append(const QVariantMap &item, const int &at) { if(item.isEmpty()) return; if(at > this->list.size() || at < 0) return; qDebug()<< "trying to append at" << at << item["title"]; emit this->preItemAppendedAt(at); FMH::MODEL model; for(auto key : item.keys()) model.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); this->list.insert(at, model); emit this->postItemAppended(); } void AlbumsModel::refresh() { this->setList(); } diff --git a/pulpo/pulpo.cpp b/pulpo/pulpo.cpp index cfedb68..c42d518 100644 --- a/pulpo/pulpo.cpp +++ b/pulpo/pulpo.cpp @@ -1,86 +1,85 @@ /* Babe - tiny music player Copyright (C) 2017 Camilo Higuita This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "pulpo.h" #include "services/lastfmService.h" //#include "services/spotifyService.h" //#include "services/lyricwikiaService.h" //#include "services/geniusService.h" //#include "services/musicbrainzService.h" //#include "services/deezerService.h" //#include "qgumbodocument.h"p //#include "qgumbonode.h" Pulpo::Pulpo(QObject *parent): QObject(parent) {} Pulpo::~Pulpo() { qDebug()<< "DELETING PULPO INSTANCE"; } void Pulpo::request(const PULPO::REQUEST &request) { this->req = request; if(this->req.track.isEmpty()) { emit this->error(); return; } if(this->req.services.isEmpty()) { qWarning()<< "Please register at least one Pulpo Service"; emit this->error(); return; } this->start(); } void Pulpo::start() { for(const auto &service : this->req.services) switch (service) { case SERVICES::LastFm: { auto lastfm = new class lastfm(); - connect(lastfm, &lastfm::responseReady,[&, lastfm = std::move(lastfm)](PULPO::REQUEST request, PULPO::RESPONSES responses) + connect(lastfm, &lastfm::responseReady,[&, _lastfm = std::move(lastfm)](PULPO::REQUEST request, PULPO::RESPONSES responses) { this->passSignal(request, responses); - lastfm->deleteLater(); + _lastfm->deleteLater(); }); - lastfm->set(this->req); break; } default: continue; } } void Pulpo::passSignal(const REQUEST &request, const RESPONSES &responses) { if(request.callback) request.callback(request, responses); else emit this->infoReady(request, responses); emit this->finished(); } diff --git a/pulpo/service.cpp b/pulpo/service.cpp index 8b00655..d9379ee 100644 --- a/pulpo/service.cpp +++ b/pulpo/service.cpp @@ -1,45 +1,42 @@ #include "service.h" Service::Service(QObject *parent) : QObject(parent) { } void Service::set(const PULPO::REQUEST &request) { this->request = request; } void Service::parse(const QByteArray &array) { switch(this->request.ontology) { case PULPO::ONTOLOGY::ALBUM: this->parseAlbum(array); break; case PULPO::ONTOLOGY::ARTIST: this->parseArtist(array); break; case PULPO::ONTOLOGY::TRACK: this->parseTrack(array); break; } } void Service::retrieve(const QString &url, const QMap &headers) { if(!url.isEmpty()) { auto downloader = new FMH::Downloader; - connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) + connect(downloader, &FMH::Downloader::dataReady, [&, _downloader = std::move(downloader)](QByteArray array) { - // qDebug()<< "DATA READY << " << array; emit this->arrayReady(array); - downloader->deleteLater(); + _downloader->deleteLater(); }); - - // qDebug()<< "trying to get ASYNC DOWNLOADER for "<< url; downloader->getArray(url, headers); } } diff --git a/pulpo/services/lastfmService.h b/pulpo/services/lastfmService.h index 6e24369..79aa6d8 100644 --- a/pulpo/services/lastfmService.h +++ b/pulpo/services/lastfmService.h @@ -1,34 +1,34 @@ #ifndef LASTFMSERVICE_H #define LASTFMSERVICE_H #include #include "../service.h" class lastfm : public Service { Q_OBJECT private: const QString API = "http://ws.audioscrobbler.com/2.0/"; const QString KEY = "&api_key=ba6f0bd3c887da9101c10a50cf2af133"; void parseSimilar(); public: explicit lastfm(); - ~lastfm(); + ~lastfm() override; void set(const PULPO::REQUEST &request) override final; protected: virtual void parseArtist(const QByteArray &array) override final; virtual void parseAlbum(const QByteArray &array) override final; // virtual void parseTrack(const QByteArray &array); /*INTERNAL IMPLEMENTATION*/ }; #endif // LASTFM_H diff --git a/services/local/player.cpp b/services/local/player.cpp index 7f4c41b..bdc565e 100644 --- a/services/local/player.cpp +++ b/services/local/player.cpp @@ -1,203 +1,210 @@ #include "player.h" #include "../../utils/bae.h" #ifdef STATIC_MAUIKIT #include "mauiaccounts.h" #else #include #endif Player::Player(QObject *parent) : QObject(parent), player(new QMediaPlayer(this)), updater(new QTimer(this)) { this->player->setVolume(this->volume); connect(this->updater, &QTimer::timeout, this, &Player::update); } inline QNetworkRequest getOcsRequest(const QNetworkRequest& request) { qDebug() << Q_FUNC_INFO; + qDebug()<< "FORMING THE REQUEST" << request.url(); + // Read raw headers out of the provided request QMap rawHeaders; for (const QByteArray& headerKey : request.rawHeaderList()) { rawHeaders.insert(headerKey, request.rawHeader(headerKey)); } const auto account = FMH::toModel(MauiAccounts::instance()->getCurrentAccount()); const QString concatenated = QString("%1:%2").arg(account[FMH::MODEL_KEY::USER], account[FMH::MODEL_KEY::PASSWORD]); const QByteArray data = concatenated.toLocal8Bit().toBase64(); const QString headerData = "Basic " + data; // Construct new QNetworkRequest with prepared header values QNetworkRequest newRequest(request); newRequest.setRawHeader(QString("Authorization").toLocal8Bit(), headerData.toLocal8Bit()); newRequest.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true")); + newRequest.setRawHeader(QByteArrayLiteral("Cache-Control"), QByteArrayLiteral("public")); + newRequest.setRawHeader(QByteArrayLiteral("Content-Description"), QByteArrayLiteral("File Transfer")); + newRequest.setHeader(QNetworkRequest::ContentTypeHeader, "audio/mpeg"); + newRequest.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true); + newRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); qDebug() << "headers" << newRequest.rawHeaderList() << newRequest.url(); return newRequest; } bool Player::play() const { if(this->url.isEmpty()) return false; if(!updater->isActive()) this->updater->start(500); this->player->play(); return true; } void Player::pause() const { if(this->player->isAvailable()) this->player->pause(); } void Player::stop() { if(this->player->isAvailable()) { this->player->stop(); this->url = QString(); this->player->setMedia(QMediaContent()); } this->playing = false; emit this->playingChanged(); this->updater->stop(); this->emitState(); } void Player::emitState() { switch(this->player->state()) { case QMediaPlayer::PlayingState: this->state = Player::STATE::PLAYING; break; case QMediaPlayer::PausedState: this->state = Player::STATE::PAUSED; break; case QMediaPlayer::StoppedState: this->state = Player::STATE::STOPED; break; } emit this->stateChanged(); } QString Player::transformTime(const int &pos) { return BAE::transformTime(pos); } void Player::setUrl(const QUrl &value) { if(value == this->url) return; this->url = value; emit this->urlChanged(); this->pos = 0; emit this->posChanged(); const auto media = this->url.isLocalFile() ? QMediaContent(this->url) : QMediaContent(getOcsRequest(QNetworkRequest(this->url))); this->player->setMedia(media); this->emitState(); } QUrl Player::getUrl() const { return this->url; } void Player::setVolume(const int &value) { if(value == this->volume) return; this->volume = value; this->player->setVolume(volume); emit this->volumeChanged(); } int Player::getVolume() const { return this->volume; } int Player::getDuration() const { return static_cast(this->player->duration()); } Player::STATE Player::getState() const { return this->state; } void Player::setPlaying(const bool &value) { this->playing = value; if(this->playing) this->play(); else this->pause(); emit this->playingChanged(); this->emitState(); } bool Player::getPlaying() const { return this->playing; } bool Player::getFinished() { return this->finished; } void Player::setPos(const int &value) { this->pos = value; this->player->setPosition(this->player->duration() / 1000 * this->pos); this->emitState(); this->posChanged(); } int Player::getPos() const { return this->pos; } void Player::update() { if(this->player->isAvailable()) { this->pos = static_cast(static_cast(this->player->position())/this->player->duration()*1000);; emit this->durationChanged(); emit this->posChanged(); } if(this->player->state() == QMediaPlayer::StoppedState && this->updater->isActive() && this->player->position() == this->player->duration()) { this->finished = true; emit this->finishedChanged(); } this->emitState(); } diff --git a/view_models/BabeTable/BabeTable.qml b/view_models/BabeTable/BabeTable.qml index 06f0ae5..7e9a067 100644 --- a/view_models/BabeTable/BabeTable.qml +++ b/view_models/BabeTable/BabeTable.qml @@ -1,473 +1,495 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import TracksList 1.0 import "../../utils/Player.js" as Player import "../../utils/Help.js" as H import "../../db/Queries.js" as Q import ".." BabeList { - id: babeTableRoot + id: control // cacheBuffer : 300 property alias list : _tracksList property alias listModel : _tracksModel property alias removeDialog : _removeDialog property bool trackNumberVisible property bool coverArtVisible : false property bool allowMenu: true property bool group : false property alias contextMenu : contextMenu property alias contextMenuItems : contextMenu.contentData property alias playAllBtn : playAllBtn property alias appendBtn : appendBtn signal rowClicked(int index) signal rowDoubleClicked(int index) signal rowPressed(int index) signal quickPlayTrack(int index) signal queueTrack(int index) signal appendTrack(int index) signal artworkDoubleClicked(int index) signal playAll() signal appendAll() focus: true listView.spacing: Maui.Style.space.small * (Kirigami.Settings.isMobile ? 1.4 : 1.2) headBar.leftContent: [ ToolButton { id : playAllBtn // text: qsTr("Play all") icon.name : "media-playlist-play" onClicked: playAll() }, ToolButton { id: appendBtn // text: qsTr("Append") icon.name : "media-playlist-append"//"media-repeat-track-amarok" onClicked: appendAll() }] headBar.rightContent: [ ToolButton { icon.name: "item-select" onClicked: selectionMode = !selectionMode checkable: false checked: selectionMode }, Maui.ToolButtonMenu { id: sortBtn icon.name: "view-sort" MenuItem { text: qsTr("Title") checkable: true checked: list.sortBy === Tracks.TITLE onTriggered: list.sortBy = Tracks.TITLE autoExclusive: true } MenuItem { text: qsTr("Track") checkable: true checked: list.sortBy === Tracks.TRACK onTriggered: list.sortBy = Tracks.TRACK autoExclusive: true } MenuItem { text: qsTr("Artist") checkable: true checked: list.sortBy === Tracks.ARTIST onTriggered: list.sortBy = Tracks.ARTIST autoExclusive: true } MenuItem { text: qsTr("Album") checkable: true checked: list.sortBy === Tracks.ALBUM onTriggered: list.sortBy = Tracks.ALBUM autoExclusive: true } MenuItem { text: qsTr("Most played") checkable: true checked: list.sortBy === Tracks.COUNT onTriggered: list.sortBy = Tracks.COUNT autoExclusive: true } MenuItem { text: qsTr("Rate") checkable: true checked: list.sortBy === Tracks.RATE onTriggered: list.sortBy = Tracks.RATE autoExclusive: true } MenuItem { text: qsTr("Favorite") checkable: true checked: list.sortBy === Tracks.FAV onTriggered: list.sortBy = Tracks.FAV autoExclusive: true } MenuItem { text: qsTr("Release date") checkable: true checked: list.sortBy === Tracks.RELEASEDATE onTriggered: list.sortBy = Tracks.RELEASEDATE autoExclusive: true } MenuItem { text: qsTr("Add date") checkable: true checked: list.sortBy === Tracks.ADDDATE onTriggered: list.sortBy = Tracks.ADDDATE autoExclusive: true } MenuSeparator{} MenuItem { text: qsTr("Group") checkable: true checked: group onTriggered: { group = !group groupBy() } } + }, + + ToolButton + { + id: _filterButton + icon.name: "view-filter" + checkable: true } ] + listView.headerPositioning: ListView.PullBackHeader + + listView.header: Maui.ToolBar + { + visible: _filterButton.checked + width: control.width + position: ToolBar.Header + middleContent: Maui.TextField + { + Layout.fillWidth: true + + } + + } + Maui.Dialog { id: _removeDialog property int index title: qsTr("Remove track") message: qsTr("You can delete the file from your computer or remove it from your collection") rejectButton.text: qsTr("Delete") acceptButton.text: qsTr("Remove") page.padding: Maui.Style.space.huge onAccepted: { list.remove(listView.currentIndex) close() } onRejected: { if(Maui.FM.removeFile(list.get(index).url)) list.remove(listView.currentIndex) close() } } TableMenu { id: contextMenu MenuSeparator {} MenuItem { text: qsTr("Go to Artist") onTriggered: goToArtist() } MenuItem { text: qsTr("Go to Album") onTriggered: goToAlbum() } onFavClicked: { list.fav(listView.currentIndex, !(list.get(listView.currentIndex).fav == "1")) } onQueueClicked: Player.queueTracks([list.get(listView.currentIndex)]) onPlayClicked: quickPlayTrack(listView.currentIndex) onAppendClicked: appendTrack(listView.currentIndex) onSaveToClicked: { playlistDialog.tracks = [list.get(listView.currentIndex).url] playlistDialog.open() } onOpenWithClicked: Maui.FM.openLocation([list.get(listView.currentIndex).url]) onRemoveClicked: { _removeDialog.index= listView.currentIndex _removeDialog.open() } onRateClicked: { list.rate(listView.currentIndex, rate); } onColorClicked: { list.color(listView.currentIndex, color); } onInfoClicked: { infoView.show(list.get(listView.currentIndex)) } onCopyToClicked: { cloudView.list.upload(listView.currentIndex) } onShareClicked: { const url = list.get(listView.currentIndex).url if(isAndroid) { Maui.Android.shareDialog(url) return } _dialogLoader.sourceComponent = _shareDialogComponent root.dialog.show([url]) } } section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { id: _sectionDelegate label: section isSection: true - width: babeTableRoot.width + width: control.width Kirigami.Theme.backgroundColor: "#333" Kirigami.Theme.textColor: "#fafafa" background: Rectangle { color: Kirigami.Theme.backgroundColor } } Maui.BaseModel { id: _tracksModel list: _tracksList } Tracks { id: _tracksList - onSortByChanged: if(babeTableRoot.group) babeTableRoot.groupBy() + onSortByChanged: if(control.group) control.groupBy() } model: _tracksModel // property alias animBabe: delegate.animBabe delegate: TableDelegate { id: delegate width: listView.width number : trackNumberVisible ? true : false - coverArt : coverArtVisible ? (babeTableRoot.width > 300) : coverArtVisible + coverArt : coverArtVisible ? (control.width > 300) : coverArtVisible onPressAndHold: if(Kirigami.Settings.isMobile && allowMenu) openItemMenu(index) onRightClicked: if(allowMenu) openItemMenu(index) onLeftEmblemClicked: H.addToSelection(list.get(index)) isSelected: selectionBar.contains(model.url) onClicked: { currentIndex = index if(selectionMode) { H.addToSelection(list.get(listView.currentIndex)) return } if(Kirigami.Settings.isMobile) rowClicked(index) } onDoubleClicked: { currentIndex = index if(!Kirigami.Settings.isMobile) rowClicked(index) } onPlay: { currentIndex = index quickPlayTrack(index) } onAppend: { currentIndex = index appendTrack(index) } onArtworkCoverClicked: { currentIndex = index goToAlbum() } Connections { target: selectionBar onPathRemoved: { if(path === model.url) delegate.isSelected = false } onPathAdded: { if(path === model.url) delegate.isSelected = true } onCleared: delegate.isSelected = false } } function openItemMenu(index) { currentIndex = index contextMenu.rate = list.get(currentIndex).rate contextMenu.fav = list.get(currentIndex).fav == "1" contextMenu.popup() rowPressed(index) } function saveList() { var trackList = [] if(listView.count > 0) { for(var i = 0; i < list.count; ++i) trackList.push(list.get(i).url) playlistDialog.tracks = trackList playlistDialog.open() } } function queueList() { var trackList = [] if(listView.count > 0) { for(var i = 0; i < listView.count; ++i) trackList.push(list.get(i)) Player.queueTracks(trackList) } } function goToAlbum() { swipeView.currentIndex = viewsIndex.albums const item = list.get(listView.currentIndex) albumsView.populateTable(item.album, item.artist) contextMenu.close() } function goToArtist() { swipeView.currentIndex = viewsIndex.artists const item = list.get(listView.currentIndex) artistsView.populateTable(undefined, item.artist) contextMenu.close() } function groupBy() { var prop = "undefined" if(group) switch(list.sortBy) { case Tracks.TITLE: prop = "title" break case Tracks.ARTIST: prop = "artist" break case Tracks.ALBUM: prop = "album" break case Tracks.RATE: prop = "rate" break case Tracks.FAV: prop = "fav" break case Tracks.ADDDATE: prop = "adddate" break case Tracks.RELEASEDATE: prop = "releasedate" break; case Tracks.COUNT: prop = "count" break } section.property = prop } } diff --git a/widgets/CloudView/CloudView.qml b/widgets/CloudView/CloudView.qml index 09a85ec..101d240 100644 --- a/widgets/CloudView/CloudView.qml +++ b/widgets/CloudView/CloudView.qml @@ -1,285 +1,285 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import org.kde.mauikit 1.0 as Maui import org.kde.kirigami 2.7 as Kirigami import "../../view_models/BabeTable" import "../../view_models/BabeGrid" import "../../utils/Player.js" as Player import CloudList 1.0 Maui.Page { id: control property alias list : _cloudList Maui.BaseModel { id: _cloudModel list: _cloudList } Cloud { id: _cloudList onFileReady: Player.addTrack(track) } headBar.visible: !_listView.holder.visible headBar.leftContent: [ ToolButton { id : playAllBtn // text: qsTr("Play all") icon.name : "media-playlist-play" // onClicked: playAll() }, ToolButton { id: appendBtn // text: qsTr("Append") icon.name : "media-playlist-append"//"media-repeat-track-amarok" // onClicked: appendAll() }] headBar.rightContent: [ ToolButton { icon.name: "item-select" onClicked: selectionMode = !selectionMode checkable: false checked: selectionMode }, Maui.ToolButtonMenu { id: sortBtn icon.name: "view-sort" MenuItem { text: qsTr("Title") checkable: true checked: list.sortBy === Cloud.TITLE onTriggered: list.sortBy = Cloud.TITLE autoExclusive: true } MenuItem { text: qsTr("Track") checkable: true checked: list.sortBy === Cloud.TRACK onTriggered: list.sortBy = Cloud.TRACK autoExclusive: true } MenuItem { text: qsTr("Artist") checkable: true checked: list.sortBy === Cloud.ARTIST onTriggered: list.sortBy = Cloud.ARTIST autoExclusive: true } MenuItem { text: qsTr("Album") checkable: true checked: list.sortBy === Cloud.ALBUM onTriggered: list.sortBy = Cloud.ALBUM autoExclusive: true } MenuItem { text: qsTr("Most played") checkable: true checked: list.sortBy === Cloud.COUNT onTriggered: list.sortBy = Cloud.COUNT autoExclusive: true } MenuItem { text: qsTr("Rate") checkable: true checked: list.sortBy === Cloud.RATE onTriggered: list.sortBy = Cloud.RATE autoExclusive: true } MenuItem { text: qsTr("Favorite") checkable: true checked: list.sortBy === Cloud.FAV onTriggered: list.sortBy = Cloud.FAV autoExclusive: true } MenuItem { text: qsTr("Release date") checkable: true checked: list.sortBy === Cloud.RELEASEDATE onTriggered: list.sortBy = Cloud.RELEASEDATE autoExclusive: true } MenuItem { text: qsTr("Add date") checkable: true checked: list.sortBy === Cloud.ADDDATE onTriggered: list.sortBy = Cloud.ADDDATE autoExclusive: true } MenuSeparator{} MenuItem { text: qsTr("Group") checkable: true - checked: group +// checked: group onTriggered: group = !group } } ] Maui.ListBrowser { id: _listView anchors.fill: parent clip: true holder.visible: count === 0 holder.emoji: "qrc:/assets/dialog-information.svg" holder.title : qsTr("Opps!") holder.body: qsTr("You don't have an account set up.\nYou can set up your account now by clicking here or under the Accounts options in the main menu") Connections { target: _listView.holder onActionTriggered: { if(root.accounts) root.accounts.open() } } topMargin: Maui.Style.space.medium model: _cloudModel section.property: "artist" section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { id: _sectionDelegate label: section isSection: true width: parent.width Kirigami.Theme.backgroundColor: "#333" Kirigami.Theme.textColor: "#fafafa" background: Rectangle { color: Kirigami.Theme.backgroundColor } } listView.header: Rectangle { Kirigami.Theme.inherit: false width: parent.width height: 150 z: _listView.listView.z+999 color: Kirigami.Theme.backgroundColor visible: _headList.count > 0 ListView { id: _headList anchors.fill: parent anchors.margins: Maui.Style.space.medium spacing: Maui.Style.space.medium orientation: ListView.Horizontal model: list.artists delegate: BabeAlbum { height: 120 width: height albumRadius: Maui.Style.radiusV isCurrentItem: ListView.isCurrentItem anchors.verticalCenter: parent.verticalCenter showLabels: true label1.text: modelData.album ? modelData.album : modelData.artist label2.text: modelData.artist && modelData.album ? modelData.artist : "" image.source: modelData.artwork ? modelData.artwork : "qrc:/assets/cover.png" } } } listView.headerPositioning: ListView.PullBackHeader delegate: TableDelegate { id: delegate width: parent.width number : false coverArt : false onClicked: { _listView.currentIndex = index // if(selectionMode) // { // H.addToSelection(control.list.get(_listView.currentIndex)) // return // } list.getFileUrl(index); // if(isMobile) // rowClicked(index) } // onDoubleClicked: // { // currentIndex = index // if(!isMobile) // rowClicked(index) // } // onPlay: // { // currentIndex = index // if(Maui.FM.fileExists("file://" + _cloudList.get(index).thumbnail)) // { // quickPlayTrack(index) // }else // { // _cloudList.requestFile(index) // } // } // onArtworkCoverClicked: // { // currentIndex = index // goToAlbum() // } } } } diff --git a/widgets/FloatingDisk.qml b/widgets/FloatingDisk.qml index 5a17e54..ba9df48 100644 --- a/widgets/FloatingDisk.qml +++ b/widgets/FloatingDisk.qml @@ -1,182 +1,178 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui Item { id: control Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.Complementary visible: opacity > 0.3 height: Maui.Style.iconSizes.large * 1.2 width: height x: root.footBar.x + Maui.Style.space.medium y: parent.height - height - Maui.Style.space.medium parent: ApplicationWindow.overlay z: parent.z + 1 ToolTip.delay: 1000 ToolTip.timeout: 5000 ToolTip.visible: _mouseArea.containsMouse && !Kirigami.Settings.isMobile ToolTip.text: currentTrack.title + " - " + currentTrack.artist Connections { target: mainPlaylist.table onCountChanged: { anim.run(control.y) } } NumberAnimation on y { id: anim property int startY running: false from : control.y to: control.y - 20 duration: 250 loops: 2 onStopped: { control.y = startY } function run(y) { if(y < 10) return startY = y anim.start() anim.running = true } } MouseArea { id: _mouseArea anchors.fill: parent hoverEnabled: true drag.target: parent drag.axis: Drag.XAndYAxis drag.minimumX: 0 drag.maximumX: root.width drag.minimumY: 0 drag.maximumY: root.height - onClicked: - { - _drawer.visible = true - } + onClicked: _drawer.visible = true Rectangle { id: diskBg anchors.centerIn: parent height: parent.height width: height // border.color: Qt.tint(Kirigami.Theme.textColor, Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.7)) color: "white" radius: Math.min(width, height) Image { id: miniArtwork focus: true anchors.fill: parent anchors.margins: Maui.Style.space.tiny anchors.centerIn: parent source: { if (currentArtwork) (currentArtwork.length > 0 && currentArtwork !== "NONE") ? currentArtwork: "qrc:/assets/cover.png" else "qrc:/assets/cover.png" } fillMode: Image.PreserveAspectFit cache: false antialiasing: true layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: miniArtwork.width height: miniArtwork.height Rectangle { anchors.centerIn: parent width: miniArtwork.adapt ? miniArtwork.width : Math.min( miniArtwork.width, miniArtwork.height) height: miniArtwork.adapt ? miniArtwork.height : width radius: Math.min(width, height) } } } } } DropShadow { anchors.fill: diskBg - visible: card.visible horizontalOffset: 0 verticalOffset: 0 radius: 8.0 samples: 17 color: "#80000000" source: diskBg } } RotationAnimator on rotation { from: 0 to: 360 duration: 5000 loops: Animation.Infinite running: isPlaying } // Rectangle // { // anchors.centerIn: parent // width: parent.width * 0.5 // height: width // radius: height // color: "transparent" // ShaderEffectSource // { // anchors.fill: parent // sourceItem: root // sourceRect:Qt.rect((control.x), // (control.y), // parent.width, // parent.height) // hideSource: true // } // } } diff --git a/widgets/FocusView.qml b/widgets/FocusView.qml index 02cf1ca..6b43579 100644 --- a/widgets/FocusView.qml +++ b/widgets/FocusView.qml @@ -1,367 +1,369 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import "../utils/Player.js" as Player import QtGraphicalEffects 1.0 Rectangle { id: control visible: focusView parent: ApplicationWindow.overlay anchors.fill: parent - z: parent.z +1 + z: parent.z + 99999 color: Kirigami.Theme.backgroundColor Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.View - Component.onCompleted: forceActiveFocus() + focus: true + Component.onCompleted: + { + _drawer.close() + forceActiveFocus() + } Keys.onBackPressed: { focusView = false event.accepted = true } Shortcut { sequence: StandardKey.Back onActivated: focusView = false } - ColumnLayout { anchors.fill: parent anchors.margins: Maui.Style.space.big ListView { id: _listView Layout.fillWidth: true Layout.preferredHeight: parent.height* 0.4 orientation: ListView.Horizontal clip: true focus: true interactive: true currentIndex: currentTrackIndex spacing: Maui.Style.space.medium cacheBuffer: control.width * 1 onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Center) highlightFollowsCurrentItem: true highlightMoveDuration: 0 snapMode: ListView.SnapToOneItem model: mainPlaylist.listModel highlightRangeMode: ListView.StrictlyEnforceRange keyNavigationEnabled: true keyNavigationWraps : true onMovementEnded: { var index = indexAt(contentX, contentY) if(index !== currentTrackIndex) Player.playAt(index) } Rectangle { visible: (_listView.currentIndex > 0) && (_listView.count > 1) height: Maui.Style.iconSizes.small width : height radius: height color: Kirigami.Theme.textColor opacity: 0.4 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter } Rectangle { visible: (_listView.currentIndex < _listView.count - 1) && (_listView.count > 1) height: Maui.Style.iconSizes.small width : height radius: height color: Kirigami.Theme.textColor opacity: 0.4 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter } delegate: Item { id: _delegate height: _listView.height width: _listView.width Rectangle { id: _bg width: parent.width * 0.7 height: width anchors.centerIn: parent radius: Maui.Style.radiusV color: Kirigami.Theme.textColor } DropShadow { anchors.fill: _bg horizontalOffset: 0 verticalOffset: 0 radius: 8.0 samples: 17 color: "#80000000" source: _bg } Image { id: _image width: parent.width * 0.7 height: width anchors.centerIn: parent sourceSize.width: height sourceSize.height: height fillMode: Image.PreserveAspectFit antialiasing: false smooth: true asynchronous: true source: model.artwork ? model.artwork : "qrc:/assets/cover.png" onStatusChanged: { if (status == Image.Error) source = "qrc:/assets/cover.png"; } layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: _image.width height: _image.height Rectangle { anchors.centerIn: parent width: _image.adapt ? _image.width : Math.min(_image.width, _image.height) height: _image.adapt ? _image.height : width radius: Maui.Style.radiusV } } } } } } RowLayout { Layout.fillWidth: true Layout.preferredHeight: Maui.Style.toolBarHeight ToolButton { icon.name: "view-list-details" onClicked: focusView = false Layout.alignment: Qt.AlignCenter - } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignCenter + spacing: 0 Label { id: _label1 visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: currentTrack.title elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.big } Label { id: _label2 visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: currentTrack.artist elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.medium opacity: 0.7 } } ToolButton { icon.name: "documentinfo" onClicked: focusView = false Layout.alignment: Qt.AlignCenter } } RowLayout { Layout.fillWidth: true Label { visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: progressTimeLabel elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.medium opacity: 0.7 } Slider { id: progressBar Layout.fillWidth: true padding: 0 from: 0 to: 1000 value: player.pos spacing: 0 focus: true onMoved: { player.pos = value } } Label { visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: player.transformTime(player.duration/1000) elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.medium opacity: 0.7 } } - - Maui.ToolBar { Layout.preferredHeight: Maui.Style.toolBarHeight * 2 Layout.fillWidth: true position: ToolBar.Footer background: null middleContent: [ ToolButton { id: babeBtnIcon icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big icon.name: "love" enabled: currentTrackIndex >= 0 icon.color: currentBabe ? babeColor : Kirigami.Theme.textColor onClicked: if (!mainlistEmpty) { mainPlaylist.list.fav(currentTrackIndex, !(mainPlaylist.list.get(currentTrackIndex).fav == "1")) currentBabe = mainPlaylist.list.get(currentTrackIndex).fav == "1" } }, ToolButton { icon.name: "media-skip-backward" icon.color: Kirigami.Theme.textColor icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big onClicked: Player.previousTrack() onPressAndHold: Player.playAt(prevTrackIndex) }, ToolButton { id: playIcon icon.width: Maui.Style.iconSizes.huge icon.height: Maui.Style.iconSizes.huge enabled: currentTrackIndex >= 0 icon.color: Kirigami.Theme.textColor icon.name: isPlaying ? "media-playback-pause" : "media-playback-start" onClicked: player.playing = !player.playing }, ToolButton { id: nextBtn icon.color: Kirigami.Theme.textColor icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big icon.name: "media-skip-forward" onClicked: Player.nextTrack() onPressAndHold: Player.playAt(Player.shuffle()) }, ToolButton { id: shuffleBtn icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big icon.color: babeColor icon.name: isShuffle ? "media-playlist-shuffle" : "media-playlist-normal" onClicked: { isShuffle = !isShuffle Maui.FM.saveSettings("SHUFFLE", isShuffle, "PLAYBACK") } } ] } } }