diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c111b..646e07a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,91 +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/local/socket.cpp services/web/youtube.cpp - services/web/NextCloud/nextmusic.cpp - services/web/abstractmusicprovider.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 +# 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/main.cpp b/main.cpp index 48fb122..9882cd9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,127 +1,123 @@ #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] -// QGuiApplication::styleHints()->setMouseDoubleClickInterval(250); 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, "Cloud"); +// 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 6118d16..c0a9822 100644 --- a/main.qml +++ b/main.qml @@ -1,857 +1,857 @@ import QtQuick 2.10 import QtQuick.Controls 2.10 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 PlaylistsList 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.listModel.get(0).artwork : "" property bool currentBabe: currentTrack.fav == "0" ? false : true property alias durationTimeLabel: player.duration property string progressTimeLabel: player.transformTime((player.duration/1000) *(player.pos/ 1000)) property alias isPlaying: player.playing property int onQueue: 0 property bool mainlistEmpty: !mainPlaylist.table.count > 0 /***************************************************/ /******************** HANDLERS ********************/ /*************************************************/ readonly property var viewsIndex: ({ tracks: 0, albums: 1, artists: 2, playlists: 3, folders: 4, cloud : 5, youtube: 6, search: 7}) property string syncPlaylist: "" property bool sync: false property bool focusView : false property bool selectionMode : false /***************************************************/ /******************** UI COLORS *******************/ /*************************************************/ readonly property color babeColor: "#f84172" /*SIGNALS*/ signal missingAlert(var track) /*HANDLE EVENTS*/ onClosing: Player.savePlaylist() onMissingAlert: { var message = qsTr("Missing file...") var messageBody = track.title + " by " + track.artist + " is missing.\nDo you want to remove it from your collection?" notify("dialog-question", message, messageBody, function () { mainPlaylist.list.remove(mainPlaylist.table.currentIndex) }) } /*COMPONENTS*/ Player { id: player volume: 100 onFinishedChanged: if (!mainlistEmpty) { if (currentTrack.url) mainPlaylist.list.countUp(currentTrackIndex) Player.nextTrack() } } FloatingDisk { id: _floatingDisk opacity: 1 - _drawer.position } headBar.middleContent : Maui.ActionGroup { id: _actionGroup // Layout.fillWidth: true Layout.fillHeight: true Layout.minimumWidth: implicitWidth currentIndex : swipeView.currentIndex onCurrentIndexChanged: swipeView.currentIndex = currentIndex hiddenActions: [ Action { text: qsTr("Folders") icon.name: "folder" }, - Action - { - text: qsTr("Cloud") - icon.name: "folder-cloud" - }, +// Action +// { +// text: qsTr("Cloud") +// icon.name: "folder-cloud" +// }, Action { text: qsTr("YouTube") icon.name: "internet-services" } ] Action { icon.name: "view-media-track" text: qsTr("Tracks") } Action { text: qsTr("Albums") icon.name: /*"album"*/ "view-media-album-cover" } Action { text: qsTr("Artists") icon.name: "view-media-artist" } Action { text: qsTr("Playlists") icon.name: "view-media-playlist" } } onSearchButtonClicked: { _actionGroup.currentIndex = viewsIndex.search // searchView.searchInput.forceActiveFocus() } Loader { id: _dialogLoader } InfoView { id: infoView maxWidth: parent.width * 0.8 maxHeight: parent.height * 0.9 } Loader { id: _focusViewLoader active: focusView source: "widgets/FocusView.qml" } Component { id: _shareDialogComponent Maui.ShareDialog {} } Component { id: _fmDialogComponent Maui.FileDialog { } } SourcesDialog { id: sourcesDialog } mainMenu: [ // Maui.MenuItem // { // text: "Vvave Stream" // icon.name: "headphones" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.vvave // } // }, // Maui.MenuItem // { // text: qsTr("Linking") // icon.name: "view-links" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.linking // if(!isLinked) linkingView.linkingConf.open() // } // }, // Maui.MenuItem // { // text: qsTr("Cloud") // icon.name: "folder-cloud" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.cloud // } // }, // Maui.MenuItem // { // text: qsTr("Spotify") // icon.name: "internet-services" // onTriggered: // { // pageStack.currentIndex = 1 // currentView = viewsIndex.spotify // } // }, MenuSeparator{}, MenuItem { text: qsTr("Sources") icon.name: "folder-add" onTriggered: sourcesDialog.open() }, MenuItem { text: qsTr("Open") icon.name: "folder-add" onTriggered: { _dialogLoader.sourceComponent = _fmDialogComponent root.dialog.settings.onlyDirs = false root.dialog.currentPath = "file:///home/camilo/Music" root.dialog.settings.filterType = Maui.FMList.AUDIO console.log("SETTIGN FILTER TYPE FISR", 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") // } // } // } ] Playlists { id: playlistsList } PlaylistDialog { id: playlistDialog } sideBar: Maui.AbstractSideBar { id: _drawer focus: true width: visible ? Math.min(Kirigami.Units.gridUnit * (Kirigami.Settings.isMobile? 18 : 15), root.width) : 0 modal: !isWide closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent height: _drawer.modal ? implicitHeight - _mainPage.footer.height : implicitHeight MainPlaylist { id: mainPlaylist anchors.fill: parent Connections { target: mainPlaylist onCoverPressed: Player.appendAll(tracks) onCoverDoubleClicked: Player.playAll(tracks) } } } Maui.Page { id: _mainPage anchors.fill: parent footer: ColumnLayout { id: _footerLayout visible: !mainlistEmpty || isPlaying height: visible ? Maui.Style.toolBarHeight * 1.2 : 0 width: parent.width spacing: 0 Kirigami.Separator { Layout.fillWidth: true } Slider { id: progressBar Layout.preferredHeight: Maui.Style.unit * (Kirigami.Settings.isMobile ? 6 : 8) Layout.fillWidth: true padding: 0 from: 0 to: 1000 value: player.pos spacing: 0 focus: true onMoved: { player.pos = value } background: Rectangle { implicitWidth: progressBar.width implicitHeight: progressBar.height width: progressBar.availableWidth height: implicitHeight color: "transparent" Rectangle { width: progressBar.visualPosition * parent.width height: progressBar.height color: Kirigami.Theme.highlightColor } } handle: Rectangle { x: progressBar.leftPadding + progressBar.visualPosition * (progressBar.availableWidth - width) y: -(progressBar.height * 0.8) implicitWidth: progressBar.pressed ? Maui.Style.iconSizes.medium : 0 implicitHeight: progressBar.pressed ? Maui.Style.iconSizes.medium : 0 radius: progressBar.pressed ? Maui.Style.iconSizes.medium : 0 color: Kirigami.Theme.highlightColor } } Maui.ToolBar { Layout.fillHeight: true Layout.fillWidth: true position: ToolBar.Footer middleContent: [ ToolButton { id: babeBtnIcon icon.name: "love" enabled: currentTrackIndex >= 0 icon.color: currentBabe ? babeColor : Kirigami.Theme.textColor onClicked: if (!mainlistEmpty) { mainPlaylist.list.fav(currentTrackIndex, !(mainPlaylist.listModel.get(currentTrackIndex).fav == "1")) currentBabe = mainPlaylist.listModel.get(currentTrackIndex).fav == "1" } }, ToolButton { icon.name: "media-skip-backward" icon.color: Kirigami.Theme.textColor onClicked: Player.previousTrack() onPressAndHold: Player.playAt(prevTrackIndex) }, ToolButton { id: playIcon enabled: currentTrackIndex >= 0 icon.color: Kirigami.Theme.textColor icon.name: isPlaying ? "media-playback-pause" : "media-playback-start" onClicked: player.playing = !player.playing }, ToolButton { id: nextBtn icon.color: Kirigami.Theme.textColor icon.name: "media-skip-forward" onClicked: Player.nextTrack() onPressAndHold: Player.playAt(Player.shuffle()) }, ToolButton { id: shuffleBtn icon.color: babeColor icon.name: isShuffle ? "media-playlist-shuffle" : "media-playlist-normal" onClicked: { isShuffle = !isShuffle Maui.FM.saveSettings("SHUFFLE", isShuffle, "PLAYBACK") } } ] } } ColumnLayout { anchors.fill: parent SwipeView { id: swipeView Layout.fillHeight: true Layout.fillWidth: true // interactive: Kirigami.Settings.isMobile currentIndex: _actionGroup.currentIndex onCurrentIndexChanged: _actionGroup.currentIndex = currentIndex clip: true onCurrentItemChanged: currentItem.forceActiveFocus() TracksView { id: tracksView Connections { target: vvave onRefreshTables: tracksView.list.refresh() } Connections { target: tracksView onRowClicked: Player.quickPlay(tracksView.listModel.get(index)) onQuickPlayTrack: Player.quickPlay(tracksView.listModel.get(index)) onAppendTrack: Player.addTrack(tracksView.listModel.get(index)) onPlayAll: Player.playAll( tracksView.listModel.getAll()) onAppendAll: Player.appendAll( tracksView.listModel.getAll()) onQueueTrack: Player.queueTracks([tracksView.listModel.get(index)], index) } } Loader { active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem || item sourceComponent: 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: vvave onRefreshTables: albumsView.list.refresh() } Connections { target: albumsView onRowClicked: Player.quickPlay(track) onAppendTrack: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: albumsView.populateTable(album, artist) onAlbumCoverPressedAndHold: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAt(0) } onPlayAll: Player.playAll(albumsView.listModel.getAll()) onAppendAll: Player.appendAll(albumsView.listModel.getAll()) } } } Loader { active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem || item sourceComponent: 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: vvave onRefreshTables: artistsView.list.refresh() } Connections { target: artistsView onRowClicked: Player.quickPlay(track) onAppendTrack: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: artistsView.populateTable(undefined, artist) onAlbumCoverPressedAndHold: { var query = Q.GET.artistTracks_.arg(artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAt(0) } onPlayAll: Player.artistsView(albumsView.listModel.getAll()) onAppendAll: Player.artistsView(albumsView.listModel.getAll()) } } } Loader { active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem || item sourceComponent: PlaylistsView { id: playlistsView Connections { target: playlistsView onRowClicked: Player.quickPlay(track) onAppendTrack: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAppendAll: Player.appendAll(playlistsView.listModel.getAll()) onSyncAndPlay: { Player.playAll(playlistsView.listModel.getAll()) root.sync = true root.syncPlaylist = playlist } onPlayAll: Player.playAll(playlistsView.listModel.getAll()) } } } Loader { active: SwipeView.isCurrentItem || item sourceComponent: FoldersView { id: foldersView Connections { target: vvave onRefreshTables: foldersView.populate() } Connections { target: foldersView.list onRowClicked: Player.quickPlay(foldersView.list.model.get(index)) onQuickPlayTrack: Player.quickPlay(foldersView.list.model.get(index)) onAppendTrack: Player.addTrack(foldersView.listModel.get(index)) onPlayAll: Player.playAll(foldersView.listModel.getAll()) onAppendAll: Player.appendAll(foldersView.listModel.getAll()) onQueueTrack: Player.queueTracks([foldersView.list.model.get(index)], index) } } } - Loader - { - active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem || item - sourceComponent: CloudView - { - id: cloudView - } - } +// Loader +// { +// active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem || item +// sourceComponent: CloudView +// { +// id: cloudView +// } +// } Loader { active: SwipeView.isCurrentItem || item sourceComponent: YouTube { id: youtubeView } } Loader { active: SwipeView.isCurrentItem || (item && item.listView.count > 0) sourceComponent: SearchTable { id: searchView Connections { target: searchView onRowClicked: Player.quickPlay(searchView.listModel.get(index)) onQuickPlayTrack: Player.quickPlay(searchView.listModel.get(index)) onAppendTrack: Player.addTrack(searchView.listModel.get(index)) onPlayAll: Player.playAll(searchView.listModel.getAll()) onAppendAll: Player.appendAll(searchView.listModel.getAll()) onArtworkDoubleClicked: { var query = Q.GET.albumTracks_.arg( searchView.listModel.get( index).album) query = query.arg(searchView.listModel.get(index).artist) mainPlaylist.list.clear() mainPlaylist.list.sortBy = Tracks.NONE mainPlaylist.list.query = query Player.playAt(0) } } } } } 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 } } } } /*CONNECTIONS*/ Connections { target: vvave onRefreshTables: { if(size>0) root.notify("emblem-info", "Collection updated", size+" new tracks added...") } // onRefreshTracks: H.refreshTracks() // onRefreshAlbums: H.refreshAlbums() // onRefreshArtists: H.refreshArtists() // onCoverReady: // { // root.currentArtwork = path // currentTrack.artwork = currentArtwork // mainPlaylist.list.update(currentTrack, currentTrackIndex); // } // onTrackLyricsReady: // { // console.log(lyrics) // if (url === currentTrack.url) // Player.setLyrics(lyrics) // } // onSkipTrack: Player.nextTrack() // onBabeIt: if (!mainlistEmpty) // { // mainPlaylist.list.fav(currentTrackIndex, !(mainPlaylist.list.get(currentTrackIndex).fav == "1")) // currentBabe = mainPlaylist.list.get(currentTrackIndex).fav == "1" // } onOpenFiles: { Player.appendTracksAt(tracks, 0) Player.playAt(0) } } Component.onCompleted: { if(isAndroid) { Maui.Android.statusbarColor(Kirigami.Theme.backgroundColor, true) Maui.Android.navBarColor(Kirigami.Theme.backgroundColor, true) } } } diff --git a/models/albums/albumsmodel.cpp b/models/albums/albumsmodel.cpp index 8d5ec5f..964c325 100644 --- a/models/albums/albumsmodel.cpp +++ b/models/albums/albumsmodel.cpp @@ -1,288 +1,288 @@ #include "albumsmodel.h" #include "db/collectionDB.h" #include "utils/brain.h" #include AlbumsModel::AlbumsModel(QObject *parent) : MauiList(parent), db(CollectionDB::getInstance()) {} void AlbumsModel::componentComplete() { this->setList(); connect(this, &AlbumsModel::queryChanged, this, &AlbumsModel::setList); } FMH::MODEL_LIST AlbumsModel::items() const { return this->list; } void AlbumsModel::setQuery(const QUERY &query) { if(this->query == query) return; this->query = query; emit this->queryChanged(); } AlbumsModel::QUERY AlbumsModel::getQuery() const { return this->query; } void AlbumsModel::setSortBy(const SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; emit this->preListChanged(); this->sortList(); emit this->postListChanged(); emit this->sortByChanged(); } AlbumsModel::SORTBY AlbumsModel::getSortBy() const { return this->sort; } void AlbumsModel::sortList() { const auto key = static_cast(this->sort); qDebug()<< "SORTING LIST BY"<< this->sort; std::sort(this->list.begin(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { const auto role = key; switch(role) { case FMH::MODEL_KEY::RELEASEDATE: { if(e1[role].toDouble() > e2[role].toDouble()) return true; break; } case FMH::MODEL_KEY::ADDDATE: { const auto date1 = QDateTime::fromString(e1[role], Qt::TextDate); const auto date2 = QDateTime::fromString(e2[role], Qt::TextDate); if(date1.secsTo(QDateTime::currentDateTime()) < date2.secsTo(QDateTime::currentDateTime())) return true; break; } case FMH::MODEL_KEY::ARTIST: case FMH::MODEL_KEY::ALBUM: { const auto str1 = QString(e1[role]).toLower(); const auto str2 = QString(e2[role]).toLower(); if(str1 < str2) return true; break; } default: if(e1[role] < e2[role]) return true; } return false; }); } void AlbumsModel::setList() { emit this->preListChanged(); QString m_Query; if(this->query == AlbumsModel::QUERY::ALBUMS) m_Query = "select * from albums order by album asc"; else if(this->query == AlbumsModel::QUERY::ARTISTS) m_Query = "select * from artists order by artist asc"; //get albums data with modifier for missing images for artworks this->list = this->db->getDBData(m_Query, [&](FMH::MODEL &item) { if(item[FMH::MODEL_KEY::ARTWORK].isEmpty()) return; if(!FMH::fileExists(item[FMH::MODEL_KEY::ARTWORK])) { const auto table = this->query == AlbumsModel::QUERY::ALBUMS ? "albums" : "artists"; this->db->removeArtwork(table, FMH::toMap(item)); item[FMH::MODEL_KEY::ARTWORK] = ""; }}); this->sortList(); emit this->postListChanged(); -if(this->query == AlbumsModel::QUERY::ALBUMS) -this->fetchInformation(); +//if(this->query == AlbumsModel::QUERY::ALBUMS) +//this->fetchInformation(); } void AlbumsModel::fetchInformation() { qDebug() << "RNUNGING BRAIN EFFORRTS"; QFutureWatcher *watcher = new QFutureWatcher; QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater); // QObject::connect(this, &AlbumsModel::destroyed, watcher, &QFutureWatcher::waitForFinished); auto func = [&, stop = bool()]() mutable { stop = false; QList requests; int index = -1; for(auto &album : this->list) { index++; if(!album[FMH::MODEL_KEY::ARTWORK].isEmpty()) continue; if(BAE::artworkCache(album, FMH::MODEL_KEY::ALBUM)) { this->db->insertArtwork(album); emit this->updateModel(index, {FMH::MODEL_KEY::ARTWORK}); continue; } PULPO::REQUEST request; request.track = album; request.ontology = this->query == AlbumsModel::QUERY::ALBUMS ? PULPO::ONTOLOGY::ALBUM : PULPO::ONTOLOGY::ARTIST; request.services = {PULPO::SERVICES::LastFm, PULPO::SERVICES::Spotify, PULPO::SERVICES::MusicBrainz}; request.info = {PULPO::INFO::ARTWORK}; request.callback = [&, index](PULPO::REQUEST request, PULPO::RESPONSES responses) { qDebug() << "DONE WITH " << request.track ; for(const auto &res : responses) { if(res.context == PULPO::CONTEXT::IMAGE && !res.value.toString().isEmpty()) { auto downloader = new FMH::Downloader; QObject::connect(downloader, &FMH::Downloader::fileSaved, [&, index, request, _downloader = std::move(downloader)](QString path) { FMH::MODEL newTrack = request.track; newTrack[FMH::MODEL_KEY::ARTWORK] = QUrl::fromLocalFile(path).toString(); this->db->insertArtwork(newTrack); // this->updateArtwork(index, path); album[FMH::MODEL_KEY::ARTWORK] = newTrack[FMH::MODEL_KEY::ARTWORK]; emit this->updateModel(index, {FMH::MODEL_KEY::ARTWORK}); _downloader->deleteLater(); }); QStringList filePathList = res.value.toString().split('/'); const auto format = "." + filePathList.at(filePathList.count() - 1).split(".").last(); QString name = !request.track[FMH::MODEL_KEY::ALBUM].isEmpty() ? request.track[FMH::MODEL_KEY::ARTIST] + "_" + request.track[FMH::MODEL_KEY::ALBUM] : request.track[FMH::MODEL_KEY::ARTIST]; name.replace("/", "-"); name.replace("&", "-"); downloader->setFile(res.value.toString(), BAE::CachePath + name + format); qDebug()<<"SAVING ARTWORK FOR: " << request.track[FMH::MODEL_KEY::ALBUM]<< BAE::CachePath + name + format; } } }; requests << request; } Pulpo pulpo; QEventLoop loop; QObject::connect(&pulpo, &Pulpo::finished, &loop, &QEventLoop::quit); QObject::connect(this, &AlbumsModel::destroyed, [&pulpo, &loop, &stop]() { qDebug()<< stop << &stop; pulpo.disconnect(); stop = true; if(loop.isRunning()) loop.quit(); qDebug()<< stop << &stop; }); for(const auto &req : requests) { if(stop) { qDebug()<< "TRYING EXITING THREAD LOADINFO" << loop.isRunning(); qDebug()<< "EXITING THREAD LOADINFO" << loop.isRunning(); break; }else { pulpo.request(req); loop.exec(); } } qDebug()<< "DISCONNET SIGNAL"; // disconnect(this, SIGNAL(destroyed())); }; QFuture t1 = QtConcurrent::run(func); watcher->setFuture(t1); } void AlbumsModel::updateArtwork(const int index, const QString &artwork) { if(index >= this->list.size() || index < 0) return; this->list[index][FMH::MODEL_KEY::ARTWORK] = artwork; emit this->updateModel(index, {FMH::MODEL_KEY::ARTWORK}); } QVariantMap AlbumsModel::get(const int &index) const { if(index >= this->list.size() || index < 0) return QVariantMap(); return FMH::toMap(this->list.at(index)); } void AlbumsModel::append(const QVariantMap &item) { if(item.isEmpty()) return; emit this->preItemAppended(); FMH::MODEL model; for(auto key : item.keys()) model.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); this->list << model; emit this->postItemAppended(); } void AlbumsModel::append(const QVariantMap &item, const int &at) { if(item.isEmpty()) return; if(at > this->list.size() || at < 0) return; qDebug()<< "trying to append at" << at << item["title"]; emit this->preItemAppendedAt(at); FMH::MODEL model; for(auto key : item.keys()) model.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); this->list.insert(at, model); emit this->postItemAppended(); } void AlbumsModel::refresh() { this->setList(); } diff --git a/vvave.pro b/vvave.pro index 8c3dcfb..cb54853 100644 --- a/vvave.pro +++ b/vvave.pro @@ -1,114 +1,114 @@ QT += quick QT += multimedia QT += sql QT += websockets QT += network QT += xml QT += qml QT += widgets QT += quickcontrols2 QT += concurrent TARGET = vvave TEMPLATE = app CONFIG += ordered CONFIG += c++11 QMAKE_LINK += -nostdlib++ linux:unix:!android { message(Building for Linux KDE) include($$PWD/kde/kde.pri) LIBS += -lMauiKit } else:android { message(Building helpers for Android) #DEFAULT COMPONENTS DEFINITIONS DEFINES *= \ COMPONENT_EDITOR \ - COMPONENT_ACCOUNTS \ +# COMPONENT_ACCOUNTS \ COMPONENT_FM \ COMPONENT_TERMINAL \ COMPONENT_TAGGING \ COMPONENT_SYNCING QT *= androidextras webview include($$PWD/3rdparty/taglib.pri) include($$PWD/3rdparty/kirigami/kirigami.pri) include($$PWD/3rdparty/mauikit/mauikit.pri) DEFINES += STATIC_KIRIGAMI } else { message("Unknown configuration") } include(pulpo/pulpo.pri) # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += main.cpp \ db/collectionDB.cpp \ services/local/taginfo.cpp \ services/local/player.cpp \ # utils/brain.cpp \ # services/local/socket.cpp \ services/web/youtube.cpp \ vvave.cpp \ services/local/youtubedl.cpp \ # services/local/linking.cpp \ - services/web/Spotify/spotify.cpp \ +# services/web/Spotify/spotify.cpp \ models/tracks/tracksmodel.cpp \ models/playlists/playlistsmodel.cpp \ models/albums/albumsmodel.cpp \ - services/web/NextCloud/nextmusic.cpp \ - services/web/abstractmusicprovider.cpp \ - models/cloud/cloud.cpp +# services/web/NextCloud/nextmusic.cpp \ +# services/web/abstractmusicprovider.cpp \ +# models/cloud/cloud.cpp RESOURCES += qml.qrc \ # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = # Additional import path used to resolve QML modules just for Qt Quick Designer QML_DESIGNER_IMPORT_PATH = HEADERS += \ db/collectionDB.h \ utils/bae.h \ services/local/fileloader.h \ services/local/taginfo.h \ services/local/player.h \ # utils/brain.h \ # services/local/socket.h \ services/web/youtube.h \ vvave.h \ services/local/youtubedl.h \ # services/local/linking.h \ - services/web/Spotify/spotify.h \ +# services/web/Spotify/spotify.h \ models/tracks/tracksmodel.h \ models/playlists/playlistsmodel.h \ models/albums/albumsmodel.h \ - services/web/NextCloud/nextmusic.h \ - services/web/abstractmusicprovider.h \ - models/cloud/cloud.h +# services/web/NextCloud/nextmusic.h \ +# services/web/abstractmusicprovider.h \ +# models/cloud/cloud.h -INCLUDEPATH += \ - $$PWD/services/web \ - $$PWD/services/web/NextCloud +#INCLUDEPATH += \ +# $$PWD/services/web \ +# $$PWD/services/web/NextCloud include(install.pri) ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android_files diff --git a/widgets/MainPlaylist/MainPlaylist.qml b/widgets/MainPlaylist/MainPlaylist.qml index a1c0fc7..ce4dba8 100644 --- a/widgets/MainPlaylist/MainPlaylist.qml +++ b/widgets/MainPlaylist/MainPlaylist.qml @@ -1,203 +1,208 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.2 as Kirigami import org.kde.mauikit 1.0 as Maui import "../../utils/Player.js" as Player import "../../db/Queries.js" as Q import "../../utils" import "../../widgets" import "../../view_models" import "../../view_models/BabeTable" Maui.Page { id: mainPlaylistRoot property alias list : table.list property alias listModel: table.listModel property alias listView : table.listView property alias table: table property alias menu : playlistMenu property alias contextMenu: table.contextMenu signal coverDoubleClicked(var tracks) signal coverPressed(var tracks) focus: true headBar.visible: false PlaylistMenu { id: playlistMenu onClearOut: Player.clearOutPlaylist() onClean: Player.cleanPlaylist() onSaveToClicked: table.saveList() } footer: AlbumsRoll { id: _albumsRoll width: table.width position: ToolBar.Footer } BabeTable { id: table anchors.fill: parent focus: true headBar.visible: false footBar.visible: false coverArtVisible: true holder.emoji: "qrc:/assets/dialog-information.svg" holder.isMask: false holder.title : "Meh!" holder.body: "Start putting together your playlist!" holder.emojiSize: Maui.Style.iconSizes.huge onRowClicked: play(index) showQuickActions: false listView.header: Maui.ToolBar { Kirigami.Theme.inherit: false z: table.z +999 width: table.width visible: table.list.count > 0 rightContent: ToolButton { icon.name: "edit-clear" onClicked: { player.stop() mainPlaylist.table.list.clear() root.sync = false root.syncPlaylist = "" } } + middleContent: Label + { + text: qsTr("Now playing") + } + leftContent: ToolButton { icon.name: "document-save" onClicked: mainPlaylist.table.saveList() } } listView.footer: Rectangle { visible: root.sync Kirigami.Theme.inherit: false Kirigami.Theme.colorSet:Kirigami.Theme.Complementary z: table.z + 999 width: table.width height: Maui.Style.rowHeightAlt color: Kirigami.Theme.backgroundColor RowLayout { anchors.fill: parent anchors.leftMargin: Maui.Style.space.small Label { Layout.fillWidth: true Layout.fillHeight: true anchors.margins: Maui.Style.space.small text: qsTr("Syncing to ") + root.syncPlaylist } ToolButton { Layout.fillHeight: true icon.name: "dialog-close" onClicked: { root.sync = false root.syncPlaylist = "" } } } } onArtworkDoubleClicked: contextMenu.babeIt(index) property int startContentY Component.onCompleted: { var lastplaylist = Maui.FM.loadSettings("LASTPLAYLIST", "PLAYLIST", []) var n = lastplaylist.length if(n>0) { 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) } }