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