From a2f1fa93b2fe37ec9b2afe892aee0e8d33b77ca5 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Jul 2019 16:20:46 +0200 Subject: [PATCH] Add support for the keypad interface in media devices --- .../connection/nymeaconnection.cpp | 10 +- libnymea-app-core/devicemanager.cpp | 2 +- nymea-app/images.qrc | 1 + nymea-app/resources.qrc | 2 + nymea-app/styles/lime/ApplicationWindow.qml | 2 +- nymea-app/styles/lime/Button.qml | 2 +- nymea-app/ui/components/KeypadButton.qml | 32 +++ nymea-app/ui/components/NavigationPad.qml | 255 ++++++++++++++++++ .../ui/components/NymeaListItemDelegate.qml | 17 +- .../ui/delegates/BrowserItemDelegate.qml | 4 +- .../devicelistpages/MediaDeviceListPage.qml | 8 +- nymea-app/ui/devicepages/MediaDevicePage.qml | 81 ++++-- nymea-app/ui/images/home.svg | 177 ++++++++++++ .../ui/magic/SelectBrowserItemActionPage.qml | 6 + 14 files changed, 554 insertions(+), 45 deletions(-) create mode 100644 nymea-app/ui/components/KeypadButton.qml create mode 100644 nymea-app/ui/components/NavigationPad.qml create mode 100644 nymea-app/ui/images/home.svg diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index abcf5f4e..ecd2c195 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -407,14 +407,8 @@ void NymeaConnection::updateActiveBearers() qDebug() << "Inactive network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); } - // Retrying with a new config manager: - QNetworkConfigurationManager *newMan = new QNetworkConfigurationManager(this); - qDebug() << "Trying with new config manager:"; - configs = newMan->allConfigurations(); - foreach (const QNetworkConfiguration &config, configs) { - qDebug() << "Config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName() << config.state(); - } - newMan->deleteLater(); + qWarning() << "Updating network manager"; + m_networkConfigManager->updateConfigurations(); } if (m_availableBearerTypes != availableBearerTypes) { diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index eb0da28f..4c17657d 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -474,7 +474,7 @@ BrowserItem *DeviceManager::browserItem(const QUuid &deviceId, const QString &it void DeviceManager::browseDeviceResponse(const QVariantMap ¶ms) { - qDebug() << "Browsing response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); +// qDebug() << "Browsing response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); int id = params.value("id").toInt(); if (!m_browsingRequests.contains(id)) { qWarning() << "Received a browsing reply for an id we don't know."; diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 03374bf9..4a886372 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -208,5 +208,6 @@ ui/images/browser/BrowserIconApplication.svg ui/images/browser/BrowserIconDocument.svg ui/images/browser/BrowserIconPackage.svg + ui/images/home.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 7e21f360..b8ab2b13 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -191,5 +191,7 @@ ui/components/BrowserContextMenu.qml ui/magic/SelectBrowserItemActionPage.qml ui/delegates/BrowserItemDelegate.qml + ui/components/NavigationPad.qml + ui/components/KeypadButton.qml diff --git a/nymea-app/styles/lime/ApplicationWindow.qml b/nymea-app/styles/lime/ApplicationWindow.qml index f946ec85..4b7e8403 100644 --- a/nymea-app/styles/lime/ApplicationWindow.qml +++ b/nymea-app/styles/lime/ApplicationWindow.qml @@ -10,7 +10,7 @@ ApplicationWindow { font.capitalization: Font.MixedCase font.family: "Ubuntu" - Material.background: "black" + Material.background: "#151515" // The core system name. property string systemName: "nymea" diff --git a/nymea-app/styles/lime/Button.qml b/nymea-app/styles/lime/Button.qml index 6cb40a2a..ac643c8b 100644 --- a/nymea-app/styles/lime/Button.qml +++ b/nymea-app/styles/lime/Button.qml @@ -25,7 +25,7 @@ T.Button { contentItem: Text { text: control.text - color: app.backgroundColor + color: control.Material.backgroundColor font.bold: control.font.bold font.capitalization: Font.AllUppercase font.family: control.font.family diff --git a/nymea-app/ui/components/KeypadButton.qml b/nymea-app/ui/components/KeypadButton.qml new file mode 100644 index 00000000..aeb6aa9c --- /dev/null +++ b/nymea-app/ui/components/KeypadButton.qml @@ -0,0 +1,32 @@ +import QtQuick 2.5 +import Nymea 1.0 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.3 + +Item { + id: root + + property alias imageSource: icon.name + + property color activeColor: app.accentColor + + function activate() { + t.start(); + } + + ColorIcon { + id: icon + anchors.fill: parent + color: active ? root.activeColor : keyColor + Behavior on color { ColorAnimation { duration: 200 } } + + property bool active: t.running + + Timer { + id: t; + interval: 200 + } + } +} + diff --git a/nymea-app/ui/components/NavigationPad.qml b/nymea-app/ui/components/NavigationPad.qml new file mode 100644 index 00000000..3811f566 --- /dev/null +++ b/nymea-app/ui/components/NavigationPad.qml @@ -0,0 +1,255 @@ +import QtQuick 2.5 +import Nymea 1.0 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.3 + +Item { + id: root + + property Device device: null + + readonly property bool isExtended: device && device.deviceClass.interfaces.indexOf("extendednavigationpad") >= 0 + + readonly property ActionType navigateActionType: device ? device.deviceClass.actionTypes.findByName("navigate") : null + + Pane { + id: pane + Material.elevation: 2 + width: Math.min(root.width, root.height) + height: Math.min(root.width, root.height) + anchors.centerIn: parent + + padding: 0 + + contentItem: Item { + + Pane { + anchors.centerIn: parent + Material.elevation: 2 + rotation: 45 + width: Math.sqrt(Math.pow(parent.width / 2, 2) + Math.pow(parent.height / 2, 2)) + height: width + } + + KeypadButton { + id: backButton + anchors { left: parent.left; top: parent.top; margins: parent.width * .1 } + height: app.iconSize + width: app.iconSize + imageSource: "../images/back.svg" + Item { id: backButtonArea; anchors.centerIn: parent; width: pane.width / 4; height: width; rotation: 45; } + } + KeypadButton { + id: menuButton + anchors { right: parent.right; top: parent.top; margins: parent.width * .1 } + height: app.iconSize + width: app.iconSize + visible: root.device.deviceClass.interfaces.indexOf("extendednavigationpad") >= 0 + imageSource: "../images/navigation-menu.svg" + Item { id: menuButtonArea; anchors.centerIn: parent; width: pane.width / 4; height: width; rotation: 45 } + } + KeypadButton { + id: homeButton + anchors { left: parent.left; bottom: parent.bottom; margins: parent.width * .1 } + height: app.iconSize + width: app.iconSize + imageSource: "../images/home.svg" + visible: root.device.deviceClass.interfaces.indexOf("extendednavigationpad") >= 0 + Item { id: homeButtonArea; anchors.centerIn: parent; width: pane.width / 4; height: width; rotation: 45 } + } + KeypadButton { + id: infoButton + anchors { right: parent.right; bottom: parent.bottom; margins: parent.width * .1 } + height: app.iconSize + width: app.iconSize + imageSource: "../images/info.svg" + visible: root.device.deviceClass.interfaces.indexOf("extendednavigationpad") >= 0 + Item { id: infoButtonArea; anchors.centerIn: parent; width: pane.width / 4; height: width; rotation: 45 } + } + Rectangle { + id: enterButton + anchors.centerIn: parent + height: app.iconSize + width: app.iconSize + radius: width / 2 + border.color: t.running ? app.accentColor : "#808080" + Behavior on border.color { ColorAnimation { duration: 200 } } + Timer { id: t; interval: 400 } + border.width: 3 + color: "transparent" + function activate() { t.start() } + } + + Item { id: enterButtonArea; anchors.centerIn: parent; width: pane.width / 4; height: width; rotation: 0 } + Item { id: leftButtonArea; anchors { left: parent.left; verticalCenter: parent.verticalCenter; } width: pane.width / 4; height: width; rotation: 0 } + Item { id: rightButtonArea; anchors { right: parent.right; verticalCenter: parent.verticalCenter; } width: pane.width / 4; height: width; rotation: 0 } + Item { id: upButtonArea; anchors { top: parent.top; horizontalCenter: parent.horizontalCenter; } width: pane.width / 4; height: width; rotation: 0 } + Item { id: downButtonArea; anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; } width: pane.width / 4; height: width; rotation: 0 } + + Repeater { + id: directionsRepeater + model: 4 + + Column { + height: parent.height * .8 + width: app.iconSize + anchors.centerIn: parent + rotation: index * 90 + + function activate() { + if (!delayTimer.running) { + delayTimer.idx = 2; + delayTimer.start(); + } else { + delayTimer.onceMore = true; + } + } + Timer { + id: delayTimer; + triggeredOnStart: true + interval: 150 + repeat: true + property int idx: 2 + property bool onceMore: false + onTriggered: { + print("activating", idx) + childRepeater.itemAt(idx--).activate() + if (idx < 0) { + if (onceMore) { + idx = 2; + onceMore = false; + } else { + delayTimer.stop(); + } + } + } + } + + + Repeater { + id: childRepeater + model: 3 + KeypadButton { + height: app.iconSize + width: height + imageSource: "../images/up.svg" + } + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + preventStealing: true + + property int startX + property int startY + + property string lockedDirection: "none" + readonly property int steps: 20 + readonly property int stepSize: height / steps + + property int horizontalSteps: 0 + property int verticalSteps: 0 + + onPressed: { + startX = mouseX + startY = mouseY + } + + onPositionChanged: { + var horizontalDiff = mouseX - startX + var verticalDiff = mouseY - startY + + if (lockedDirection == "none") { + if (Math.abs(horizontalDiff) > stepSize) { + lockedDirection = "horizontal" + trigger(horizontalDiff > 0 ? "right" : "left") + directionsRepeater.itemAt(horizontalDiff > 0 ? 1 : 2).activate() + } else if (Math.abs(verticalDiff) > stepSize) { + lockedDirection = "vertical" + trigger(verticalDiff > 0 ? "down" : "up") + directionsRepeater.itemAt(verticalDiff > 0 ? 2 : 0).activate() + } + } + + horizontalSteps = horizontalDiff / stepSize + verticalSteps = verticalDiff / stepSize + } + + onReleased: { + if (lockedDirection === "none") { + if (checkButton(backButtonArea)) { + trigger("back"); + backButton.activate(); + } else if (checkButton(leftButtonArea)) { + trigger("left"); + directionsRepeater.itemAt(3).activate() + } else if (checkButton(rightButtonArea)) { + trigger("right"); + directionsRepeater.itemAt(1).activate() + } else if (checkButton(upButtonArea)) { + trigger("up"); + directionsRepeater.itemAt(0).activate() + } else if (checkButton(downButtonArea)) { + trigger("down"); + directionsRepeater.itemAt(2).activate() + } else if (checkButton(enterButtonArea)) { + trigger("enter"); + enterButton.activate(); + } else if (checkButton(menuButtonArea)) { + trigger("menu"); + menuButton.activate(); + } else if (checkButton(homeButtonArea)) { + trigger("home"); + homeButton.activate(); + } else if (checkButton(infoButtonArea)) { + trigger("info"); + infoButton.activate(); + } + } + + lockedDirection = "none" + } + + function checkButton(button) { + var coords = mouseArea.mapToItem(button, mouseX, mouseY) + if (coords.x > 0 && coords.x < button.width && coords.y > 0 && coords.y < button.height) { + return true; + } + return false; + } + + function trigger(direction) { + var params = [] + var param = {} + param["paramTypeId"] = root.navigateActionType.paramTypes.findByName("to").id; + param["value"] = direction; + params.push(param); + engine.deviceManager.executeAction(root.device.id, root.navigateActionType.id, params) + PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection) + } + + Timer { + id: repeatTimer + running: mouseArea.lockedDirection !== "none" + interval: 1000 + repeat: true + onRunningChanged: interval = 1000 + onTriggered: { + if (mouseArea.lockedDirection === "horizontal") { + mouseArea.trigger(mouseArea.horizontalSteps > 0 ? "right" : "left") + directionsRepeater.itemAt(mouseArea.horizontalSteps > 0 ? 1 : 3).activate() + } else if (mouseArea.lockedDirection === "vertical") { + mouseArea.trigger(mouseArea.verticalSteps > 0 ? "down" : "up") + directionsRepeater.itemAt(mouseArea.verticalSteps > 0 ? 2 : 0).activate() + } + interval = Math.max(50, 1000 - Math.abs(mouseArea.lockedDirection === "horizontal" ? mouseArea.horizontalSteps : mouseArea.verticalSteps) * 100) + } + } + } + } +} diff --git a/nymea-app/ui/components/NymeaListItemDelegate.qml b/nymea-app/ui/components/NymeaListItemDelegate.qml index ea67ae96..f649d962 100644 --- a/nymea-app/ui/components/NymeaListItemDelegate.qml +++ b/nymea-app/ui/components/NymeaListItemDelegate.qml @@ -15,7 +15,7 @@ SwipeDelegate { property bool prominentSubText: true property string iconName - property string fallbackIcon + property string thumbnail property int iconSize: app.iconSize property color iconColor: app.accentColor property alias iconKeyColor: icon.keyColor @@ -41,21 +41,24 @@ SwipeDelegate { Item { Layout.preferredHeight: root.iconSize Layout.preferredWidth: height - visible: root.iconName || root.fallbackIcon + visible: root.iconName.length > 0 || root.thumbnail.length > 0 ColorIcon { id: icon anchors.fill: parent name: root.iconName color: root.iconColor - visible: root.iconName + visible: root.iconName && thumbnailImage.status !== Image.Ready } - ColorIcon { + Image { + id: thumbnailImage anchors.fill: parent - name: root.fallbackIcon - color: root.iconColor - visible: root.fallbackIcon && (!root.iconName || icon.status === Image.Error) + source: root.thumbnail + visible: root.thumbnail.length > 0 + fillMode: Image.PreserveAspectFit + horizontalAlignment: Image.AlignHCenter + verticalAlignment: Image.AlignVCenter } BusyIndicator { diff --git a/nymea-app/ui/delegates/BrowserItemDelegate.qml b/nymea-app/ui/delegates/BrowserItemDelegate.qml index c245da99..58b22ca3 100644 --- a/nymea-app/ui/delegates/BrowserItemDelegate.qml +++ b/nymea-app/ui/delegates/BrowserItemDelegate.qml @@ -12,8 +12,8 @@ NymeaListItemDelegate { progressive: model.browsable subText: model.description prominentSubText: false - iconName: model.thumbnail - fallbackIcon: "../images/browser/" + model.icon + ".svg" + iconName: "../images/browser/" + model.icon + ".svg" + thumbnail: model.thumbnail enabled: model.browsable || model.executable secondaryIconName: model.actionTypeIds.length > 0 ? "../images/navigation-menu.svg" : "" secondaryIconClickable: true diff --git a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml index 9d0afcd5..e7c5dca0 100644 --- a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml @@ -43,7 +43,7 @@ DeviceListPageBase { readonly property StateType playerTypeStateType: deviceClass.stateTypes.findByName("playerType") readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null - bottomPadding: index === ListView.view.count - 1 ? topPadding : 0 + bottomPadding: index === root.devicesProxy.count - 1 ? topPadding : 0 contentItem: Pane { id: contentItem Material.elevation: 2 @@ -96,7 +96,7 @@ DeviceListPageBase { qsTr("No playback") : itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value horizontalAlignment: Text.AlignHCenter - // font.pixelSize: app.largeFont + // font.pixelSize: app.largeFont elide: Text.ElideRight } Label { @@ -193,10 +193,8 @@ DeviceListPageBase { } } } + } } - } - - } } } diff --git a/nymea-app/ui/devicepages/MediaDevicePage.qml b/nymea-app/ui/devicepages/MediaDevicePage.qml index a7720940..61f473ae 100644 --- a/nymea-app/ui/devicepages/MediaDevicePage.qml +++ b/nymea-app/ui/devicepages/MediaDevicePage.qml @@ -14,15 +14,7 @@ DevicePageBase { showBrowserButton: false onBackPressed: { - if (swipeView.currentIndex > 0) { - if (internalPageStack.depth > 1) { - internalPageStack.pop(); - } else { - swipeView.currentIndex = 0; - } - } else { - pageStack.pop(); - } + swipeView.currentItem.backPressed() } Component.onCompleted: { @@ -83,9 +75,22 @@ DevicePageBase { SwipeView { id: swipeView anchors.fill: parent - interactive: root.deviceClass.browsable + + 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 @@ -139,8 +144,21 @@ DevicePageBase { } } + } + + Component { + id: browserComponent + Item { + function backPressed() { + if (internalPageStack.depth > 1) { + internalPageStack.pop(); + } else { + swipeView.currentIndex-- + } + } + StackView { id: internalPageStack anchors.fill: parent @@ -150,7 +168,6 @@ DevicePageBase { id: internalBrowserPage ListView { id: listView - anchors.fill: parent model: browserItems ScrollBar.vertical: ScrollBar {} @@ -163,7 +180,7 @@ DevicePageBase { } delegate: BrowserItemDelegate { - fallbackIcon: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg" + iconName: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg" busy: d.pendingItemId === model.id device: root.device @@ -188,10 +205,34 @@ DevicePageBase { } } } + } + } + } + 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 + device: root.device + } + } + } } @@ -246,10 +287,10 @@ DevicePageBase { Item { Layout.fillWidth: true Layout.preferredHeight: 2 - visible: root.deviceClass.browsable + visible: swipeView.count > 1 Rectangle { height: parent.height - width: parent.width / 2 + width: parent.width / swipeView.count color: app.accentColor x: swipeView.currentIndex * width Behavior on x { NumberAnimation { duration: 150 } } @@ -259,14 +300,14 @@ DevicePageBase { RowLayout { Item { Layout.fillHeight: true - Layout.preferredWidth: root.deviceClass.browsable && swipeView.currentIndex > 0 ? parent.width / 4 : 0 + 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: root.deviceClass.browsable && swipeView.currentIndex == 1 ? 1 : 0 + opacity: swipeView.count > 1 && swipeView.currentIndex > 0 ? 1 : 0 Behavior on opacity { NumberAnimation {} } - onClicked: swipeView.currentIndex = 0 + onClicked: swipeView.currentIndex-- } } Item { @@ -329,13 +370,13 @@ DevicePageBase { } Item { Layout.fillHeight: true - Layout.preferredWidth: root.deviceClass.browsable && swipeView.currentIndex == 0 ? parent.width / 4 : 0 + 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 = 1 - opacity: root.deviceClass.browsable && swipeView.currentIndex == 0 ? 1 : 0 + onClicked: swipeView.currentIndex++ + opacity: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? 1 : 0 Behavior on opacity { NumberAnimation {} } } } diff --git a/nymea-app/ui/images/home.svg b/nymea-app/ui/images/home.svg new file mode 100644 index 00000000..163d5b0d --- /dev/null +++ b/nymea-app/ui/images/home.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/magic/SelectBrowserItemActionPage.qml b/nymea-app/ui/magic/SelectBrowserItemActionPage.qml index 7f28c83b..b6178da7 100644 --- a/nymea-app/ui/magic/SelectBrowserItemActionPage.qml +++ b/nymea-app/ui/magic/SelectBrowserItemActionPage.qml @@ -26,6 +26,12 @@ Page { anchors.fill: parent ScrollBar.vertical: ScrollBar {} + BusyIndicator { + anchors.centerIn: parent + running: listView.model.busy + visible: running + } + delegate: BrowserItemDelegate { width: parent.width device: root.device