Merge PR #466: Rework media views for new interfaces

This commit is contained in:
Jenkins nymea 2020-11-28 21:09:16 +01:00
commit 4a80b7aea8
35 changed files with 1185 additions and 1577 deletions

View File

@ -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);

View File

@ -712,9 +712,9 @@ void DeviceManager::browserItemResponse(int commandId, const QVariantMap &params
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 &params)
@ -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 &params)
{
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)

View File

@ -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;

View File

@ -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());

View File

@ -238,5 +238,8 @@
<file>ui/images/connections/network-wired-disabled.svg</file>
<file>ui/images/nfc.svg</file>
<file>ui/images/smartphone.svg</file>
<file>ui/images/state-in.svg</file>
<file>ui/images/state-out.svg</file>
<file>ui/images/like.svg</file>
</qresource>
</RCC>

View File

@ -168,3 +168,9 @@ INSTALLS += target
ANDROID_ABIS = armeabi-v7a arm64-v8a
contains(ANDROID_TARGET_ARCH,) {
ANDROID_ABIS = \
armeabi-v7a \
arm64-v8a
}

View File

@ -49,10 +49,8 @@
<file>ui/customviews/GenericTypeLogView.qml</file>
<file>ui/customviews/CustomViewBase.qml</file>
<file>ui/customviews/WeatherView.qml</file>
<file>ui/customviews/MediaControllerView.qml</file>
<file>ui/customviews/NotificationsView.qml</file>
<file>ui/customviews/ExtendedVolumeController.qml</file>
<file>ui/devicepages/MediaDevicePage.qml</file>
<file>ui/devicepages/MediaThingPage.qml</file>
<file>ui/devicepages/ButtonDevicePage.qml</file>
<file>ui/devicepages/GenericDevicePage.qml</file>
<file>ui/devicepages/WeatherDevicePage.qml</file>
@ -77,7 +75,6 @@
<file>ui/devicelistpages/SensorsDeviceListPage.qml</file>
<file>ui/devicelistpages/WeatherDeviceListPage.qml</file>
<file>ui/devicelistpages/DeviceListPageBase.qml</file>
<file>ui/magic/SelectActionPage.qml</file>
<file>ui/magic/DeviceRulesPage.qml</file>
<file>ui/magic/EditRulePage.qml</file>
<file>ui/magic/SelectThingPage.qml</file>
@ -224,5 +221,6 @@
<file>ui/components/SetupStatusIcon.qml</file>
<file>ui/components/UpdateStatusIcon.qml</file>
<file>ui/magic/WriteNfcTagPage.qml</file>
<file>ui/components/MediaPlayer.qml</file>
</qresource>
</RCC>

View File

@ -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

View File

@ -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")

View File

@ -150,8 +150,8 @@ ToolBar {
id: menuRepeater
MouseArea {
height: app.iconSize * 3
width: app.iconSize * 3
height: 80
width: height
onClicked: {
menuOpen = false

View File

@ -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

View File

@ -43,4 +43,6 @@ MouseArea {
preventStealing: true
onWheel: wheel.accepted = true
property string title: ""
}

View File

@ -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
}
}

View File

@ -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})
}
}

View File

@ -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)
}
}
}

View File

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

View File

@ -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();

View File

@ -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
}

View File

@ -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}]);

View File

