diff --git a/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java b/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java
index b3febd4f..9fc1d737 100644
--- a/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java
+++ b/androidservice/java/io/guh/nymeaapp/NymeaAppControlService.java
@@ -189,7 +189,7 @@ public class NymeaAppControlService extends ControlsProviderService {
actionTypeId = thing.actionByName("close").typeId;
}
param = "";
- } else if (thing.interfaces.contains("extendedvolumecontroller")) {
+ } else if (thing.interfaces.contains("volumecontroller") && thing.stateByName("volume") != null) {
actionTypeId = thing.stateByName("volume").typeId;
FloatAction fAction = (FloatAction) action;
param = String.valueOf(Math.round(fAction.getNewValue()));
@@ -271,10 +271,12 @@ public class NymeaAppControlService extends ControlsProviderService {
// FIXME: There doesn't seem to be a speaker DeviceType!?!
builder.setDeviceType(DeviceTypes.TYPE_TV);
}
- if (thing.interfaces.contains("extendedvolumecontroller")) {
+ if (thing.interfaces.contains("volumecontroller")) {
State volumeState = thing.stateByName("volume");
- RangeTemplate rangeTemplate = new RangeTemplate(thing.id.toString(), 0, 100, Float.parseFloat(volumeState.value), 1, volumeState.displayName);
- builder.setControlTemplate(rangeTemplate);
+ if (volumeState != null) {
+ RangeTemplate rangeTemplate = new RangeTemplate(thing.id.toString(), 0, 100, Float.parseFloat(volumeState.value), 1, volumeState.displayName);
+ builder.setControlTemplate(rangeTemplate);
+ }
}
} else {
builder.setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF);
diff --git a/libnymea-app/devicemanager.cpp b/libnymea-app/devicemanager.cpp
index b2ce20d7..a1ac1532 100644
--- a/libnymea-app/devicemanager.cpp
+++ b/libnymea-app/devicemanager.cpp
@@ -712,9 +712,9 @@ void DeviceManager::browserItemResponse(int commandId, const QVariantMap ¶ms
int DeviceManager::executeBrowserItem(const QUuid &deviceId, const QString &itemId)
{
QVariantMap params;
- params.insert("deviceId", deviceId);
+ params.insert("thingId", deviceId);
params.insert("itemId", itemId);
- return m_jsonClient->sendCommand("Actions.ExecuteBrowserItem", params, this, "executeBrowserItemResponse");
+ return m_jsonClient->sendCommand("Integrations.ExecuteBrowserItem", params, this, "executeBrowserItemResponse");
}
void DeviceManager::executeBrowserItemResponse(int commandId, const QVariantMap ¶ms)
@@ -726,12 +726,12 @@ void DeviceManager::executeBrowserItemResponse(int commandId, const QVariantMap
int DeviceManager::executeBrowserItemAction(const QUuid &deviceId, const QString &itemId, const QUuid &actionTypeId, const QVariantList ¶ms)
{
QVariantMap data;
- data.insert("deviceId", deviceId);
+ data.insert("thingId", deviceId);
data.insert("itemId", itemId);
data.insert("actionTypeId", actionTypeId);
data.insert("params", params);
qDebug() << "params:" << params;
- return m_jsonClient->sendCommand("Actions.ExecuteBrowserItemAction", data, this, "executeBrowserItemActionResponse");
+ return m_jsonClient->sendCommand("Integrations.ExecuteBrowserItemAction", data, this, "executeBrowserItemActionResponse");
}
int DeviceManager::connectIO(const QUuid &inputThingId, const QUuid &inputStateTypeId, const QUuid &outputThingId, const QUuid &outputStateTypeId, bool inverted)
diff --git a/libnymea-app/jsonrpc/jsonrpcclient.cpp b/libnymea-app/jsonrpc/jsonrpcclient.cpp
index 71709fad..4426977a 100644
--- a/libnymea-app/jsonrpc/jsonrpcclient.cpp
+++ b/libnymea-app/jsonrpc/jsonrpcclient.cpp
@@ -589,7 +589,8 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
}
if (dataMap.value("status").toString() == "error") {
- qWarning() << "An error happened in the JSONRPC layer!";
+ qWarning() << "An error happened in the JSONRPC layer:" << dataMap.value("error").toString();
+ qWarning() << "Request was:" << qUtf8Printable(QJsonDocument::fromVariant(reply->requestMap()).toJson());
if (reply->nameSpace() == "JSONRPC" && reply->method() == "Hello") {
qWarning() << "Hello call failed. Trying again without locale";
m_id = 0;
diff --git a/libnymea-app/types/interfaces.cpp b/libnymea-app/types/interfaces.cpp
index 79301d4a..8ddd2fa8 100644
--- a/libnymea-app/types/interfaces.cpp
+++ b/libnymea-app/types/interfaces.cpp
@@ -144,10 +144,10 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addActionType("mediacontroller", "pause", tr("Pause playback"), new ParamTypes());
addActionType("mediacontroller", "skipBack", tr("Skip back"), new ParamTypes());
addActionType("mediacontroller", "skipNext", tr("Skip next"), new ParamTypes());
-
- addInterface("extendedmediacontroller", tr("Media controllers with seeking"), {"mediacontroller"});
- addActionType("extendedmediacontroller", "fastForward", tr("Fast forward"), new ParamTypes());
- addActionType("extendedmediacontroller", "fastRewind", tr("Fast rewind"), new ParamTypes());
+ addActionType("mediacontroller", "fastForward", tr("Fast forward"), new ParamTypes());
+ addActionType("mediacontroller", "fastRewind", tr("Fast rewind"), new ParamTypes());
+ addStateType("mediacontroller", "shuffle", QVariant::Bool, true, tr("Shuffle"), tr("Shuffle changed"), tr("Set shuffle"));
+ addStateType("mediacontroller", "repeat", QVariant::Bool, true, tr("Repeat"), tr("Repeat changed"), tr("Set repeat"));
addInterface("navigationpad", tr("Navigation pad"));
pts = createParamTypes("to", tr("To"), QVariant::String, QVariant(), {"up", "down", "left", "right", "enter", "back"});
@@ -175,10 +175,6 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addInterface("extendedsmartmeterproducer", tr("Smart meters"), {"smartmeterproducer"});
addStateType("extendedsmartmeterproducer", "currentPower", QVariant::Double, false, tr("Current power"), tr("Current power changed"));
- addInterface("extendedvolumecontroller", tr("Volume control"), {"media"});
- addStateType("extendedvolumecontroller", "mute", QVariant::Bool, true, tr("Mute"), tr("Muted"), tr("Mute"));
- addStateType("extendedvolumecontroller", "volume", QVariant::Bool, true, tr("Volume"), tr("Volume changed"), tr("Set volume"), 0, 100);
-
addInterface("useraccesscontrol", tr("User access control systems"), {"accesscontrol"});
addStateType("useraccesscontrol", "users", QVariant::StringList, false, tr("Users"), tr("Users changed"));
pts = createParamTypes("user", tr("User"), QVariant::String);
@@ -268,10 +264,6 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addInterface("pressuresensor", tr("Pressure sensors"), {"sensor"});
addStateType("pressuresensor", "pressure", QVariant::Double, false, tr("Pressure"), tr("Pressure changed"));
- addInterface("shufflerepeat", tr("Shuffle and repeat controllers"));
- addStateType("shufflerepeat", "shuffle", QVariant::Bool, true, tr("Shuffle"), tr("Shuffle changed"), tr("Set shuffle"));
- addStateType("shufflerepeat", "repeat", QVariant::Bool, true, tr("Repeat"), tr("Repeat changed"), tr("Set repeat"));
-
addInterface("smartlock", tr("Smart locks"));
addStateType("smartlock", "state", QVariant::String, false, tr("State"), tr("State changed"));
addActionType("smartlock", "unlatch", tr("Unlatch"), new ParamTypes());
@@ -285,6 +277,8 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addInterface("ventilation", tr("Ventilation"), {"power"});
addInterface("volumecontroller", tr("Speakers"));
+ addStateType("volumecontroller", "mute", QVariant::Bool, true, tr("Mute"), tr("Muted"), tr("Mute"));
+ addStateType("volumecontroller", "volume", QVariant::Bool, true, tr("Volume"), tr("Volume changed"), tr("Set volume"), 0, 100);
addActionType("volumecontroller", "increaseVolume", tr("Increase volume"), new ParamTypes());
addActionType("volumecontroller", "decreaseVolume", tr("Decrease volume"), new ParamTypes());
diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc
index dd6ab995..4e3c404a 100644
--- a/nymea-app/images.qrc
+++ b/nymea-app/images.qrc
@@ -238,5 +238,8 @@
ui/images/connections/network-wired-disabled.svg
ui/images/nfc.svg
ui/images/smartphone.svg
+ ui/images/state-in.svg
+ ui/images/state-out.svg
+ ui/images/like.svg
diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro
index 0dc30a95..a1f7fb36 100644
--- a/nymea-app/nymea-app.pro
+++ b/nymea-app/nymea-app.pro
@@ -168,3 +168,9 @@ INSTALLS += target
ANDROID_ABIS = armeabi-v7a arm64-v8a
+contains(ANDROID_TARGET_ARCH,) {
+ ANDROID_ABIS = \
+ armeabi-v7a \
+ arm64-v8a
+}
+
diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index 3867539f..e9f818f9 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -49,10 +49,8 @@
ui/customviews/GenericTypeLogView.qml
ui/customviews/CustomViewBase.qml
ui/customviews/WeatherView.qml
- ui/customviews/MediaControllerView.qml
ui/customviews/NotificationsView.qml
- ui/customviews/ExtendedVolumeController.qml
- ui/devicepages/MediaDevicePage.qml
+ ui/devicepages/MediaThingPage.qml
ui/devicepages/ButtonDevicePage.qml
ui/devicepages/GenericDevicePage.qml
ui/devicepages/WeatherDevicePage.qml
@@ -77,7 +75,6 @@
ui/devicelistpages/SensorsDeviceListPage.qml
ui/devicelistpages/WeatherDeviceListPage.qml
ui/devicelistpages/DeviceListPageBase.qml
- ui/magic/SelectActionPage.qml
ui/magic/DeviceRulesPage.qml
ui/magic/EditRulePage.qml
ui/magic/SelectThingPage.qml
@@ -224,5 +221,6 @@
ui/components/SetupStatusIcon.qml
ui/components/UpdateStatusIcon.qml
ui/magic/WriteNfcTagPage.qml
+ ui/components/MediaPlayer.qml
diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml
index e35105cf..a6867b23 100644
--- a/nymea-app/ui/MainPage.qml
+++ b/nymea-app/ui/MainPage.qml
@@ -45,7 +45,9 @@ Page {
header: FancyHeader {
id: mainHeader
- title: d.configOverlay !== null ? qsTr("Configure main view") : filteredContentModel.data(swipeView.currentIndex, "displayName")
+ title: d.configOverlay !== null ?
+ qsTr("Configure main view")
+ : swipeView.currentItem.item.title.length > 0 ? swipeView.currentItem.item.title : filteredContentModel.data(swipeView.currentIndex, "displayName")
leftButtonVisible: true
leftButtonImageSource: {
if (app.hasOwnProperty("headerIcon")) {
@@ -288,13 +290,13 @@ Page {
}
footer: Item {
readonly property bool shown: tabsRepeater.count > 1 || mainHeader.menuOpen || d.configOverlay
- implicitHeight: shown ? 70 + (app.landscape ? -20 : 0) : 0
+ implicitHeight: shown ? 64 + (app.landscape ? -20 : 0) : 0
Behavior on implicitHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }}
TabBar {
id: tabBar
anchors { left: parent.left; top: parent.top; right: parent.right }
- height: 70 + (app.landscape ? -20 : 0)
+ height: 64 + (app.landscape ? -20 : 0)
Material.elevation: 3
position: TabBar.Footer
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index c94ebc69..30495b61 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -59,7 +59,7 @@ ApplicationWindow {
property int smallFont: 13
property int mediumFont: 16
property int largeFont: 20
- property int iconSize: 30
+ property int iconSize: 24
property int delegateHeight: 60
property color backgroundColor: Material.background
@@ -259,7 +259,6 @@ ApplicationWindow {
return Qt.resolvedUrl("images/sensors/windspeed.svg")
case "media":
case "mediacontroller":
- case "extendedmediacontroller":
case "mediaplayer":
return Qt.resolvedUrl("images/media.svg")
case "powersocket":
@@ -337,7 +336,6 @@ ApplicationWindow {
case "extendednavigationpad":
return Qt.resolvedUrl("images/navigationpad.svg")
case "volumecontroller":
- case "extendedvolumecontroller":
return Qt.resolvedUrl("images/audio-speakers-symbolic.svg")
case "shufflerepeat":
return Qt.resolvedUrl("images/media-playlist-shuffle.svg")
diff --git a/nymea-app/ui/components/FancyHeader.qml b/nymea-app/ui/components/FancyHeader.qml
index 69aadafd..ce07fcae 100644
--- a/nymea-app/ui/components/FancyHeader.qml
+++ b/nymea-app/ui/components/FancyHeader.qml
@@ -150,8 +150,8 @@ ToolBar {
id: menuRepeater
MouseArea {
- height: app.iconSize * 3
- width: app.iconSize * 3
+ height: 80
+ width: height
onClicked: {
menuOpen = false
diff --git a/nymea-app/ui/components/MainPageTile.qml b/nymea-app/ui/components/MainPageTile.qml
index 733d65e4..4565ed46 100644
--- a/nymea-app/ui/components/MainPageTile.qml
+++ b/nymea-app/ui/components/MainPageTile.qml
@@ -92,7 +92,7 @@ Item {
ColorIcon {
id: colorIcon
anchors.centerIn: parent
- height: app.iconSize * 1.3
+ height: app.iconSize * 1.5
width: height
ColorIcon {
id: fallbackIcon
diff --git a/nymea-app/ui/components/MainViewBase.qml b/nymea-app/ui/components/MainViewBase.qml
index a33bbbf8..baf58b3a 100644
--- a/nymea-app/ui/components/MainViewBase.qml
+++ b/nymea-app/ui/components/MainViewBase.qml
@@ -43,4 +43,6 @@ MouseArea {
preventStealing: true
onWheel: wheel.accepted = true
+ property string title: ""
+
}
diff --git a/nymea-app/ui/components/MediaArtworkImage.qml b/nymea-app/ui/components/MediaArtworkImage.qml
index c643367a..b39e11cc 100644
--- a/nymea-app/ui/components/MediaArtworkImage.qml
+++ b/nymea-app/ui/components/MediaArtworkImage.qml
@@ -44,14 +44,23 @@ Item {
readonly property StateType playerTypeStateType: thing ? thing.thingClass.stateTypes.findByName("playerType") : null
readonly property State playerTypeState: playerTypeStateType ? thing.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"
+ readonly property int paintedWidth: fallbackImage.visible ? fallbackImage.width : artworkImage.paintedWidth
+ readonly property int paintedHeight: fallbackImage.visible ? fallbackImage.height : artworkImage.paintedHeight
+
+ Rectangle {
+ id: fallbackImage
+ anchors { left: parent.left; top: parent.top }
+ height: visible ? Math.min(parent.height, parent.width) : artworkImage.paintedHeight - 1
+ width: visible ? Math.min(parent.height, parent.width) : artworkImage.paintedWidth - 1
+ visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
+ color: "black"
+
+ ColorIcon {
+ 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"
+ color: "white"
}
}
@@ -61,17 +70,7 @@ Item {
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"
+ horizontalAlignment: Image.AlignLeft
+ verticalAlignment: Image.AlignTop
}
}
diff --git a/nymea-app/ui/components/MediaBrowser.qml b/nymea-app/ui/components/MediaBrowser.qml
index 99e1d262..77fe07bb 100644
--- a/nymea-app/ui/components/MediaBrowser.qml
+++ b/nymea-app/ui/components/MediaBrowser.qml
@@ -42,11 +42,46 @@ Item {
property Thing thing: null
- function backPressed() {
+ signal exit();
+ signal itemLaunched()
+
+ property ListModel path: ListModel {
+ id: pathModel
+ dynamicRoles: true
+ Component.onCompleted: pathModel.append({modelData: root.thing.name})
+ }
+
+
+ function backPressed(immediate) {
if (internalPageStack.depth > 1) {
- internalPageStack.pop();
+ pathModel.remove(pathModel.count - 1)
+ internalPageStack.pop(immediate ? StackView.Immediate : StackView.PopTransition);
} else {
- swipeView.currentIndex--
+ root.exit();
+ }
+ }
+
+ QtObject {
+ id: d
+ property int pendingItemExecutionId: -1
+ }
+
+ Connections {
+ target: engine.thingManager
+ onExecuteBrowserItemReply: {
+ if (commandId == d.pendingItemExecutionId) {
+ if (params.thingError === "ThingErrorNoError") {
+ root.itemLaunched();
+ } else {
+ var errorDialog = Qt.createComponent(Qt.resolvedUrl("ErrorDialog.qml"));
+ var text = qsTr("Sorry. An error happened launching the item. (Error code: %1)").arg(params.error);
+ if (params.displayMessage.length > 0) {
+ text = params.displayMessage;
+ }
+ var popup = errorDialog.createObject(app, {text: text})
+ popup.open()
+ }
+ }
}
}
@@ -78,8 +113,9 @@ Item {
onClicked: {
print("clicked:", model.id)
if (model.executable) {
- engine.thingManager.executeBrowserItem(root.thing.id, model.id)
+ d.pendingItemExecutionId = engine.thingManager.executeBrowserItem(root.thing.id, model.id)
} else if (model.browsable) {
+ pathModel.append({modelData: model.displayName})
internalPageStack.push(internalBrowserPage, {nodeId: model.id})
}
}
diff --git a/nymea-app/ui/components/MediaControls.qml b/nymea-app/ui/components/MediaControls.qml
index 3f1dcff6..533a1ed4 100644
--- a/nymea-app/ui/components/MediaControls.qml
+++ b/nymea-app/ui/components/MediaControls.qml
@@ -36,12 +36,15 @@ import Nymea 1.0
RowLayout {
id: root
- implicitHeight: iconSize + app.margins
+ implicitHeight: app.iconSize * (showExtendedControls ? 2 : 1) + app.margins
property Thing thing: null
- property int iconSize: app.iconSize * 1.5
+
+ property bool showExtendedControls: false
readonly property State playbackState: thing.stateByName("playbackStatus")
+ readonly property State shuffleState: thing.stateByName("shuffle")
+ readonly property State repeatState: thing.stateByName("repeat")
function executeAction(actionName, params) {
if (params === undefined) {
@@ -51,12 +54,31 @@ RowLayout {
engine.thingManager.executeAction(thing.id, actionTypeId, params)
}
+ ProgressButton {
+ Layout.preferredHeight: app.iconSize
+ Layout.preferredWidth: height
+ imageSource: "../images/media-playlist-shuffle.svg"
+ longpressEnabled: false
+ enabled: root.shuffleState !== null
+ opacity: enabled ? 1 : .5
+ visible: root.showExtendedControls
+ onClicked: {
+ var params = []
+ var param = {}
+ param["paramTypeId"] = root.shuffleState.stateTypeId
+ param["value"] = !root.shuffleState.value
+ params.push(param)
+ root.executeAction("shuffle", params)
+ }
+ }
+
Item { Layout.fillWidth: true }
ProgressButton {
- Layout.preferredHeight: root.iconSize * .6
+ Layout.preferredHeight: app.iconSize * (root.showExtendedControls ? 1.5 : 1)
Layout.preferredWidth: height
imageSource: "../images/media-skip-backward.svg"
longpressImageSource: "../images/media-seek-backward.svg"
+ longpressEnabled: root.thing.thingClass.actionTypes.findByName("fastRewind") !== null
enabled: root.playbackState && root.playbackState.value !== "Stopped"
opacity: enabled ? 1 : .5
@@ -70,7 +92,7 @@ RowLayout {
}
Item { Layout.fillWidth: true }
ProgressButton {
- Layout.preferredHeight: root.iconSize
+ Layout.preferredHeight: app.iconSize * (root.showExtendedControls ? 2 : 1)
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"
@@ -90,10 +112,11 @@ RowLayout {
}
Item { Layout.fillWidth: true }
ProgressButton {
- Layout.preferredHeight: root.iconSize * .6
+ Layout.preferredHeight: app.iconSize * (root.showExtendedControls ? 1.5 : 1)
Layout.preferredWidth: height
imageSource: "../images/media-skip-forward.svg"
longpressImageSource: "../images/media-seek-forward.svg"
+ longpressEnabled: root.thing.thingClass.actionTypes.findByName("fastForward") !== null
enabled: root.playbackState && root.playbackState.value !== "Stopped"
opacity: enabled ? 1 : .5
repeat: true
@@ -105,4 +128,24 @@ RowLayout {
}
}
Item { Layout.fillWidth: true }
+
+ ProgressButton {
+ Layout.preferredHeight: app.iconSize
+ Layout.preferredWidth: height
+ imageSource: root.repeatState.value === "One" ? "../images/media-playlist-repeat-one.svg" : "../images/media-playlist-repeat.svg"
+ color: root.repeatState.value === "None" ? keyColor : app.accentColor
+ longpressEnabled: false
+ enabled: root.repeatState !== null
+ opacity: enabled ? 1 : .5
+ visible: root.showExtendedControls
+ property var allowedValues: ["None", "All", "One"]
+ onClicked: {
+ var params = []
+ var param = {}
+ param["paramTypeId"] = root.repeatState.stateTypeId;
+ param["value"] = allowedValues[(allowedValues.indexOf(root.repeatState.value) + 1) % 3]
+ params.push(param)
+ root.executeAction("repeat", params)
+ }
+ }
}
diff --git a/nymea-app/ui/components/MediaPlayer.qml b/nymea-app/ui/components/MediaPlayer.qml
new file mode 100644
index 00000000..feb64c20
--- /dev/null
+++ b/nymea-app/ui/components/MediaPlayer.qml
@@ -0,0 +1,468 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 .
+*
+* 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();
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/nymea-app/ui/components/NymeaHeader.qml b/nymea-app/ui/components/NymeaHeader.qml
index 3d4b2a4a..7741040e 100644
--- a/nymea-app/ui/components/NymeaHeader.qml
+++ b/nymea-app/ui/components/NymeaHeader.qml
@@ -40,6 +40,7 @@ Item {
property alias backButtonVisible: backButton.visible
property alias menuButtonVisible: menuButton.visible
default property alias children: layout.data
+ property alias elide: label.elide
signal backPressed();
signal menuPressed();
diff --git a/nymea-app/ui/components/ProgressButton.qml b/nymea-app/ui/components/ProgressButton.qml
index 6ee9d458..52ef6279 100644
--- a/nymea-app/ui/components/ProgressButton.qml
+++ b/nymea-app/ui/components/ProgressButton.qml
@@ -33,10 +33,14 @@ import QtQuick.Layouts 1.3
Item {
id: root
+ implicitHeight: app.iconSize
+ implicitWidth: app.iconSize
property string imageSource
property string longpressImageSource: imageSource
property bool repeat: false
+ property alias keyColor: icon.keyColor
+ property alias color: icon.color
property bool longpressEnabled: true
@@ -46,6 +50,7 @@ Item {
MouseArea {
id: buttonDelegate
anchors.fill: parent
+ hoverEnabled: true
property bool longpressed: false
@@ -94,7 +99,7 @@ Item {
anchors.margins: -app.margins / 2
radius: width / 2
color: app.foregroundColor
- opacity: buttonDelegate.pressed ? .08 : 0
+ opacity: buttonDelegate.pressed || buttonDelegate.containsMouse ? .08 : 0
Behavior on opacity {
NumberAnimation { duration: 200 }
}
@@ -144,6 +149,7 @@ Item {
}
ColorIcon {
+ id: icon
anchors.fill: parent
name: buttonDelegate.longpressed ? root.longpressImageSource : root.imageSource
}
diff --git a/nymea-app/ui/components/ShuffleRepeatVolumeControl.qml b/nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
index cd82b8bb..28391abe 100644
--- a/nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
+++ b/nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
@@ -93,7 +93,9 @@ RowLayout {
HeaderButton {
id: volumeButton
anchors.centerIn: parent
- imageSource: "../images/audio-speakers-symbolic.svg"
+ 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))
@@ -125,8 +127,20 @@ RowLayout {
property int pendingVolumeValue: -1
contentItem: ColumnLayout {
+ HeaderButton {
+ visible: root.volumeState === null
+ imageSource: "../images/up.svg"
+ onClicked: engine.thingManager.executeAction(root.thing.id, root.thing.thingClass.actionTypes.findByName("increaseVolume").id);
+ }
+ HeaderButton {
+ visible: root.volumeState === null
+ 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
@@ -135,6 +149,7 @@ RowLayout {
}
HeaderButton {
+ visible: root.muteState !== null
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}]);
diff --git a/nymea-app/ui/components/ShutterControls.qml b/nymea-app/ui/components/ShutterControls.qml
index 652e82af..e845d395 100644
--- a/nymea-app/ui/components/ShutterControls.qml
+++ b/nymea-app/ui/components/ShutterControls.qml
@@ -34,10 +34,11 @@ import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.3
import Nymea 1.0
-Row {
+RowLayout {
id: root
- property Device device: null
+ property Device thing: null
+ property alias device: root.thing
readonly property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null
readonly property var openState: device ? device.states.getState(deviceClass.stateTypes.findByName("state").id) : null
readonly property bool canStop: device && device.deviceClass.actionTypes.findByName("stop")
@@ -46,54 +47,41 @@ Row {
signal activated(string button);
- ItemDelegate {
- width: app.iconSize * 2
- height: width
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
- ColorIcon {
- anchors.fill: parent
- anchors.margins: app.margins
- name: root.invert ? "../images/down.svg" : "../images/up.svg"
- color: root.openState && root.openState.value === "opening" ? Material.accent : keyColor
- }
+ ProgressButton {
+ longpressEnabled: false
+ imageSource: root.invert ? "../images/down.svg" : "../images/up.svg"
+ color: root.openState && root.openState.value === "opening" ? Material.accent : keyColor
onClicked: {
engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("open").id)
root.activated("open")
}
}
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
- ItemDelegate {
- width: app.iconSize * 2
- height: width
+ ProgressButton {
visible: root.canStop
-// color: Material.foreground
-// radius: height / 2
-
- ColorIcon {
- anchors.fill: parent
- anchors.margins: app.margins
- name: "../images/media-playback-stop.svg"
- }
+ longpressEnabled: false
+ imageSource: "../images/media-playback-stop.svg"
onClicked: {
engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("stop").id)
root.activated("stop")
}
}
- ItemDelegate {
- width: app.iconSize * 2
- height: width
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
- ColorIcon {
- anchors.fill: parent
- anchors.margins: app.margins
- name: root.invert ? "../images/up.svg" : "../images/down.svg"
- color: root.openState && root.openState.value === "closing" ? Material.accent : keyColor
- }
+ ProgressButton {
+ imageSource: root.invert ? "../images/up.svg" : "../images/down.svg"
+ longpressEnabled: false
+ color: root.openState && root.openState.value === "closing" ? Material.accent : keyColor
onClicked: {
engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("close").id)
root.activated("close")
}
}
+
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
}
diff --git a/nymea-app/ui/customviews/ExtendedVolumeController.qml b/nymea-app/ui/customviews/ExtendedVolumeController.qml
deleted file mode 100644
index 3d9f34f4..00000000
--- a/nymea-app/ui/customviews/ExtendedVolumeController.qml
+++ /dev/null
@@ -1,83 +0,0 @@
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-*
-* 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 .
-*
-* 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.5
-import QtQuick.Layouts 1.1
-import QtQuick.Controls 2.1
-import Nymea 1.0
-import "../components"
-
-CustomViewBase {
- id: root
- height: row.implicitHeight + app.margins * 2
-
- RowLayout {
- id: row
- anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins }
-
- AbstractButton {
- width: app.iconSize * 2
- height: width
-
- property var muteState: root.device.states.getState(deviceClass.stateTypes.findByName("mute").id)
- property bool isMuted: muteState.value === true
-
- ColorIcon {
- anchors.fill: parent
- name: "../images/audio-speakers-muted-symbolic.svg"
- color: parent.isMuted ? app.accentColor : keyColor
- }
-
- onClicked: {
- var paramList = []
- var muteParam = {}
- muteParam["paramTypeId"] = deviceClass.stateTypes.findByName("mute").id
- muteParam["value"] = !isMuted
- paramList.push(muteParam)
- engine.deviceManager.executeAction(root.device.id, deviceClass.actionTypes.findByName("mute").id, paramList)
- }
- }
-
- ThrottledSlider {
- Layout.fillWidth: true
- value: root.device.stateValue(deviceClass.stateTypes.findByName("volume").id)
- from: 0
- to: 100
- onMoved: {
- var paramList = []
- var muteParam = {}
- muteParam["paramTypeId"] = deviceClass.stateTypes.findByName("volume").id
- muteParam["value"] = value
- paramList.push(muteParam)
- engine.deviceManager.executeAction(root.device.id, deviceClass.actionTypes.findByName("volume").id, paramList)
- }
- }
- }
-}
diff --git a/nymea-app/ui/customviews/MediaControllerView.qml b/nymea-app/ui/customviews/MediaControllerView.qml
deleted file mode 100644
index cb223a30..00000000
--- a/nymea-app/ui/customviews/MediaControllerView.qml
+++ /dev/null
@@ -1,100 +0,0 @@
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-*
-* 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 .
-*
-* 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.5
-import QtQuick.Layouts 1.1
-import QtQuick.Controls 2.1
-import Nymea 1.0
-import "../components"
-
-CustomViewBase {
- id: root
- height: column.implicitHeight + app.margins * 2
-
- function executeAction(actionName) {
- var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
- print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes)
- engine.deviceManager.executeAction(device.id, actionTypeId)
- }
-
- property var playbackState: device.states.getState(deviceClass.stateTypes.findByName("playbackStatus").id)
- property var playbackStateValue: playbackState.value
- onPlaybackStateValueChanged: populateControls()
- Component.onCompleted: populateControls()
-
- function populateControls() {
- print("generating controls")
- controlsModel.clear();
- controlsModel.append({image: "../images/media-skip-backward.svg", action: "skipBack"})
- controlsModel.append({image: "../images/media-seek-backward.svg", action: "rewind"})
- controlsModel.append({image: "../images/media-playback-stop.svg", action: "stop"})
- if (playbackState.value === "Paused" || playbackState.value === "Stopped") {
- controlsModel.append({image: "../images/media-playback-start.svg", action: "play"})
- }
- if (playbackState.value === "Playing") {
- controlsModel.append({image: "../images/media-playback-pause.svg", action: "pause"})
- }
-
- controlsModel.append({image: "../images/media-seek-forward.svg", action: "fastForward"})
- controlsModel.append({image: "../images/media-skip-forward.svg", action: "skipNext"})
- }
-
- ColumnLayout {
- id: column
- anchors { left: parent.left; right: parent.right }
-
- Row {
- id: controlsRow
- Layout.fillWidth: true
-
- property int iconSize: Math.max(app.iconSize * 2, column.width / controlsModel.count)
-
- Repeater {
- model: ListModel {
- id: controlsModel
- }
- delegate: AbstractButton {
-
- height: app.iconSize * 2
- width: controlsRow.iconSize
- ColorIcon {
- height: parent.height
- width: height
- name: model.image
- anchors.horizontalCenter: parent.horizontalCenter
- }
- onClicked: {
- executeAction(model.action)
- }
- }
- }
- }
- }
-}
diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml
index b9b71d69..6c730d93 100644
--- a/nymea-app/ui/delegates/InterfaceTile.qml
+++ b/nymea-app/ui/delegates/InterfaceTile.qml
@@ -161,7 +161,7 @@ MainPageTile {
case "extendedsmartmeterproducer":
case "heating":
return sensorComponent;
-// return labelComponent;
+ // return labelComponent;
case "light":
case "garagedoor":
@@ -247,7 +247,6 @@ MainPageTile {
}
MediaControls {
- iconSize: app.iconSize * 1.2
thing: inlineMediaControl.currentDevice
}
}
@@ -255,8 +254,11 @@ MainPageTile {
Component {
id: buttonComponent
- ColumnLayout {
- spacing: 0
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
Label {
id: label
@@ -281,22 +283,6 @@ MainPageTile {
}
return count === 0 ? qsTr("All off") : qsTr("%1 on").arg(count)
case "garagedoor":
- var statefulCount = 0;
- var count = 0;
- for (var i = 0; i < devicesProxy.count; i++) {
- var thing = devicesProxy.get(i);
- if (thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0 || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
- statefulCount++;
- var stateType = thing.thingClass.stateTypes.findByName("state");
- if (stateType && thing.states.getState(stateType.id).value !== "closed") {
- count++;
- }
- }
- }
- if (statefulCount > 0) {
- return count === 0 ? qsTr("All closed") : qsTr("%1 open").arg(count)
- }
- return "";
case "blind":
case "extendedblind":
case "awning":
@@ -311,307 +297,282 @@ MainPageTile {
font.pixelSize: app.smallFont
elide: Text.ElideRight
}
- RowLayout {
-// Layout.alignment: Qt.AlignRight
- Layout.fillWidth: true
- spacing: (parent.width - app.iconSize * 3) / 2
- ItemDelegate {
- Layout.preferredHeight: app.iconSize
- Layout.preferredWidth: height
-
- ColorIcon {
- id: leftIcon
- width: app.iconSize
- height: width
- color: app.accentColor
-
- name: {
- switch (iface.name) {
- case "media":
- case "light":
- case "irrigation":
- case "ventilation":
- return ""
- case "garagedoor":
- var dev = devicesProxy.get(0)
- if (dev.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("garagegate") >= 0) {
- return "../images/up.svg"
- }
- return ""
- case "blind":
- case "extendedblind":
- case "awning":
- case "extendedawning":
- case "shutter":
- case "extendedshutter":
- return "../images/up.svg"
- default:
- console.warn("InterfaceTile", "inlineButtonControl image: Unhandled interface", iface.name)
- }
- return ""
- }
- }
-
- onClicked: {
- switch (iface.name) {
- case "light":
- case "media":
- case "irrigation":
- case "ventilation":
- break;
- case "garagedoor":
- for (var i = 0; i < devicesProxy.count; i++) {
- var thing = devicesProxy.get(i);
- if (thing.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
-
- var actionType = thing.thingClass.actionTypes.findByName("open");
- engine.deviceManager.executeAction(thing.id, actionType.id)
- }
- }
- break;
- case "shutter":
- case "extendedshutter":
- case "blind":
- case "extendedblind":
- case "awning":
- case "extendedawning":
- case "simpleclosable":
- for (var i = 0; i < devicesProxy.count; i++) {
- var device = devicesProxy.get(i);
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var actionType = deviceClass.actionTypes.findByName("open");
- engine.deviceManager.executeAction(device.id, actionType.id)
- }
- break;
- default:
- console.warn("InterfaceTile:", "inlineButtonControl clicked: Unhandled interface", iface.name)
+ ProgressButton {
+ longpressEnabled: false
+ visible: imageSource.length > 0
+ imageSource: {
+ switch (iface.name) {
+ case "media":
+ case "light":
+ case "irrigation":
+ case "ventilation":
+ return ""
+ case "garagedoor":
+ var dev = devicesProxy.get(0)
+ if (dev.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("garagegate") >= 0) {
+ return "../images/up.svg"
}
+ return ""
+ case "blind":
+ case "extendedblind":
+ case "awning":
+ case "extendedawning":
+ case "shutter":
+ case "extendedshutter":
+ return "../images/up.svg"
+ default:
+ console.warn("InterfaceTile", "inlineButtonControl image: Unhandled interface", iface.name)
}
+ return ""
}
- ItemDelegate {
- Layout.preferredHeight: app.iconSize
- Layout.preferredWidth: height
- enabled: centerIcon.name.length > 0
- ColorIcon {
- id: centerIcon
- width: app.iconSize
- height: width
- color: app.accentColor
+ onClicked: {
+ switch (iface.name) {
+ case "light":
+ case "media":
+ case "irrigation":
+ case "ventilation":
+ break;
+ case "garagedoor":
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var thing = devicesProxy.get(i);
+ if (thing.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
- name: {
- switch (iface.name) {
- case "media":
- case "light":
- case "irrigation":
- case "ventilation":
- return ""
- case "garagedoor":
- var dev = devicesProxy.get(0)
- if (dev.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("garagegate") >= 0) {
- return "../images/media-playback-stop.svg"
- }
- return ""
- case "blind":
- case "awning":
- case "shutter":
- case "extendedblind":
- case "extendedawning":
- case "extendedshutter":
- return "../images/media-playback-stop.svg"
- default:
- console.warn("InterfaceTile, inlineButtonControl image: Unhandled interface", iface.name)
- }
- return "";
- }
- }
-
- onClicked: {
- switch (iface.name) {
- case "light":
- case "media":
- case "irrigation":
- case "ventilation":
- break;
- case "garagedoor":
- for (var i = 0; i < devicesProxy.count; i++) {
- var thing = devicesProxy.get(i);
- if (thing.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
-
- var actionType = thing.thingClass.actionTypes.findByName("stop");
- engine.thingManager.executeAction(thing.id, actionType.id)
- }
- }
- break;
- case "shutter":
- case "extendedshutter":
- case "blind":
- case "extendedblind":
- case "awning":
- case "extendedawning":
- case "simpleclosable":
- for (var i = 0; i < devicesProxy.count; i++) {
- var device = devicesProxy.get(i);
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var actionType = deviceClass.actionTypes.findByName("stop");
- engine.deviceManager.executeAction(device.id, actionType.id)
- }
- break;
- default:
- console.warn("InterfaceTile, inlineButtonControl clicked: Unhandled interface", iface.name)
- }
- }
- }
-
- ItemDelegate {
- Layout.preferredHeight: app.iconSize
- Layout.preferredWidth: height
-
- ColorIcon {
- id: icon
- width: app.iconSize
- height: width
- color: app.accentColor
-
- name: {
- switch (iface.name) {
- case "media":
- var device = devicesProxy.get(0)
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var stateType = deviceClass.stateTypes.findByName("playbackStatus");
- var state = device.states.getState(stateType.id)
- return state.value === "Playing" ? "../images/media-playback-pause.svg" :
- state.value === "Paused" ? "../images/media-playback-start.svg" :
- ""
- case "light":
- case "powersocket":
- case "irrigation":
- case "ventilation":
- return "../images/system-shutdown.svg"
- case "garagedoor":
- var dev = devicesProxy.get(0)
- if (dev.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
- || dev.thingClass.interfaces.indexOf("garagegate") >= 0) {
- return "../images/down.svg"
- }
- if (dev.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0) {
- return "../images/closable-move.svg"
- }
- return ""
- case "blind":
- case "extendedblind":
- case "awning":
- case "extendedawning":
- case "shutter":
- case "extendedshutter":
- return "../images/down.svg"
- default:
- console.warn("InterfaceTile, inlineButtonControl image: Unhandled interface", iface.name)
+ var actionType = thing.thingClass.actionTypes.findByName("open");
+ engine.deviceManager.executeAction(thing.id, actionType.id)
}
}
- }
-
- onClicked: {
- switch (iface.name) {
- case "light":
- case "powersocket":
- case "irrigation":
- case "ventilation":
- var allOff = true;
- for (var i = 0; i < devicesProxy.count; i++) {
- var device = devicesProxy.get(i);
- if (device.states.getState(device.deviceClass.stateTypes.findByName("power").id).value === true) {
- allOff = false;
- break;
- }
- }
-
- for (var i = 0; i < devicesProxy.count; i++) {
- var device = devicesProxy.get(i);
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var actionType = deviceClass.actionTypes.findByName("power");
-
- var params = [];
- var param1 = {};
- param1["paramTypeId"] = actionType.paramTypes.get(0).id;
- param1["value"] = allOff ? true : false;
- params.push(param1)
- engine.deviceManager.executeAction(device.id, actionType.id, params)
- }
- break;
- case "media":
- var device = devicesProxy.get(0)
+ break;
+ case "shutter":
+ case "extendedshutter":
+ case "blind":
+ case "extendedblind":
+ case "awning":
+ case "extendedawning":
+ case "simpleclosable":
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var device = devicesProxy.get(i);
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var stateType = deviceClass.stateTypes.findByName("playbackStatus");
- var state = device.states.getState(stateType.id)
-
- var actionName
- switch (state.value) {
- case "Playing":
- actionName = "pause";
- break;
- case "Paused":
- actionName = "play";
- break;
- }
- var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
-
- print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes)
-
- engine.deviceManager.executeAction(device.id, actionTypeId)
- case "garagedoor":
- for (var i = 0; i < devicesProxy.count; i++) {
- var thing = devicesProxy.get(i);
- if (thing.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
- || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
-
- var actionType = thing.thingClass.actionTypes.findByName("close");
- engine.deviceManager.executeAction(thing.id, actionType.id)
- }
- if (thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0) {
- var actionType = thing.thingClass.actionTypes.findByName("triggerImpulse");
- engine.deviceManager.executeAction(thing.id, actionType.id)
- }
- }
- break;
- case "shutter":
- case "extendedshutter":
- case "blind":
- case "extendedblind":
- case "awning":
- case "extendedawning":
- case "simpleclosable":
- for (var i = 0; i < devicesProxy.count; i++) {
- var device = devicesProxy.get(i);
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var actionType = deviceClass.actionTypes.findByName("close");
- engine.deviceManager.executeAction(device.id, actionType.id)
- }
-
- default:
- console.warn("InterfaceTile, inlineButtonControl clicked: Unhandled interface", iface.name)
+ var actionType = deviceClass.actionTypes.findByName("open");
+ engine.deviceManager.executeAction(device.id, actionType.id)
}
+ break;
+ default:
+ console.warn("InterfaceTile:", "inlineButtonControl clicked: Unhandled interface", iface.name)
}
}
}
+
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
+
+ ProgressButton {
+ longpressEnabled: false
+ visible: imageSource.length > 0
+ imageSource: {
+ switch (iface.name) {
+ case "media":
+ case "light":
+ case "irrigation":
+ case "ventilation":
+ return ""
+ case "garagedoor":
+ var dev = devicesProxy.get(0)
+ if (dev.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("garagegate") >= 0) {
+ return "../images/media-playback-stop.svg"
+ }
+ return ""
+ case "blind":
+ case "awning":
+ case "shutter":
+ case "extendedblind":
+ case "extendedawning":
+ case "extendedshutter":
+ return "../images/media-playback-stop.svg"
+ default:
+ console.warn("InterfaceTile, inlineButtonControl image: Unhandled interface", iface.name)
+ }
+ return "";
+ }
+
+ onClicked: {
+ switch (iface.name) {
+ case "light":
+ case "media":
+ case "irrigation":
+ case "ventilation":
+ break;
+ case "garagedoor":
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var thing = devicesProxy.get(i);
+ if (thing.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
+
+ var actionType = thing.thingClass.actionTypes.findByName("stop");
+ engine.thingManager.executeAction(thing.id, actionType.id)
+ }
+ }
+ break;
+ case "shutter":
+ case "extendedshutter":
+ case "blind":
+ case "extendedblind":
+ case "awning":
+ case "extendedawning":
+ case "simpleclosable":
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var device = devicesProxy.get(i);
+ var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
+ var actionType = deviceClass.actionTypes.findByName("stop");
+ engine.deviceManager.executeAction(device.id, actionType.id)
+ }
+ break;
+ default:
+ console.warn("InterfaceTile, inlineButtonControl clicked: Unhandled interface", iface.name)
+ }
+ }
+ }
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
+
+ ProgressButton {
+ longpressEnabled: false
+ imageSource: {
+ switch (iface.name) {
+ case "media":
+ var device = devicesProxy.get(0)
+ var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
+ var stateType = deviceClass.stateTypes.findByName("playbackStatus");
+ var state = device.states.getState(stateType.id)
+ return state.value === "Playing" ? "../images/media-playback-pause.svg" :
+ state.value === "Paused" ? "../images/media-playback-start.svg" :
+ ""
+ case "light":
+ case "powersocket":
+ case "irrigation":
+ case "ventilation":
+ return "../images/system-shutdown.svg"
+ case "garagedoor":
+ var dev = devicesProxy.get(0)
+ if (dev.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
+ || dev.thingClass.interfaces.indexOf("garagegate") >= 0) {
+ return "../images/down.svg"
+ }
+ if (dev.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0) {
+ return "../images/closable-move.svg"
+ }
+ return ""
+ case "blind":
+ case "extendedblind":
+ case "awning":
+ case "extendedawning":
+ case "shutter":
+ case "extendedshutter":
+ return "../images/down.svg"
+ default:
+ console.warn("InterfaceTile, inlineButtonControl image: Unhandled interface", iface.name)
+ }
+ }
+
+ onClicked: {
+ switch (iface.name) {
+ case "light":
+ case "powersocket":
+ case "irrigation":
+ case "ventilation":
+ var allOff = true;
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var device = devicesProxy.get(i);
+ if (device.states.getState(device.deviceClass.stateTypes.findByName("power").id).value === true) {
+ allOff = false;
+ break;
+ }
+ }
+
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var device = devicesProxy.get(i);
+ var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
+ var actionType = deviceClass.actionTypes.findByName("power");
+
+ var params = [];
+ var param1 = {};
+ param1["paramTypeId"] = actionType.paramTypes.get(0).id;
+ param1["value"] = allOff ? true : false;
+ params.push(param1)
+ engine.deviceManager.executeAction(device.id, actionType.id, params)
+ }
+ break;
+ case "media":
+ var device = devicesProxy.get(0)
+ var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
+ var stateType = deviceClass.stateTypes.findByName("playbackStatus");
+ var state = device.states.getState(stateType.id)
+
+ var actionName
+ switch (state.value) {
+ case "Playing":
+ actionName = "pause";
+ break;
+ case "Paused":
+ actionName = "play";
+ break;
+ }
+ var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
+
+ print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes)
+
+ engine.deviceManager.executeAction(device.id, actionTypeId)
+ case "garagedoor":
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var thing = devicesProxy.get(i);
+ if (thing.thingClass.interfaces.indexOf("simplegaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0
+ || thing.thingClass.interfaces.indexOf("garagegate") >= 0) {
+
+ var actionType = thing.thingClass.actionTypes.findByName("close");
+ engine.deviceManager.executeAction(thing.id, actionType.id)
+ }
+ if (thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0) {
+ var actionType = thing.thingClass.actionTypes.findByName("triggerImpulse");
+ engine.deviceManager.executeAction(thing.id, actionType.id)
+ }
+ }
+ break;
+ case "shutter":
+ case "extendedshutter":
+ case "blind":
+ case "extendedblind":
+ case "awning":
+ case "extendedawning":
+ case "simpleclosable":
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var device = devicesProxy.get(i);
+ var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
+ var actionType = deviceClass.actionTypes.findByName("close");
+ engine.deviceManager.executeAction(device.id, actionType.id)
+ }
+
+ default:
+ console.warn("InterfaceTile, inlineButtonControl clicked: Unhandled interface", iface.name)
+ }
+ }
+ }
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
}
+
}
Component {
@@ -728,7 +689,7 @@ MainPageTile {
text: sensorsRoot.shownStateType
? (Math.round(Types.toUiValue(sensorsRoot.device.states.getState(sensorsRoot.shownStateType.id).value, sensorsRoot.shownStateType.unit) * 100) / 100) + " " + Types.toUiUnit(sensorsRoot.shownStateType.unit)
: ""
- // font.pixelSize: app.smallFont
+ // font.pixelSize: app.smallFont
Layout.fillWidth: true
visible: sensorsRoot.shownStateType && sensorsRoot.shownStateType.type.toLowerCase() !== "bool"
elide: Text.ElideRight
diff --git a/nymea-app/ui/delegates/ThingTile.qml b/nymea-app/ui/delegates/ThingTile.qml
index bd0e7c89..930ac767 100644
--- a/nymea-app/ui/delegates/ThingTile.qml
+++ b/nymea-app/ui/delegates/ThingTile.qml
@@ -208,16 +208,11 @@ MainPageTile {
currentStateIndex = 0
}
- ItemDelegate {
- Layout.preferredWidth: app.iconSize
- Layout.preferredHeight: width
- Layout.leftMargin: app.margins / 2
- Layout.alignment: Qt.AlignVCenter
- padding: 0; topPadding: 0; bottomPadding: 0
+ Item { Layout.fillHeight: true; Layout.fillWidth: true }
+ ProgressButton {
visible: sensorsRoot.shownInterfaces.length > 1
- contentItem: ColorIcon {
- name: "../images/back.svg"
- }
+ longpressEnabled: false
+ imageSource: "../images/back.svg"
onClicked: {
var newIndex = sensorsRoot.currentStateIndex - 1;
if (newIndex < 0) newIndex = sensorsRoot.shownInterfaces.length - 1
@@ -236,7 +231,7 @@ MainPageTile {
Item { Layout.fillHeight: true; Layout.fillWidth: true }
ColumnLayout {
- Layout.fillWidth: true
+ Layout.fillWidth: false
spacing: 0
visible: sensorsRoot.currentStateType.type.toLowerCase() !== "bool"
@@ -260,151 +255,34 @@ MainPageTile {
Item { Layout.fillHeight: true; Layout.fillWidth: true }
- ItemDelegate {
- Layout.preferredWidth: app.iconSize
- Layout.preferredHeight: width
- Layout.rightMargin: app.margins / 2
- Layout.alignment: Qt.AlignVCenter
- padding: 0; topPadding: 0; bottomPadding: 0
+ ProgressButton {
visible: sensorsRoot.shownInterfaces.length > 1
- contentItem: ColorIcon {
- name: "../images/next.svg"
- }
+ longpressEnabled: false
+ imageSource: "../images/next.svg"
onClicked: {
var newIndex = sensorsRoot.currentStateIndex + 1;
if (newIndex >= sensorsRoot.shownInterfaces.length) newIndex = 0;
sensorsRoot.currentStateIndex = newIndex;
}
}
+ Item { Layout.fillHeight: true; Layout.fillWidth: true }
}
}
Component {
id: closableComponent
- RowLayout {
- property var device: null
- property var deviceClass: null
- ItemDelegate {
- Layout.preferredWidth: app.iconSize
- Layout.preferredHeight: width
- Layout.leftMargin: app.margins / 2
- Layout.alignment: Qt.AlignVCenter
- padding: 0; topPadding: 0; bottomPadding: 0
- contentItem: ColorIcon {
- name: "../images/up.svg"
- color: app.accentColor
- }
- onClicked: {
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var actionType = deviceClass.actionTypes.findByName("open");
- engine.deviceManager.executeAction(device.id, actionType.id);
- }
- }
+ ShutterControls {
- Slider {
- id: closableSlider
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- visible: deviceClass.interfaces.indexOf("extendedclosable") >= 0
- readonly property var percentageStateType: deviceClass.stateTypes.findByName("percentage");
- readonly property var percentateState: percentageStateType ? device.states.getState(percentageStateType.id) : null
- from: 0
- to: 100
- value: percentateState ? percentateState.value : 0
- }
- Item {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- visible: !closableSlider.visible
- }
-
- ItemDelegate {
- Layout.preferredWidth: app.iconSize
- Layout.preferredHeight: width
- Layout.rightMargin: app.margins / 2
- Layout.alignment: Qt.AlignVCenter
- padding: 0; topPadding: 0; bottomPadding: 0
- contentItem: ColorIcon {
- name: "../images/down.svg"
- color: app.accentColor
- }
- onClicked: {
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
- var actionType = deviceClass.actionTypes.findByName("close");
- engine.deviceManager.executeAction(device.id, actionType.id);
- }
- }
}
}
Component {
id: mediaComponent
- RowLayout {
- id: mediaRoot
+ MediaControls {
property Device device: null
- property DeviceClass deviceClass: null
-
- readonly property State playbackState: device.states.getState(deviceClass.stateTypes.findByName("playbackStatus").id)
-
- function executeAction(actionName, params) {
- var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
- engine.deviceManager.executeAction(device.id, actionTypeId, params)
- }
- Item { Layout.fillWidth: true }
-
- ProgressButton {
- Layout.preferredHeight: app.iconSize * .9
- Layout.preferredWidth: height
- imageSource: "../images/media-skip-backward.svg"
- longpressImageSource: "../images/media-seek-backward.svg"
- repeat: true
-
- onClicked: {
- mediaRoot.executeAction("skipBack")
- }
- onLongpressed: {
- mediaRoot.executeAction("fastRewind")
- }
- }
- Item { Layout.fillWidth: true }
-
- ProgressButton {
- Layout.preferredHeight: app.iconSize * 1.3
- Layout.preferredWidth: height
- imageSource: mediaRoot.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
- longpressImageSource: "../images/media-playback-stop.svg"
- longpressEnabled: mediaRoot.playbackState.value !== "Stopped"
-
- onClicked: {
- if (mediaRoot.playbackState.value === "Playing") {
- mediaRoot.executeAction("pause")
- } else {
- mediaRoot.executeAction("play")
- }
- }
-
- onLongpressed: {
- mediaRoot.executeAction("stop")
- }
- }
-
- Item { Layout.fillWidth: true }
- ProgressButton {
- Layout.preferredHeight: app.iconSize * .9
- Layout.preferredWidth: height
- imageSource: "../images/media-skip-forward.svg"
- longpressImageSource: "../images/media-seek-forward.svg"
- repeat: true
- onClicked: {
- mediaRoot.executeAction("skipNext")
- }
- onLongpressed: {
- mediaRoot.executeAction("fastForward")
- }
- }
- Item { Layout.fillWidth: true }
+ thing: device
}
}
}
diff --git a/nymea-app/ui/devicepages/DeviceBrowserPage.qml b/nymea-app/ui/devicepages/DeviceBrowserPage.qml
index 000b859f..ac39fa5d 100644
--- a/nymea-app/ui/devicepages/DeviceBrowserPage.qml
+++ b/nymea-app/ui/devicepages/DeviceBrowserPage.qml
@@ -73,11 +73,11 @@ Page {
if (commandId === d.pendingBrowserItemId) {
d.pendingBrowserItemId = -1;
d.pendingItemId = ""
- if (params.deviceError !== "DeviceErrorNoError") {
+ if (params.thinggError !== "ThingErrorNoError") {
if (params.displayMessage.length > 0) {
header.showInfo(qsTr("Error: %1").arg(params.displayMessage), true)
} else {
- header.showInfo(qsTr("Error: %1").arg(params.deviceError), true)
+ header.showInfo(qsTr("Error: %1").arg(params.thingError), true)
}
}
}
diff --git a/nymea-app/ui/devicepages/GarageThingPage.qml b/nymea-app/ui/devicepages/GarageThingPage.qml
index e314ad76..0a16ba3c 100644
--- a/nymea-app/ui/devicepages/GarageThingPage.qml
+++ b/nymea-app/ui/devicepages/GarageThingPage.qml
@@ -58,10 +58,6 @@ DevicePageBase {
readonly property StateType intermediatePositionStateType: thing.thingClass.stateTypes.findByName("intermediatePosition")
readonly property var intermediatePositionState: intermediatePositionStateType ? device.states.getState(intermediatePositionStateType.id) : null
- // Some garages may also implement the light interface
- readonly property var lightStateType: thing.thingClass.stateTypes.findByName("power")
- readonly property var lightState: lightStateType ? thing.states.getState(lightStateType.id) : null
-
Component.onCompleted: {
print("Creating garage page. Impulse based:", isImpulseBased, "stateful:", isStateful, "extended:", isExtended, "legacy:", intermediatePositionState !== null)
}
@@ -140,19 +136,14 @@ DevicePageBase {
Layout.fillWidth: true
Layout.margins: app.margins * 2
Layout.fillHeight: true
- property int minimumWidth: app.iconSize * 2.5 * (root.lightState ? 4 : 3)
+ property int minimumWidth: app.iconSize * 2.5
property int minimumHeight: app.iconSize * 2.5
- ItemDelegate {
- height: app.iconSize * 2
- width: height
+ ProgressButton {
anchors.centerIn: parent
visible: root.isImpulseBased
- ColorIcon {
- anchors.fill: parent
- name: "../images/closable-move.svg"
- anchors.margins: app.margins
- }
+ longpressEnabled: false
+ imageSource: "../images/closable-move.svg"
onClicked: {
var actionTypeId = root.thing.thingClass.actionTypes.findByName("triggerImpulse").id
print("Triggering impulse", actionTypeId)
@@ -163,31 +154,10 @@ DevicePageBase {
ShutterControls {
id: shutterControls
device: root.device
+ width: parent.width
anchors.centerIn: parent
spacing: (parent.width - app.iconSize*2*children.length) / (children.length - 1)
visible: !root.isImpulseBased
-
- ItemDelegate {
- width: app.iconSize * 2
- height: width
- visible: root.lightStateType !== null
-
- ColorIcon {
- anchors.fill: parent
- anchors.margins: app.margins
- name: "../images/light-" + (root.lightState && root.lightState.value === true ? "on" : "off") + ".svg"
- color: root.lightState && root.lightState.value === true ? Material.accent : keyColor
- }
- onClicked: {
- print("blabla", root.lightState, root.lightState.value, root.lightStateType.name, root.lightState.stateTypeId, root.lightStateType.id)
- var params = [];
- var param = {};
- param["paramTypeId"] = root.lightStateType.id;
- param["value"] = !root.lightState.value;
- params.push(param)
- engine.deviceManager.executeAction(root.device.id, root.lightStateType.id, params)
- }
- }
}
}
}
diff --git a/nymea-app/ui/devicepages/MediaDevicePage.qml b/nymea-app/ui/devicepages/MediaDevicePage.qml
deleted file mode 100644
index 454b7bfe..00000000
--- a/nymea-app/ui/devicepages/MediaDevicePage.qml
+++ /dev/null
@@ -1,305 +0,0 @@
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-*
-* 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 .
-*
-* 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.5
-import QtQuick.Controls 2.1
-import QtQuick.Controls.Material 2.1
-import QtQuick.Layouts 1.1
-import QtGraphicalEffects 1.0
-import Nymea 1.0
-import "../components"
-import "../customviews"
-import "../delegates"
-
-DevicePageBase {
- id: root
- popStackOnBackButton: false
- showBrowserButton: false
-
- onBackPressed: {
- swipeView.currentItem.backPressed()
- }
-
- Component.onCompleted: {
- if (root.deviceClass.browsable && playbackState.value === "Stopped") {
- swipeView.currentIndex = 1;
- }
- }
-
- function stateValue(name) {
- var stateType = root.deviceClass.stateTypes.findByName(name);
- if (!stateType) return null
- return root.device.states.getState(stateType.id).value
- }
-
- function executeAction(actionName, params) {
- var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
- print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes, params)
- engine.deviceManager.executeAction(device.id, actionTypeId, params)
- }
-
- function executeBrowserItem(itemId) {
- d.pendingItemId = itemId
- d.pendingBrowserItemId = engine.deviceManager.executeBrowserItem(device.id, itemId);
- }
- function executeBrowserItemAction(itemId, actionTypeId, params) {
- print("params2:", JSON.stringify(params))
- d.pendingItemId = itemId
- d.pendingBrowserItemId = engine.deviceManager.executeBrowserItemAction(device.id, itemId, actionTypeId, params);
- }
-
- function adjustVolume(volume) {
- d.pendingVolumeValue = volume;
-
- if (d.pendingVolumeId !== -1) {
- // busy
- return;
- }
-
- var params = []
- var volParam = {}
- volParam["paramTypeId"] = root.deviceClass.actionTypes.findByName("volume").id
- volParam["value"] = volume;
- params.push(volParam)
- var actionTypeId = deviceClass.actionTypes.findByName("volume").id;
- d.pendingVolumeId = engine.deviceManager.executeAction(device.id, actionTypeId, params);
- print("exec", d.pendingVolumeId)
- return;
- }
-
- readonly property State playbackState: device.states.getState(deviceClass.stateTypes.findByName("playbackStatus").id)
- readonly property State volumeState: device.states.getState(deviceClass.stateTypes.findByName("volume").id)
-
- QtObject {
- id: d
- property int pendingBrowserItemId: -1
- property string pendingItemId: ""
-
- property int pendingVolumeId: -1
- property int pendingVolumeValue: -1
- }
-
- Connections {
- target: engine.deviceManager
- onExecuteBrowserItemReply: executionFinished(commandId, params)
- onExecuteBrowserItemActionReply: executionFinished(commandId, params)
- onExecuteActionReply: {
- print("actionfinished", commandId)
- if (commandId === d.pendingVolumeId) {
- d.pendingVolumeId = -1
- print("volume action finished")
- if (params.deviceError !== "DeviceErrorNoError") {
- print("Error setting volume", params.deviceError)
- d.pendingVolumeValue = -1;
- return;
- }
-
- if (d.pendingVolumeValue !== volumeState.value) {
- root.adjustVolume(d.pendingVolumeValue);
- } else {
- d.pendingVolumeValue = -1;
- }
- }
- }
- }
- function executionFinished(commandId, params) {
- print("Execute reply:", params, commandId, d.pendingBrowserItemId)
- if (commandId === d.pendingBrowserItemId) {
- d.pendingBrowserItemId = -1;
- d.pendingItemId = ""
- print("yep finished")
- if (params.deviceError === "DeviceErrorNoError") {
- swipeView.currentIndex = 0;
- } else {
- header.showInfo(qsTr("Error: %1").arg(params.deviceError), true)
- }
- }
- }
-
- SwipeView {
- id: swipeView
- anchors.fill: parent
-
- Component.onCompleted: {
- if (root.deviceClass.browsable) {
- browserComponent.createObject(swipeView)
- }
-
- if (root.deviceClass.interfaces.indexOf("navigationpad") >= 0) {
- navigationComponent.createObject(swipeView)
- }
- }
-
- Item {
- function backPressed() {
- pageStack.pop();
- }
-
- GridLayout {
- id: contentColumn
- anchors.fill: parent
- anchors.margins: app.margins
- columns: app.landscape ? 2 : 1
- columnSpacing: app.margins
- rowSpacing: app.margins
-
- MediaArtworkImage {
- Layout.fillHeight: true
- Layout.preferredWidth: parent.width / parent.columns
- thing: root.device
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: app.margins
-
- Label {
- Layout.fillWidth: true
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.WordWrap
- maximumLineCount: 2
- elide: Text.ElideRight
- font.pixelSize: app.largeFont
- font.bold: true
- text: root.stateValue("title")
- }
- Label {
- Layout.fillWidth: true
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.WordWrap
- maximumLineCount: 2
- elide: Text.ElideRight
- text: root.stateValue("artist")
- }
- Label {
- Layout.fillWidth: true
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.WordWrap
- maximumLineCount: 2
- elide: Text.ElideRight
- text: root.stateValue("collection")
- }
-
- MediaControls {
- thing: root.device
- iconSize: app.iconSize * 2
- }
- }
- }
- }
-
- }
-
- Component {
- id: browserComponent
-
- MediaBrowser {
- thing: root.device
- }
- }
-
- Component {
- id: navigationComponent
- Item {
- function backPressed() {
- swipeView.currentIndex--;
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: app.margins
-
- NavigationPad {
- Layout.fillWidth: true
- Layout.fillHeight: true
- device: root.device
- }
-
- MediaControls {
- Layout.fillWidth: true
- thing: root.device
- }
- }
-
- }
- }
-
- footer: Pane {
- Material.elevation: 1
- height: 52
- padding: 0
- contentItem: ColumnLayout {
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 2
- visible: swipeView.count > 1
- Rectangle {
- height: parent.height
- width: parent.width / swipeView.count
- color: app.accentColor
- x: swipeView.currentIndex * width
- Behavior on x { NumberAnimation { duration: 150 } }
- }
- }
-
- RowLayout {
- Item {
- Layout.fillHeight: true
- Layout.preferredWidth: swipeView.count > 1 && swipeView.currentIndex > 0 ? parent.width / 4 : 0
- Behavior on Layout.preferredWidth { NumberAnimation {} }
- HeaderButton {
- anchors.centerIn: parent
- imageSource: "../images/back.svg"
- opacity: swipeView.count > 1 && swipeView.currentIndex > 0 ? 1 : 0
- Behavior on opacity { NumberAnimation {} }
- onClicked: swipeView.currentIndex--
- }
- }
- ShuffleRepeatVolumeControl {
- Layout.fillWidth: true
- thing: root.device
- }
-
- Item {
- Layout.fillHeight: true
- Layout.preferredWidth: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? parent.width / 4 : 0
- Behavior on Layout.preferredWidth { NumberAnimation {} }
- HeaderButton {
- anchors.centerIn: parent
- imageSource: "../images/next.svg"
- onClicked: swipeView.currentIndex++
- opacity: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? 1 : 0
- Behavior on opacity { NumberAnimation {} }
- }
- }
- }
- }
- }
-}
diff --git a/nymea-app/ui/devicepages/MediaThingPage.qml b/nymea-app/ui/devicepages/MediaThingPage.qml
new file mode 100644
index 00000000..dc180e04
--- /dev/null
+++ b/nymea-app/ui/devicepages/MediaThingPage.qml
@@ -0,0 +1,49 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 .
+*
+* 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.5
+import QtQuick.Controls 2.1
+import QtQuick.Controls.Material 2.1
+import QtQuick.Layouts 1.1
+import QtGraphicalEffects 1.0
+import Nymea 1.0
+import "../components"
+import "../customviews"
+import "../delegates"
+
+DevicePageBase {
+ id: root
+ showBrowserButton: false
+
+ MediaPlayer {
+ anchors.fill: parent
+ thing: root.thing
+ }
+}
diff --git a/nymea-app/ui/images/like.svg b/nymea-app/ui/images/like.svg
new file mode 100644
index 00000000..3cbf3b70
--- /dev/null
+++ b/nymea-app/ui/images/like.svg
@@ -0,0 +1,23 @@
+
+
+
diff --git a/nymea-app/ui/images/state-in.svg b/nymea-app/ui/images/state-in.svg
new file mode 100644
index 00000000..6b2ad0b1
--- /dev/null
+++ b/nymea-app/ui/images/state-in.svg
@@ -0,0 +1,75 @@
+
+
diff --git a/nymea-app/ui/images/state-out.svg b/nymea-app/ui/images/state-out.svg
new file mode 100644
index 00000000..6fda8bc1
--- /dev/null
+++ b/nymea-app/ui/images/state-out.svg
@@ -0,0 +1,84 @@
+
+
+
+
diff --git a/nymea-app/ui/magic/SelectActionPage.qml b/nymea-app/ui/magic/SelectActionPage.qml
deleted file mode 100644
index 2e28280b..00000000
--- a/nymea-app/ui/magic/SelectActionPage.qml
+++ /dev/null
@@ -1,401 +0,0 @@
-/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
-*
-* 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 .
-*
-* 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.5
-import QtQuick.Controls 2.1
-import QtQuick.Layouts 1.1
-import Nymea 1.0
-import "../components"
-import "../paramdelegates"
-
-Page {
- id: root
-
- // input
- property string text
-
- // output
- property var actions: []
- signal complete();
-
- header: NymeaHeader {
- text: "Select action"
- onBackPressed: pageStack.pop()
- }
-
- ListModel {
- id: actionModel
- ListElement { interfaceName: "light"; text: qsTr("Switch lights..."); identifier: "switchLights" }
- ListElement { interfaceName: "mediacontroller"; text: qsTr("Control media playback..."); identifier: "controlMedia" }
- ListElement { interfaceName: "extendedvolumecontroller"; text: qsTr("Mute media playback..."); identifier: "muteMedia" }
- ListElement { interfaceName: "notifications"; text: qsTr("Notify me..."); identifier: "notify" }
- ListElement { interfaceName: ""; text: qsTr("Manually configure an action..."); identifier: "manualAction" }
- }
-
- DevicesProxy {
- id: ifaceFilterModel
- engine: _engine
- }
-
- Component.onCompleted: {
- actualModel.clear()
- for (var i = 0; i < actionModel.count; i++) {
- ifaceFilterModel.shownInterfaces = [actionModel.get(i).interfaceName];
- if (actionModel.get(i).interfaceName === "" || ifaceFilterModel.count > 0) {
- actualModel.append(actionModel.get(i))
- }
- }
- }
-
- function actionSelected(identifier) {
- switch (identifier) {
- case "switchLights":
- pageStack.push(switchLightsCompoent)
- break;
- case "controlMedia":
- break;
- case "muteMedia":
- break;
- case "manualAction":
- pageStack.push(selectDeviceComponent)
- break;
- case "notify":
- pageStack.push(notificationActionComponent)
- }
- }
-
- ColumnLayout {
- anchors.fill: parent
-
- Label {
- Layout.fillWidth: true
- Layout.margins: app.margins
- text: root.text
- font.pixelSize: app.largeFont
- }
-
- ListView {
- Layout.fillWidth: true
- Layout.fillHeight: true
- model: ListModel {
- id: actualModel
- }
- delegate: ItemDelegate {
- width: parent.width
- text: model.text
- onClicked: {
- root.actionSelected(model.identifier)
- }
- }
- }
- }
-
- Component {
- id: selectDeviceComponent
- Page {
- header: NymeaHeader {
- text: qsTr("Select device")
- onBackPressed: pageStack.pop()
- }
-
- ColumnLayout {
- anchors.fill: parent
-
- ListView {
- Layout.fillHeight: true
- Layout.fillWidth: true
- model: engine.deviceManager.devices
- delegate: ItemDelegate {
- width: parent.width
- Label {
- anchors.fill: parent
- anchors.margins: app.margins
- text: model.name
- verticalAlignment: Text.AlignVCenter
- }
- onClicked: {
- var device = engine.deviceManager.devices.get(index)
- pageStack.push(selectDeviceActionComponent, {device: device})
- }
- }
- }
- }
- }
- }
-
- Component {
- id: selectDeviceActionComponent
- Page {
- id: page
- property var device
- readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
-
- header: NymeaHeader {
- text: qsTr("Select action")
- onBackPressed: pageStack.pop()
- }
-
- ColumnLayout {
- anchors.fill: parent
- ListView {
- Layout.fillHeight: true
- Layout.fillWidth: true
- model: page.deviceClass.actionTypes
-
- delegate: ItemDelegate {
- width: parent.width
- Label {
- anchors.fill: parent
- anchors.margins: app.margins
- text: model.name
- verticalAlignment: Text.AlignVCenter
- }
-
- onClicked: {
- var actionType = page.deviceClass.actionTypes.get(index)
- if (page.deviceClass.actionTypes.get(index).paramTypes.count === 0) {
- // We're all set.
- var action = {}
- action["deviceId"] = page.device.id
- action["actionTypeId"] = actionType.id
- root.actions.push(action)
- root.complete();
- } else {
- // need to fill in params
- pageStack.push(selectDeviceActionParamComponent, {device: page.device, actionType: actionType})
- }
- }
- }
- }
- }
- }
- }
-
- Component {
- id: selectDeviceActionParamComponent
- Page {
- id: page
- property var device
- property var actionType
- header: NymeaHeader {
- text: qsTr("params")
- onBackPressed: pageStack.pop()
- }
-
- ColumnLayout {
- anchors.fill: parent
- Repeater {
- id: delegateRepeater
- model: page.actionType.paramTypes
- delegate: ParamDelegate {
- paramType: page.actionType.paramTypes.get(index)
- value: paramType.defaultValue
-
- }
- }
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- Button {
- text: qsTr("OK")
- Layout.fillWidth: true
- Layout.margins: app.margins
- onClicked: {
- var params = [];
- for (var i = 0; i < delegateRepeater.count; i++) {
- var paramDelegate = delegateRepeater.itemAt(i);
- var param = {}
- param["paramTypeId"] = paramDelegate.paramType.id
- param["value"] = paramDelegate.value
- params.push(param)
- }
- var action = {};
- action["deviceId"] = page.device.id
- action["actionTypeId"] = page.actionType.id
- action["ruleActionParams"] = params
- root.actions.push(action)
- root.complete()
- }
- }
- }
- }
- }
-
- Component {
- id: switchLightsCompoent
- Page {
- header: NymeaHeader {
- text: qsTr("Switch lights")
- onBackPressed: pageStack.pop()
- }
-
- ColumnLayout {
- anchors.fill: parent
-
- SwitchDelegate {
- id: switchDelegate
- Layout.fillWidth: true
- text: qsTr("Set selected lights power to")
- position: 0
- }
- ThinDivider {}
-
- Flickable {
- Layout.fillHeight: true
- Layout.fillWidth: true
- interactive: contentHeight > height
- clip: true
-
- Column {
- width: parent.width
-
- Repeater {
- id: lightsRepeater
-
- model: DevicesProxy {
- id: lightsModel
- engine: _engine
- shownInterfaces: ["light"]
- }
- delegate: CheckDelegate {
- width: parent.width
- text: model.name
- }
- }
- }
- }
-
- Button {
- Layout.fillWidth: true
- Layout.margins: app.margins
- text: qsTr("OK")
- onClicked: {
- for (var i = 0; i < lightsRepeater.count; i++) {
- if (lightsRepeater.itemAt(i).checkState === Qt.Unchecked) {
- continue;
- }
- var device = lightsModel.get(i);
- var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
-
- var action = {}
- action["deviceId"] = device.id
-
- var actionType = deviceClass.actionTypes.findByName("power")
- action["actionTypeId"] = actionType.id
-
- var params = [];
- var paramType = actionType.paramTypes.getParamType("power");
- var param = {}
- param["paramTypeId"] = paramType.id
- param["value"] = switchDelegate.position === 1 ? true : false;
- params.push(param)
-
- action["ruleActionParams"] = params
- root.actions.push(action)
- }
- root.complete();
- }
- }
- }
- }
- }
-
- Component {
- id: notificationActionComponent
- Page {
- header: NymeaHeader {
- text: qsTr("Send notification")
- onBackPressed: pageStack.pop()
- }
-
- ColumnLayout {
- anchors.fill: parent
- spacing: app.margins
- Label {
- Layout.fillWidth: true
- text: qsTr("Notification text")
- Layout.topMargin: app.margins
- Layout.leftMargin: app.margins
- Layout.rightMargin: app.margins
- }
- TextField {
- id: notificationTextField
- Layout.fillWidth: true
- Layout.leftMargin: app.margins
- Layout.rightMargin: app.margins
- }
- ThinDivider {}
- Flickable {
- Layout.fillHeight: true
- Layout.fillWidth: true
- interactive: contentHeight > height
- clip: true
-
- Column {
- width: parent.width
-
- Repeater {
- id: notificationsRepeater
-
- model: DevicesProxy {
- id: notificationsModel
- engine: _engine
- shownInterfaces: ["notifications"]
- }
- delegate: CheckDelegate {
- width: parent.width
- text: model.name
- checked: true
- }
- }
- }
- }
- Button {
- Layout.fillWidth: true
- Layout.margins: app.margins
- text: qsTr("OK")
- onClicked: {
- var action = {}
- action["interface"] = "notifications";
- action["interfaceAction"] = "notify";
- action["ruleActionParams"] = [];
- var ruleActionParam = {};
- ruleActionParam["paramName"] = "title";
- ruleActionParam["value"] = notificationTextField.text
- action["ruleActionParams"].push(ruleActionParam)
- root.actions.push(action)
- root.complete()
- }
- }
- }
- }
- }
-}
diff --git a/nymea-app/ui/mainviews/GroupsView.qml b/nymea-app/ui/mainviews/GroupsView.qml
index 4d4ad117..479b58b1 100644
--- a/nymea-app/ui/mainviews/GroupsView.qml
+++ b/nymea-app/ui/mainviews/GroupsView.qml
@@ -477,7 +477,6 @@ MainViewBase {
id: mediaControllerDelegate
MediaControls {
property var devices: null
- iconSize: app.iconSize
DevicesProxy {
id: mediaControllers
engine: _engine
diff --git a/nymea-app/ui/mainviews/MediaView.qml b/nymea-app/ui/mainviews/MediaView.qml
index 9eee5971..6a295f2b 100644
--- a/nymea-app/ui/mainviews/MediaView.qml
+++ b/nymea-app/ui/mainviews/MediaView.qml
@@ -28,7 +28,7 @@
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-import QtQuick 2.8
+import QtQuick 2.9
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
@@ -40,6 +40,8 @@ import "../delegates"
MainViewBase {
id: root
+ title: swipeView.currentItem ? swipeView.currentItem.thing.name : ""
+
ThingsProxy {
id: mediaDevices
engine: _engine
@@ -48,125 +50,20 @@ MainViewBase {
SwipeView {
id: swipeView
- anchors.fill: parent
+ anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom }
currentIndex: pageIndicator.currentIndex
Repeater {
model: mediaDevices
- delegate: Item {
- id: playerDelegate
- height: swipeView.height
- width: swipeView.width
- property Thing thing: mediaDevices.get(index)
- property State titleState: thing.stateByName("title")
- property State artistState: thing.stateByName("artist")
- property State collectionState: thing.stateByName("collection")
-
- GridLayout {
- anchors.fill: parent
- anchors.margins: app.margins
- columns: 1
- rowSpacing: app.margins
-
- MediaArtworkImage {
- Layout.fillWidth: true
- Layout.fillHeight: true
- thing: playerDelegate.thing
- }
- ColumnLayout {
- spacing: app.margins
- Label {
- text: playerDelegate.titleState.value
- Layout.fillWidth: true
- elide: Text.ElideRight
- horizontalAlignment: Text.AlignHCenter
- font.pixelSize: app.largeFont
- }
- Label {
- text: playerDelegate.artistState.value
- Layout.fillWidth: true
- elide: Text.ElideRight
- horizontalAlignment: Text.AlignHCenter
- }
- Label {
- text: playerDelegate.collectionState.value
- Layout.fillWidth: true
- elide: Text.ElideRight
- horizontalAlignment: Text.AlignHCenter
- }
- }
-
- MediaControls {
- Layout.fillWidth: true
- thing: playerDelegate.thing
- }
-
- RowLayout {
-
- Item {
- Layout.preferredHeight: app.iconSize
- Layout.fillWidth: true
- visible: playerDelegate.thing.thingClass.browsable
-
- HeaderButton {
- anchors.centerIn: parent
- imageSource: "../images/navigationpad.svg"
- onClicked: {
- pageStack.push(navigationPadPage)
- }
- }
- Component {
- id: navigationPadPage
- Page {
- header: NymeaHeader { text: playerDelegate.thing.name; onBackPressed: pageStack.pop() }
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: app.margins
- spacing: app.margins
-
- NavigationPad { Layout.fillWidth: true; Layout.fillHeight: true; device: playerDelegate.thing }
- MediaControls { Layout.fillWidth: true; thing: playerDelegate.thing }
- ShuffleRepeatVolumeControl { Layout.fillWidth: true; Layout.fillHeight: false; Layout.preferredHeight: app.iconSize; thing: playerDelegate.thing }
- }
- }
- }
- }
-
- ShuffleRepeatVolumeControl {
- Layout.fillWidth: true
- Layout.fillHeight: false
- Layout.preferredHeight: app.iconSize
- thing: playerDelegate.thing
- }
-
- Item {
- Layout.preferredHeight: app.iconSize
- Layout.fillWidth: true
- visible: playerDelegate.thing.thingClass.interfaces.indexOf("navigationpad") >= 0
-
- HeaderButton {
- anchors.centerIn: parent
- imageSource: "../images/folder-symbolic.svg"
- onClicked: {
- pageStack.push(browserPage)
- }
- }
- Component {
- id: browserPage
- Page {
- header: NymeaHeader { text: playerDelegate.thing.name; onBackPressed: pageStack.pop() }
- MediaBrowser { anchors.fill: parent; thing: playerDelegate.thing }
- }
- }
- }
- }
- }
+ delegate: MediaPlayer {
+ thing: mediaDevices.get(index)
}
}
}
PageIndicator {
id: pageIndicator
count: swipeView.count
+ visible: count > 1
currentIndex: swipeView.currentIndex
interactive: true
anchors.bottom: parent.bottom
@@ -183,5 +80,4 @@ MainViewBase {
buttonText: qsTr("Add things")
onButtonClicked: pageStack.push(Qt.resolvedUrl("../thingconfiguration/NewThingPage.qml"))
}
-
}
diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml
index a705e64c..adfd3381 100644
--- a/nymea-app/ui/utils/NymeaUtils.qml
+++ b/nymea-app/ui/utils/NymeaUtils.qml
@@ -20,7 +20,7 @@ Item {
print("**** getting page for interfaces", interfaceList)
var page;
if (interfaceList.indexOf("media") >= 0) {
- page = "MediaDevicePage.qml";
+ page = "MediaThingPage.qml";
} else if (interfaceList.indexOf("button") >= 0) {
page = "ButtonDevicePage.qml";
} else if (interfaceList.indexOf("powerswitch") >= 0) {