Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls.Fusion
import QtQuick.Dialogs
import QtQuick.Layouts
import QtCore
import MediaControls
import Config
import io.qt.filenameprovider
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 && mediaCount > 1) {
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(FileNameProvider.getFileName(url.toString()))
})
})
playlistUpdated()
}
function addFile(index, selectedFile) {
if (index > mediaCount || index < 0) {
index = 0
currentIndex = 0
}
files.insert(index,
{
path: selectedFile,
isMovie: isMovie(FileNameProvider.getFileName(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
}
CustomButton {
icon.source: ControlImages.iconSource("Add_file")
onClicked: folderView.open()
}
}
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: Images.iconSource("Movie_Active", false)
}
},
State {
name: "inactiveMovie"
when: root.currentIndex !== row.index && row.isMovie
PropertyChanges {
mediaIcon.source: Images.iconSource("Movie_Icon")
}
},
State {
name: "activeMusic"
when: root.currentIndex === row.index && !row.isMovie
PropertyChanges {
mediaIcon.source: Images.iconSource("Music_Active", false)
}
},
State {
name: "inactiveMusic"
when: root.currentIndex !== row.index && !row.isMovie
PropertyChanges {
mediaIcon.source: Images.iconSource("Music_Icon")
}
}
]
}
Label {
Layout.fillWidth: true
elide: Text.ElideRight
font.bold: root.currentIndex === row.index
color: root.currentIndex === row.index ? Config.highlightColor : Config.secondaryColor
font.pixelSize: 18
text: {
return FileNameProvider.getFileName(row.path)
}
}
CustomButton {
icon.source: ControlImages.iconSource("Trash_Icon")
onClicked: {
const removedIndex = row.index
files.remove(row.index)
if (root.currentIndex === removedIndex) {
root.currentFileRemoved()
} else if (root.currentIndex > removedIndex) {
--root.currentIndex
}
}
}
}
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
}
}
}
}
}