diff --git a/main.cpp b/main.cpp index 19564d8..73c27ad 100644 --- a/main.cpp +++ b/main.cpp @@ -1,126 +1,126 @@ #include #include #include #include #include #include #include #include #include #include "vvave.h" #include "services/local/player.h" #ifdef STATIC_KIRIGAMI #include "3rdparty/kirigami/src/kirigamiplugin.h" #endif #ifdef STATIC_MAUIKIT #include "3rdparty/mauikit/src/mauikit.h" #endif #ifdef Q_OS_ANDROID #include #include #include #include "mauiandroid.h" #else #include #include #endif #include "utils/bae.h" #include "services/web/youtube.h" //#include "services/web/Spotify/spotify.h" //#include "services/local/linking.h" #include "services/local/player.h" #include "models/tracks/tracksmodel.h" #include "models/albums/albumsmodel.h" #include "models/playlists/playlistsmodel.h" -//#include "models/cloud/cloud.h" +#include "models/cloud/cloud.h" #ifdef Q_OS_ANDROID Q_DECL_EXPORT #endif int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #ifdef Q_OS_ANDROID QGuiApplication app(argc, argv); QGuiApplication::styleHints()->setMousePressAndHoldInterval(1000); // in [ms] if (!MAUIAndroid::checkRunTimePermissions()) return -1; #else QApplication app(argc, argv); #endif app.setApplicationName(BAE::appName); app.setApplicationVersion(BAE::version); app.setApplicationDisplayName(BAE::displayName); app.setOrganizationName(BAE::orgName); app.setOrganizationDomain(BAE::orgDomain); app.setWindowIcon(QIcon("qrc:/assets/vvave.png")); QCommandLineParser parser; parser.setApplicationDescription(BAE::description); const QCommandLineOption versionOption = parser.addVersionOption(); parser.process(app); const QStringList args = parser.positionalArguments(); QStringList urls; if(!args.isEmpty()) urls = args; vvave vvave; /* Services */ YouTube youtube; // Spotify spotify; QFontDatabase::addApplicationFont(":/assets/materialdesignicons-webfont.ttf"); QQmlApplicationEngine engine; QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, [&]() { qDebug()<<"FINISHED LOADING QML APP"; const auto currentSources = vvave.getSourceFolders(); vvave.scanDir(currentSources.isEmpty() ? BAE::defaultSources : currentSources); if(!urls.isEmpty()) vvave.openUrls(urls); }); auto context = engine.rootContext(); context->setContextProperty("vvave", &vvave); context->setContextProperty("youtube", &youtube); qmlRegisterType("TracksList", 1, 0, "Tracks"); qmlRegisterType("PlaylistsList", 1, 0, "Playlists"); qmlRegisterType("AlbumsList", 1, 0, "Albums"); -// qmlRegisterType("CloudList", 1, 0, "CloudList"); + qmlRegisterType("CloudList", 1, 0, "Cloud"); qmlRegisterType("Player", 1, 0, "Player"); #ifdef STATIC_KIRIGAMI KirigamiPlugin::getInstance().registerTypes(); #endif #ifdef STATIC_MAUIKIT MauiKit::getInstance().registerTypes(); #endif #ifdef Q_OS_ANDROID QtWebView::initialize(); #else QtWebEngine::initialize(); #endif engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); } diff --git a/main.qml b/main.qml index a9e8fcb..61009b8 100644 --- a/main.qml +++ b/main.qml @@ -1,981 +1,992 @@ 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 "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.position/1000) property alias isPlaying: player.playing property int onQueue: 0 property bool mainlistEmpty: !mainPlaylist.table.count > 0 /***************************************************/ /******************** HANDLERS ********************/ /*************************************************/ readonly property var viewsIndex: ({ tracks: 0, albums: 1, artists: 2, playlists: 3, folders: 4, youtube: 5, search: 6}) property string syncPlaylist: "" property bool sync: false property string infoMsg: "" property bool infoLabels: Maui.FM.loadSettings("LABELS", "PLAYBACK", false) == "true" ? true : false // property bool isLinked: false // property bool isServing: false // property bool focusMode : 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() } } 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" } } footer: ColumnLayout { id: _footerLayout visible: !mainlistEmpty height: visible ? Maui.Style.toolBarHeight * 1.2 : 0 width: root.width spacing: 0 Kirigami.Separator { Layout.fillWidth: true } Slider { id: progressBar Layout.preferredHeight: Maui.Style.unit * (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 leftContent: ToolButton { icon.name: "headphones" checked: _drawer.visible icon.color: _drawer.visible ? babeColor : Kirigami.Theme.textColor onClicked: { _drawer.visible = !_drawer.visible } Kirigami.Theme.highlightColor: babeColor Connections { target: mainPlaylist.table onCountChanged: anim.start() } NumberAnimation on x { property int count : 0 id: anim to: 10 duration: 100 onStopped: { if(anim.to===10) { anim.from=10; anim.to=0; } else { anim.from=0; anim.to=10 } count++; if(count < 5) start() else { anim.to = 0 count = 0 } } } } 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") } } ] } } onSearchButtonClicked: { _actionGroup.currentIndex = viewsIndex.search searchView.searchInput.forceActiveFocus() } Loader { id: _dialogLoader } InfoView { id: infoView maxWidth: parent.width * 0.8 maxHeight: parent.height * 0.9 } 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 MainPlaylist { id: mainPlaylist anchors.fill: parent Connections { target: mainPlaylist onCoverPressed: Player.appendAll(tracks) onCoverDoubleClicked: Player.playAll(tracks) } } } ColumnLayout { anchors.fill: parent SwipeView { id: swipeView Layout.fillHeight: true Layout.fillWidth: true interactive: isMobile currentIndex: _actionGroup.currentIndex onCurrentIndexChanged: _actionGroup.currentIndex = currentIndex clip: true onCurrentItemChanged: currentItem.forceActiveFocus() TracksView { id: tracksView Connections { target: tracksView onRowClicked: Player.addTrack(tracksView.list.get(index)) onQuickPlayTrack: Player.quickPlay(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.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.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.addTrack(track) onQuickPlayTrack: 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.addTrack(foldersView.list.model.get(index)) onQuickPlayTrack: Player.quickPlay(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.addTrack(searchView.list.get(index)) onQuickPlayTrack: Player.quickPlay(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/cloud/cloud.cpp b/models/cloud/cloud.cpp index acb3cdf..bae278c 100644 --- a/models/cloud/cloud.cpp +++ b/models/cloud/cloud.cpp @@ -1,158 +1,177 @@ #include "cloud.h" #include "abstractmusicprovider.h" #include "NextCloud/nextmusic.h" #ifdef STATIC_MAUIKIT #include "mauiaccounts.h" #else #include #endif Cloud::Cloud(QObject *parent) : MauiList (parent), provider(new NextMusic(this)) { - this->setList(); - connect(MauiAccounts::instance(), &MauiAccounts::currentAccountChanged, [this](QVariantMap account) { this->provider->setCredentials(FMH::toModel(account)); + this->setList(); }); connect(provider, &AbstractMusicProvider::collectionReady, [=](FMH::MODEL_LIST data) { - emit this->preListChanged(); - this->list = data; - this->sortList(); - emit this->postListChanged(); + emit this->albumsChanged(); + emit this->artistsChanged(); + + emit this->preListChanged(); + this->list = data; + this->sortList(); + emit this->postListChanged(); }); + connect(static_cast (provider), &NextMusic::trackPathReady, [=](QString id, QString path) + { + qDebug() << "track path remot eurl is ready at "<< path << id; + }); } void Cloud::componentComplete() { } void Cloud::setSortBy(const Cloud::SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; emit this->preListChanged(); this->sortList(); emit this->postListChanged(); emit this->sortByChanged(); } Cloud::SORTBY Cloud::getSortBy() const { - return this->sort; + return this->sort; +} + +QVariantList Cloud::getAlbums() const +{ + return this->provider->getAlbumsList(); +} + +QVariantList Cloud::getArtists() const +{ + return this->provider->getArtistsList(); } FMH::MODEL_LIST Cloud::items() const { return this->list; } void Cloud::setList() { this->provider->getCollection(); } void Cloud::sortList() { if(this->sort == Cloud::SORTBY::NONE) return; const auto key = static_cast(this->sort); std::sort(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; }); } QVariantMap Cloud::get(const int &index) const { if(index >= this->list.size() || index < 0) return QVariantMap(); QVariantMap res; const auto item = this->list.at(index); for(auto key : item.keys()) res.insert(FMH::MODEL_NAME[key], item[key]); return res; } QVariantList Cloud::getAll() { -return QVariantList(); + return QVariantList(); } void Cloud::upload(const QUrl &url) { } void Cloud::getFileUrl(const QString &id) { - + static_cast(this->provider)->getTrackPath(id); } void Cloud::getFileUrl(const int &index) { + if(index >= this->list.size() || index < 0) + return; + this->getFileUrl(this->list.at(index)[FMH::MODEL_KEY::ID]); } diff --git a/models/cloud/cloud.h b/models/cloud/cloud.h index 093b6be..5ce949c 100644 --- a/models/cloud/cloud.h +++ b/models/cloud/cloud.h @@ -1,70 +1,77 @@ #ifndef CLOUD_H #define CLOUD_H #include #ifdef STATIC_MAUIKIT #include "fmh.h" #include "mauilist.h" #else #include #include #endif class FM; class AbstractMusicProvider; class Cloud : public MauiList { Q_OBJECT Q_PROPERTY(Cloud::SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged) + Q_PROPERTY(QVariantList artists READ getArtists NOTIFY artistsChanged) + Q_PROPERTY(QVariantList albums READ getAlbums NOTIFY albumsChanged) public: enum SORTBY : uint_fast8_t { ADDDATE = FMH::MODEL_KEY::ADDDATE, RELEASEDATE = FMH::MODEL_KEY::RELEASEDATE, FORMAT = FMH::MODEL_KEY::FORMAT, ARTIST = FMH::MODEL_KEY::ARTIST, TITLE = FMH::MODEL_KEY::TITLE, ALBUM = FMH::MODEL_KEY::ALBUM, RATE = FMH::MODEL_KEY::RATE, FAV = FMH::MODEL_KEY::FAV, TRACK = FMH::MODEL_KEY::TRACK, COUNT = FMH::MODEL_KEY::COUNT, NONE }; Q_ENUM(SORTBY) explicit Cloud(QObject *parent = nullptr); void componentComplete() override final; FMH::MODEL_LIST items() const override; void setSortBy(const Cloud::SORTBY &sort); Cloud::SORTBY getSortBy() const; + QVariantList getAlbums() const; + QVariantList getArtists() const; + private: AbstractMusicProvider *provider; FMH::MODEL_LIST list; void sortList(); void setList(); - Cloud::SORTBY sort = Cloud::SORTBY::ADDDATE; - + Cloud::SORTBY sort = Cloud::SORTBY::ARTIST; public slots: QVariantMap get(const int &index) const; QVariantList getAll(); void upload(const QUrl &url); void getFileUrl(const QString &id); void getFileUrl(const int &index); signals: void sortByChanged(); void fileUrlReady(QString id, QUrl url); void warning(QString error); + + void artistsChanged(); + void albumsChanged(); }; #endif // CLOUD_H diff --git a/models/tracks/tracksmodel.cpp b/models/tracks/tracksmodel.cpp index 42cf687..14ef5f7 100644 --- a/models/tracks/tracksmodel.cpp +++ b/models/tracks/tracksmodel.cpp @@ -1,355 +1,342 @@ #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() { - auto provider = new NextMusic(this); - - connect(provider, &NextMusic::collectionReady, [=](FMH::MODEL_LIST data) - { - emit this->preListChanged(); - this->list = data; - this->sortList(); - emit this->postListChanged(); - }); - 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(); + 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/services/web/NextCloud/nextmusic.cpp b/services/web/NextCloud/nextmusic.cpp index 391a5de..c5d1e36 100644 --- a/services/web/NextCloud/nextmusic.cpp +++ b/services/web/NextCloud/nextmusic.cpp @@ -1,221 +1,241 @@ #include "nextmusic.h" #include #include #include #include #include #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(); + 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; + 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); + // Construct new QNetworkRequest with prepared header values + QNetworkRequest newRequest(url); - newRequest.setRawHeader(QString("Authorization").toLocal8Bit(), headerData.toLocal8Bit()); - // newRequest.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true")); + newRequest.setRawHeader(QString("Authorization").toLocal8Bit(), headerData.toLocal8Bit()); + // newRequest.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true")); - qDebug() << "headers" << newRequest.rawHeaderList() << newRequest.url(); + qDebug() << "headers" << newRequest.rawHeaderList() << newRequest.url(); - return newRequest; + return newRequest; } const QString NextMusic::API = QStringLiteral("https://PROVIDER/index.php/apps/music/api/"); NextMusic::NextMusic(QObject *parent) : AbstractMusicProvider(parent) {} +QVariantList NextMusic::getAlbumsList() const +{ + return this->m_albums; +} + +QVariantList NextMusic::getArtistsList() const +{ + qDebug () << "ASKING FOR ARISTS" << this->m_artists; + return this->m_artists; +} + FMH::MODEL_LIST NextMusic::parseCollection(const QByteArray &array) { - FMH::MODEL_LIST res; -// qDebug()<< "trying to parse array" << array; - QJsonParseError jsonParseError; - QJsonDocument jsonResponse = QJsonDocument::fromJson(static_cast(array).toUtf8(), &jsonParseError); + 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; - } + if (jsonParseError.error != QJsonParseError::NoError) + { + qDebug()<< "ERROR PARSING" << array; + return res; + } - const auto data = jsonResponse.toVariant(); + const auto data = jsonResponse.toVariant(); - if(data.isNull() || !data.isValid()) - return res; + if(data.isNull() || !data.isValid()) + return res; - const auto list = data.toList(); - qDebug()<< "SOFAR GOOD PARSING"; + const auto list = data.toList(); + qDebug()<< "SOFAR GOOD PARSING"; - if(!list.isEmpty()) - { - for(const auto &item : list) + if(!list.isEmpty()) { - const auto map = item.toMap(); - const auto artist = map.value("name").toString(); - const auto artistId = map.value("id").toString(); - qDebug()<< "ARTIST" << artist << artistId; - - const auto albumsList = map.value("albums").toList(); - for(const auto &albumItem : albumsList) - { - const auto albumMap = albumItem.toMap(); - const auto album = albumMap.value("name").toString(); - const auto albumId = albumMap.value("id").toString(); - const auto albumYear = albumMap.value("year").toString(); - const auto albumCover = albumMap.value("cover").toString(); - - qDebug()<< "ARTIST && ALBUM" << artist << album << artistId; - - const auto tracksList = albumMap.value("tracks").toList(); - for(const auto &trackItem : tracksList) + for(const auto &item : list) { - const auto trackMap = trackItem.toMap(); - - const auto title = trackMap.value("title").toString(); - const auto track = trackMap.value("number").toString(); - const auto id = trackMap.value("id").toString(); - - const auto filesMap = trackMap.value("files").toMap(); - for(const auto &fileKey : filesMap.keys()) - { - const auto mime = fileKey; - const auto url = filesMap[fileKey].toString(); - - res << FMH::MODEL({ - {FMH::MODEL_KEY::ID, url}, - {FMH::MODEL_KEY::TITLE, title}, - {FMH::MODEL_KEY::TRACK, track}, - {FMH::MODEL_KEY::ALBUM, album}, - {FMH::MODEL_KEY::ARTIST, artist}, - {FMH::MODEL_KEY::ARTWORK, albumCover}, - {FMH::MODEL_KEY::RELEASEDATE, albumYear}, - {FMH::MODEL_KEY::SOURCE, this->m_provider} - }); - - } + const auto map = item.toMap(); + const auto artist = map.value("name").toString(); + const auto artistId = map.value("id").toString(); + + this->m_artists.append(QVariantMap{{"artist", artist}, {"id", artistId}}); + + qDebug()<< "ARTIST" << artist << artistId; + + const auto albumsList = map.value("albums").toList(); + for(const auto &albumItem : albumsList) + { + const auto albumMap = albumItem.toMap(); + const auto album = albumMap.value("name").toString(); + const auto albumId = albumMap.value("id").toString(); + const auto albumYear = albumMap.value("year").toString(); + const auto albumCover = albumMap.value("cover").toString(); + + this->m_albums.append(QVariantMap {{"album", album}, {"artist", artist}, {"release_date", albumYear}, {"artwork", albumCover}, {"id", albumId}}); + + + qDebug()<< "ARTIST && ALBUM" << artist << album << artistId; + + const auto tracksList = albumMap.value("tracks").toList(); + for(const auto &trackItem : tracksList) + { + const auto trackMap = trackItem.toMap(); + + const auto title = trackMap.value("title").toString(); + const auto track = trackMap.value("number").toString(); + const auto id = trackMap.value("id").toString(); + + const auto filesMap = trackMap.value("files").toMap(); + for(const auto &fileKey : filesMap.keys()) + { + const auto mime = fileKey; + const auto url = filesMap[fileKey].toString(); + + res << FMH::MODEL({ + {FMH::MODEL_KEY::ID, url}, + {FMH::MODEL_KEY::TITLE, title}, + {FMH::MODEL_KEY::TRACK, track}, + {FMH::MODEL_KEY::ALBUM, album}, + {FMH::MODEL_KEY::ARTIST, artist}, + {FMH::MODEL_KEY::ARTWORK, albumCover}, + {FMH::MODEL_KEY::RELEASEDATE, albumYear}, + {FMH::MODEL_KEY::SOURCE, this->m_provider} + }); + + } + } + } } - } } - } - qDebug()<< res; - return res; + qDebug()<< res; + return res; } void NextMusic::getTrackPath(const QString &id) { - auto url = QString(NextMusic::API+"file/%1/path").replace("PROVIDER", this->m_provider).arg(id); + auto url = QString(NextMusic::API+"file/%1/path").replace("PROVIDER", QUrl(this->m_provider).host()).arg(id); - QString concatenated = this->m_user + ":" + this->m_password; - QByteArray data = concatenated.toLocal8Bit().toBase64(); - QString headerData = "Basic " + data; + QString concatenated = this->m_user + ":" + this->m_password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; - QMap header {{"Authorization", headerData.toLocal8Bit()}}; + QMap header {{"Authorization", headerData.toLocal8Bit()}}; - const auto downloader = new FMH::Downloader; - connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) - { - QJsonParseError jsonParseError; - QJsonDocument jsonResponse = QJsonDocument::fromJson(static_cast(array).toUtf8(), &jsonParseError); - - if (jsonParseError.error != QJsonParseError::NoError) + const auto downloader = new FMH::Downloader; + connect(downloader, &FMH::Downloader::dataReady, [this, id, _downloader = std::move(downloader)](QByteArray array) { - qDebug()<< "ERROR PARSING"; - return; - } + QJsonParseError jsonParseError; + QJsonDocument jsonResponse = QJsonDocument::fromJson(static_cast(array).toUtf8(), &jsonParseError); - const auto data = jsonResponse.toVariant(); + if (jsonParseError.error != QJsonParseError::NoError) + { + qDebug()<< "ERROR PARSING"; + return; + } - if(data.isNull() || !data.isValid()) - return; + const auto data = jsonResponse.toVariant(); - const auto map = data.toMap(); - const auto url = map["path"].toString(); - emit this->trackPathReady(id, url); - }); + if(data.isNull() || !data.isValid()) + return; - downloader->getArray(url, header); + const auto map = data.toMap(); + const auto url = this->provider() + map["path"].toString(); + + qDebug()<< "TRACK PATH...." << url; + emit this->trackPathReady(id, url); + }); + + downloader->getArray(url, header); } void NextMusic::getCollection(const std::initializer_list ¶meters) { - auto url = QString(NextMusic::API).replace("PROVIDER", this->m_provider).append("collection"); + auto url = QString(NextMusic::API).replace("PROVIDER", QUrl(this->m_provider).host()).append("collection"); - QString concatenated = this->m_user + ":" + this->m_password; - QByteArray data = concatenated.toLocal8Bit().toBase64(); - QString headerData = "Basic " + data; + QString concatenated = this->m_user + ":" + this->m_password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; - QMap header {{"Authorization", headerData.toLocal8Bit()}}; + QMap header {{"Authorization", headerData.toLocal8Bit()}}; - const auto downloader = new FMH::Downloader; - connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) - { - const auto data = this->parseCollection(array); - emit this->collectionReady(data); - }); + const auto downloader = new FMH::Downloader; + connect(downloader, &FMH::Downloader::dataReady, [&, _downloader = std::move(downloader)](QByteArray array) + { + const auto data = this->parseCollection(array); + emit this->collectionReady(data); + _downloader->deleteLater(); + }); - downloader->getArray(url, header); + 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 97fb965..77caca3 100644 --- a/services/web/NextCloud/nextmusic.h +++ b/services/web/NextCloud/nextmusic.h @@ -1,40 +1,45 @@ #ifndef NEXTMUSIC_H #define NEXTMUSIC_H #include #include "abstractmusicprovider.h" class NextMusic : public AbstractMusicProvider { Q_OBJECT public: explicit NextMusic(QObject *parent = nullptr); + QVariantList getAlbumsList() const override final; + QVariantList getArtistsList() const override final; private: const static QString API; static const QString formatUrl(const QString &user, const QString &password, const QString &provider); FMH::MODEL_LIST parseCollection(const QByteArray &array); + QVariantList m_artists; + QVariantList m_albums; + signals: public slots: // AbstractMusicProvider interface public: void getTrackPath(const QString &id); 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.h b/services/web/abstractmusicprovider.h index 07be1fc..c3ee8f1 100644 --- a/services/web/abstractmusicprovider.h +++ b/services/web/abstractmusicprovider.h @@ -1,76 +1,78 @@ #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; + virtual QVariantList getAlbumsList() const {return QVariantList();} + virtual QVariantList getArtistsList() const {return QVariantList();} /** * @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(); + this->m_provider = account[FMH::MODEL_KEY::SERVER]; } 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: void collectionReady(FMH::MODEL_LIST data); void tracksReady(FMH::MODEL_LIST data); void trackReady(FMH::MODEL data); void artistsRedy(FMH::MODEL_LIST data); void artistReady(FMH::MODEL data); void albumsReady(FMH::MODEL_LIST data); void albumReady(FMH::MODEL data); void playlistsReady(FMH::MODEL_LIST data); void playlistReady(FMH::MODEL data); void trackPathReady(QString id, QString path); public slots: }; #endif // ABSTRACTMUSICPROVIDER_H diff --git a/view_models/BabeGrid/BabeAlbum.qml b/view_models/BabeGrid/BabeAlbum.qml index 9d6b58e..44bb0ef 100644 --- a/view_models/BabeGrid/BabeAlbum.qml +++ b/view_models/BabeGrid/BabeAlbum.qml @@ -1,232 +1,234 @@ import QtQuick 2.9 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import AlbumsList 1.0 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui Maui.ItemDelegate { id: control property int albumSize : Maui.Style.iconSizes.huge property int albumRadius : 0 property bool albumCard : true property string fillColor : Qt.darker(Kirigami.Theme.backgroundColor, 1.1) property bool hide : false property bool showLabels : true property bool showIndicator : false property bool hideRepeated : false property bool increaseCurrentItem : false + + property alias label1 : _label1 + property alias label2 : _label2 + property alias image : _image + + isCurrentItem: GridView.isCurrentItem // height: typeof album === 'undefined' ? parseInt(albumSize+(albumSize*0.3)) : parseInt(albumSize+(albumSize*0.4)) readonly property bool sameAlbum : { if(hideRepeated) { if(albumsRollRoot.model.get(index-1)) { if(albumsRollRoot.model.get(index-1).album === album && albumsRollRoot.model.get(index-1).artist === artist) true else false }else false }else false } visible: !sameAlbum Item { anchors.fill: parent anchors.margins: Maui.Style.space.tiny DropShadow { anchors.fill: card visible: card.visible horizontalOffset: 0 verticalOffset: 0 radius: 8.0 samples: 17 color: "#80000000" source: card } Rectangle { id: card z: -999 visible: albumCard - anchors.centerIn: img - anchors.fill: img + anchors.fill: _image color: fillColor radius: albumRadius } Image { - id: img + id: _image width: parent.width height: width sourceSize.width: width sourceSize.height: height fillMode: Image.PreserveAspectFit smooth: true asynchronous: true - source: model.artwork ? model.artwork : "qrc:/assets/cover.png" onStatusChanged: { if (status == Image.Error) - source = "qrc:/assets/cover.png"; - - } + source = "qrc:/assets/cover.png"; + } layer.enabled: albumRadius layer.effect: OpacityMask { maskSource: Item { - width: img.width - height: img.height + width: _image.width + height: _image.height Rectangle { anchors.centerIn: parent - width: img.adapt ? img.width : Math.min(img.width, img.height) - height: img.adapt ? img.height : width + width: _image.adapt ? _image.width : Math.min(_image.width, _image.height) + height: _image.adapt ? _image.height : width radius: albumRadius } } } } Rectangle { visible : showIndicator && currentTrackIndex === index - height: img.height * 0.1 - width: img.width * 0.1 + height: _image.height * 0.1 + width: _image.width * 0.1 anchors.bottom: parent.bottom anchors.bottomMargin: Maui.Style.space.big anchors.horizontalCenter:parent.horizontalCenter radius: Math.min(width, height) color: "#f84172" AnimatedImage { source: "qrc:/assets/heart_indicator_white.gif" anchors.centerIn: parent height: parent.height * 0.6 width: parent.width * 0.6 playing: parent.visible } } Item { id: _labelBg height: Math.min (parent.height * 0.3, _labelsLayout.implicitHeight ) + Maui.Style.space.big width: parent.width anchors.bottom: parent.bottom visible: showLabels Kirigami.Theme.inherit: false Kirigami.Theme.backgroundColor: "#333"; Kirigami.Theme.textColor: "#fafafa" FastBlur { id: blur anchors.fill: parent source: ShaderEffectSource { - sourceItem: img + sourceItem: _image sourceRect:Qt.rect(0, - img.height - _labelBg.height, + _image.height - _labelBg.height, _labelBg.width, _labelBg.height) } radius: 50 Rectangle { anchors.fill: parent color: _labelBg.Kirigami.Theme.backgroundColor opacity: 0.4 } layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: blur.width height: blur.height Rectangle { anchors.centerIn: parent width: parent.width height: parent.height radius: albumRadius Rectangle { anchors.top: parent.top width: parent.width height: parent.radius } } } - } + } } ColumnLayout { id: _labelsLayout anchors.centerIn: parent width: parent.width * 0.9 height: Math.min(parent.height * 0.9, implicitHeight) spacing: 0 Label { + id: _label1 Layout.fillWidth: visible Layout.fillHeight: visible - text: list.query === Albums.ALBUMS ? model.album : model.artist visible: text && control.width > 50 horizontalAlignment: Qt.AlignLeft elide: Text.ElideRight font.pointSize: Maui.Style.fontSizes.default font.bold: true font.weight: Font.Bold color: Kirigami.Theme.textColor wrapMode: Text.NoWrap } Label { + id: _label2 Layout.fillWidth: visible Layout.fillHeight: visible - - text: list.query === Albums.ALBUMS ? model.artist : "" visible: text && (control.width > 70) horizontalAlignment: Qt.AlignLeft elide: Text.ElideRight font.pointSize: Maui.Style.fontSizes.medium color: Kirigami.Theme.textColor wrapMode: Text.NoWrap } } } } } diff --git a/view_models/BabeGrid/BabeGrid.qml b/view_models/BabeGrid/BabeGrid.qml index f956118..8c4579b 100644 --- a/view_models/BabeGrid/BabeGrid.qml +++ b/view_models/BabeGrid/BabeGrid.qml @@ -1,96 +1,100 @@ import QtQuick.Controls 2.2 import QtQuick 2.9 import ".." import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import AlbumsList 1.0 Maui.Page { id: gridPage property int albumCoverSize: Math.min(160, width * 0.3) property int albumCoverRadius : Maui.Style.radiusV property bool albumCardVisible : true property alias list: _albumsList property alias listModel: _albumsModel property alias grid: grid property alias holder: grid.holder property alias count: grid.count signal albumCoverClicked(string album, string artist) signal albumCoverPressed(string album, string artist) signal bgClicked() MouseArea { anchors.fill: parent onClicked: bgClicked() } Albums { id: _albumsList } Maui.BaseModel { id: _albumsModel list: _albumsList } Maui.GridView { id: grid onAreaClicked: bgClicked() adaptContent: true anchors.fill: parent topMargin: Maui.Style.space.big itemSize: albumCoverSize holder.visible: count === 0 model: _albumsModel delegate: Item { height: grid.cellHeight width: grid.cellWidth property bool isCurrentItem: GridView.isCurrentItem BabeAlbum { id: albumDelegate anchors.centerIn: parent albumRadius: albumCoverRadius albumCard: albumCardVisible padding: Maui.Style.space.small height: parent.height width: height isCurrentItem: parent.isCurrentItem + label1.text: model.album ? model.album : model.artist + label2.text: model.artist && model.album ? model.artist : "" + image.source: model.artwork ? model.artwork : "qrc:/assets/cover.png" + Connections { target: albumDelegate onClicked: { var album = _albumsList.get(index).album var artist = _albumsList.get(index).artist albumCoverClicked(album, artist) grid.currentIndex = index } onPressAndHold: { var album = grid.model.get(index).album var artist = grid.model.get(index).artist albumCoverPressed(album, artist) } } } } } } diff --git a/view_models/BabeTable/BabeTable.qml b/view_models/BabeTable/BabeTable.qml index 5a2c07d..5eaaba4 100644 --- a/view_models/BabeTable/BabeTable.qml +++ b/view_models/BabeTable/BabeTable.qml @@ -1,454 +1,453 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 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 // cacheBuffer : 300 property alias list : _tracksList property alias listModel : _tracksModel property alias removeDialog : _removeDialog property bool trackNumberVisible property bool quickPlayVisible : true property bool coverArtVisible : false property bool menuItemVisible : isMobile property bool trackDuration property bool trackRating property bool allowMenu: true property bool isArtworkRemote : false property bool showIndicator : false 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 rowPressed(int index) signal quickPlayTrack(int index) signal queueTrack(int index) signal artworkDoubleClicked(int index) signal playAll() signal appendAll() - // altToolBars: true - onGroupChanged: groupBy() focus: true - //headBar.middleStrech: false - headBar.rightSretch: false 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 + onTriggered: + { + group = !group + groupBy() + } } } ] - 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) 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 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() } model: _tracksModel // property alias animBabe: delegate.animBabe delegate: TableDelegate { id: delegate width: listView.width number : trackNumberVisible ? true : false quickPlay: quickPlayVisible coverArt : coverArtVisible ? (babeTableRoot.width > 300) : coverArtVisible trackDurationVisible : trackDuration trackRatingVisible : trackRating menuItem: menuItemVisible remoteArtwork: isArtworkRemote playingIndicator: showIndicator onPressAndHold: if(isMobile && allowMenu) openItemMenu(index) onRightClicked: if(allowMenu) openItemMenu(index) onClicked: { currentIndex = index if(selectionMode) { H.addToSelection(list.get(listView.currentIndex)) return } if(isMobile) rowClicked(index) } onDoubleClicked: { currentIndex = index if(!isMobile) rowClicked(index) } onPlay: { currentIndex = index quickPlayTrack(index) } onArtworkCoverClicked: { currentIndex = index goToAlbum() } } 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() { root.currentView = viewsIndex.albums var item = list.get(listView.currentIndex) albumsView.populateTable(item.album, item.artist) contextMenu.close() } function goToArtist() { root.currentView = viewsIndex.artists var 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 10eed26..c271ce0 100644 --- a/widgets/CloudView/CloudView.qml +++ b/widgets/CloudView/CloudView.qml @@ -1,88 +1,275 @@ 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 CloudList 1.0 -BabeTable +Maui.Page { id: control - - headBarExit: false - property alias list : _cloudList Maui.BaseModel { id: _cloudModel list: _cloudList } - CloudList + Cloud { id: _cloudList - account: currentAccount } - model: _cloudModel + headBar.leftContent: [ - delegate: TableDelegate - { - id: delegate + 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() + }] - width: listView.width - number : false - quickPlay: true - coverArt : false - trackDurationVisible : false - trackRatingVisible : false - menuItem: false - remoteArtwork: false - playingIndicator: false + headBar.rightContent: [ - onPressAndHold: if(isMobile && allowMenu) openItemMenu(index) - onRightClicked: if(allowMenu) openItemMenu(index) + ToolButton + { + icon.name: "item-select" + onClicked: selectionMode = !selectionMode + checkable: false + checked: selectionMode + }, - onClicked: + Maui.ToolButtonMenu { - currentIndex = index - if(selectionMode) + 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 { - H.addToSelection(listView.model.get(listView.currentIndex)) - return + text: qsTr("Release date") + checkable: true + checked: list.sortBy === Cloud.RELEASEDATE + onTriggered: list.sortBy = Cloud.RELEASEDATE + autoExclusive: true } - if(isMobile) - rowClicked(index) + 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 + onTriggered: group = !group + } } + ] + - onDoubleClicked: + Maui.ListBrowser + { + id: _listView + anchors.fill: parent + clip: true + holder.visible: count === 0 + topMargin: Maui.Style.space.medium + model: _cloudModel + section.property: "artist" + section.criteria: ViewSection.FullString + section.delegate: Maui.LabelDelegate { - currentIndex = index - if(!isMobile) - rowClicked(index) + id: _sectionDelegate + label: section + isSection: true + width: parent.width + Kirigami.Theme.backgroundColor: "#333" + Kirigami.Theme.textColor: "#fafafa" + + background: Rectangle + { + color: Kirigami.Theme.backgroundColor + } } - onPlay: + listView.header: Rectangle { - currentIndex = index - if(Maui.FM.fileExists("file://" + _cloudList.get(index).thumbnail)) + Kirigami.Theme.inherit: false + width: parent.width + height: 150 + z: _listView.listView.z+999 + color: Kirigami.Theme.backgroundColor + ListView { - quickPlayTrack(index) - }else - { - _cloudList.requestFile(index) + 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" + } } } - onArtworkCoverClicked: + listView.headerPositioning: ListView.PullBackHeader + + + delegate: TableDelegate { - currentIndex = index - goToAlbum() + id: delegate + + width: parent.width + + number : false + quickPlay: true + coverArt : false + trackDurationVisible : false + trackRatingVisible : false + menuItem: false + remoteArtwork: false + playingIndicator: false + +// onPressAndHold: if(isMobile && allowMenu) openItemMenu(index) +// onRightClicked: if(allowMenu) openItemMenu(index) + + 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() +// } } + } + }