diff --git a/main.cpp b/main.cpp index 7f33c38..e337c3a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,144 +1,147 @@ #include #include #include #include #include #ifdef STATIC_KIRIGAMI #include "3rdparty/kirigami/src/kirigamiplugin.h" #endif #ifdef STATIC_MAUIKIT #include "3rdparty/mauikit/src/mauikit.h" #include "fmstatic.h" #include "mauiapp.h" #else #include #include #endif #if defined Q_OS_ANDROID || defined Q_OS_IOS #include #include #else #include #endif #ifdef Q_OS_ANDROID #include "mauiandroid.h" #endif #ifdef Q_OS_MACOS #include "mauimacos.h" #endif #include "vvave.h" #include "utils/bae.h" #include "services/web/youtube.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" #ifdef Q_OS_ANDROID Q_DECL_EXPORT #endif int main(int argc, char *argv[]) { - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #ifdef Q_OS_WIN32 - qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "w"); + qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "w"); #endif #if defined Q_OS_ANDROID | defined Q_OS_IOS - QGuiApplication app(argc, argv); + QGuiApplication app(argc, argv); #else - QApplication app(argc, argv); + QApplication app(argc, argv); #endif #ifdef Q_OS_ANDROID - if (!MAUIAndroid::checkRunTimePermissions({"android.permission.WRITE_EXTERNAL_STORAGE"})) - return -1; + if (!MAUIAndroid::checkRunTimePermissions({"android.permission.WRITE_EXTERNAL_STORAGE"})) + return -1; #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")); - - MauiApp::instance()->setCredits ({QVariantMap({{"name", "Camilo Higuita"}, {"email", "milo.h@aol.com"}, {"year", "2019-2020"}})}); - - QCommandLineParser parser; - parser.setApplicationDescription(BAE::description); - - const QCommandLineOption versionOption = parser.addVersionOption(); - parser.process(app); - - const QStringList args = parser.positionalArguments(); - static auto babe = new vvave; - static auto youtube = new YouTube; - // Spotify spotify; - - QFontDatabase::addApplicationFont(":/assets/materialdesignicons-webfont.ttf"); - - QQmlApplicationEngine engine; - const QUrl url(QStringLiteral("qrc:/main.qml")); - QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, - &app, [url, args](QObject *obj, const QUrl &objUrl) - { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - if(FMStatic::loadSettings("Settings", "ScanCollectionOnStartUp", true ).toBool()) - { - const auto currentSources = vvave::getSourceFolders(); - babe->scanDir(currentSources.isEmpty() ? BAE::defaultSources : currentSources); - } - - if(!args.isEmpty()) - babe->openUrls(args); - - }, Qt::QueuedConnection); - - qmlRegisterSingletonType("org.maui.vvave", 1, 0, "Vvave", - [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) - return babe; - }); - - qmlRegisterSingletonType("org.maui.vvave", 1, 0, "YouTube", - [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) - return youtube; - }); - - qmlRegisterType("TracksList", 1, 0, "Tracks"); - qmlRegisterType("PlaylistsList", 1, 0, "Playlists"); - qmlRegisterType("AlbumsList", 1, 0, "Albums"); - qmlRegisterType("CloudList", 1, 0, "Cloud"); - qmlRegisterType("Player", 1, 0, "Player"); + 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")); + + MauiApp::instance()->setCredits ({QVariantMap({{"name", "Camilo Higuita"}, {"email", "milo.h@aol.com"}, {"year", "2019-2020"}})}); + MauiApp::instance()->setIconName("qrc:/assets/vvave.svg"); + MauiApp::instance()->setDescription("VVAVE will handle your whole music collection by retreaving semantic information from the web. Just relax, enjoy and discover your new music "); + MauiApp::instance()->setWebPage("https://mauikit.org"); + MauiApp::instance()->setReportPage("https://invent.kde.org/maui/vvave/-/issues"); + + QCommandLineParser parser; + parser.setApplicationDescription(BAE::description); + + const QCommandLineOption versionOption = parser.addVersionOption(); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + static auto babe = new vvave; + static auto youtube = new YouTube; + + QFontDatabase::addApplicationFont(":/assets/materialdesignicons-webfont.ttf"); + + QQmlApplicationEngine engine; + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url, args](QObject *obj, const QUrl &objUrl) + { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + if(FMStatic::loadSettings("Settings", "ScanCollectionOnStartUp", true ).toBool()) + { + const auto currentSources = vvave::getSourceFolders(); + babe->scanDir(currentSources.isEmpty() ? BAE::defaultSources : currentSources); + } + + if(!args.isEmpty()) + babe->openUrls(args); + + }, Qt::QueuedConnection); + + qmlRegisterSingletonType("org.maui.vvave", 1, 0, "Vvave", + [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return babe; + }); + + qmlRegisterSingletonType("org.maui.vvave", 1, 0, "YouTube", + [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return youtube; + }); + + qmlRegisterType("TracksList", 1, 0, "Tracks"); + qmlRegisterType("PlaylistsList", 1, 0, "Playlists"); + qmlRegisterType("AlbumsList", 1, 0, "Albums"); + qmlRegisterType("CloudList", 1, 0, "Cloud"); + qmlRegisterType("Player", 1, 0, "Player"); #ifdef STATIC_KIRIGAMI - KirigamiPlugin::getInstance().registerTypes(); + KirigamiPlugin::getInstance().registerTypes(); #endif #ifdef STATIC_MAUIKIT - MauiKit::getInstance().registerTypes(); + MauiKit::getInstance().registerTypes(); #endif - engine.load(url); + engine.load(url); #ifdef Q_OS_MACOS -// MAUIMacOS::removeTitlebarFromWindow(); + // MAUIMacOS::removeTitlebarFromWindow(); #endif - return app.exec(); + return app.exec(); } diff --git a/main.qml b/main.qml index f5bf67d..ec49862 100644 --- a/main.qml +++ b/main.qml @@ -1,788 +1,774 @@ import QtQuick 2.10 import QtQuick.Controls 2.10 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import org.kde.mauikit 1.1 as MauiLab import org.maui.vvave 1.0 as Vvave import Player 1.0 import AlbumsList 1.0 import TracksList 1.0 import PlaylistsList 1.0 import "utils" import "widgets" import "widgets/PlaylistsView" import "widgets/MainPlaylist" import "widgets/SettingsView" import "widgets/SearchView" import "widgets/CloudView" import "view_models" import "view_models/BabeTable" import "services/local" import "services/web" import "view_models/BabeGrid" import "widgets/InfoView" import "db/Queries.js" as Q import "utils/Help.js" as H import "utils/Player.js" as Player Maui.ApplicationWindow { id: root title: currentTrack ? currentTrack.title + " - " + currentTrack.artist + " | " + currentTrack.album : "" /***************************************************/ /******************** 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 ") - background.opacity: translucency ? 0.5 : 1 + background.opacity: translucency ? 0.5 : 1 // floatingHeader: swipeView.currentIndex === viewsIndex.albums || swipeView.currentIndex === viewsIndex.artists // autoHideHeader: true floatingFooter: false /***************************************************/ /******************** PLAYBACK ********************/ /*************************************************/ property bool isShuffle: Maui.FM.loadSettings("SHUFFLE","PLAYBACK", false) property var currentTrack: mainPlaylist.listView.itemAtIndex(currentTrackIndex) property int currentTrackIndex: -1 property int prevTrackIndex: 0 readonly property string currentArtwork: currentTrack ? currentTrack.artwork : "" 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, cloud: 4, folders: 5, youtube: 6}) property string syncPlaylist: "" property bool sync: false property bool focusView : false property bool selectionMode : false /***************************************************/ /******************** UI COLORS *******************/ /*************************************************/ readonly property color babeColor: "#f84172" property bool translucency : Maui.Handy.isLinux /*SIGNALS*/ signal missingAlert(var track) // flickable: swipeView.currentItem.flickable || swipeView.currentItem.item.flickable footerPositioning: ListView.InlineFooter /*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 && currentTrack.url) mainPlaylist.list.countUp(currentTrackIndex) Player.nextTrack() } } headBar.visible: !focusView headBar.rightContent: ToolButton { visible: Maui.Handy.isTouch icon.name: "item-select" onClicked: selectionMode = !selectionMode checkable: false checked: selectionMode } Loader { id: _dialogLoader } InfoView { id: infoView maxWidth: parent.width * 0.8 maxHeight: parent.height * 0.9 } Loader { id: _focusViewLoader anchors.fill: parent active: focusView source: "widgets/FocusView.qml" } Component { id: _shareDialogComponent MauiLab.ShareDialog {} } Component { id: _fmDialogComponent Maui.FileDialog {} } Component { id: _settingsDialogComponent SettingsDialog {} } - SourcesDialog - { - id: sourcesDialog - } - FloatingDisk { id: _floatingDisk } mainMenu: [ MenuSeparator{}, MenuItem { text: qsTr("Settings") icon.name: "settings-configure" onTriggered: { _dialogLoader.sourceComponent = _settingsDialogComponent dialog.open() } }, - MenuItem - { - text: qsTr("Sources") - icon.name: "folder-add" - onTriggered: sourcesDialog.open() - }, - MenuSeparator{}, 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.Vvave.openUrls(paths) root.dialog.close() }) } } ] Playlists { id: playlistsList } PlaylistDialog { id: playlistDialog } sideBar: Maui.AbstractSideBar { id: _drawer width: visible ? Math.min(Kirigami.Units.gridUnit * 16, root.width) : 0 collapsed: !isWide collapsible: true dragMargin: Maui.Style.space.big overlay.visible: collapsed && position > 0 && visible Connections { target: _drawer.overlay onClicked: _drawer.close() } background: Rectangle { color: Kirigami.Theme.backgroundColor opacity: translucency ? 0.5 : 1 } MainPlaylist { id: mainPlaylist anchors.fill: parent Connections { target: mainPlaylist onCoverPressed: Player.appendAll(tracks) onCoverDoubleClicked: Player.playAll(tracks) } } } // autoHideFooter: true // autoHideFooterMargins: root.height * 0.2 // autoHideFooterDelay: 5000 footer: ItemDelegate { visible: !focusView width: parent.width height: visible ? _footerLayout.implicitHeight : 0 background: Item { Image { id: artworkBg height: parent.height width: parent.width sourceSize.width: 500 sourceSize.height: height fillMode: Image.PreserveAspectCrop antialiasing: true smooth: true asynchronous: true cache: true source: currentArtwork } FastBlur { id: fastBlur anchors.fill: parent source: artworkBg radius: 100 transparentBorder: false cached: true Rectangle { anchors.fill: parent color: Kirigami.Theme.backgroundColor opacity: 0.8 } } Kirigami.Separator { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right } } ColumnLayout { id: _footerLayout anchors.fill: parent spacing: 0 Maui.ToolBar { Layout.fillWidth: true preferredHeight: Maui.Style.toolBarHeightAlt * 0.8 position: ToolBar.Footer visible: isPlaying leftContent: Label { id: _label1 visible: text.length verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: progressTimeLabel elide: Text.ElideMiddle wrapMode: Text.NoWrap color: Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.default } middleContent: Item { Layout.fillHeight: true Layout.fillWidth: true Label { anchors.fill: parent visible: text.length verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: root.title elide: Text.ElideMiddle wrapMode: Text.NoWrap color: Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.default } } rightContent: Label { id: _label2 visible: text.length verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: player.transformTime(player.duration/1000) elide: Text.ElideMiddle wrapMode: Text.NoWrap color: Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.default opacity: 0.7 } background: Slider { id: progressBar padding: 0 from: 0 to: 1000 value: player.pos spacing: 0 focus: true onMoved: player.pos = value enabled: player.playing Kirigami.Separator { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right } background: Rectangle { implicitWidth: progressBar.width implicitHeight: progressBar.height width: progressBar.availableWidth height: implicitHeight color: "transparent" opacity: 0.4 Rectangle { width: progressBar.visualPosition * parent.width height: progressBar.height color: Kirigami.Theme.highlightColor } } handle: Rectangle { x: progressBar.leftPadding + progressBar.visualPosition * (progressBar.availableWidth - width) y: 0 implicitWidth: Maui.Style.iconSizes.medium implicitHeight: progressBar.height color: progressBar.pressed ? Qt.lighter(Kirigami.Theme.highlightColor, 1.2) : "transparent" } } } Maui.ToolBar { Layout.fillWidth: true Layout.preferredHeight: Maui.Style.toolBarHeight position: ToolBar.Footer background: Item {} rightContent: ToolButton { icon.name: _volumeSlider.value === 0 ? "player-volume-muted" : "player-volume" onPressAndHold : { player.volume = player.volume === 0 ? 100 : 0 } onClicked: { _sliderPopup.visible ? _sliderPopup.close() : _sliderPopup.open() } Popup { id: _sliderPopup height: 150 width: parent.width y: -150 x: 0 // closePolicy: Popup.CloseOnEscape | Popup.CloseOnPress Slider { id: _volumeSlider visible: true height: parent.height width: 20 anchors.horizontalCenter: parent.horizontalCenter from: 0 to: 100 value: player.volume orientation: Qt.Vertical onMoved: { player.volume = value } } } } middleContent: [ ToolButton { id: babeBtnIcon icon.name: "love" enabled: currentTrackIndex >= 0 checked: currentTrack ? Maui.FM.isFav(currentTrack.url) : false icon.color: checked ? babeColor : Kirigami.Theme.textColor onClicked: if (!mainlistEmpty) { mainPlaylist.list.fav(currentTrackIndex, !Maui.FM.isFav(currentTrack.url)) } }, Maui.ToolActions { expanded: true autoExclusive: false checkable: false Action { icon.name: "media-skip-backward" onTriggered: Player.previousTrack() // onPressAndHold: Player.playAt(prevTrackIndex) } //ambulatorios1@clinicaantioquia.com.co, copago martha hilda restrepo, cc 22146440 eps salud total, consulta expecialista urologo, hora 3:40 pm Action { id: playIcon text: qsTr("Play and pause") enabled: currentTrackIndex >= 0 icon.name: isPlaying ? "media-playback-pause" : "media-playback-start" onTriggered: player.playing = !player.playing } Action { text: qsTr("Next") icon.name: "media-skip-forward" onTriggered: 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") } } ] } } } Maui.Page { anchors.fill: parent visible: !focusView flickable: swipeView.currentItem.item.flickable MauiLab.AppViews { id: swipeView anchors.fill: parent MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("Tracks") MauiLab.AppView.iconName: "view-media-track" TracksView { id: 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) Connections { target: Vvave.Vvave onRefreshTables: tracksView.list.refresh() } } } MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("Albums") MauiLab.AppView.iconName: "view-media-album-cover" AlbumsView { id: albumsView holder.emoji: "qrc:/assets/dialog-information.svg" holder.isMask: false holder.title : qsTr("No Albums!") holder.body: qsTr("Add new music sources") holder.emojiSize: Maui.Style.iconSizes.huge list.query: Albums.ALBUMS list.sortBy: Albums.ALBUM 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()) Connections { target: Vvave.Vvave onRefreshTables: albumsView.list.refresh() } } } MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("Artists") MauiLab.AppView.iconName: "view-media-artist" AlbumsView { id: artistsView holder.emoji: "qrc:/assets/dialog-information.svg" holder.isMask: false holder.title : qsTr("No Artists!") holder.body: qsTr("Add new music sources") holder.emojiSize: Maui.Style.iconSizes.huge list.query: Albums.ARTISTS list.sortBy: Albums.ARTIST table.list.sortBy: Tracks.NONE 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.playAll(artistsView.listModel.getAll()) onAppendAll: Player.appendAll(artistsView.listModel.getAll()) Connections { target: Vvave.Vvave onRefreshTables: artistsView.list.refresh() } } } MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("Playlists") MauiLab.AppView.iconName: "view-media-playlist" PlaylistsView { id: 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()) } } MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("Cloud") MauiLab.AppView.iconName: "folder-cloud" CloudView { id: cloudView } } MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("Folders") MauiLab.AppView.iconName: "folder" FoldersView { id: foldersView Connections { target: Vvave.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) } } } MauiLab.AppViewLoader { MauiLab.AppView.title: qsTr("YouTube") MauiLab.AppView.iconName: "internet-services" YouTube { id: youtubeView } } } footer: SelectionBar { id: _selectionBar property alias listView: _selectionBar.selectionList anchors.horizontalCenter: parent.horizontalCenter width: Math.min(parent.width-(Maui.Style.space.medium*2), implicitWidth) padding: Maui.Style.space.big maxListHeight: swipeView.height - Maui.Style.space.medium onExitClicked: { root.selectionMode = false clear() } } } /*CONNECTIONS*/ Connections { target: Vvave.Vvave onRefreshTables: { if(size>0) root.notify("emblem-info", "Collection updated", size+" new tracks added...") } 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/qml.qrc b/qml.qrc index 0acddc6..8872cbd 100644 --- a/qml.qrc +++ b/qml.qrc @@ -1,59 +1,58 @@ main.qml widgets/TracksView.qml widgets/AlbumsView.qml assets/cover.png db/script.sql utils/Player.js db/Queries.js utils/Help.js assets/bars.gif view_models/BabeGrid/BabeAlbum.qml view_models/BabeGrid/BabeGrid.qml view_models/BabeTable/TableDelegate.qml view_models/BabeTable/BabeTable.qml view_models/BabeTable/TableMenu.qml view_models/ColorTagsBar.qml widgets/MainPlaylist/MainPlaylist.qml widgets/MainPlaylist/PlaylistMenu.qml widgets/PlaylistsView/PlaylistsView.qml widgets/InfoView/InfoView.qml widgets/InfoView/LyricsView.qml widgets/PlaylistsView/PlaylistsViewModel.qml - widgets/SettingsView/SourcesDialog.qml view_models/BabeTable/PlaylistDialog.qml view_models/BabeList.qml widgets/SearchView/SearchTable.qml widgets/SearchView/SearchSuggestions.qml services/web/YouTube.qml services/web/YoutubeViewer.qml services/web/YoutubeHelper.js services/local/LinkingDialog.qml services/local/LinkingView.qml services/local/LinkingListModel.qml assets/vvave.notifyrc assets/vvave.png widgets/MainPlaylist/AlbumsRoll.qml assets/heart_indicator.gif assets/heart_indicator_white.gif services/web/Spotify/Spotify.qml services/web/Spotify/LoginForm.qml services/web/Spotify/spotify.html widgets/FloatingDisk.qml widgets/FoldersView.qml assets/vvave.svg assets/materialdesignicons-webfont.ttf widgets/CloudView/CloudView.qml widgets/SelectionBar.qml widgets/FocusView.qml widgets/MiniMode.qml widgets/SettingsView/SettingsDialog.qml services/web/WebViewLinux.qml services/web/WebViewOther.qml services/web/WebView.qml db/script.sql diff --git a/services/local/fileloader.h b/services/local/fileloader.h index 2497360..2a1ae23 100644 --- a/services/local/fileloader.h +++ b/services/local/fileloader.h @@ -1,90 +1,150 @@ +/*** +Pix Copyright (C) 2018 Camilo Higuita +This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +This is free software, and you are welcome to redistribute it +under certain conditions; type `show c' for details. + + This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +***/ + #ifndef FILELOADER_H #define FILELOADER_H #include #include -#include +#include +#include + +#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) +#include +#else +#include "fmh.h" +#endif + #include "services/local/taginfo.h" -#include "db/collectionDB.h" #include "utils/bae.h" namespace FLoader { -static inline QList getPathContents(QList &urls, const QUrl &url) +class FileLoader : public QObject { - if(!FMH::fileExists(url) && !url.isLocalFile()) - return urls; + Q_OBJECT - if (QFileInfo(url.toLocalFile()).isDir()) +public: + FileLoader(QObject *parent = nullptr) : QObject(parent) { - QDirIterator it(url.toLocalFile(), QStringList() << FMH::FILTER_LIST[FMH::FILTER_TYPE::AUDIO] << "*.m4a", QDir::Files, QDirIterator::Subdirectories); - - while (it.hasNext()) - urls << QUrl::fromLocalFile(it.next()); - - }else if (QFileInfo(url.toLocalFile()).isFile()) - urls << url.toString(); - - return urls; -} - -// returns the number of new items added to the collection db -static inline uint getTracks(const QList& paths) -{ - const auto db = CollectionDB::getInstance(); - const auto urls = std::accumulate(paths.begin(), paths.end(), QList(), getPathContents); - - for(const auto &path : paths) - if(path.isLocalFile() && FMH::fileExists(path)) - db->addFolder(path.toString()); - - uint newTracks = 0; - - if(urls.isEmpty()) - return newTracks; + this->moveToThread(&t); + connect(this, &FileLoader::start, this, &FileLoader::fetch); + this->t.start(); + } - for(const auto &url : urls) + ~FileLoader() { - if(db->check_existance(BAE::TABLEMAP[BAE::TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url.toString())) - continue; + t.quit(); + t.wait(); + } - TagInfo info(url.toLocalFile()); - if(info.isNull()) - continue; + void requestPath(const QList &urls, const bool &recursive) + { + qDebug()<<"FROM file loader"<< urls; + emit this->start(urls, recursive); + } - qDebug()<< url << "HHH"; +private slots: + void fetch(QList paths, bool recursive) + { - const auto track = info.getTrack(); - const auto genre = info.getGenre(); - const auto album = BAE::fixString(info.getAlbum()); - const auto title = BAE::fixString(info.getTitle()); /* to fix*/ - const auto artist = BAE::fixString(info.getArtist()); - const auto sourceUrl = FMH::parentDir(url).toString(); - const auto duration = info.getDuration(); - const auto year = info.getYear(); + qDebug()<<"GETTING TRACKS"; + const uint m_bsize = 5000; + uint i = 0; + uint batch = 0; + FMH::MODEL_LIST res; + FMH::MODEL_LIST res_batch; + QList urls; - FMH::MODEL trackMap = + for(const auto &path : paths) { - {FMH::MODEL_KEY::URL, url.toString()}, - {FMH::MODEL_KEY::TRACK, QString::number(track)}, - {FMH::MODEL_KEY::TITLE, title}, - {FMH::MODEL_KEY::ARTIST, artist}, - {FMH::MODEL_KEY::ALBUM, album}, - {FMH::MODEL_KEY::DURATION,QString::number(duration)}, - {FMH::MODEL_KEY::GENRE, genre}, - {FMH::MODEL_KEY::SOURCE, sourceUrl}, - {FMH::MODEL_KEY::FAV, "0"}, - {FMH::MODEL_KEY::RELEASEDATE, QString::number(year)} - }; - - BAE::artworkCache(trackMap, FMH::MODEL_KEY::ALBUM); - - if(db->addTrack(trackMap)) - newTracks++; + if (QFileInfo(path.toLocalFile()).isDir() && path.isLocalFile() && FMH::fileExists(path)) + { + QDirIterator it(path.toLocalFile(), QStringList() << FMH::FILTER_LIST[FMH::FILTER_TYPE::AUDIO] << "*.m4a", QDir::Files, recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags); + + while (it.hasNext()) + { + const auto url = QUrl::fromLocalFile(it.next()); + urls << url; + + TagInfo info(url.toLocalFile()); + if(info.isNull()) + continue; + + qDebug()<< url << "HHH"; + + const auto track = info.getTrack(); + const auto genre = info.getGenre(); + const auto album = BAE::fixString(info.getAlbum()); + const auto title = BAE::fixString(info.getTitle()); /* to fix*/ + const auto artist = BAE::fixString(info.getArtist()); + const auto sourceUrl = FMH::parentDir(url).toString(); + const auto duration = info.getDuration(); + const auto year = info.getYear(); + + FMH::MODEL map = + { + {FMH::MODEL_KEY::URL, url.toString()}, + {FMH::MODEL_KEY::TRACK, QString::number(track)}, + {FMH::MODEL_KEY::TITLE, title}, + {FMH::MODEL_KEY::ARTIST, artist}, + {FMH::MODEL_KEY::ALBUM, album}, + {FMH::MODEL_KEY::DURATION,QString::number(duration)}, + {FMH::MODEL_KEY::GENRE, genre}, + {FMH::MODEL_KEY::SOURCE, sourceUrl}, + {FMH::MODEL_KEY::FAV, "0"}, + {FMH::MODEL_KEY::RELEASEDATE, QString::number(year)} + }; + + BAE::artworkCache(map, FMH::MODEL_KEY::ALBUM); + + emit itemReady(map); + res << map; + res_batch << map; + i++; + + if(i == m_bsize) //send a batch + { + emit itemsReady(res_batch); + res_batch.clear (); + batch++; + i = 0; + } + } + } + } + emit itemsReady(res_batch); + emit finished(res); } - return newTracks; -} + +signals: + void finished(FMH::MODEL_LIST items); + void start(QList urls, bool recursive); + + void itemsReady(FMH::MODEL_LIST items); + void itemReady(FMH::MODEL item); + +private: + QThread t; +}; } #endif // FILELOADER_H diff --git a/vvave.cpp b/vvave.cpp index de590c5..a2940f6 100644 --- a/vvave.cpp +++ b/vvave.cpp @@ -1,142 +1,154 @@ #include "vvave.h" #include "db/collectionDB.h" #include "services/local/fileloader.h" #include "utils/brain.h" #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) #include "kde/notify.h" #endif #include #include #ifdef STATIC_MAUIKIT #include "fm.h" #else #include #endif /* * Sets upthe app default config paths * BrainDeamon to get collection information * YoutubeFetcher ? * * */ vvave::vvave(QObject *parent) : QObject(parent), db(CollectionDB::getInstance()) { for(const auto &path : {BAE::CachePath, BAE::YoutubeCachePath}) { QDir dirPath(path); if (!dirPath.exists()) dirPath.mkpath("."); } //#if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) // if(!FMH::fileExists(BAE::NotifyDir+"/vvave.notifyrc")) // QFile::copy(":/assets/vvave.notifyrc", BAE::NotifyDir+"/vvave.notifyrc"); //#endif } //// PUBLIC SLOTS QVariantList vvave::sourceFolders() { const auto sources = CollectionDB::getInstance()->getDBData("select * from sources"); QVariantList res; for(const auto &item : sources) res << FMH::getDirInfo(item[FMH::MODEL_KEY::URL]); return res; } +void vvave::addSources(const QStringList &paths) +{ + QStringList urls = sources() << paths; + + urls.removeDuplicates(); + scanDir(urls); + + emit sourcesChanged(); +} + bool vvave::removeSource(const QString &source) { if(!this->getSourceFolders().contains(source)) return false; return this->db->removeSource(source); + + emit sourcesChanged(); } QString vvave::moodColor(const int &index) { if(index < BAE::MoodColors.size() && index > -1) return BAE::MoodColors.at(index); else return ""; } QStringList vvave::moodColors() { return BAE::MoodColors; } void vvave::scanDir(const QStringList &paths) { - QFutureWatcher *watcher = new QFutureWatcher; - connect(watcher, &QFutureWatcher::finished, [&, watcher]() - { - qDebug()<< "FINISHED SCANING CXOLLECTION"; - emit this->refreshTables( watcher->future().result()); - watcher->deleteLater(); - }); - - const auto func = [=]() -> uint - { - return FLoader::getTracks(QUrl::fromStringList(paths)); - }; - - QFuture t1 = QtConcurrent::run(func); - watcher->setFuture(t1); +// QFutureWatcher *watcher = new QFutureWatcher; +// connect(watcher, &QFutureWatcher::finished, [&, watcher]() +// { +// qDebug()<< "FINISHED SCANING CXOLLECTION"; +// emit this->refreshTables( watcher->future().result()); +// watcher->deleteLater(); +// }); + +// const auto func = [=]() -> uint +// { +// return FLoader::getTracks(QUrl::fromStringList(paths)); +// }; + +// QFuture t1 = QtConcurrent::run(func); +// watcher->setFuture(t1); } QStringList vvave::getSourceFolders() { return CollectionDB::getInstance()-> getSourcesFolders(); } void vvave::openUrls(const QStringList &urls) { if(urls.isEmpty()) return; QVariantList data; for(const auto &url : urls) { auto _url = QUrl::fromUserInput(url); if(db->check_existance(BAE::TABLEMAP[BAE::TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], _url.toString())) { data << FMH::toMap(this->db->getDBData(QStringList() << _url.toString()).first()); }else { TagInfo info(_url.toLocalFile()); if(!info.isNull()) { const auto album = BAE::fixString(info.getAlbum()); const auto track= info.getTrack(); const auto title = BAE::fixString(info.getTitle()); /* to fix*/ const auto artist = BAE::fixString(info.getArtist()); const auto genre = info.getGenre(); const auto sourceUrl = QFileInfo(_url.toLocalFile()).dir().path(); const auto duration = info.getDuration(); const auto year = info.getYear(); data << QVariantMap({ {FMH::MODEL_NAME[FMH::MODEL_KEY::URL], _url.toString()}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TRACK], QString::number(track)}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TITLE], title}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album}, {FMH::MODEL_NAME[FMH::MODEL_KEY::DURATION],QString::number(duration)}, {FMH::MODEL_NAME[FMH::MODEL_KEY::GENRE], genre}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCE], sourceUrl}, {FMH::MODEL_NAME[FMH::MODEL_KEY::FAV],"0"}, {FMH::MODEL_NAME[FMH::MODEL_KEY::RELEASEDATE], QString::number(year)} }); } } } emit this->openFiles(data); } diff --git a/vvave.h b/vvave.h index c65a365..56fc96a 100644 --- a/vvave.h +++ b/vvave.h @@ -1,39 +1,51 @@ #ifndef VVAVE_H #define VVAVE_H #include #include "utils/bae.h" #include class CollectionDB; class vvave : public QObject { Q_OBJECT + Q_PROPERTY(QStringList sources READ sources NOTIFY sourcesChanged FINAL) + private: CollectionDB *db; void checkCollection(const QStringList &paths = BAE::defaultSources, std::function cb = nullptr); public: explicit vvave(QObject *parent = nullptr); signals: void refreshTables(uint size); void refreshTracks(); void refreshAlbums(); void refreshArtists(); void openFiles(QVariantList tracks); + void sourcesChanged(); public slots: ///DB Interfaces /// useful functions for non modeled views and actions with not direct access to a tracksmodel or its own model static QVariantList sourceFolders(); + + QStringList sources() const + { + return getSourceFolders(); + } + + void addSources(const QStringList &paths); + bool removeSource(const QString &source); static QString moodColor(const int &index); static QStringList moodColors(); void scanDir(const QStringList &paths = BAE::defaultSources); static QStringList getSourceFolders(); void openUrls(const QStringList &urls); + }; #endif // VVAVE_H diff --git a/widgets/SettingsView/SettingsDialog.qml b/widgets/SettingsView/SettingsDialog.qml index 66aa899..b2666e5 100644 --- a/widgets/SettingsView/SettingsDialog.qml +++ b/widgets/SettingsView/SettingsDialog.qml @@ -1,89 +1,181 @@ /* * Copyright 2020 Camilo Higuita * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.9 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import org.kde.mauikit 1.1 as MauiLab +import org.maui.vvave 1.0 as Vvave +import "../../utils/Help.js" as H MauiLab.SettingsDialog { id: control property bool fetchArtwork : Maui.FM.loadSettings("Settings", "FetchArtwork", true) property bool scanCollectionOnStartUp : Maui.FM.loadSettings("Settings", "ScanCollectionOnStartUp", true) property bool darkMode: Maui.FM.loadSettings("Settings", "DarkMode", false) + Maui.Dialog + { + id: confirmationDialog + property string url : "" + + page.margins: Maui.Style.space.medium + title : "Remove source" + message : "Are you sure you want to remove the source: \n "+url + + onAccepted: + { + if(url.length>0) + if( Vvave.Vvave.removeSource(url)) + H.refreshCollection() + confirmationDialog.close() + } + onRejected: confirmationDialog.close() + } + MauiLab.SettingsSection { title: qsTr("Behaviour") description: qsTr("Configure the app plugins and behavior.") Switch { Layout.fillWidth: true checkable: true checked: control.fetchArtwork Kirigami.FormData.label: qsTr("Fetch Artwork Online") onToggled: { control.fetchArtwork = !control.fetchArtwork Maui.FM.saveSettings("Settings", control.fetchArtWork, "FetchArtwork") } } Switch { Layout.fillWidth: true Kirigami.FormData.label: qsTr("Scan Collection on Start Up") checkable: true checked: control.scanCollectionOnStartUp onToggled: { control.scanCollectionOnStartUp = !control.scanCollectionOnStartUp Maui.FM.saveSettings("Settings", control.scanCollectionOnStartUp, "ScanCollectionOnStartUp") } } } MauiLab.SettingsSection { title: qsTr("Interface") description: qsTr("Configure the app UI.") Switch { Kirigami.FormData.label: qsTr("Translucent Sidebar") checkable: true checked: root.translucency onToggled: root.translucency = !root.translucency } Switch { Layout.fillWidth: true Kirigami.FormData.label: qsTr("Dark Mode") checkable: true checked: control.darkMode onToggled: control.darkMode = !control.darkMode } } + + MauiLab.SettingsSection + { + title: qsTr("Sources") + description: qsTr("Add new sources to manage and browse your image collection") + + + ColumnLayout + { + anchors.fill: parent + + Maui.ListBrowser + { + id: _sourcesList + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumHeight: Math.min(500, contentHeight) + model: Vvave.Vvave.sources + delegate: Maui.ListDelegate + { + width: parent.width + iconName: "folder" + iconSize: Maui.Style.iconSizes.small + label: modelData + onClicked: _sourcesList.currentIndex = index + } + +// Maui.Holder +// { +// anchors.fill: parent +// visible: !_sourcesList.count +// emoji: "qrc:/assets/dialog-information.svg" +// isMask: true +// title : qsTr("No Sources!") +// body: qsTr("Add new sources to organize and play your music collection") +// emojiSize: Maui.Style.iconSizes.huge +// } + } + + RowLayout + { + Layout.fillWidth: true + Button + { + Layout.fillWidth: true + text: qsTr("Remove") + onClicked: + { + confirmationDialog.url = _sourcesList.model[_sourcesList.currentIndex] + confirmationDialog.open() + } + } + + Button + { + Layout.fillWidth: true + text: qsTr("Add") + onClicked: + { + _dialogLoader.sourceComponent = _fmDialogComponent + root.dialog.settings.onlyDirs = true + root.dialog.show(function(paths) + { + console.log("SCAN DIR <<", paths) + Vvave.Vvave.addSources([paths]) + }) + } + } + } + } + } } diff --git a/widgets/SettingsView/SourcesDialog.qml b/widgets/SettingsView/SourcesDialog.qml deleted file mode 100644 index 0324b1e..0000000 --- a/widgets/SettingsView/SourcesDialog.qml +++ /dev/null @@ -1,109 +0,0 @@ -import QtQuick 2.10 -import QtQuick.Layouts 1.3 -import QtQuick.Controls 2.10 -import org.kde.mauikit 1.0 as Maui -import org.maui.vvave 1.0 as Vvave - -import "../../view_models" -import "../../utils/Help.js" as H - -Maui.Dialog -{ - property string pathToRemove : "" - - maxWidth: Maui.Style.unit * 600 - maxHeight: Maui.Style.unit * 500 - defaultButtons: true - acceptButton.text: qsTr("Add") - rejectButton.text: qsTr("Remove") - page.margins: Maui.Style.space.medium - - onRejected: - { - var index = sources.currentIndex - var url = sources.model.get(index).url - pathToRemove = url - confirmationDialog.title = "Remove source" - confirmationDialog.message = "Are you sure you want to remove the source: \n "+url - confirmationDialog.open() - } - - onAccepted: - { - _dialogLoader.sourceComponent = _fmDialogComponent - root.dialog.settings.onlyDirs = true - root.dialog.show(function(paths) - { - - console.log("SCAN DIR <<", paths) - for(var i in paths) - listModel.append({url: paths[i]}) - Vvave.Vvave.scanDir([paths]) - }) - - getSources() - } - - Maui.Dialog - { - id: confirmationDialog - page.margins: Maui.Style.space.medium - - onAccepted: - { - if(pathToRemove.length>0) - if( Vvave.Vvave.removeSource(pathToRemove)) - H.refreshCollection() - getSources() - confirmationDialog.close() - } - onRejected: confirmationDialog.close() - } - - Maui.Holder - { - anchors.fill: parent - visible: !sources.count - emoji: "qrc:/assets/MusicCloud.png" - isMask: true - title : qsTr("No Sources!") - body: qsTr("Add new sources to organize and play your music collection") - emojiSize: Maui.Style.iconSizes.huge - } - - BabeList - { - id: sources - Layout.fillHeight: true - Layout.fillWidth: true - headBar.visible: false - title: qsTr("Sources") - width: parent.width - - ListModel { id: listModel } - - model: listModel - - delegate: Maui.ListDelegate - { - id: delegate - label: url - - Connections - { - target: delegate - onClicked: sources.currentIndex = index - } - } - } - - onOpened: getSources() - - function getSources() - { - sources.model.clear() - var folders = Vvave.Vvave.getSourceFolders() - for(var i in folders) - sources.model.append({url : folders[i]}) - } -}