diff --git a/CMakeLists.txt b/CMakeLists.txt index d8467a2..7e9c1ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,85 +1,91 @@ project(vvave) cmake_minimum_required(VERSION 3.0) find_package(ECM 1.7.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${ECM_MODULE_PATH}) set(CMAKE_CXX_STANDARD 17) find_package(MauiKit REQUIRED) find_package(Qt5 REQUIRED NO_MODULE COMPONENTS Qml Quick Network WebSockets Sql QuickControls2 Xml Multimedia Widgets DBus Svg) find_package(KF5 ${KF5_VERSION} REQUIRED COMPONENTS I18n Notifications Config KIO Attica SyntaxHighlighting) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMInstallIcons) include(FeatureSummary) include(ECMAddAppIcon) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTORCC ON) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/services/web + ${CMAKE_CURRENT_BINARY_DIR}/services/web + ) + add_executable(vvave qml.qrc main.cpp vvave.cpp # pulpo/services/geniusService.cpp # pulpo/services/deezerService.cpp pulpo/services/lastfmService.cpp # pulpo/services/lyricwikiaService.cpp # pulpo/services/spotifyService.cpp # pulpo/services/musicbrainzService.cpp pulpo/pulpo.cpp pulpo/htmlparser.cpp pulpo/service.cpp services/local/taginfo.cpp services/local/player.cpp services/local/youtubedl.cpp # services/local/linking.cpp services/local/socket.cpp services/web/youtube.cpp services/web/NextCloud/nextmusic.cpp + services/web/abstractmusicprovider.cpp # services/web/Spotify/spotify.cpp db/collectionDB.cpp # utils/brain.cpp models/tracks/tracksmodel.cpp models/playlists/playlistsmodel.cpp models/albums/albumsmodel.cpp # models/cloud/cloud.cpp ) if (ANDROID) find_package(Qt5 REQUIRED COMPONENTS AndroidExtras WebView Xml) find_package(OpenSSL REQUIRED) include(ExternalProject) externalproject_add(taglib URL http://taglib.org/releases/taglib-1.11.1.tar.gz CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} ) set(TAGLIB_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include ${CMAKE_INSTALL_PREFIX}/include/taglib) set(TAGLIB_LIBRARIES ${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/libtag.a) target_link_libraries(vvave MauiKit Qt5::AndroidExtras Qt5::WebView Qt5::Xml OpenSSL::SSL) add_dependencies(vvave taglib) kde_source_files_enable_exceptions(vvave) else() find_package(Taglib REQUIRED) find_package(Qt5 REQUIRED COMPONENTS WebEngine) target_sources(vvave PRIVATE kde/mpris2.cpp kde/notify.cpp ) target_link_libraries(vvave Qt5::WebEngine KF5::ConfigCore KF5::Notifications KF5::KIOCore KF5::I18n Qt5::DBus KF5::Attica KF5::SyntaxHighlighting) endif() target_include_directories(vvave PRIVATE ${TAGLIB_INCLUDE_DIRS}) target_link_libraries(vvave MauiKit Qt5::Network Qt5::Sql Qt5::WebSockets Qt5::Qml Qt5::Xml Qt5::Multimedia Qt5::Widgets Qt5::QuickControls2 ${TAGLIB_LIBRARIES}) install(TARGETS vvave ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES org.kde.vvave.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) #TODO: port to ecm_install_icons() install(FILES assets/vvave.svg DESTINATION ${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps) install(FILES org.kde.vvave.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/assets/Astronaut.png b/assets/Astronaut.png deleted file mode 100644 index 5940afb..0000000 Binary files a/assets/Astronaut.png and /dev/null differ diff --git a/assets/Bread.png b/assets/Bread.png deleted file mode 100644 index c43f807..0000000 Binary files a/assets/Bread.png and /dev/null differ diff --git a/assets/BugSearch.png b/assets/BugSearch.png deleted file mode 100644 index 76d9946..0000000 Binary files a/assets/BugSearch.png and /dev/null differ diff --git a/assets/ElectricPlug.png b/assets/ElectricPlug.png deleted file mode 100644 index 9d4132d..0000000 Binary files a/assets/ElectricPlug.png and /dev/null differ diff --git a/assets/Electricity.png b/assets/Electricity.png deleted file mode 100644 index 6ccdba1..0000000 Binary files a/assets/Electricity.png and /dev/null differ diff --git a/assets/MusicBox.png b/assets/MusicBox.png deleted file mode 100644 index 74ed2ca..0000000 Binary files a/assets/MusicBox.png and /dev/null differ diff --git a/assets/MusicCloud.png b/assets/MusicCloud.png deleted file mode 100644 index da5123d..0000000 Binary files a/assets/MusicCloud.png and /dev/null differ diff --git a/assets/Radio.png b/assets/Radio.png deleted file mode 100644 index 8e4bee3..0000000 Binary files a/assets/Radio.png and /dev/null differ diff --git a/assets/USB.png b/assets/USB.png deleted file mode 100644 index ca60807..0000000 Binary files a/assets/USB.png and /dev/null differ diff --git a/assets/Video tutorial.png b/assets/Video tutorial.png deleted file mode 100644 index f461706..0000000 Binary files a/assets/Video tutorial.png and /dev/null differ diff --git a/models/tracks/tracksmodel.cpp b/models/tracks/tracksmodel.cpp index 573a58d..a67f52d 100644 --- a/models/tracks/tracksmodel.cpp +++ b/models/tracks/tracksmodel.cpp @@ -1,343 +1,348 @@ #include "tracksmodel.h" #include "db/collectionDB.h" +#include "NextCloud/nextmusic.h" TracksModel::TracksModel(QObject *parent) : MauiList(parent), db(CollectionDB::getInstance()) {} void TracksModel::componentComplete() { connect(this, &TracksModel::queryChanged, this, &TracksModel::setList); } FMH::MODEL_LIST TracksModel::items() const { return this->list; } void TracksModel::setQuery(const QString &query) { if(this->query == query) return; this->query = query; emit this->queryChanged(); } QString TracksModel::getQuery() const { return this->query; } void TracksModel::setSortBy(const SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; emit this->preListChanged(); this->sortList(); emit this->postListChanged(); emit this->sortByChanged(); } TracksModel::SORTBY TracksModel::getSortBy() const { return this->sort; } void TracksModel::sortList() { if(this->sort == TracksModel::SORTBY::NONE) return; const auto key = static_cast(this->sort); qSort(this->list.begin(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { switch(key) { case FMH::MODEL_KEY::RELEASEDATE: case FMH::MODEL_KEY::RATE: case FMH::MODEL_KEY::FAV: case FMH::MODEL_KEY::COUNT: { if(e1[key].toInt() > e2[key].toInt()) return true; break; } case FMH::MODEL_KEY::TRACK: { if(e1[key].toInt() < e2[key].toInt()) return true; break; } case FMH::MODEL_KEY::ADDDATE: { auto currentTime = QDateTime::currentDateTime(); auto date1 = QDateTime::fromString(e1[key], Qt::TextDate); auto date2 = QDateTime::fromString(e2[key], Qt::TextDate); if(date1.secsTo(currentTime) < date2.secsTo(currentTime)) return true; break; } case FMH::MODEL_KEY::TITLE: case FMH::MODEL_KEY::ARTIST: case FMH::MODEL_KEY::ALBUM: case FMH::MODEL_KEY::FORMAT: { const auto str1 = QString(e1[key]).toLower(); const auto str2 = QString(e2[key]).toLower(); if(str1 < str2) return true; break; } default: if(e1[key] < e2[key]) return true; } return false; }); } void TracksModel::setList() { - emit this->preListChanged(); - this->list = this->db->getDBData(this->query); - this->sortList(); - emit this->postListChanged(); + auto provider = new NextMusic(this); + + provider->setCredentials({{FMH::MODEL_KEY::SERVER, "https://cloud.opendesktop.cc/remote.php/webdav/"},{FMH::MODEL_KEY::USER, "mauitest"},{FMH::MODEL_KEY::PASSWORD, "mauitest"}}); + provider->getCollection(); +// emit this->preListChanged(); +// this->list = this->db->getDBData(this->query); +// this->sortList(); +// emit this->postListChanged(); } QVariantMap TracksModel::get(const int &index) const { if(index >= this->list.size() || index < 0) return QVariantMap(); return FMH::toMap(this->list.at(index)); } QVariantList TracksModel::getAll() { QVariantList res; for(const auto &item : this->list) res << FMH::toMap(item); return res; } void TracksModel::append(const QVariantMap &item) { if(item.isEmpty()) return; emit this->preItemAppended(); this->list << FMH::toModel(item); emit this->postItemAppended(); } void TracksModel::append(const QVariantMap &item, const int &at) { if(item.isEmpty()) return; if(at > this->list.size() || at < 0) return; emit this->preItemAppendedAt(at); this->list.insert(at, FMH::toModel(item)); emit this->postItemAppended(); } void TracksModel::appendQuery(const QString &query) { emit this->preListChanged(); this->list << this->db->getDBData(query); emit this->postListChanged(); } void TracksModel::searchQueries(const QStringList &queries) { emit this->preListChanged(); this->list.clear(); bool hasKey = false; for(auto searchQuery : queries) { if(searchQuery.contains(BAE::SearchTMap[BAE::SearchT::LIKE]+":") || searchQuery.startsWith("#")) { if(searchQuery.startsWith("#")) searchQuery = searchQuery.replace("#","").trimmed(); else searchQuery = searchQuery.replace(BAE::SearchTMap[BAE::SearchT::LIKE]+":","").trimmed(); searchQuery = searchQuery.trimmed(); if(!searchQuery.isEmpty()) { this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::WIKI, searchQuery); this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::TAG, searchQuery); this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::LYRICS, searchQuery); } }else if(searchQuery.contains((BAE::SearchTMap[BAE::SearchT::SIMILAR]+":"))) { searchQuery=searchQuery.replace(BAE::SearchTMap[BAE::SearchT::SIMILAR]+":","").trimmed(); searchQuery=searchQuery.trimmed(); if(!searchQuery.isEmpty()) this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::TAG, searchQuery); }else { FMH::MODEL_KEY key; QHashIterator k(FMH::MODEL_NAME); while (k.hasNext()) { k.next(); if(searchQuery.contains(QString(k.value()+":"))) { hasKey=true; key=k.key(); searchQuery = searchQuery.replace(k.value()+":","").trimmed(); } } searchQuery = searchQuery.trimmed(); if(!searchQuery.isEmpty()) { if(hasKey) this->list << this->db->getSearchedTracks(key, searchQuery); else { auto queryTxt = QString("SELECT t.*, al.artwork FROM tracks t INNER JOIN albums al ON t.album = al.album AND t.artist = al.artist WHERE t.title LIKE \"%"+searchQuery+"%\" OR t.artist LIKE \"%"+searchQuery+"%\" OR t.album LIKE \"%"+searchQuery+"%\"OR t.genre LIKE \"%"+searchQuery+"%\"OR t.url LIKE \"%"+searchQuery+"%\" ORDER BY strftime(\"%s\", t.addDate) desc LIMIT 1000"); this->list << this->db->getDBData(queryTxt); } } } } emit this->postListChanged(); } void TracksModel::clear() { emit this->preListChanged(); this->list.clear(); emit this->postListChanged(); } bool TracksModel::color(const int &index, const QString &color) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->colorTagTrack(item[FMH::MODEL_KEY::URL], color)) { this->list[index][FMH::MODEL_KEY::COLOR] = color; emit this->updateModel(index, {FMH::MODEL_KEY::COLOR}); return true; } return false; } bool TracksModel::fav(const int &index, const bool &value) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->favTrack(item[FMH::MODEL_KEY::URL], value)) { this->list[index][FMH::MODEL_KEY::FAV] = value ? "1" : "0"; emit this->updateModel(index, {FMH::MODEL_KEY::FAV}); qDebug()<< "FAVVING TRACKS"<< item; return true; } return false; } bool TracksModel::rate(const int &index, const int &value) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->rateTrack(item[FMH::MODEL_KEY::URL], value)) { this->list[index][FMH::MODEL_KEY::RATE] = QString::number(value); emit this->updateModel(index, {FMH::MODEL_KEY::RATE}); return true; } return false; } bool TracksModel::countUp(const int &index) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->playedTrack(item[FMH::MODEL_KEY::URL])) { this->list[index][FMH::MODEL_KEY::COUNT] = QString::number(item[FMH::MODEL_KEY::COUNT].toInt() + 1); emit this->updateModel(index, {FMH::MODEL_KEY::COUNT}); return true; } return false; } bool TracksModel::remove(const int &index) { if(index >= this->list.size() || index < 0) return false; emit this->preItemRemoved(index); this->list.removeAt(index); emit this->postItemRemoved(); return true; } void TracksModel::refresh() { this->setList(); } bool TracksModel::update(const QVariantMap &data, const int &index) { if(index >= this->list.size() || index < 0) return false; auto newData = this->list[index]; QVector roles; for(auto key : data.keys()) if(newData[FMH::MODEL_NAME_KEY[key]] != data[key].toString()) { newData.insert(FMH::MODEL_NAME_KEY[key], data[key].toString()); roles << FMH::MODEL_NAME_KEY[key]; } this->list[index] = newData; emit this->updateModel(index, roles); return true; } diff --git a/qml.qrc b/qml.qrc index 3e63a4d..02d8207 100644 --- a/qml.qrc +++ b/qml.qrc @@ -1,72 +1,62 @@ main.qml widgets/TracksView.qml widgets/AlbumsView.qml assets/cover.png data_models/db_model.qml db/script.sql utils/Player.js db/Queries.js utils/Help.js assets/bars.gif view_models/BabeGrid/BabeAlbum.qml view_models/BabeGrid/BabeGrid.qml view_models/BabeTable/TableDelegate.qml view_models/BabeTable/BabeTable.qml view_models/BabeTable/TableMenu.qml view_models/ColorTagsBar.qml widgets/MainPlaylist/MainPlaylist.qml widgets/MainPlaylist/PlaylistMenu.qml widgets/PlaylistsView/PlaylistsView.qml widgets/InfoView/InfoView.qml widgets/InfoView/LyricsView.qml widgets/PlaylistsView/PlaylistsViewModel.qml utils/Props.js widgets/SettingsView/SourcesDialog.qml assets/banner.png assets/banner.svg view_models/BabeTable/PlaylistDialog.qml view_models/BabeList.qml assets/banner-yellow.png widgets/SearchView/SearchTable.qml widgets/SearchView/SearchSuggestions.qml services/web/YouTube.qml services/web/YoutubeViewer.qml services/web/YoutubePlayer.qml services/web/YoutubeHelper.js services/web/YoutubePlayer_A.qml services/local/LinkingDialog.qml services/local/LinkingView.qml services/local/LinkingListModel.qml assets/vvave.notifyrc assets/vvave.png widgets/MainPlaylist/AlbumsRoll.qml assets/heart_indicator.gif assets/heart_indicator_white.gif services/web/Spotify/Spotify.qml services/web/Spotify/LoginForm.qml services/web/Spotify/spotify.html widgets/FloatingDisk.qml services/web/WebView.qml services/web/WebView_A.qml - assets/Bread.png - assets/ElectricPlug.png - assets/Electricity.png - assets/USB.png - assets/Radio.png - assets/MusicBox.png - assets/Video tutorial.png - assets/BugSearch.png - assets/Astronaut.png - assets/MusicCloud.png widgets/FoldersView.qml assets/vvave.svg assets/materialdesignicons-webfont.ttf widgets/CloudView/CloudView.qml widgets/SelectionBarMenu.qml db/script.sql diff --git a/services/local/player.cpp b/services/local/player.cpp index a356726..1e979c0 100644 --- a/services/local/player.cpp +++ b/services/local/player.cpp @@ -1,233 +1,232 @@ #include "player.h" #include "../../utils/bae.h" Player::Player(QObject *parent) : QObject(parent), player(new QMediaPlayer(this)), updater(new QTimer(this)) { this->buffer = new QBuffer(this->player); // connect(this->player, &QMediaPlayer::durationChanged, this, [&](qint64 dur) // { // emit this->durationChanged(/*BAE::transformTime(dur/1000)*/); // }); this->player->setVolume(this->volume); connect(this->updater, &QTimer::timeout, this, &Player::update); } inline QNetworkRequest getOcsRequest(const QNetworkRequest& request) { qDebug() << Q_FUNC_INFO; // Read raw headers out of the provided request QMap rawHeaders; for (const QByteArray& headerKey : request.rawHeaderList()) { rawHeaders.insert(headerKey, request.rawHeader(headerKey)); } - const QString concatenated = "mauitest:mauitest"; 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")); 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::appendBuffe(QByteArray &array) { qDebug()<<"APENDING TO BUFFER"<< array << this->array; this->array.append(array, array.length()); amountBuffers++; if(amountBuffers == 1) playBuffer(); } void Player::playRemote(const QString &url) { qDebug()<<"Trying to play remote"<url = url; this->player->setMedia(QUrl::fromUserInput(url)); this->play(); } 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::playBuffer() { buffer->setData(array); buffer->open(QIODevice::ReadOnly); if(!buffer->isReadable()) qDebug()<<"Cannot read buffer"; player->setMedia(QMediaContent(),buffer); this->url = "buffer"; this->play(); this->emitState(); } void Player::update() { if(this->player->isAvailable()) { this->pos = static_cast(static_cast(this->player->position())/this->player->duration()*1000);; 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/services/web/NextCloud/nextmusic.cpp b/services/web/NextCloud/nextmusic.cpp index 0991d51..c72b91d 100644 --- a/services/web/NextCloud/nextmusic.cpp +++ b/services/web/NextCloud/nextmusic.cpp @@ -1,6 +1,141 @@ #include "nextmusic.h" +#include +#include +#include +#include +#include -NextMusic::NextMusic(QObject *parent) : QObject(parent) +#ifdef STATIC_MAUIKIT +#include "fm.h" +#else +#include +#endif + +static const inline QNetworkRequest formRequest(const QUrl &url, const QString &user, const QString &password) +{ + if(!url.isValid() && !user.isEmpty() && !password.isEmpty()) + return QNetworkRequest(); + + const QString concatenated = QString("%1:%2").arg(user, password); + const QByteArray data = concatenated.toLocal8Bit().toBase64(); + const QString headerData = "Basic " + data; + + + // Construct new QNetworkRequest with prepared header values + QNetworkRequest newRequest(url); + + newRequest.setRawHeader(QString("Authorization").toLocal8Bit(), headerData.toLocal8Bit()); +// newRequest.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true")); + + + qDebug() << "headers" << newRequest.rawHeaderList() << newRequest.url(); + + return newRequest; +} + +const QString NextMusic::API = QStringLiteral("https://PROVIDER/index.php/apps/music/api/"); + +NextMusic::NextMusic(QObject *parent) : AbstractMusicProvider(parent) {} + +FMH::MODEL_LIST NextMusic::parseResponse(const QByteArray &array) +{ + FMH::MODEL_LIST res; + qDebug()<< "trying to parse array" << array; + QJsonParseError jsonParseError; + QJsonDocument jsonResponse = QJsonDocument::fromJson(static_cast(array).toUtf8(), &jsonParseError); + + if (jsonParseError.error != QJsonParseError::NoError) + { + qDebug()<< "ERROR PARSING"; + return res; + } + + const auto data = jsonResponse.toVariant(); + + if(data.isNull() || !data.isValid()) + return res; + + const auto list = data.toList(); + + if(!list.isEmpty()) + { + for(const auto &map : list) + res << FMH::toModel(map.toMap()); + } + else + res << FMH::toModel(data.toMap()); + + return res; +} + +void NextMusic::getCollection(const std::initializer_list ¶meters) +{ + auto url = QString(NextMusic::API).replace("PROVIDER", this->m_provider).append("collection"); + + qDebug()<< "QQQQQQQQQQ" << url; + QString concatenated = this->m_user + ":" + this->m_password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; + + QMap header {{"Authorization", headerData.toLocal8Bit()}}; + + const auto downloader = new FMH::Downloader; + connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) + { + qDebug()<< array; + this->parseResponse(array); + }); + + downloader->getArray(url, header); +} + +void NextMusic::getTracks() +{ + +} + +void NextMusic::getTrack(const QString &id) { } + +void NextMusic::getArtists() +{ + +} + +void NextMusic::getArtist(const QString &id) +{ + +} + +void NextMusic::getAlbums() +{ + +} + +void NextMusic::getAlbum(const QString &id) +{ + +} + +void NextMusic::getPlaylists() +{ + +} + +void NextMusic::getPlaylist(const QString &id) +{ + +} + +void NextMusic::getFolders() +{ + +} + +void NextMusic::getFolder(const QString &id) +{ + +} + diff --git a/services/web/NextCloud/nextmusic.h b/services/web/NextCloud/nextmusic.h index 6ceec51..e38becf 100644 --- a/services/web/NextCloud/nextmusic.h +++ b/services/web/NextCloud/nextmusic.h @@ -1,17 +1,38 @@ #ifndef NEXTMUSIC_H #define NEXTMUSIC_H #include +#include "abstractmusicprovider.h" -class NextMusic : public QObject +class NextMusic : public AbstractMusicProvider { Q_OBJECT public: - explicit NextMusic(QObject *parent = nullptr); + explicit NextMusic(QObject *parent = nullptr); + +private: + const static QString API; + static const QString formatUrl(const QString &user, const QString &password, const QString &provider); + + FMH::MODEL_LIST parseResponse(const QByteArray &array); signals: public slots: + + // AbstractMusicProvider interface +public: + void getCollection(const std::initializer_list ¶meters = {}) override final; + void getTracks() override final; + void getTrack(const QString &id) override final; + void getArtists() override final; + void getArtist(const QString &id) override final; + void getAlbums() override final; + void getAlbum(const QString &id) override final; + void getPlaylists() override final; + void getPlaylist(const QString &id) override final; + void getFolders() override final; + void getFolder(const QString &id) override final; }; #endif // NEXTMUSIC_H diff --git a/services/web/abstractmusicprovider.cpp b/services/web/abstractmusicprovider.cpp new file mode 100644 index 0000000..488ffbd --- /dev/null +++ b/services/web/abstractmusicprovider.cpp @@ -0,0 +1,6 @@ +#include "abstractmusicprovider.h" + +AbstractMusicProvider::AbstractMusicProvider(QObject *parent) : QObject(parent) +{ + +} diff --git a/services/web/abstractmusicprovider.h b/services/web/abstractmusicprovider.h new file mode 100644 index 0000000..354fff4 --- /dev/null +++ b/services/web/abstractmusicprovider.h @@ -0,0 +1,65 @@ +#ifndef ABSTRACTMUSICPROVIDER_H +#define ABSTRACTMUSICPROVIDER_H + +#include +#ifdef STATIC_MAUIKIT +#include "fmh.h" +#else +#include +#endif +/** + * @brief The AbstractMusicSyncer class + * is an abstraction for different services backend to stream music. + * Different services to be added to VVave are expected to derived from this. + */ + +class AbstractMusicProvider : public QObject +{ + Q_OBJECT +public: + explicit AbstractMusicProvider(QObject *parent = nullptr); + virtual ~AbstractMusicProvider() {} + + virtual void getCollection(const std::initializer_list ¶meters = {}) = 0; + + virtual void getTracks() = 0; + virtual void getTrack(const QString &id) = 0; + + virtual void getArtists() = 0; + virtual void getArtist(const QString &id) = 0; + + virtual void getAlbums() = 0; + virtual void getAlbum(const QString &id) = 0; + + virtual void getPlaylists() = 0; + virtual void getPlaylist(const QString &id) = 0; + + virtual void getFolders() = 0; + virtual void getFolder(const QString &id) = 0; + + /** + * @brief setCredentials + * sets the credential to authenticate to the provider server + * @param account + * the account data is represented by FMH::MODEL + */ + virtual void setCredentials(const FMH::MODEL &account) final + { + this->m_user = account[FMH::MODEL_KEY::USER]; + this->m_password = account[FMH::MODEL_KEY::PASSWORD]; + this->m_provider = QUrl(account[FMH::MODEL_KEY::SERVER]).host(); + } + + virtual QString user() final { return this->m_user; } + virtual QString provider() final { return this->m_provider; } +protected: + QString m_user = ""; + QString m_password = ""; + QString m_provider = ""; + +signals: + +public slots: +}; + +#endif // ABSTRACTMUSICPROVIDER_H diff --git a/widgets/AlbumsView.qml b/widgets/AlbumsView.qml index ed22006..61b41d9 100644 --- a/widgets/AlbumsView.qml +++ b/widgets/AlbumsView.qml @@ -1,236 +1,236 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import "../view_models/BabeGrid" import "../view_models/BabeTable" import "../db/Queries.js" as Q import "../utils/Help.js" as H import org.kde.kirigami 2.6 as Kirigami import org.kde.mauikit 1.0 as Maui import TracksList 1.0 import AlbumsList 1.0 BabeGrid { id: albumsViewGrid property string currentAlbum: "" property string currentArtist: "" property var tracks: [] property alias table : albumsViewTable // property alias tagBar : tagBar signal rowClicked(var track) signal playTrack(var track) signal queueTrack(var track) signal appendAll(string album, string artist) signal playAll(string album, string artist) // signal albumCoverClicked(string album, string artist) signal albumCoverPressedAndHold(string album, string artist) visible: true // topPadding: Maui.Style.space.large onAlbumCoverPressed: albumCoverPressedAndHold(album, artist) headBar.visible: false // headBar.rightContent: Kirigami.ActionToolBar // { // Layout.fillWidth: true // actions: [ // Kirigami.Action // { // id: sortBtn // icon.name: "view-sort" // text: qsTr("Sort") // Kirigami.Action // { // text: qsTr("Artist") // checkable: true // checked: list.sortBy === Albums.ARTIST // onTriggered: list.sortBy = Albums.ARTIST // } // Kirigami.Action // { // text: qsTr("Album") // checkable: true // checked: list.sortBy === Albums.ALBUM // onTriggered: list.sortBy = Albums.ALBUM // } // Kirigami.Action // { // text: qsTr("Release date") // checkable: true // checked: list.sortBy === Albums.RELEASEDATE // onTriggered: list.sortBy = Albums.RELEASEDATE // } // Kirigami.Action // { // text: qsTr("Add date") // checkable: true // checked: list.sortBy === Albums.ADDDATE // onTriggered: list.sortBy = Albums.ADDDATE // } // } // ] // } // headBar.rightContent: [ // ToolButton // { // id: appendBtn // visible: headBar.visible && albumsViewGrid.count > 0 // anim : true // icon.name : "media-playlist-append"//"media-repeat-track-amarok" // onClicked: appendAll() // } // ] Maui.Dialog { id: albumDialog parent: parent maxHeight: maxWidth maxWidth: Maui.Style.unit * 600 widthHint: 0.9 heightHint: 0.9 defaultButtons: false page.padding: 0 ColumnLayout { id: albumFilter anchors.fill: parent spacing: 0 BabeTable { id: albumsViewTable Layout.fillHeight: true Layout.fillWidth: true trackNumberVisible: true trackRating: true headBar.visible: true coverArtVisible: true quickPlayVisible: true focus: true list.sortBy: Tracks.TRACK - holder.emoji: "qrc:/assets/ElectricPlug.png" + holder.emoji: "qrc:/assets/dialog-information.svg" holder.isMask: false holder.title : "Oops!" holder.body: "This list is empty" holder.emojiSize: Maui.Style.iconSizes.huge onRowClicked: { albumsViewGrid.rowClicked(list.get(index)) } onQuickPlayTrack: { albumsViewGrid.playTrack(list.get(index)) } onQueueTrack: { albumsViewGrid.queueTrack(list.get(index)) } onPlayAll: { albumDialog.close() albumsViewGrid.playAll(currentAlbum, currentArtist) } onAppendAll: { albumDialog.close() albumsViewGrid.appendAll(currentAlbum, currentArtist) } } // Maui.TagsBar // { // id: tagBar // visible:false // Layout.fillWidth: true // allowEditMode: false // onTagClicked: H.searchFor("tag:"+tag) // } } } function populateTable(album, artist) { console.log("PAPULATE ALBUMS VIEW") albumDialog.open() var query = "" var tagq = "" currentAlbum = album === undefined ? "" : album currentArtist= artist if(album && artist) { query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) albumsViewTable.title = album tagq = Q.GET.albumTags_.arg(album) }else if(artist && album === undefined) { query = Q.GET.artistTracks_.arg(artist) albumsViewTable.title = artist tagq = Q.GET.artistTags_.arg(artist) } albumsViewTable.list.query = query /*dunoooo*/ // if(tracks.length > 0) // { // tagq = tagq.arg(artist) // var tags = bae.get(tagq) // console.log(tagq, "TAGS", tags) // tagBar.populate(tags) // } } function filter(tracks) { var matches = [] for(var i = 0; i0) { for(var i = 0; i < n; i++) { var where = "url = \""+lastplaylist[i]+"\"" var query = Q.GET.tracksWhere_.arg(where) table.list.appendQuery(query); } }else { where = "fav = 1" query = Q.GET.tracksWhere_.arg(where) table.list.appendQuery(query); } // if(autoplay) // Player.playAt(0) } } // function goFocusMode() // { // if(focusMode) // { // if(isMobile) // { // root.width = screenWidth // root.height= screenHeight // }else // { // cover.y = 0 // root.maximumWidth = screenWidth // root.minimumWidth = columnWidth // root.maximumHeight = screenHeight // root.minimumHeight = columnWidth // root.width = columnWidth // root.height = 700 // } // }else // { // if(isMobile) // { // }else // { // root.maximumWidth = columnWidth // root.minimumWidth = columnWidth // root.maximumHeight = columnWidth // root.minimumHeight = columnWidth // // root.footer.visible = false // // mainlistContext.visible = false // } // } // focusMode = !focusMode // } function play(index) { prevTrackIndex = currentTrackIndex Player.playAt(index) } } diff --git a/widgets/PlaylistsView/PlaylistsView.qml b/widgets/PlaylistsView/PlaylistsView.qml index d2fcfd0..5f56373 100644 --- a/widgets/PlaylistsView/PlaylistsView.qml +++ b/widgets/PlaylistsView/PlaylistsView.qml @@ -1,216 +1,216 @@ import QtQuick 2.9 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import "../../view_models/BabeTable" import "../../view_models" import "../../db/Queries.js" as Q import "../../utils/Help.js" as H ColumnLayout { id: control spacing: 0 property string playlistQuery property alias playlistModel : playlistViewModel.model property alias playlistList : playlistViewModel.list property alias playlistViewList : playlistViewModel signal rowClicked(var track) signal quickPlayTrack(var track) signal playAll() signal playSync(var playlist) signal appendAll() SwipeView { id: playlistSwipe Layout.fillHeight: true Layout.fillWidth: true interactive: false clip: true PlaylistsViewModel { id: playlistViewModel onPlaySync: syncAndPlay(index) } BabeList { id: playlistViewModelFilter // headBarExitIcon: "go-previous" headBar.leftContent: ToolButton { icon.name: "go-previous" onClicked: playlistSwipe.currentIndex = 0 } model : ListModel {} delegate: Maui.LabelDelegate { id: delegate label : tag Connections { target: delegate onClicked: {} } } } } ColorTagsBar { Layout.fillWidth: true height: Maui.Style.rowHeightAlt recSize: Kirigami.Settings.isMobile ? Maui.Style.iconSizes.medium : Maui.Style.iconSizes.small onColorClicked: populate(Q.GET.colorTracks_.arg(color.toLowerCase())) } Maui.Dialog { id: _filterDialog parent: parent maxHeight: maxWidth maxWidth: Maui.Style.unit * 600 defaultButtons: false page.padding: 0 BabeTable { id: filterList anchors.fill: parent clip: true quickPlayVisible: true coverArtVisible: true trackRating: true trackDuration: false headBar.visible: !holder.visible title: playlistViewModel.list.get(playlistViewModel.currentIndex).playlist - holder.emoji: "qrc:/assets/Electricity.png" + holder.emoji: "qrc:/assets/dialog-information.svg" holder.isMask: false holder.title : playlistViewModel.list.get(playlistViewModel.currentIndex).playlist holder.body: "Your playlist is empty,
start adding new music to it" holder.emojiSize: Maui.Style.iconSizes.huge contextMenuItems: MenuItem { text: qsTr("Remove from playlist") } // headerMenu.menuItem: [ // Maui.MenuItem // { // enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon // text: "Sync tags" // onTriggered: {} // }, // Maui.MenuItem // { // enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon // text: "Play-n-Sync" // onTriggered: // { // filterList.headerMenu.close() // syncAndPlay(playlistViewModel.currentIndex) // } // }, // Maui.MenuItem // { // enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon // text: "Remove playlist" // onTriggered: removePlaylist() // } // ] // contextMenu.menuItem: [ // MenuItem // { // text: qsTr("Remove from playlist") // onTriggered: // { // bae.removePlaylistTrack(filterList.model.get(filterList.currentIndex).url, playlistViewModel.model.get(playlistViewModel.currentIndex).playlist) // populate(playlistQuery) // } // } // ] section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { label: filterList.section.property === qsTr("stars") ? H.setStars(section) : section isSection: true labelTxt.font.family: "Material Design Icons" width: filterList.width } Connections { target: filterList onRowClicked: control.rowClicked(filterList.list.get(index)) onQuickPlayTrack: control.quickPlayTrack(filterList.list.get(filterList.currentIndex)) onPlayAll: playAll() onAppendAll: appendAll() onPulled: populate(playlistQuery) } Connections { target: filterList.contextMenu onRemoveClicked: { playlistList.removeTrack(playlistViewList.currentIndex, filterList.list.get(filterList.currentIndex).url) populate(playlistQuery) } } } } function populateExtra(query, title) { // playlistSwipe.currentIndex = 1 // var res = bae.get(query) // playlistViewModelFilter.clearTable() // playlistViewModelFilter.headBarTitle = title // appendToExtraList(res) } function appendToExtraList(res) { if(res.length>0) for(var i in res) playlistViewModelFilter.model.append(res[i]) } function populate(query) { playlistQuery = query filterList.list.query = playlistQuery _filterDialog.open() } function syncAndPlay(index) { if(!playlistList.get(index).playlistIcon) playSync(playlistList.get(index).playlist) } function removePlaylist() { playlistList.removePlaylist(playlistViewList.currentIndex) } }