diff --git a/main.qml b/main.qml index b3dd019..6118d16 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 "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, - cloud : 1, - albums: 2, - artists: 3, - playlists: 4, - folders: 5, + 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("YouTube") icon.name: "internet-services" } ] Action { icon.name: "view-media-track" text: qsTr("Tracks") } - Action - { - text: qsTr("Cloud") - icon.name: "folder-cloud" - } - Action { text: qsTr("Albums") icon.name: /*"album"*/ "view-media-album-cover" } Action { text: qsTr("Artists") icon.name: "view-media-artist" } Action { text: qsTr("Playlists") icon.name: "view-media-playlist" } } onSearchButtonClicked: { _actionGroup.currentIndex = viewsIndex.search // searchView.searchInput.forceActiveFocus() } Loader { id: _dialogLoader } InfoView { id: infoView maxWidth: parent.width * 0.8 maxHeight: parent.height * 0.9 } Loader { id: _focusViewLoader 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 + 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: CloudView - { - id: cloudView - } - } - 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 || 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/playlists/playlistsmodel.cpp b/models/playlists/playlistsmodel.cpp index e5be5c7..473b489 100644 --- a/models/playlists/playlistsmodel.cpp +++ b/models/playlists/playlistsmodel.cpp @@ -1,262 +1,262 @@ #include "playlistsmodel.h" #include "db/collectionDB.h" PlaylistsModel::PlaylistsModel(QObject *parent) : MauiList(parent), db(CollectionDB::getInstance()) { this->setList(); } FMH::MODEL_LIST PlaylistsModel::items() const { return this->list; } void PlaylistsModel::setSortBy(const SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; this->preListChanged(); this->sortList(); this->postListChanged(); emit this->sortByChanged(); } PlaylistsModel::SORTBY PlaylistsModel::getSortBy() const { return this->sort; } void PlaylistsModel::sortList() { const auto key = static_cast(this->sort); qDebug()<< "SORTING LIST BY"<< this->sort; qSort(this->list.begin() + this->defaultPlaylists().size(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { auto role = key; switch(role) { case FMH::MODEL_KEY::ADDDATE: { auto currentTime = QDateTime::currentDateTime(); auto date1 = QDateTime::fromString(e1[role], Qt::TextDate); auto date2 = QDateTime::fromString(e2[role], Qt::TextDate); if(date1.secsTo(currentTime) < date2.secsTo(currentTime)) return true; break; } case FMH::MODEL_KEY::TITLE: { 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 PlaylistsModel::setList() { emit this->preListChanged(); this->list << this->db->getPlaylists(); // this->sortList(); emit this->postListChanged(); } FMH::MODEL PlaylistsModel::packPlaylist(const QString &playlist) { return FMH::MODEL { {FMH::MODEL_KEY::PLAYLIST, playlist}, - {FMH::MODEL_KEY::TYPE, "public"}, + {FMH::MODEL_KEY::TYPE, "personal"}, {FMH::MODEL_KEY::ADDDATE, QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} // {FMH::MODEL_KEY::ICON, "view-media-playlist"} }; } QVariantList PlaylistsModel::defaultPlaylists() { const auto model = FMH::MODEL_LIST { { {FMH::MODEL_KEY::TYPE, "default"}, {FMH::MODEL_KEY::DESCRIPTION, "Favorite tracks"}, {FMH::MODEL_KEY::COLOR, "#EC407A"}, {FMH::MODEL_KEY::PLAYLIST, "Favs"}, {FMH::MODEL_KEY::ICON, "love"}, {FMH::MODEL_KEY::ADDDATE, QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} }, { {FMH::MODEL_KEY::TYPE, "default"}, {FMH::MODEL_KEY::DESCRIPTION, "Top listened tracks"}, {FMH::MODEL_KEY::COLOR, "#FFA000"}, {FMH::MODEL_KEY::PLAYLIST, "Most Played"}, {FMH::MODEL_KEY::ICON, "view-media-playcount"}, {FMH::MODEL_KEY::ADDDATE, QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} }, { {FMH::MODEL_KEY::TYPE, "default"}, {FMH::MODEL_KEY::DESCRIPTION, "Highest rated tracks"}, {FMH::MODEL_KEY::COLOR, "#42A5F5"}, {FMH::MODEL_KEY::PLAYLIST, "Rating"}, {FMH::MODEL_KEY::ICON, "view-media-favorite"}, {FMH::MODEL_KEY::ADDDATE, QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} }, // { // {FMH::MODEL_KEY::TYPE, "default"}, // {FMH::MODEL_KEY::PLAYLIST, "Recent"}, // {FMH::MODEL_KEY::ICON, "view-media-recent"}, // {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} // }, { {FMH::MODEL_KEY::TYPE, "default"}, {FMH::MODEL_KEY::COLOR, "#26A69A"}, {FMH::MODEL_KEY::DESCRIPTION, "Online tracks"}, {FMH::MODEL_KEY::PLAYLIST, "YouTube"}, {FMH::MODEL_KEY::ICON, "internet-services"}, {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} }, // { // {FMH::MODEL_KEY::PLAYLIST, "Tags"}, // {FMH::MODEL_KEY::ICON, "tag"}, // {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} // }, // { // {FMH::MODEL_KEY::PLAYLIST, "Relationships"}, // {FMH::MODEL_KEY::ICON, "view-media-similarartists"}, // {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} // }, // { // {FMH::MODEL_KEY::PLAYLIST, "Popular"}, // {FMH::MODEL_KEY::ICON, "view-media-chart"}, // {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} // }, // { // {FMH::MODEL_KEY::PLAYLIST, "Genres"}, // {FMH::MODEL_KEY::ICON, "view-media-genre"}, // {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} // } }; return FMH::toMapList(model); } QVariantMap PlaylistsModel::get(const int &index) const { if(index >= this->list.size() || index < 0) return QVariantMap(); return FMH::toMap(this->list.at(index)); } void PlaylistsModel::append(const QVariantMap &item) { if(item.isEmpty()) return; emit this->preItemAppended(); this->list << FMH::toModel(item); emit this->postItemAppended(); } void PlaylistsModel::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 PlaylistsModel::insert(const QString &playlist) { if(playlist.isEmpty()) return; emit this->preItemAppended(); if(this->db->addPlaylist(playlist)) this->list << (this->packPlaylist(playlist)); emit this->postItemAppended(); } void PlaylistsModel::insertAt(const QString &playlist, const int &at) { if(playlist.isEmpty()) return; if(at > this->list.size() || at < 0) return; emit this->preItemAppendedAt(at); if(this->db->addPlaylist(playlist)) this->list.insert(at, this->packPlaylist(playlist)); emit this->postItemAppended(); } void PlaylistsModel::addTrack(const int &index, const QStringList &urls) { if(index >= this->list.size() || index < 0) return; this->addTrack(this->list[index][FMH::MODEL_KEY::PLAYLIST], urls); } void PlaylistsModel::addTrack(const QString &playlist, const QStringList &urls) { for(const auto &url : urls) this->db->trackPlaylist(url, playlist); } void PlaylistsModel::removeTrack(const int &index, const QString &url) { if(index >= this->list.size() || index < 0) return; this->db->removePlaylistTrack(url, this->list.at(index)[FMH::MODEL_KEY::PLAYLIST]); } void PlaylistsModel::removePlaylist(const int &index) { if(index >= this->list.size() || index < 0) return; if(this->db->removePlaylist(this->list.at(index)[FMH::MODEL_KEY::PLAYLIST])) { emit this->preItemRemoved(index); this->list.removeAt(index); emit this->postItemRemoved(); } } diff --git a/models/tracks/tracksmodel.cpp b/models/tracks/tracksmodel.cpp index 14ef5f7..4c15db1 100644 --- a/models/tracks/tracksmodel.cpp +++ b/models/tracks/tracksmodel.cpp @@ -1,342 +1,342 @@ #include "tracksmodel.h" #include "db/collectionDB.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; +// if(this->query == query) +// return; this->query = query; emit this->queryChanged(); } QString TracksModel::getQuery() const { return this->query; } void TracksModel::setSortBy(const SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; emit this->preListChanged(); this->sortList(); emit this->postListChanged(); emit this->sortByChanged(); } TracksModel::SORTBY TracksModel::getSortBy() const { return this->sort; } void TracksModel::sortList() { if(this->sort == TracksModel::SORTBY::NONE) return; const auto key = static_cast(this->sort); qSort(this->list.begin(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { switch(key) { case FMH::MODEL_KEY::RELEASEDATE: case FMH::MODEL_KEY::RATE: case FMH::MODEL_KEY::FAV: case FMH::MODEL_KEY::COUNT: { if(e1[key].toInt() > e2[key].toInt()) return true; break; } case FMH::MODEL_KEY::TRACK: { if(e1[key].toInt() < e2[key].toInt()) return true; break; } case FMH::MODEL_KEY::ADDDATE: { auto currentTime = QDateTime::currentDateTime(); auto date1 = QDateTime::fromString(e1[key], Qt::TextDate); auto date2 = QDateTime::fromString(e2[key], Qt::TextDate); if(date1.secsTo(currentTime) < date2.secsTo(currentTime)) return true; break; } case FMH::MODEL_KEY::TITLE: case FMH::MODEL_KEY::ARTIST: case FMH::MODEL_KEY::ALBUM: case FMH::MODEL_KEY::FORMAT: { const auto str1 = QString(e1[key]).toLower(); const auto str2 = QString(e2[key]).toLower(); if(str1 < str2) return true; break; } default: if(e1[key] < e2[key]) return true; } return false; }); } void TracksModel::setList() { emit this->preListChanged(); this->list = this->db->getDBData(this->query); this->sortList(); emit this->postListChanged(); } 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/view_models/BabeList.qml b/view_models/BabeList.qml index ee33872..fe590ff 100644 --- a/view_models/BabeList.qml +++ b/view_models/BabeList.qml @@ -1,36 +1,36 @@ -import QtQuick 2.10 -import QtQuick.Controls 2.10 +import QtQuick 2.12 +import QtQuick.Controls 2.12 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.6 as Kirigami import org.kde.mauikit 1.0 as Maui Maui.Page { id: control property alias listView : babeList.listView property alias model : babeList.model property alias delegate : babeList.delegate property alias count : babeList.count property alias currentIndex : babeList.currentIndex property alias currentItem : babeList.currentItem property alias holder : babeList.holder property alias section : babeList.section property bool wasPulled : false signal pulled() Maui.ListBrowser { id: babeList anchors.fill: parent holder.visible: count === 0 topMargin: Maui.Style.space.medium listView.headerPositioning: ListView.PullBackHeader listView.footerPositioning: ListView.OverlayFooter Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.inherit: false - } + } } diff --git a/view_models/BabeTable/TableDelegate.qml b/view_models/BabeTable/TableDelegate.qml index cfe49d3..5f6a892 100644 --- a/view_models/BabeTable/TableDelegate.qml +++ b/view_models/BabeTable/TableDelegate.qml @@ -1,113 +1,114 @@ import QtQuick 2.10 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.10 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import "../../view_models" import "../../utils/Help.js" as H Maui.ItemDelegate { id: control isCurrentItem: ListView.isCurrentItem || isSelected property bool showQuickActions: true property bool number : false property bool coverArt : false property bool showEmblem: true property bool keepEmblemOverlay: selectionMode property bool isSelected : false - property string trackMood : model.color + property color trackMood : model.color readonly property color bgColor : Kirigami.Theme.backgroundColor readonly property int altHeight : Maui.Style.rowHeight * 1.4 readonly property bool sameAlbum : { if(coverArt) { if(list.get(index-1)) { if(list.get(index-1).album === album && list.get(index-1).artist === artist) true else false }else false }else false } width: parent.width height: sameAlbum ? Maui.Style.rowHeight : altHeight padding: 0 rightPadding: leftPadding leftPadding: Maui.Style.space.small signal play() signal append() signal leftClicked() signal leftEmblemClicked(int index) signal artworkCoverClicked() signal artworkCoverDoubleClicked() - Kirigami.Theme.backgroundColor: trackMood.length > 0 ? Qt.tint(bgColor, Qt.rgba(Qt.lighter(trackMood, 1.3).r, Qt.lighter(trackMood, 1.3).g, Qt.lighter(trackMood, 1.3).b, 0.3)): bgColor + Kirigami.Theme.backgroundColor: model.color.length > 0 ? Qt.rgba(trackMood.r, trackMood.g, trackMood.b, 0.2): bgColor function rate(stars) { trackRating.text = stars } RowLayout { anchors.fill: parent Item { Layout.fillHeight: true Layout.preferredWidth: _leftEmblemIcon.height + Maui.Style.space.small visible: (control.keepEmblemOverlay || control.isSelected) && control.showEmblem Maui.Badge { id: _leftEmblemIcon anchors.centerIn: parent iconName: control.isSelected ? "list-remove" : "list-add" onClicked: control.leftEmblemClicked(index) size: Maui.Style.iconSizes.small } } Maui.ListItemTemplate { id: _template Layout.fillWidth: true Layout.fillHeight: true isCurrentItem: control.isCurrentItem iconSizeHint: height - Maui.Style.space.small label1.text: control.number ? model.track + ". " + model.title : model.title label2.text: model.artist + " | " + model.album label2.visible: control.coverArt ? !control.sameAlbum : true label3.text: model.fav ? (model.fav == "1" ? "\uf2D1" : "") : "" label3.font.family: "Material Design Icons" label4.font.family: "Material Design Icons" label4.text: model.rate ? H.setStars(model.rate) : "" iconVisible: !control.sameAlbum && control.coverArt imageSource: model.artwork ? model.artwork : "qrc:/assets/cover.png" ToolButton { Layout.fillHeight: true Layout.preferredWidth: implicitWidth visible: showQuickActions && (Kirigami.Settings.isMobile ? true : control.hovered) icon.name: "media-playlist-append" onClicked: control.append() + opacity: control.hovered ? 0.8 : 0.6 } } } } diff --git a/widgets/FloatingDisk.qml b/widgets/FloatingDisk.qml index 1ad9cfb..a20ecd1 100644 --- a/widgets/FloatingDisk.qml +++ b/widgets/FloatingDisk.qml @@ -1,183 +1,160 @@ 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 Item { id: control Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.Complementary - visible: opacity > 0.3 + visible: opacity > 0.3 && !mainlistEmpty height: Maui.Style.iconSizes.large * 1.2 width: height x: root.footBar.x + Maui.Style.space.medium y: parent.height - height - Maui.Style.space.medium parent: ApplicationWindow.overlay z: parent.z + 1 ToolTip.delay: 1000 ToolTip.timeout: 5000 ToolTip.visible: _mouseArea.containsMouse && !Kirigami.Settings.isMobile ToolTip.text: currentTrack.title + " - " + currentTrack.artist Maui.Badge { anchors.fill: parent anchors.margins: Maui.Style.space.tiny visible: anim.running text: mainPlaylist.table.count } Connections { target: mainPlaylist.table onCountChanged: { anim.run(control.y) } } NumberAnimation on y { id: anim property int startY running: false from : control.y to: control.y - 20 duration: 250 loops: 2 onStopped: { control.y = startY } function run(y) { if(y < 10) return startY = y anim.start() anim.running = true } } MouseArea { id: _mouseArea anchors.fill: parent hoverEnabled: true drag.target: parent drag.axis: Drag.XAndYAxis drag.minimumX: 0 drag.maximumX: root.width drag.minimumY: 0 drag.maximumY: root.height onClicked: _drawer.visible = true + + Rectangle { id: diskBg anchors.centerIn: parent height: parent.height width: height - // border.color: Qt.tint(Kirigami.Theme.textColor, Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.7)) color: "white" radius: Math.min(width, height) - - Image { id: miniArtwork focus: true anchors.fill: parent anchors.margins: Maui.Style.space.tiny anchors.centerIn: parent source: { if (currentArtwork) (currentArtwork.length > 0 && currentArtwork !== "NONE") ? currentArtwork: "qrc:/assets/cover.png" else "qrc:/assets/cover.png" } fillMode: Image.PreserveAspectFit cache: false antialiasing: true layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: miniArtwork.width height: miniArtwork.height Rectangle { anchors.centerIn: parent width: miniArtwork.adapt ? miniArtwork.width : Math.min( miniArtwork.width, miniArtwork.height) height: miniArtwork.adapt ? miniArtwork.height : width radius: Math.min(width, height) } } } } } DropShadow { anchors.fill: diskBg horizontalOffset: 0 verticalOffset: 0 radius: 8.0 samples: 17 color: "#80000000" source: diskBg } RotationAnimator on rotation { from: 0 to: 360 duration: 5000 loops: Animation.Infinite running: isPlaying } } - - - // Rectangle - // { - // anchors.centerIn: parent - // width: parent.width * 0.5 - // height: width - // radius: height - // color: "transparent" - - // ShaderEffectSource - // { - // anchors.fill: parent - // sourceItem: root - // sourceRect:Qt.rect((control.x), - // (control.y), - // parent.width, - // parent.height) - // hideSource: true - // } - // } - } diff --git a/widgets/FocusView.qml b/widgets/FocusView.qml index 9a9e700..e13f29d 100644 --- a/widgets/FocusView.qml +++ b/widgets/FocusView.qml @@ -1,372 +1,432 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import "../utils/Player.js" as Player import QtGraphicalEffects 1.0 -Rectangle +Maui.Page { id: control visible: focusView parent: ApplicationWindow.overlay anchors.fill: parent z: parent.z + 99999 - color: Kirigami.Theme.backgroundColor Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.View focus: true Component.onCompleted: { _drawer.visible = false forceActiveFocus() } Component.onDestruction: { _drawer.visible = true } + headBar.background: null + headBar.height: Maui.Style.toolBarHeight + headBar.leftContent: ToolButton + { + icon.name: "go-previous" + onClicked: focusView = false + } + Keys.onBackPressed: { focusView = false event.accepted = true } Shortcut { sequence: StandardKey.Back onActivated: focusView = false } ColumnLayout { anchors.fill: parent anchors.margins: Maui.Style.space.big - ListView + RowLayout { - id: _listView Layout.fillWidth: true - Layout.preferredHeight: parent.height* 0.4 - orientation: ListView.Horizontal - clip: true - focus: true - interactive: true - currentIndex: currentTrackIndex - spacing: Maui.Style.space.medium - cacheBuffer: control.width * 1 - onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Center) - - highlightFollowsCurrentItem: true - highlightMoveDuration: 0 - snapMode: ListView.SnapToOneItem - model: mainPlaylist.listModel - highlightRangeMode: ListView.StrictlyEnforceRange - keyNavigationEnabled: true - keyNavigationWraps : true - onMovementEnded: - { - var index = indexAt(contentX, contentY) - if(index !== currentTrackIndex) - Player.playAt(index) - } + Layout.preferredHeight: width - Rectangle + Item { - visible: (_listView.currentIndex > 0) && (_listView.count > 1) - - height: Maui.Style.iconSizes.small - width : height - - radius: height - - color: Kirigami.Theme.textColor - opacity: 0.4 + Layout.fillHeight: true + Layout.preferredWidth: Maui.Style.iconSizes.big - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - } + Rectangle + { + visible: (_listView.currentIndex > 0) && (_listView.count > 1) - Rectangle - { - visible: (_listView.currentIndex < _listView.count - 1) && (_listView.count > 1) - height: Maui.Style.iconSizes.small - width : height + height: Maui.Style.iconSizes.small + width : height - radius: height + radius: height - color: Kirigami.Theme.textColor - opacity: 0.4 + color: Kirigami.Theme.textColor + opacity: 0.4 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter + anchors.centerIn: parent + } } - delegate: Item + ListView { - id: _delegate - height: _listView.height - width: _listView.width - - Rectangle + id: _listView + Layout.fillWidth: true + Layout.fillHeight: true + orientation: ListView.Horizontal + clip: true + focus: true + interactive: true + currentIndex: currentTrackIndex + spacing: Maui.Style.space.medium + cacheBuffer: control.width * 1 + onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Center) + + highlightFollowsCurrentItem: true + highlightMoveDuration: 0 + snapMode: ListView.SnapToOneItem + model: mainPlaylist.listModel + highlightRangeMode: ListView.StrictlyEnforceRange + keyNavigationEnabled: true + keyNavigationWraps : true + onCurrentItemChanged: { - id: _bg - width: parent.height * 0.7 - height: width - anchors.centerIn: parent - radius: Maui.Style.radiusV - color: Kirigami.Theme.textColor - + var index = indexAt(contentX, contentY) + if(index !== currentTrackIndex) + Player.playAt(index) } - DropShadow + delegate: Item { - anchors.fill: _bg - horizontalOffset: 0 - verticalOffset: 0 - radius: 8.0 - samples: 17 - color: "#80000000" - source: _bg - } + id: _delegate + height: _listView.height + width: _listView.width - Image - { - id: _image - width: parent.height * 0.7 - height: width - anchors.centerIn: parent + property bool isCurrentItem : ListView.isCurrentItem - sourceSize.width: height - sourceSize.height: height + Rectangle + { + id: _bg + width: _image.width + height: width + anchors.centerIn: parent + radius: height + color: Kirigami.Theme.textColor - fillMode: Image.PreserveAspectFit - antialiasing: false - smooth: true - asynchronous: true + } - source: model.artwork ? model.artwork : "qrc:/assets/cover.png" + DropShadow + { + anchors.fill: _bg + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + samples: 17 + color: "#80000000" + source: _bg + } - onStatusChanged: + RotationAnimator on rotation { - if (status == Image.Error) - source = "qrc:/assets/cover.png"; + from: 0 + to: 360 + duration: 5000 + loops: Animation.Infinite + running: root.isPlaying && isCurrentItem } - layer.enabled: true - layer.effect: OpacityMask + Image { - maskSource: Item + id: _image + width: Math.min(parent.width, parent.height) * 0.9 + height: width + anchors.centerIn: parent + + sourceSize.width: height + sourceSize.height: height + + fillMode: Image.PreserveAspectFit + antialiasing: false + smooth: true + asynchronous: true + + source: model.artwork ? model.artwork : "qrc:/assets/cover.png" + + onStatusChanged: + { + if (status == Image.Error) + source = "qrc:/assets/cover.png"; + } + + Rectangle + { + id: _roundRec + color: control.Kirigami.Theme.backgroundColor + height: parent.height * 0.2 + width: height + anchors.centerIn: parent + radius: height + } + + InnerShadow { - width: _image.width - height: _image.height + anchors.fill: _roundRec + radius: 8.0 + samples: 16 + horizontalOffset: 0 + verticalOffset: 0 + color: "#b0000000" + source: _roundRec + } - Rectangle + layer.enabled: true + layer.effect: OpacityMask + { + maskSource: Item { - anchors.centerIn: parent - width: _image.adapt ? _image.width : Math.min(_image.width, _image.height) - height: _image.adapt ? _image.height : width - radius: Maui.Style.radiusV + width: _image.width + height: _image.height + + Rectangle + { + anchors.centerIn: parent + width: _image.width + height: _image.height + radius: height + } } } } } } + + + + Item + { + Layout.fillHeight: true + Layout.preferredWidth: Maui.Style.iconSizes.big + + Rectangle + { + visible: (_listView.currentIndex < _listView.count - 1) && (_listView.count > 1) + height: Maui.Style.iconSizes.small + width : height + + radius: height + + color: Kirigami.Theme.textColor + opacity: 0.4 + + anchors.centerIn: parent + + } + } + } + RowLayout { Layout.fillWidth: true Layout.preferredHeight: Maui.Style.toolBarHeight ToolButton { icon.name: "view-list-details" onClicked: focusView = false Layout.alignment: Qt.AlignCenter } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignCenter spacing: 0 Label { id: _label1 visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: currentTrack.title elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal - font.pointSize: Maui.Style.fontSizes.big + font.pointSize: Maui.Style.fontSizes.huge } Label { id: _label2 visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: currentTrack.artist elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal - font.pointSize: Maui.Style.fontSizes.medium + font.pointSize: Maui.Style.fontSizes.big opacity: 0.7 } } ToolButton { icon.name: "documentinfo" onClicked: focusView = false Layout.alignment: Qt.AlignCenter } } RowLayout { Layout.fillWidth: true Label { visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: progressTimeLabel elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.medium opacity: 0.7 } Slider { id: progressBar Layout.fillWidth: true padding: 0 from: 0 to: 1000 value: player.pos spacing: 0 focus: true onMoved: { player.pos = value } } Label { visible: text.length Layout.fillWidth: true Layout.fillHeight: false verticalAlignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter text: player.transformTime(player.duration/1000) elide: Text.ElideMiddle wrapMode: Text.NoWrap color: control.Kirigami.Theme.textColor font.weight: Font.Normal font.pointSize: Maui.Style.fontSizes.medium opacity: 0.7 } } Maui.ToolBar { Layout.preferredHeight: Maui.Style.toolBarHeight * 2 Layout.fillWidth: true position: ToolBar.Footer background: null middleContent: [ ToolButton { id: babeBtnIcon icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big icon.name: "love" enabled: currentTrackIndex >= 0 icon.color: currentBabe ? babeColor : Kirigami.Theme.textColor onClicked: if (!mainlistEmpty) { mainPlaylist.list.fav(currentTrackIndex, !(mainPlaylist.list.get(currentTrackIndex).fav == "1")) currentBabe = mainPlaylist.list.get(currentTrackIndex).fav == "1" } }, ToolButton { icon.name: "media-skip-backward" icon.color: Kirigami.Theme.textColor icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big onClicked: Player.previousTrack() onPressAndHold: Player.playAt(prevTrackIndex) }, ToolButton { id: playIcon icon.width: Maui.Style.iconSizes.huge icon.height: Maui.Style.iconSizes.huge enabled: currentTrackIndex >= 0 icon.color: Kirigami.Theme.textColor icon.name: isPlaying ? "media-playback-pause" : "media-playback-start" onClicked: player.playing = !player.playing }, ToolButton { id: nextBtn icon.color: Kirigami.Theme.textColor icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big icon.name: "media-skip-forward" onClicked: Player.nextTrack() onPressAndHold: Player.playAt(Player.shuffle()) }, ToolButton { id: shuffleBtn icon.width: Maui.Style.iconSizes.big icon.height: Maui.Style.iconSizes.big icon.color: babeColor icon.name: isShuffle ? "media-playlist-shuffle" : "media-playlist-normal" onClicked: { isShuffle = !isShuffle Maui.FM.saveSettings("SHUFFLE", isShuffle, "PLAYBACK") } } ] } } } diff --git a/widgets/FoldersView.qml b/widgets/FoldersView.qml index 3d46116..2a77d65 100644 --- a/widgets/FoldersView.qml +++ b/widgets/FoldersView.qml @@ -1,82 +1,82 @@ import QtQuick 2.0 import QtQuick.Controls 2.10 import org.kde.mauikit 1.0 as Maui import "../view_models/BabeTable" import "../db/Queries.js" as Q Item { id: control property alias list : _filterList property alias listModel : _filterList.model property var tracks : [] property string currentFolder : "" Maui.GridBrowser { id: browser - anchors.margins: Maui.Style.space.big anchors.fill: parent showEmblem: false model: ListModel {} + itemWidth: itemSize * 1.2 + itemHeight: itemSize * 1.2 onItemClicked: { var item = browser.model.get(index) _filterList.title= item.label currentFolder = item.path filter() _listDialog.open() } } - Maui.Holder { anchors.fill: parent visible: !browser.count emoji: "qrc:/assets/MusicCloud.png" isMask: false title : "No Folders!" body: "Add new music to your sources to browse by folders" emojiSize: Maui.Style.iconSizes.huge } Maui.Dialog { id: _listDialog parent: parent maxHeight: maxWidth maxWidth: Maui.Style.unit * 600 defaultButtons: false page.padding: 0 BabeTable { id: _filterList anchors.fill: parent coverArtVisible: true holder.emoji: "qrc:/assets/MusicCloud.png" holder.isMask: false holder.title : "No Tracks!" holder.body: "This source folder seems to be empty!" holder.emojiSize: Maui.Style.iconSizes.huge } } Component.onCompleted: populate() function populate() { browser.model.clear() var folders = vvave.sourceFolders(); if(folders.length > 0) for(var i in folders) browser.model.append(folders[i]) } function filter() { var where = "source = \""+currentFolder+"\"" _filterList.list.query = (Q.GET.tracksWhere_.arg(where)) } } diff --git a/widgets/MainPlaylist/AlbumsRoll.qml b/widgets/MainPlaylist/AlbumsRoll.qml index 953d2c2..3326b9e 100644 --- a/widgets/MainPlaylist/AlbumsRoll.qml +++ b/widgets/MainPlaylist/AlbumsRoll.qml @@ -1,118 +1,117 @@ import QtQuick.Controls 2.10 import QtQuick 2.10 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import QtGraphicalEffects 1.0 import "../../view_models/BabeGrid" import "../../utils/Player.js" as Player Maui.ToolBar { id: control visible: !mainlistEmpty padding: 0 height: Maui.Style.toolBarHeight * 1.2 background: Item { Image { id: artworkBg height: parent.height width: parent.width sourceSize.width: parent.width sourceSize.height: parent.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 } } middleContent: ListView { id: _listView Layout.fillWidth: true Layout.preferredHeight: Maui.Style.toolBarHeight orientation: ListView.Horizontal clip: true focus: true interactive: true currentIndex: currentTrackIndex spacing: Maui.Style.space.medium - cacheBuffer: control.width * 1 +// cacheBuffer: control.width * 1 onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Center) highlightFollowsCurrentItem: true highlightMoveDuration: 0 snapMode: ListView.SnapToOneItem model: mainPlaylist.listModel highlightRangeMode: ListView.StrictlyEnforceRange keyNavigationEnabled: true keyNavigationWraps : true - onMovementEnded: + onCurrentItemChanged: { var index = indexAt(contentX, contentY) if(index !== currentTrackIndex) Player.playAt(index) } delegate: Maui.ItemDelegate { id: _delegate height: _listView.height width: _listView.width padding: 0 Kirigami.Theme.inherit: true Maui.ListItemTemplate { anchors.fill: parent iconSizeHint: height - Maui.Style.space.small imageSource: model.artwork ? model.artwork : "qrc:/assets/cover.png" label1.text: model.title label2.text: model.artist + " | " + model.album } onClicked: focusView = true background: null } } } diff --git a/widgets/MainPlaylist/MainPlaylist.qml b/widgets/MainPlaylist/MainPlaylist.qml index 235fd42..a1c0fc7 100644 --- a/widgets/MainPlaylist/MainPlaylist.qml +++ b/widgets/MainPlaylist/MainPlaylist.qml @@ -1,197 +1,203 @@ 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: mainPlaylist.table.list.clear() + onClicked: + { + player.stop() + mainPlaylist.table.list.clear() + root.sync = false + root.syncPlaylist = "" + } } 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) } } diff --git a/widgets/PlaylistsView/PlaylistsView.qml b/widgets/PlaylistsView/PlaylistsView.qml index ba15348..13eef91 100644 --- a/widgets/PlaylistsView/PlaylistsView.qml +++ b/widgets/PlaylistsView/PlaylistsView.qml @@ -1,196 +1,198 @@ import QtQuick 2.10 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.10 import org.kde.kirigami 2.7 as Kirigami import org.kde.mauikit 1.0 as Maui import TracksList 1.0 import "../../view_models/BabeTable" import "../../view_models" import "../../db/Queries.js" as Q import "../../utils/Help.js" as H Maui.Page { id: control spacing: Maui.Style.space.medium property string currentPlaylist property string playlistQuery property alias playlistModel : playlistViewModel.model property alias playlistViewList : playlistViewModel property alias listModel : filterList.listModel signal rowClicked(var track) signal quickPlayTrack(var track) + signal appendTrack(var track) signal playAll() signal syncAndPlay(string playlist) signal appendAll() footBar.rightContent: [ ToolButton { id : createPlaylistBtn // text: qsTr("Add") icon.name : "list-add" onClicked: newPlaylistDialog.open() } ] PlaylistsViewModel { id: playlistViewModel anchors.fill: parent } Maui.NewDialog { id: newPlaylistDialog title: qsTr("New Playlist...") onFinished: addPlaylist(text) acceptText: qsTr("Create") rejectButton.visible: false } Maui.Dialog { id: _filterDialog property bool isPublic: true parent: parent maxHeight: maxWidth maxWidth: Maui.Style.unit * 600 defaultButtons: false page.padding: 0 BabeTable { id: filterList anchors.fill: parent clip: true coverArtVisible: true headBar.visible: !holder.visible title: control.currentPlaylist holder.emoji: "qrc:/assets/dialog-information.svg" holder.isMask: false holder.title : title holder.body: "Your playlist is empty,
start adding new music to it" holder.emojiSize: Maui.Style.iconSizes.huge contextMenuItems: MenuItem { text: qsTr("Remove from playlist") } // headerMenu.menuItem: [ // Maui.MenuItem // { // enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon // text: "Sync tags" // onTriggered: {} // }, // Maui.MenuItem // { // enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon // text: "Play-n-Sync" // onTriggered: // { // filterList.headerMenu.close() // syncAndPlay(playlistViewModel.currentIndex) // } // }, // Maui.MenuItem // { // enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon // text: "Remove playlist" // onTriggered: removePlaylist() // } // ] // contextMenu.menuItem: [ // MenuItem // { // text: qsTr("Remove from playlist") // onTriggered: // { // bae.removePlaylistTrack(filterList.model.get(filterList.currentIndex).url, playlistViewModel.model.get(playlistViewModel.currentIndex).playlist) // populate(playlistQuery) // } // } // ] section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { label: filterList.section.property === qsTr("stars") ? H.setStars(section) : section isSection: true labelTxt.font.family: "Material Design Icons" width: filterList.width } Connections { target: filterList onRowClicked: control.rowClicked(filterList.listModel.get(index)) onQuickPlayTrack: control.quickPlayTrack(filterList.listModel.get(filterList.currentIndex)) + onAppendTrack: control.appendTrack(filterList.listModel.get(filterList.currentIndex)) onPlayAll: { if(_filterDialog.isPublic) control.syncAndPlay(control.currentPlaylist) else control.playAll() _filterDialog.close() } onAppendAll: appendAll() onPulled: populate(playlistQuery) } Connections { target: filterList.contextMenu onRemoveClicked: { playlistsList.removeTrack(playlistViewList.currentIndex, filterList.listModel.get(filterList.currentIndex).url) populate(playlistQuery) } } } } function appendToExtraList(res) { if(res.length>0) for(var i in res) playlistViewModelFilter.model.append(res[i]) } function populate(query, isPublic) { playlistQuery = query _filterDialog.isPublic = isPublic filterList.list.query = playlistQuery _filterDialog.open() } function removePlaylist() { playlistsList.removePlaylist(playlistViewList.currentIndex) } function addPlaylist(text) { var title = text.trim() if(playlistsList.insert(title)) control.listView.positionViewAtEnd() } } diff --git a/widgets/PlaylistsView/PlaylistsViewModel.qml b/widgets/PlaylistsView/PlaylistsViewModel.qml index e95221f..dc1f881 100644 --- a/widgets/PlaylistsView/PlaylistsViewModel.qml +++ b/widgets/PlaylistsView/PlaylistsViewModel.qml @@ -1,194 +1,242 @@ import QtQuick 2.10 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.10 import QtGraphicalEffects 1.13 import org.kde.kirigami 2.6 as Kirigami import org.kde.mauikit 1.0 as Maui import PlaylistsList 1.0 import TracksList 1.0 import "../../utils" import "../../view_models" import "../../db/Queries.js" as Q import "../../utils/Help.js" as H BabeList { id: control - signal playSync(int index) topPadding: Maui.Style.contentMargins + holder.emoji: "qrc:/assets/dialog-information.svg" + holder.title : qsTr("No Playlists!") + holder.body: qsTr("Start creating new custom playlists") + + Connections + { + target: holder + onActionTriggered: newPlaylistDialog.open() + } + + Menu + { + id: _playlistMenu + + MenuItem + { + text: qsTr("Play") + onTriggered: populate(Q.GET.playlistTracks_.arg(currentPlaylist), true) + } + + MenuItem + { + text: qsTr("Rename") + } + + MenuSeparator{} + + MenuItem + { + text: qsTr("Delete") + Kirigami.Theme.textColor: Kirigami.Theme.negativeTextColor + onTriggered: removePlaylist() + } + } + Maui.BaseModel { id: _playlistsModel list: playlistsList } model: _playlistsModel section.criteria: ViewSection.FullString section.property: "type" section.delegate: Maui.LabelDelegate { - label: "Public" + label: "Personal" isSection: true width: control.width } delegate : Maui.ListDelegate { id: delegate width: control.width label: model.playlist Connections { target : delegate onClicked : { control.currentIndex = index currentPlaylist = playlistsList.get(index).playlist filterList.group = false populate(Q.GET.playlistTracks_.arg(currentPlaylist), true); } + + onRighClicked: + { + control.currentIndex = index + currentPlaylist = playlistsList.get(index).playlist + _playlistMenu.popup() + } + + onPressAndHold: + { + control.currentIndex = index + currentPlaylist = playlistsList.get(index).playlist + _playlistMenu.popup() + } } } listView.header: Rectangle { z: control.z + 999 width: control.width height: 120 + Maui.Style.rowHeight Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.View color: Kirigami.Theme.backgroundColor ColumnLayout { anchors.fill: parent ListView { id: _defaultPlaylists Layout.fillHeight: true Layout.fillWidth: true Layout.margins: Maui.Style.space.medium spacing: Maui.Style.space.medium orientation :ListView.Horizontal model: playlistsList.defaultPlaylists() delegate: ItemDelegate { id: _delegate readonly property color m_color: modelData.color readonly property string playlist : modelData.playlist Kirigami.Theme.inherit: false Kirigami.Theme.backgroundColor: Qt.rgba(m_color.r, m_color.g, m_color.b, 0.9) Kirigami.Theme.textColor: "white" anchors.verticalCenter: parent.verticalCenter width: 200 height: parent.height * 0.9 background: Rectangle { color : Kirigami.Theme.backgroundColor radius: Maui.Style.radiusV * 2 border.color: m_color } Maui.ListItemTemplate { anchors.fill: parent iconSizeHint: Maui.Style.iconSizes.big label1.text: playlist label1.font.pointSize: Maui.Style.fontSizes.big label1.font.weight: Font.Bold label1.font.bold: true label2.text: modelData.description iconSource: modelData.icon iconVisible: true } Connections { target: _delegate onClicked: { _defaultPlaylists.currentIndex = index currentPlaylist = _delegate.playlist switch(currentPlaylist) { case "Most Played": populate(Q.GET.mostPlayedTracks, false); filterList.list.sortBy = Tracks.COUNT break; case "Rating": filterList.list.sortBy = Tracks.RATE filterList.group = true populate(Q.GET.favoriteTracks, false); break; case "Recent": populate(Q.GET.recentTracks, false); filterList.list.sortBy = Tracks.ADDDATE filterList.group = true break; case "Favs": populate(Q.GET.babedTracks, false); break; case "Online": populate(Q.GET.favoriteTracks, false); break; case "Tags": populateExtra(Q.GET.tags, "Tags") break; case "Relationships": populate(Q.GET.favoriteTracks, false); break; case "Popular": populate(Q.GET.favoriteTracks, false); break; case "Genres": populateExtra(Q.GET.genres, "Genres") break; default: break; } } } } } Item { Layout.fillWidth: true Layout.margins: Maui.Style.space.medium Layout.preferredHeight: Maui.Style.rowHeight ColorTagsBar { anchors.fill: parent onColorClicked: populate(Q.GET.colorTracks_.arg(color.toLowerCase()), true) } } } } }