@ -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 }
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.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)
}
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.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)
}
}
}
}
}
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.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 {} }
}
}
}
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.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
}
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg4874" width="96" height="96" version="1.1" viewBox="0 0 96 96" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata id="metadata4879">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1" transform="translate(67.857 -78.505)">
<g id="g4845" transform="matrix(0 -1 -1 0 373.51 516.51)">
<g id="g4778" transform="matrix(-.9996 0 0 1 575.94 -611)">
<g id="g4780" transform="matrix(-1 0 0 1 576 611)">
<rect id="rect4782" transform="scale(-1,1)" x="-438" y="345.36" width="96.038" height="96" style="color:#000000;fill:none"/>
<path id="path4849" d="m430 411.93c0 11.675-9.3424 21.174-20.961 21.429-5.3915 0-9.5995-1.413-13.487-2.8571-16.066-6.7837-29.485-20-45.584-37.143 16.098-17.143 29.518-30.359 45.584-37.143 3.888-1.4441 8.096-2.8571 13.487-2.8571 11.618 0.25447 20.961 9.7538 20.961 21.429 0 7.9422-4.3266 14.87-10.748 18.571 6.4217 3.7014 10.748 10.629 10.748 18.571z" style="color:#000000;fill:#808080"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg4874"
width="96"
height="96"
version="1.1"
viewBox="0 0 96 96"
sodipodi:docname="state-in.svg"
inkscape:version="1.0.1 (1.0.1+r74)">
<defs
id="defs11" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1380"
inkscape:window-height="873"
id="namedview9"
showgrid="false"
inkscape:zoom="6.6458333"
inkscape:cx="41.905956"
inkscape:cy="48"
inkscape:window-x="60"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg4874" />
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="rotate(-90,-25.180998,53.323997)">
<rect
id="rect4782-2"
transform="rotate(90)"
x="78.504997"
y="-28.143"
width="96"
height="96"
style="color:#000000;fill:none" />
<path
id="path4643-2"
d="m -39.869,98.504 -0.01134,0.002 c -5.0328,0.0582 -8.7136,-0.12019 -11.725,1.541 -1.5055,0.83062 -2.6968,2.2356 -3.3555,3.9902 -0.65866,1.7546 -0.89647,3.8364 -0.89647,6.4668 v 48.002 c 0,2.6304 0.23773,4.7122 0.89647,6.4668 0.65866,1.7546 1.85,3.1596 3.3555,3.9902 3.011,1.6613 6.6918,1.4848 11.725,1.543 H -39.869 0.154 0.16534 c 5.0328,-0.0582 8.7136,0.1183 11.725,-1.543 1.5055,-0.83066 2.6968,-2.2356 3.3555,-3.9902 0.65866,-1.7546 0.8965,-3.8364 0.8965,-6.4668 v -48.002 c 0,-2.6304 -0.23773,-4.7122 -0.8965,-6.4668 -0.65866,-1.7547 -1.85,-3.1596 -3.3555,-3.9902 -3.011,-1.6613 -6.6918,-1.4829 -11.725,-1.5411 l -0.01134,-0.002 -12.012,0.002 v 4 l 11.977,-0.002 c 5.0542,0.0586 8.3726,0.23547 9.8398,1.0449 0.73364,0.40479 1.1527,0.85493 1.543,1.8945 0.39024,1.0396 0.64059,2.691 0.64059,5.0606 v 48.002 c 0,2.3695 -0.2502,4.0209 -0.64059,5.0605 -0.39027,1.0396 -0.80935,1.4898 -1.543,1.8945 -1.4645,0.80806 -4.7782,0.98615 -9.8164,1.0449 h -39.977 -0.02268 c -5.0383,-0.059 -8.3519,-0.23697 -9.8164,-1.0449 -0.73364,-0.40475 -1.1508,-0.85489 -1.541,-1.8945 -0.39027,-1.0396 -0.6426,-2.691 -0.6426,-5.0605 v -48.002 c 0,-2.3696 0.25247,-4.0209 0.6426,-5.0606 0.39024,-1.0396 0.80734,-1.4897 1.541,-1.8945 1.4645,-0.80807 4.7782,-0.98616 9.8164,-1.0449 l 12,0.002 v -4 z"
style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#808080;color-rendering:auto;image-rendering:auto;shape-rendering:auto" />
<path
id="path4237"
d="m -21.858,82.505 v 50 h 4 v -50 z"
style="fill:#808080" />
<path
id="path5588-9-2-96-04"
d="m -7.8571,124.51 -24,0.008 c 1.6687,3.6501 3.5359,7.366 5.5987,11.149 2.0678,3.7477 4.2005,7.3628 6.4003,10.843 2.1557,-3.48 4.268,-7.0951 6.3358,-10.843 2.0639,-3.7854 3.9519,-7.5033 5.6652,-11.155 z"
style="color:#000000;fill:#808080" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg4874"
width="96"
height="96"
version="1.1"
viewBox="0 0 96 96"
sodipodi:docname="state-out.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<defs
id="defs11" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2792"
inkscape:window-height="1698"
id="namedview9"
showgrid="true"
inkscape:zoom="2.4583333"
inkscape:cx="-47.999999"
inkscape:cy="47.999999"
inkscape:window-x="88"
inkscape:window-y="44"
inkscape:window-maximized="1"
inkscape:current-layer="svg4874">
<inkscape:grid
type="xygrid"
id="grid1441" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="color:#000000;fill:none;stroke-width:1"
height="83.999992"
width="83.999992"
y="6"
x="-83.5"
id="rect4782-2"
transform="scale(-1,1)" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#808080;stroke-width:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
d="m 66.00087,65.510496 -0.0017,0.0099 c -0.05093,4.4037 0.105166,7.6244 -1.348375,10.259374 -0.726792,1.317313 -1.95615,2.3597 -3.491425,2.936063 -1.535275,0.576327 -3.35685,0.784411 -5.658449,0.784411 H 13.499174 c -2.3016,0 -4.123175,-0.208014 -5.65845,-0.784411 C 6.305449,78.139505 5.076074,77.097083 4.349299,75.77977 2.895662,73.145146 3.050099,69.924446 2.999174,65.520396 v -0.0099 -35.020123 -0.0099 c 0.05093,-4.4037 -0.103512,-7.6244 1.350125,-10.259375 0.726828,-1.317312 1.95615,-2.3597 3.491425,-2.936062 1.535275,-0.576328 3.35685,-0.784438 5.65845,-0.784438 h 42.001697 c 2.301599,0 4.123174,0.208014 5.658449,0.784438 1.535363,0.576327 2.76465,1.61875 3.491425,2.936062 1.453637,2.634625 1.297537,5.855325 1.348462,10.259375 l 0.0018,0.0099 -0.0018,10.510499 h -3.499999 l 0.0017,-10.479874 c -0.05127,-4.422425 -0.206037,-7.326024 -0.914288,-8.609824 -0.354191,-0.641935 -0.748063,-1.008613 -1.657687,-1.350125 -0.90965,-0.34146 -2.354625,-0.560517 -4.428025,-0.560517 H 13.499161 c -2.073312,0 -3.518287,0.218925 -4.427937,0.560517 -0.90965,0.341486 -1.303575,0.708181 -1.657688,1.350125 -0.707052,1.281437 -0.862881,4.180924 -0.914287,8.589349 v 34.979873 0.01985 c 0.05163,4.408512 0.207349,7.307912 0.914287,8.589349 0.354157,0.641935 0.748029,1.00695 1.657688,1.348375 0.90965,0.341486 2.354625,0.562275 4.427937,0.562275 h 42.001797 c 2.0734,0 3.518287,-0.220911 4.428025,-0.562275 0.90965,-0.34146 1.303487,-0.706423 1.657687,-1.348375 0.707061,-1.281437 0.86289,-4.180925 0.914288,-8.589349 l -0.0017,-10.5 h 3.499999 z"
id="path4643-2" />
<g
id="g1449"
transform="translate(22.495617)">
<path
id="path4237"
d="m 15.500006,49.750872 h 43.749997 v -3.5 H 15.500006 Z"
style="fill:#808080;stroke-width:1"
inkscape:connector-curvature="0" />
<path
id="path5588-9-2-96-04"
d="m 52.254379,37.500085 0.007,20.999999 c 3.193837,-1.460113 6.445249,-3.093913 9.755374,-4.898863 3.279237,-1.809324 6.442449,-3.675437 9.487624,-5.600262 -3.045,-1.886237 -6.208212,-3.734499 -9.487624,-5.543824 -3.312225,-1.805913 -6.565387,-3.457913 -9.760624,-4.95705 z"
style="color:#000000;fill:#808080;stroke-width:1"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.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()
}
}
}
}
}
}

View File

@ -477,7 +477,6 @@ MainViewBase {
id: mediaControllerDelegate
MediaControls {
property var devices: null
iconSize: app.iconSize
DevicesProxy {
id: mediaControllers
engine: _engine

View File

@ -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"))
}
}

View File

@ -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) {