// Copyright (C) 2023 The Qt Company Ltd. // Copyright (C) 2024 Automotive Grade Linux // SPDX-License-Identifier: GPL-3.0+ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls.Fusion import QtQuick.Dialogs import QtQuick.Layouts import QtCore import MediaControls import Config Rectangle { id: root implicitWidth: 380 color: Config.mainColor border.color: "lightgrey" radius: 10 property int currentIndex: -1 property bool isShuffled: false property alias mediaCount: files.count signal playlistUpdated() signal currentFileRemoved() function getSource() { if (isShuffled) { let randomIndex = Math.floor(Math.random() * mediaCount) while (randomIndex == currentIndex) { randomIndex = Math.floor(Math.random() * mediaCount) } currentIndex = randomIndex } return files.get(currentIndex).path } function addFiles(index, selectedFiles) { selectedFiles.forEach(function (file){ const url = new URL(file) files.insert(index, { path: url, isMovie: isMovie(url.toString()) }) }) playlistUpdated() } function addFile(index, selectedFile) { if (index > mediaCount || index < 0) { index = 0 currentIndex = 0 } files.insert(index, { path: selectedFile, isMovie: isMovie(selectedFile.toString()) }) } function isMovie(path) { const paths = path.split('.') const extension = paths[paths.length - 1] const musicFormats = ["mp3", "wav", "aac"] for (const format of musicFormats) { if (format === extension) { return false } } return true } MouseArea { anchors.fill: root preventStealing: true } // FileDialog { // id: folderView // title: qsTr("Add files to playlist") // currentFolder: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0] // fileMode: FileDialog.OpenFiles // onAccepted: { // root.addFiles(files.count, folderView.selectedFiles) // close() // } // } ListModel { id: files } Item { id: playlist anchors.fill: root anchors.margins: 30 RowLayout { id: header width: playlist.width Label { font.bold: true font.pixelSize: 20 text: qsTr("Playlist") color: Config.secondaryColor Layout.fillWidth: true } } ListView { id: listView model: files anchors.fill: playlist anchors.topMargin: header.height + 30 spacing: 20 delegate: RowLayout { id: row width: listView.width spacing: 15 required property string path required property int index required property bool isMovie Image { id: mediaIcon states: [ State { name: "activeMovie" when: root.currentIndex === row.index && row.isMovie PropertyChanges { mediaIcon.source: Config.iconSource("Movie_Active", false) } }, State { name: "inactiveMovie" when: root.currentIndex !== row.index && row.isMovie PropertyChanges { mediaIcon.source: Config.iconSource("Movie_Icon") } }, State { name: "activeMusic" when: root.currentIndex === row.index && !row.isMovie PropertyChanges { mediaIcon.source: Config.iconSource("Music_Active", false) } }, State { name: "inactiveMusic" when: root.currentIndex !== row.index && !row.isMovie PropertyChanges { mediaIcon.source: Config.iconSource("Music_Icon") } } ] } Label { Layout.fillWidth: true elide: Text.ElideRight font.bold: root.currentIndex === row.index color: root.currentIndex === row.index ? "#41CD52" : Config.secondaryColor font.pixelSize: 18 text: { const paths = row.path.split('/') return paths[paths.length - 1] } } } remove: Transition { NumberAnimation { property: "opacity" from: 1.0 to: 0.0 duration: 400 } } add: Transition { NumberAnimation { property: "opacity" from: 0.0 to: 1.0 duration: 400 } NumberAnimation { property: "scale" from: 0.5 to: 1.0 duration: 400 } } displaced: Transition { NumberAnimation { properties: "y" duration: 600 easing.type: Easing.OutBounce } } } } }