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