598 lines
19 KiB
QML
598 lines
19 KiB
QML
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Dialogs
|
|
import QtQuick.Layouts
|
|
import QtMultimedia
|
|
|
|
import com.github.digitalartifex.komplex as Komplex
|
|
|
|
Item
|
|
{
|
|
property alias selectedFile: searchModel.lastSavedFile
|
|
|
|
id: mainItem
|
|
anchors.fill: parent
|
|
|
|
Komplex.PexelsVideoSearchModel
|
|
{
|
|
id: searchModel
|
|
}
|
|
|
|
ColumnLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
RowLayout
|
|
{
|
|
Layout.fillHeight: false
|
|
Layout.fillWidth: true
|
|
Layout.margins: 6
|
|
|
|
TextField
|
|
{
|
|
Layout.preferredHeight: 32
|
|
Layout.fillWidth: true
|
|
|
|
id: searchField
|
|
placeholderText: "Search"
|
|
onEditingFinished: mainItem.updateSearch()
|
|
Keys.onPressed: (event) =>
|
|
{
|
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
|
|
{
|
|
searchField.focus = false; // Unfocus the TextField
|
|
event.accepted = true; // Prevent further propagation of the key event
|
|
}
|
|
}
|
|
}
|
|
|
|
Button
|
|
{
|
|
Layout.preferredHeight: 32
|
|
Layout.preferredWidth: 32
|
|
|
|
icon.name: "search-symbolic"
|
|
|
|
onClicked: mainItem.updateSearch()
|
|
}
|
|
}
|
|
|
|
Component
|
|
{
|
|
id: highlight
|
|
Rectangle
|
|
{
|
|
width: view.cellWidth; height: view.cellHeight
|
|
color: palette.highlight; radius: 5
|
|
x: view.currentItem.x
|
|
y: view.currentItem.y
|
|
Behavior on x { SpringAnimation { spring: 3; damping: 0.2 } }
|
|
Behavior on y { SpringAnimation { spring: 3; damping: 0.2 } }
|
|
}
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
color: palette.base
|
|
clip: true
|
|
|
|
RowLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
GridView
|
|
{
|
|
// The standard size
|
|
property int idealCellHeight: 220
|
|
property int idealCellWidth: 250
|
|
cellWidth: width / Math.floor(width / idealCellWidth)
|
|
cellHeight: idealCellHeight
|
|
|
|
id: view
|
|
|
|
model: searchModel
|
|
highlight: highlight
|
|
highlightFollowsCurrentItem: false
|
|
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
Layout.margins: 6
|
|
|
|
delegate: Column
|
|
{
|
|
property int itemIndex: index
|
|
property int originalHeight: videoHeight
|
|
property int originalWidth: videoWidth
|
|
property int videoId: id
|
|
property string author: user
|
|
property string authorUrl: userUrl
|
|
property string videoUrl: videoUrl
|
|
property string thumbnailUrl: thumbnail
|
|
|
|
id: entry
|
|
|
|
leftPadding: Math.floor((width - thumbnailImage.width) / 2)
|
|
topPadding: 10
|
|
rightPadding: Math.floor((width - thumbnailImage.width) / 2)
|
|
bottomPadding: 10
|
|
width: view.cellWidth
|
|
|
|
Image
|
|
{
|
|
width: 250
|
|
height: 140
|
|
id: thumbnailImage
|
|
source: thumbnail
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
Rectangle
|
|
{
|
|
color: palette.base
|
|
anchors.fill: parent
|
|
visible: thumbnailImage.status === Image.Loading
|
|
|
|
RowLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
BusyIndicator
|
|
{
|
|
Layout.alignment: Qt.AlignCenter
|
|
Layout.preferredHeight: 64
|
|
Layout.preferredWidth: 64
|
|
visible: running
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea
|
|
{
|
|
anchors.fill: parent
|
|
onClicked: (mouse) =>
|
|
{
|
|
view.currentIndex = parent.parent.itemIndex
|
|
searchModel.currentIndex = view.currentIndex
|
|
}
|
|
}
|
|
}
|
|
|
|
Text
|
|
{
|
|
property string externalLink: authorUrl
|
|
|
|
elide: Text.ElideRight
|
|
topPadding: 4
|
|
bottomPadding: 2
|
|
text: "<h3>" + author + "</h3>"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: 280
|
|
color: palette.link
|
|
|
|
MouseArea
|
|
{
|
|
anchors.fill: parent
|
|
onClicked: (mouse) =>
|
|
{
|
|
Qt.openUrlExternally(parent.externalLink)
|
|
}
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
}
|
|
|
|
RowLayout
|
|
{
|
|
visible: parent.itemIndex === view.currentIndex
|
|
width: thumbnailImage.width
|
|
Button
|
|
{
|
|
Layout.topMargin: 4
|
|
Layout.alignment: Qt.AlignRight
|
|
Layout.preferredHeight: 32
|
|
Layout.fillWidth: true
|
|
icon.name: "download-symbolic"
|
|
icon.source: "./icons/download.svg"
|
|
text: qsTr("Preview & Download")
|
|
|
|
onClicked: () =>
|
|
{
|
|
downloadDialog.user = entry.author
|
|
downloadDialog.userUrl = entry.authorUrl
|
|
downloadDialog.thumbnail = entry.thumbnailUrl
|
|
downloadDialog.id = entry.videoId
|
|
downloadDialog.open()
|
|
searchModel.videoModel.update()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
populate: Transition
|
|
{
|
|
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout
|
|
{
|
|
Layout.margins: 6
|
|
Layout.fillWidth: true
|
|
|
|
Button
|
|
{
|
|
text: "Previous"
|
|
enabled: searchModel.previousPage !== ""
|
|
onClicked: searchModel.back()
|
|
}
|
|
|
|
RowLayout
|
|
{
|
|
Layout.margins: 6
|
|
Layout.fillWidth: true
|
|
|
|
Text
|
|
{
|
|
visible: searchModel.totalResults > 0
|
|
color: palette.text
|
|
text: ((searchModel.resultsPerPage * searchModel.currentPage) - searchModel.resultsPerPage + 1) + "-" + (searchModel.resultsPerPage * searchModel.currentPage) + " of " + searchModel.totalResults
|
|
}
|
|
|
|
Text
|
|
{
|
|
color: palette.text
|
|
Layout.fillWidth: true
|
|
text: "Page " + searchModel.currentPage + " of " + Math.ceil(searchModel.totalResults / searchModel.resultsPerPage)
|
|
}
|
|
|
|
Text
|
|
{
|
|
color: palette.text
|
|
text: "Videos provided by Pexels"
|
|
font.bold: true
|
|
|
|
onLinkActivated: (link) => Qt.openUrlExternally(link)
|
|
|
|
MouseArea
|
|
{
|
|
anchors.fill: parent
|
|
onClicked: (mouse) =>
|
|
{
|
|
Qt.openUrlExternally("https://www.pexels.com")
|
|
}
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
}
|
|
}
|
|
Button
|
|
{
|
|
text: "Next"
|
|
enabled: searchModel.nextPage !== ""
|
|
onClicked: searchModel.next()
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
color: palette.base
|
|
anchors.fill: parent
|
|
visible: searchModel.status === Komplex.PexelsVideoSearchModel.Searching
|
|
|
|
RowLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
BusyIndicator
|
|
{
|
|
Layout.alignment: Qt.AlignCenter
|
|
Layout.preferredHeight: 128
|
|
Layout.preferredWidth: 128
|
|
visible: running
|
|
}
|
|
}
|
|
}
|
|
|
|
Dialog
|
|
{
|
|
property string user
|
|
property string userUrl
|
|
property string thumbnail
|
|
property string preview
|
|
property int id
|
|
|
|
id: downloadDialog
|
|
modal: Qt.WindowModal
|
|
width: 600
|
|
height: 440
|
|
|
|
anchors.centerIn: parent
|
|
clip: true
|
|
|
|
ColumnLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
Text
|
|
{
|
|
property string externalLink: downloadDialog.userUrl
|
|
|
|
elide: Text.ElideRight
|
|
text: "<h3>" + downloadDialog.user + "</h3>"
|
|
width: 280
|
|
color: palette.link
|
|
|
|
MouseArea
|
|
{
|
|
anchors.fill: parent
|
|
onClicked: (mouse) =>
|
|
{
|
|
Qt.openUrlExternally(parent.externalLink)
|
|
}
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 300
|
|
color: "black"
|
|
ColumnLayout
|
|
{
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
VideoOutput
|
|
{
|
|
property alias duration: mediaPlayer.duration
|
|
property alias mediaSource: mediaPlayer.source
|
|
property alias metaData: mediaPlayer.metaData
|
|
property alias playbackRate: mediaPlayer.playbackRate
|
|
property alias position: mediaPlayer.position
|
|
property alias seekable: mediaPlayer.seekable
|
|
property alias volume: audioOutput.volume
|
|
|
|
signal sizeChanged
|
|
signal fatalError
|
|
|
|
id: videoOutput
|
|
|
|
visible: true
|
|
Layout.preferredWidth: 500
|
|
Layout.preferredHeight: 281
|
|
Layout.alignment: Qt.AlignHCenter
|
|
fillMode: VideoOutput.PreserveAspectCrop
|
|
smooth: true
|
|
|
|
onHeightChanged: this.sizeChanged()
|
|
|
|
MediaPlayer
|
|
{
|
|
id: mediaPlayer
|
|
videoOutput: videoOutput
|
|
source: Qt.resolvedUrl(downloadSelector.currentValue ? downloadSelector.currentValue : "")
|
|
|
|
audioOutput: AudioOutput
|
|
{
|
|
id: audioOutput
|
|
volume: 0
|
|
}
|
|
|
|
onErrorOccurred: function(error, errorString)
|
|
{
|
|
if (MediaPlayer.NoError !== error)
|
|
{
|
|
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
|
videoOutput.fatalError()
|
|
}
|
|
}
|
|
|
|
onSourceChanged:
|
|
{
|
|
if(mediaPlayer.source !== "")
|
|
mediaPlayer.play()
|
|
else
|
|
mediaPlayer.stop()
|
|
}
|
|
}
|
|
|
|
function start() { mediaPlayer.play() }
|
|
function stop() { mediaPlayer.stop() }
|
|
function seek(position) { mediaPlayer.setPosition(position); }
|
|
|
|
Image
|
|
{
|
|
visible: !mediaPlayer.playing
|
|
anchors.fill: parent
|
|
|
|
fillMode: Image.PreserveAspectFill
|
|
source: downloadDialog.thumbnail
|
|
}
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 18
|
|
color: palette.alternateBase
|
|
RowLayout
|
|
{
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
ProgressBar
|
|
{
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
from: 0
|
|
to: mediaPlayer.duration
|
|
value: mediaPlayer.position
|
|
}
|
|
Button
|
|
{
|
|
Layout.preferredHeight: 18
|
|
Layout.preferredWidth: 18
|
|
icon.name: mediaPlayer.playing ? "stop-symbolic" : "play-symbolic"
|
|
hoverEnabled: true
|
|
ToolTip.text: mediaPlayer.playing ? "Play" : "Stop"
|
|
ToolTip.visible: hovered
|
|
icon.source: mediaPlayer.playing ? "./icons/stop.svg" : "./icons/play.svg"
|
|
icon.height: 16
|
|
icon.width: 16
|
|
onClicked: () =>
|
|
{
|
|
if(mediaPlayer.playing)
|
|
mediaPlayer.stop()
|
|
else
|
|
mediaPlayer.play()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Text
|
|
{
|
|
text: qsTr("Download Options")
|
|
color: palette.text
|
|
font.bold: true
|
|
Layout.fillHeight: true
|
|
verticalAlignment: Text.AlignBottom
|
|
}
|
|
|
|
RowLayout
|
|
{
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: downloadSelector.height
|
|
ComboBox
|
|
{
|
|
id: downloadSelector
|
|
Layout.fillWidth: true
|
|
model: searchModel.videoModel
|
|
textRole: "text"
|
|
valueRole: "url"
|
|
}
|
|
|
|
Button
|
|
{
|
|
enabled: downloadSelector.currentIndex >= 0
|
|
Layout.preferredHeight: downloadSelector.height
|
|
Layout.preferredWidth: downloadSelector.height
|
|
icon.source: "./icons/download.svg"
|
|
icon.name: "download-symbolic"
|
|
hoverEnabled: true
|
|
ToolTip.text: "Download"
|
|
ToolTip.visible: hovered
|
|
|
|
id: downloadButton
|
|
|
|
onClicked: () =>
|
|
{
|
|
downloadDialog.close();
|
|
progressDialog.thumbnail = downloadDialog.thumbnail
|
|
progressDialog.author = downloadDialog.user
|
|
progressDialog.open()
|
|
searchModel.videoModel.download(downloadSelector.currentIndex)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections
|
|
{
|
|
target: searchModel.videoModel
|
|
function onStatusChanged()
|
|
{
|
|
if(searchModel.videoModel.status === 0)
|
|
downloadSelector.currentIndex = 0
|
|
}
|
|
}
|
|
|
|
onClosed: () =>
|
|
{
|
|
mediaPlayer.stop()
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
color: palette.base
|
|
anchors.fill: parent
|
|
visible: searchModel.videoModel.status === 1
|
|
|
|
RowLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
BusyIndicator
|
|
{
|
|
Layout.alignment: Qt.AlignCenter
|
|
Layout.preferredHeight: 128
|
|
Layout.preferredWidth: 128
|
|
visible: running
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Dialog
|
|
{
|
|
property string author
|
|
property string thumbnail
|
|
|
|
modal: Qt.WindowModal
|
|
width: 600
|
|
height: 420
|
|
|
|
anchors.centerIn: parent
|
|
clip: true
|
|
|
|
id: progressDialog
|
|
|
|
ColumnLayout
|
|
{
|
|
anchors.fill: parent
|
|
|
|
Image
|
|
{
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
|
|
fillMode: Image.PreserveAspectCrop
|
|
source: downloadDialog.thumbnail
|
|
}
|
|
|
|
Text
|
|
{
|
|
text: "Downloading Video..."
|
|
color: palette.text
|
|
}
|
|
|
|
ProgressBar
|
|
{
|
|
value: searchModel.videoModel.downloadProgress
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 6
|
|
}
|
|
}
|
|
|
|
Connections
|
|
{
|
|
target: searchModel.videoModel
|
|
function onDownloadFinished()
|
|
{
|
|
progressDialog.close()
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateSearch()
|
|
{
|
|
console.log(searchField.text)
|
|
searchModel.query = searchField.text
|
|
}
|
|
}
|