diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index 1b9790da..b3a8aa00 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -185,5 +185,8 @@
ui/connection/SetupWizard.qml
ui/system/NetworkSettingsPage.qml
ui/devicepages/DeviceBrowserPage.qml
+ ui/devicelistpages/MediaDeviceListPage.qml
+ ui/components/MediaControls.qml
+ ui/components/MediaArtworkImage.qml
diff --git a/nymea-app/ui/components/MainPageTile.qml b/nymea-app/ui/components/MainPageTile.qml
index cd0a3c0c..c5801426 100644
--- a/nymea-app/ui/components/MainPageTile.qml
+++ b/nymea-app/ui/components/MainPageTile.qml
@@ -30,6 +30,8 @@ Item {
anchors.fill: parent
anchors.margins: 1
z: -1
+ fillMode: Image.PreserveAspectCrop
+// horizontalAlignment: Image.AlignTop
// opacity: .5
// Rectangle {
// anchors.fill: parent
diff --git a/nymea-app/ui/components/MediaArtworkImage.qml b/nymea-app/ui/components/MediaArtworkImage.qml
new file mode 100644
index 00000000..45fefba4
--- /dev/null
+++ b/nymea-app/ui/components/MediaArtworkImage.qml
@@ -0,0 +1,47 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+
+Item {
+ id: root
+ property Device device: null
+
+ readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
+ readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
+
+ readonly property StateType playerTypeStateType: device ? device.deviceClass.stateTypes.findByName("playerType") : null
+ readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null
+
+ Pane {
+ Material.elevation: 2
+ anchors.centerIn: parent
+ height: fallback.visible ? Math.min(parent.height, parent.width) : artworkImage.paintedHeight - 1
+ width: fallback.visible ? Math.min(parent.height, parent.width) : artworkImage.paintedWidth - 1
+ padding: 0
+ contentItem: Rectangle {
+ color: "black"
+ }
+ }
+
+ Image {
+ id: artworkImage
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectFit
+ source: root.artworkState.value
+ visible: source !== ""
+ }
+
+ ColorIcon {
+ id: fallback
+ anchors.centerIn: parent
+ width: Math.min(parent.height, parent.width) - app.margins * 2
+ height: Math.min(parent.height, parent.width) - app.margins * 2
+
+ name: root.playerTypeState.value === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg"
+ visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
+// color: app.primaryColor
+ color: "white"
+ }
+}
diff --git a/nymea-app/ui/components/MediaControls.qml b/nymea-app/ui/components/MediaControls.qml
new file mode 100644
index 00000000..b6719c5b
--- /dev/null
+++ b/nymea-app/ui/components/MediaControls.qml
@@ -0,0 +1,73 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+
+RowLayout {
+ id: root
+ implicitHeight: iconSize + app.margins
+
+ property Device device: null
+ property int iconSize: app.iconSize * 1.5
+
+ readonly property StateType playbackStateType: device ? device.deviceClass.stateTypes.findByName("playbackStatus") : null
+ readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
+
+ function executeAction(actionName, params) {
+ var actionTypeId = device.deviceClass.actionTypes.findByName(actionName).id;
+ engine.deviceManager.executeAction(device.id, actionTypeId, params)
+ }
+
+ Item { Layout.fillWidth: true }
+ ProgressButton {
+ Layout.preferredHeight: root.iconSize * .6
+ Layout.preferredWidth: height
+ imageSource: "../images/media-skip-backward.svg"
+ longpressImageSource: "../images/media-seek-backward.svg"
+ enabled: root.playbackState.value !== "Stopped"
+ repeat: true
+ onClicked: {
+ root.executeAction("skipBack")
+ }
+ onLongpressed: {
+ root.executeAction("fastRewind")
+ }
+ }
+ Item { Layout.fillWidth: true }
+ ProgressButton {
+ Layout.preferredHeight: root,iconSize
+ Layout.preferredWidth: height
+ imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
+ longpressImageSource: "../images/media-playback-stop.svg"
+ longpressEnabled: root.playbackState.value !== "Stopped"
+
+ onClicked: {
+ if (root.playbackState.value === "Playing") {
+ root.executeAction("pause")
+ } else {
+ root.executeAction("play")
+ }
+ }
+
+ onLongpressed: {
+ root.executeAction("stop")
+ }
+ }
+ Item { Layout.fillWidth: true }
+ ProgressButton {
+ Layout.preferredHeight: root.iconSize * .6
+ Layout.preferredWidth: height
+ imageSource: "../images/media-skip-forward.svg"
+ longpressImageSource: "../images/media-seek-forward.svg"
+ enabled: root.playbackState.value !== "Stopped"
+ repeat: true
+ onClicked: {
+ root.executeAction("skipNext")
+ }
+ onLongpressed: {
+ root.executeAction("fastForward")
+ }
+ }
+ Item { Layout.fillWidth: true }
+}
diff --git a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml
new file mode 100644
index 00000000..3b6629f9
--- /dev/null
+++ b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml
@@ -0,0 +1,170 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import QtGraphicalEffects 1.0
+import "../components"
+
+DeviceListPageBase {
+ id: root
+
+ header: NymeaHeader {
+ text: qsTr("Media")
+ onBackPressed: pageStack.pop()
+ }
+
+ ListView {
+ anchors.fill: parent
+ model: root.devicesProxy
+
+ delegate: ItemDelegate {
+ id: itemDelegate
+ width: parent.width
+
+ property bool inline: width > 500
+
+ property Device device: devicesProxy.get(index);
+ property DeviceClass deviceClass: device.deviceClass
+
+ readonly property StateType playbackStateType: deviceClass.stateTypes.findByName("playbackStatus")
+ readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
+
+ bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
+ contentItem: Pane {
+ id: contentItem
+ Material.elevation: 2
+ leftPadding: 0
+ rightPadding: 0
+ topPadding: 0
+ bottomPadding: 0
+
+ contentItem: ItemDelegate {
+ leftPadding: 0
+ rightPadding: 0
+ topPadding: 0
+ bottomPadding: 0
+ contentItem: ColumnLayout {
+ spacing: 0
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: app.mediumFont + app.margins
+ color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)
+ RowLayout {
+ anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins }
+ Label {
+ Layout.fillWidth: true
+ text: model.name
+ elide: Text.ElideRight
+ }
+ ColorIcon {
+ Layout.preferredHeight: app.iconSize * .5
+ Layout.preferredWidth: height
+ name: "../images/battery/battery-020.svg"
+ visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true
+ }
+ ColorIcon {
+ Layout.preferredHeight: app.iconSize * .5
+ Layout.preferredWidth: height
+ name: "../images/dialog-warning-symbolic.svg"
+ visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false
+ color: "red"
+ }
+ }
+
+ }
+ RowLayout {
+ ColumnLayout {
+ id: leftColummn
+ Layout.margins: app.margins
+ Label {
+ Layout.fillWidth: true
+ text: itemDelegate.playbackState.value === "Stopped" ?
+ qsTr("No playback")
+ : itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value
+ horizontalAlignment: Text.AlignHCenter
+// font.pixelSize: app.largeFont
+ elide: Text.ElideRight
+ }
+ Label {
+ Layout.fillWidth: true
+ text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("artist").id).value
+ font.pixelSize: app.smallFont
+ horizontalAlignment: Text.AlignHCenter
+ elide: Text.ElideRight
+ }
+ Label {
+ Layout.fillWidth: true
+ text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("collection").id).value
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: app.smallFont
+ elide: Text.ElideRight
+ }
+ MediaControls {
+ visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
+ device: itemDelegate.device
+ }
+ }
+ Item {
+ Layout.preferredHeight: leftColummn.height + app.margins * 2
+ Layout.preferredWidth: height * .7
+
+ Item {
+ id: artworkContainer
+ anchors.fill: parent
+ Image {
+ id: artworkImage
+ width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height
+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
+ readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
+ readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
+ source: artworkState ? artworkState.value : ""
+ }
+ }
+
+ Rectangle {
+ id: maskRect
+ anchors.centerIn: parent
+ height: parent.width
+ width: parent.height
+ gradient: Gradient {
+ GradientStop { position: 0; color: "transparent" }
+ GradientStop { position: 1; color: "red" }
+ }
+ }
+
+ ShaderEffect {
+ anchors.fill: parent
+ property variant source: ShaderEffectSource {
+ sourceItem: artworkContainer
+ hideSource: true
+ }
+ property variant mask: ShaderEffectSource {
+ sourceItem: maskRect
+ hideSource: true
+ }
+
+ fragmentShader: "
+ varying highp vec2 qt_TexCoord0;
+ uniform sampler2D source;
+ uniform sampler2D mask;
+ void main(void)
+ {
+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
+ highp float alpha = texture2D(mask, vec2(qt_TexCoord0.y, qt_TexCoord0.x)).a;
+ sourceColor *= alpha;
+ gl_FragColor = sourceColor;
+ }
+ "
+ }
+ }
+ }
+ }
+ onClicked: {
+ enterPage(index, false)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/nymea-app/ui/devicepages/MediaDevicePage.qml b/nymea-app/ui/devicepages/MediaDevicePage.qml
index 33b8ec23..d7066726 100644
--- a/nymea-app/ui/devicepages/MediaDevicePage.qml
+++ b/nymea-app/ui/devicepages/MediaDevicePage.qml
@@ -41,6 +41,7 @@ DevicePageBase {
SwipeView {
id: swipeView
anchors.fill: parent
+ interactive: root.deviceClass.browsable
Item {
GridLayout {
@@ -51,32 +52,10 @@ DevicePageBase {
columnSpacing: app.margins
rowSpacing: app.margins
- Pane {
- Layout.fillWidth: true
+ MediaArtworkImage {
Layout.fillHeight: true
- Layout.minimumWidth: parent.width / 2
- Material.elevation: 2
- padding: 0
-
- contentItem: Rectangle {
- color: app.foregroundColor
-
- Image {
- id: artworkImage
- anchors.fill: parent
- fillMode: Image.PreserveAspectFit
- source: root.stateValue("artwork")
- }
-
- ColorIcon {
- id: fallback
- anchors.fill: parent
- anchors.margins: app.margins * 2
- name: root.stateValue("playerType") === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg"
- visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
- color: app.primaryColor
- }
- }
+ Layout.preferredWidth: parent.width / parent.columns
+ device: root.device
}
ColumnLayout {
@@ -110,62 +89,9 @@ DevicePageBase {
text: root.stateValue("collection")
}
- RowLayout {
- Layout.fillWidth: true
- Item { Layout.fillWidth: true }
-
- ProgressButton {
- Layout.preferredHeight: app.iconSize
- Layout.preferredWidth: height
- imageSource: "../images/media-skip-backward.svg"
- longpressImageSource: "../images/media-seek-backward.svg"
- repeat: true
-
- onClicked: {
- root.executeAction("skipBack")
- }
- onLongpressed: {
- root.executeAction("fastRewind")
- }
- }
-
- Item { Layout.fillWidth: true }
-
- ProgressButton {
- Layout.preferredHeight: app.iconSize * 2
- Layout.preferredWidth: height
- imageSource: root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
- longpressImageSource: "../images/media-playback-stop.svg"
- longpressEnabled: root.playbackState.value !== "Stopped"
-
- onClicked: {
- if (root.playbackState.value === "Playing") {
- root.executeAction("pause")
- } else {
- root.executeAction("play")
- }
- }
-
- onLongpressed: {
- root.executeAction("stop")
- }
- }
-
- Item { Layout.fillWidth: true }
- ProgressButton {
- Layout.preferredHeight: app.iconSize
- Layout.preferredWidth: height
- imageSource: "../images/media-skip-forward.svg"
- longpressImageSource: "../images/media-seek-forward.svg"
- repeat: true
- onClicked: {
- root.executeAction("skipNext")
- }
- onLongpressed: {
- root.executeAction("fastForward")
- }
- }
- Item { Layout.fillWidth: true }
+ MediaControls {
+ device: root.device
+ iconSize: app.iconSize * 2
}
}
}
@@ -279,6 +205,7 @@ DevicePageBase {
Item {
Layout.fillWidth: true
Layout.preferredHeight: 2
+ visible: root.deviceClass.browsable
Rectangle {
height: parent.height
width: parent.width / 2
diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml
index a93fc9c4..105f3fa9 100644
--- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml
+++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml
@@ -13,13 +13,7 @@ MainPageTile {
disconnected: devicesSubProxyConnectables.count > 0
batteryCritical: devicesSubProxyBattery.count > 0
- backgroundImage: currentDevice.deviceClass.interfaces.indexOf("mediametadataprovider") >= 0 ?
- currentDevice.states.getState(currentDevice.deviceClass.stateTypes.findByName("artwork").id).value : ""
-
- property int currentDeviceIndex: 0
- readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)
-// readonly property State currentBackgroundState: currentDevice.state
-
+ backgroundImage: inlineControlLoader.item && inlineControlLoader.item.hasOwnProperty("backgroundImage") ? inlineControlLoader.item.backgroundImage : ""
onClicked: {
var page;
@@ -55,6 +49,9 @@ MainPageTile {
case "powersocket":
page = "PowerSocketsDeviceListPage.qml";
break;
+ case "media":
+ page = "MediaDeviceListPage.qml";
+ break;
default:
page = "GenericDeviceListPage.qml"
}
@@ -84,6 +81,9 @@ MainPageTile {
filterBatteryCritical: true
}
+ property int currentDeviceIndex: 0
+ readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)
+
contentItem: Loader {
id: inlineControlLoader
anchors {
@@ -105,7 +105,6 @@ MainPageTile {
// return labelComponent;
case "light":
- case "media":
case "garagegate":
case "blind":
case "extendedblind":
@@ -115,12 +114,66 @@ MainPageTile {
case "extendedawning":
case "powersocket":
return buttonComponent
+ case "media":
+ return mediaControlComponent
default:
console.warn("DevicesPageDelegate, inlineControl: Unhandled interface", model.name)
}
}
}
+ Component {
+ id: mediaControlComponent
+ RowLayout {
+ id: inlineMediaControl
+
+ property string backgroundImage: artworkState ? artworkState.value : ""
+
+ property int currentDeviceIndex: 0
+ readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)
+ readonly property StateType playbackStateType: currentDevice.deviceClass.stateTypes.findByName("playbackStatus")
+ readonly property State playbackState: currentDevice.states.getState(playbackStateType.id)
+ readonly property StateType artworkStateType: currentDevice.deviceClass.stateTypes.findByName("artwork")
+ readonly property State artworkState: artworkStateType ? currentDevice.states.getState(artworkStateType.id) : null
+
+ Component.onCompleted: {
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var d = devicesProxy.get(i);
+ var st = d.deviceClass.stateTypes.findByName("playbackStatus")
+ var s = d.states.getState(st.id)
+ s.valueChanged.connect(function() {updateTile()})
+ }
+ updateTile();
+ }
+
+ function updateTile() {
+ var playingIndex = -1;
+ var pausedIndex = -1;
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var d = devicesProxy.get(i);
+ var st = d.deviceClass.stateTypes.findByName("playbackStatus");
+ if (!st) continue;
+ var s = d.states.getState(st.id);
+ if (playingIndex === -1 && s.value === "Playing") {
+ playingIndex = i;
+ } else if (pausedIndex === -1 && s.value === "Paused") {
+ pausedIndex = -i;
+ }
+ }
+ if (playingIndex !== -1) {
+ currentDeviceIndex = playingIndex;
+ } else if (pausedIndex !== -1) {
+ currentDeviceIndex = pausedIndex;
+ }
+ }
+
+ MediaControls {
+ iconSize: app.iconSize * 1.2
+ device: inlineMediaControl.currentDevice
+ }
+ }
+ }
+
Component {
id: buttonComponent
ColumnLayout {