This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
2020-11-26 22:37:19 +01:00

469 lines
19 KiB
QML

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project 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 project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.9
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import Nymea 1.0
import QtGraphicalEffects 1.0
import "../delegates"
Item {
id: root
height: swipeView.height
width: swipeView.width
property Thing thing: null
readonly property State playbackState: thing.stateByName("playbackStatus")
readonly property State inputSourceState: thing.stateByName("inputSource")
readonly property State playDurationState: thing.stateByName("playDuration")
readonly property State playTimeState: thing.stateByName("playTime")
readonly property State titleState: thing.stateByName("title")
readonly property State artistState: thing.stateByName("artist")
readonly property State collectionState: thing.stateByName("collection")
readonly property State artworkState: thing.stateByName("artwork")
readonly property bool hasVolumeControl: thing.thingClass.interfaces.indexOf("volumecontroller") >= 0
readonly property State volumeState: thing.stateByName("volume")
readonly property State muteState: thing.stateByName("mute")
readonly property State likeState: thing.stateByName("like")
readonly property bool hasNavigationPatd: thing.thingClass.interfaces.indexOf("navigationpad") >= 0
clip: true
QtObject {
id: d
property var browser: null
property int pendingInputSourceSelectId: -1
}
Connections {
target: engine.thingManager
onExecuteActionReply: {
if (commandId == d.pendingInputSourceSelectId) {
if (params.deviceError !== "DeviceErrorNoError") {
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var text
if (params.displayMessage.length > 0) {
text = params.displayMessage
}
var popup = errorDialog.createObject(app, {text: text})
popup.open()
}
}
}
}
MediaArtworkImage {
id: artworkImage
anchors { left: parent.left; top: parent.top; right: parent.right }
height: parent.height
thing: root.thing
}
Rectangle {
id: gradientMask
anchors.centerIn: parent
height: Math.max(artworkImage.height, artworkImage.width)
width: Math.max(artworkImage.height, artworkImage.width)
rotation: app.landscape ? -90 : 0
visible: contentStartPos < artworkEndPos
property double artworkEndPos: app.landscape ?
artworkImage.paintedWidth / artworkImage.width
: artworkImage.paintedHeight / artworkImage.height
property double contentStartPos: app.landscape ?
(artworkImage.width - content.width - app.margins * 2) / artworkImage.width
: (artworkImage.height - content.height - app.margins * 2) / artworkImage.height
property double gradientEnd: Math.min(artworkEndPos, contentStartPos + .2)
gradient: Gradient {
GradientStop { position: gradientMask.gradientEnd - .5; color: "transparent"}
GradientStop { position: gradientMask.gradientEnd; color: app.backgroundColor }
}
}
ColumnLayout {
id: content
anchors {
bottom: parent.bottom;
left: parent.left;
right: parent.right
leftMargin: app.landscape ? root.width / 2 : app.margins
rightMargin: app.margins
bottomMargin: app.margins
}
spacing: app.margins
RowLayout {
ColumnLayout {
Label {
text: root.playbackState.value === "Stopped" ?
qsTr("No playback")
: root.titleState.value
maximumLineCount: 2
wrapMode: Text.WordWrap
Layout.fillWidth: true
elide: Text.ElideRight
font.pixelSize: app.largeFont
}
Label {
text: root.artistState.value
Layout.fillWidth: true
elide: Text.ElideRight
visible: text.length > 0
font.pixelSize: app.smallFont
}
Label {
text: root.collectionState.value
Layout.fillWidth: true
elide: Text.ElideRight
visible: text.length > 0
font.pixelSize: app.smallFont
}
}
ProgressButton {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
longpressEnabled: false
imageSource: "../images/like.svg"
visible: root.likeState !== null
color: root.likeState && root.likeState.value === true ? app.accentColor : keyColor
onClicked: {
engine.thingManager.executeAction(root.thing.id, root.likeState.stateTypeId, [{ paramTypeId: root.likeState.stateTypeId, value: !root.likeState.value}])
}
}
}
RowLayout {
visible: root.playTimeState !== null || root.playDurationState != null
function timeString(seconds) {
var hours = Math.floor(seconds / 3600);
seconds = seconds % 3600;
var minutes = Math.floor(seconds / 60);
seconds = seconds % 60;
var ret = "";
if (hours > 0) {
ret += hours + ":";
}
ret += NymeaUtils.pad(minutes, 2) + ":";
ret += NymeaUtils.pad(seconds, 2);
return ret;
}
Label {
font.pixelSize: app.smallFont
text: root.playTimeState ? parent.timeString(root.playTimeState.value) : "00:00"
}
Slider {
Layout.fillWidth: true
from: 0
to: root.playDurationState ? root.playDurationState.value : 0
value: root.playTimeState ? root.playTimeState.value : 0
property ActionType playTimeActionType: root.thing.thingClass.actionTypes.findByName("playTime")
enabled: playTimeActionType !== null
onPressedChanged: {
if (!pressed) {
engine.thingManager.executeAction(root.thing.id, playTimeActionType.id, [{paramTypeId: playTimeActionType.id, value: value}]);
}
}
}
Label {
font.pixelSize: app.smallFont
text: root.playDurationState ? parent.timeString(root.playDurationState.value) : "00:00"
}
}
MediaControls {
thing: root.thing
showExtendedControls: true
}
RowLayout {
spacing: app.margins
ProgressButton {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
longpressEnabled: false
visible: root.thing.thingClass.browsable
imageSource: "../images/folder-symbolic.svg"
onClicked: {
if (!d.browser) {
d.browser = browserPage.createObject(root, {x: 0, y: root.height})
}
d.browser.show();
}
}
RowLayout {
ProgressButton {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
longpressEnabled: false
visible: root.inputSourceState !== null
imageSource: "../images/state-in.svg"
onClicked: {
var popup = inputSourceSelectDialogComponent.createObject(root)
popup.open()
}
}
Label {
Layout.fillWidth: true
text: root.inputSourceState ? root.inputSourceState.value : ""
font.pixelSize: app.smallFont
}
}
ProgressButton {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
longpressEnabled: false
visible: root.hasNavigationPatd
imageSource: "../images/navigationpad.svg"
onClicked: pageStack.push(navigationPadPage)
}
ProgressButton {
id: volumeButton
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
visible: root.hasVolumeControl
imageSource: root.muteState && root.muteState.value === true ?
"../images/audio-speakers-muted-symbolic.svg"
: "../images/audio-speakers-symbolic.svg"
onClicked: {
print(volumeButton.x, volumeButton.y)
print(Qt.point(volumeButton.x, volumeButton.y))
print(volumeButton.mapToItem(root, volumeButton.x,0))
var buttonPosition = root.mapFromItem(volumeButton, 0, 0)
var sliderHeight = 200
var props = {}
props["x"] = buttonPosition.x - app.margins
props["y"] = buttonPosition.y - sliderHeight
props["height"] = sliderHeight
var sliderPane = volumeSliderPaneComponent.createObject(root, props)
sliderPane.open()
}
onLongpressed: {
}
}
}
}
Component {
id: volumeSliderPaneComponent
Dialog {
id: volumeSliderDialog
leftPadding: 0
topPadding: app.margins / 2
rightPadding: 0
bottomPadding: app.margins / 2
modal: true
property int pendingVolumeValue: -1
contentItem: ColumnLayout {
ProgressButton {
visible: root.volumeState === null
Layout.alignment: Qt.AlignHCenter
longpressEnabled: false
imageSource: "../images/up.svg"
onClicked: engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("increaseVolume").id);
}
ProgressButton {
visible: root.volumeState === null
Layout.alignment: Qt.AlignHCenter
longpressEnabled: false
imageSource: "../images/down.svg"
onClicked: engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("decreaseVolume").id);
}
ThrottledSlider {
Layout.fillHeight: true
visible: root.volumeState !== null
from: 0
to: 100
value: root.volumeState.value
orientation: Qt.Vertical
onMoved: engine.thingManager.executeAction(root.thing.id, root.volumeState.stateTypeId, [{paramTypeId: root.volumeState.stateTypeId, value: value}])
}
ProgressButton {
visible: root.muteState !== null
Layout.alignment: Qt.AlignHCenter
imageSource: "../images/audio-speakers-muted-symbolic.svg"
color: root.muteState.value === true ? app.accentColor : keyColor
onClicked: engine.thingManager.executeAction(root.thing.id, root.muteState.stateTypeId, [{paramTypeId: root.muteState.stateTypeId, value: !root.muteState.value}]);
}
}
}
}
Component {
id: navigationPadPage
Page {
header: NymeaHeader { text: root.thing.name; onBackPressed: pageStack.pop() }
ColumnLayout {
anchors.fill: parent
anchors.margins: app.margins
spacing: app.margins
NavigationPad { Layout.fillWidth: true; Layout.fillHeight: true; device: root.thing }
MediaControls { Layout.fillWidth: true; thing: root.thing }
ShuffleRepeatVolumeControl { Layout.fillWidth: true; Layout.fillHeight: false; Layout.preferredHeight: app.iconSize; thing: root.thing }
}
}
}
Component {
id: browserPage
Page {
width: root.width
height: root.height
y: root.height
function show() { y = 0 }
function hide() { y = root.height }
header: ToolBar {
RowLayout {
anchors.fill: parent
HeaderButton {
imageSource: "../images/down.svg"
onClicked: d.browser.hide()
}
Flickable {
id: pathFlickable
Layout.fillWidth: true
Layout.margins: app.margins / 2
Layout.fillHeight: true
contentX: Math.max(0, contentWidth - width)
contentWidth: pathRow.width
clip: true
onContentWidthChanged: {
print("contentWidth", contentWidth, "width", width, contentX)
}
Row {
id: pathRow
Repeater {
model: mediaBrowser.path
// orientation: ListView.Horizontal
Rectangle {
height: pathFlickable.height
width: Math.min(150, folderLabel.implicitWidth + app.margins)
border.color: app.backgroundColor
border.width: 1
radius: 4
color: Qt.lighter(app.backgroundColor)
Label {
id: folderLabel
text: modelData
width: parent.width
elide: Text.ElideRight
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
}
MouseArea {
anchors.fill: parent
onClicked: {
var backCount = mediaBrowser.path.count - index - 1;
print("backCount:", backCount)
for (var i = 0; i < backCount - 1; i++) {
mediaBrowser.backPressed(true)
}
if (backCount > 0) {
mediaBrowser.backPressed(false)
}
}
}
}
}
}
}
}
}
Behavior on y { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
MediaBrowser {
id: mediaBrowser
anchors.fill: parent
thing: root.thing
onExit: {
d.browser.hide()
}
onItemLaunched: {
d.browser.hide()
}
}
}
}
Component {
id: inputSourceSelectDialogComponent
MeaDialog {
id: inputSourceSelectDialog
title: qsTr("Select input")
standardButtons: Dialog.NoButton
ListView {
id: inputSourceListView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: 200
clip: true
model: root.thing.thingClass.stateTypes.findByName("inputSource").allowedValues
delegate: RadioDelegate {
width: inputSourceListView.width
text: modelData
checked: root.inputSourceState.value === modelData
onClicked: {
d.pendingInputSourceSelectId = engine.thingManager.executeAction(root.thing.id, root.inputSourceState.stateTypeId, [{paramTypeId: root.inputSourceState.stateTypeId, value: modelData}])
inputSourceSelectDialog.close();
}
}
}
}
}
}