diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp
index bbb9933b..b9222cb3 100644
--- a/libnymea-app-core/devicemanager.cpp
+++ b/libnymea-app-core/devicemanager.cpp
@@ -155,7 +155,7 @@ void DeviceManager::getVendorsResponse(const QVariantMap ¶ms)
void DeviceManager::getSupportedDevicesResponse(const QVariantMap ¶ms)
{
-// qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented));
+ qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented));
if (params.value("params").toMap().keys().contains("deviceClasses")) {
QVariantList deviceClassList = params.value("params").toMap().value("deviceClasses").toList();
foreach (QVariant deviceClassVariant, deviceClassList) {
@@ -238,6 +238,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms)
value.convert(QVariant::Int);
}
device->setStateValue(stateTypeId, value);
+ qDebug() << "Set device state value:" << device->stateValue(stateTypeId) << value;
}
devices()->addDevice(device);
}
diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp
index ef2b07a7..31a293d8 100644
--- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp
+++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp
@@ -303,7 +303,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request)
{
QVariantMap newRequest = request;
newRequest.insert("token", m_token);
- qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
+// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n");
}
diff --git a/libnymea-app-core/models/logsmodel.cpp b/libnymea-app-core/models/logsmodel.cpp
index 6a2f0601..3e08301c 100644
--- a/libnymea-app-core/models/logsmodel.cpp
+++ b/libnymea-app-core/models/logsmodel.cpp
@@ -179,6 +179,9 @@ void LogsModel::update()
void LogsModel::fetchEarlier(int hours)
{
+ if (!m_engine) {
+ return;
+ }
if (m_busy) {
return;
}
diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index b4d7f4bb..6a377420 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -254,5 +254,8 @@
ui/devicelistpages/DeviceListPageBase.qml
ui/MainPage.qml
ui/RootItem.qml
+ ui/devicepages/FingerprintReaderDevicePage.qml
+ ui/images/account.svg
+ ui/images/contact-new.svg
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index b12048fc..3ae2e51d 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -53,7 +53,7 @@ ApplicationWindow {
rootItem.handleCloseEvent(close)
}
- property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "extendedawning", "extendedshutter", "extendedblind", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
+ property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "extendedawning", "extendedshutter", "extendedblind", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
function interfaceToString(name) {
switch(name) {
case "light":
@@ -91,9 +91,14 @@ ApplicationWindow {
return qsTr("Awnings");
case "garagegate":
return qsTr("Garage gates");
+ case "accesscontrol":
+ return qsTr("Access control");
case "uncategorized":
return qsTr("Uncategorized")
+ default:
+ console.warn("interfaceToString unhandled interface:", name)
}
+ return ""
}
function interfacesToIcon(interfaces) {
@@ -166,6 +171,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/select-none.svg")
case "simpleclosable":
return Qt.resolvedUrl("images/sort-listitem.svg")
+ case "accesscontrol":
+ return Qt.resolvedUrl("images/network-secure.svg");
default:
console.warn("InterfaceToIcon: Unhandled interface", name)
}
@@ -233,6 +240,8 @@ ApplicationWindow {
page = "AwningDevicePage.qml";
} else if (interfaceList.indexOf("notifications") >= 0) {
page = "NotificationsDevicePage.qml";
+ } else if (interfaceList.indexOf("fingerprintreader") >= 0) {
+ page = "FingerprintReaderDevicePage.qml";
} else {
page = "GenericDevicePage.qml";
}
diff --git a/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml b/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml
new file mode 100644
index 00000000..b248a147
--- /dev/null
+++ b/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml
@@ -0,0 +1,295 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../components"
+import "../customviews"
+
+DevicePageBase {
+ id: root
+
+ readonly property var usersStateType: deviceClass.stateTypes.findByName("users")
+ readonly property var usersState: device.states.getState(usersStateType.id)
+
+ readonly property var accessGrantedEventType: deviceClass.eventTypes.findByName("accessGranted")
+ readonly property var accessDeniedEventType: deviceClass.eventTypes.findByName("accessDenied")
+
+ ColumnLayout {
+ anchors.fill: parent
+
+// Item {
+// Layout.fillWidth: true
+// Layout.preferredHeight: root.inputVisible ? inputColumn.implicitHeight : 0
+// Behavior on Layout.preferredHeight { NumberAnimation { duration: 130; easing.type: Easing.InOutQuad } }
+
+// ColumnLayout {
+// id: inputColumn
+// anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
+
+// TextField {
+// id: titleTextField
+// Layout.fillWidth: true
+// Layout.topMargin: app.margins
+// Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
+// placeholderText: qsTr("Title")
+// }
+
+// TextArea {
+// id: bodyTextField
+// Layout.fillWidth: true
+// Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
+// placeholderText: qsTr("Text")
+// wrapMode: Text.WordWrap
+// }
+// }
+// }
+
+
+
+
+ Button {
+ Layout.fillWidth: true
+ Layout.margins: app.margins
+ text: qsTr("Manage access")
+ onClicked: {
+ pageStack.push(manageUsersComponent)
+ }
+ }
+
+// ThinDivider {}
+
+ GenericTypeLogView {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: qsTr("%1 fingerprints recognized on this device in the last %2 days.")
+
+ logsModel: LogsModel {
+ deviceId: root.device.id
+ engine: _engine
+ live: true
+ Component.onCompleted: update()
+ typeIds: [root.accessGrantedEventType.id, root.accessDeniedEventType.id];
+
+ }
+
+ delegate: MeaListItemDelegate {
+ width: parent.width
+ iconName: "../images/notification.svg"
+ text: model.typeId === root.accessGrantedEventType.id ? qsTr("Access granted for user %1").arg(model.value) : qsTr("Access denied")
+ subText: Qt.formatDateTime(model.timestamp)
+ progressive: false
+
+ onClicked: {
+ var parts = model.value.trim().split(', ')
+ var popup = detailsPopup.createObject(root, {timestamp: model.timestamp, notificationTitle: parts[1], notificationBody: parts[0]});
+ popup.open();
+ }
+ }
+ }
+ }
+
+ Component {
+ id: manageUsersComponent
+ Page {
+ header: GuhHeader {
+ text: qsTr("Manage users")
+ onBackPressed: pageStack.pop()
+
+ HeaderButton {
+ imageSource: "../images/contact-new.svg"
+ onClicked: pageStack.push(addUserComponent)
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ Label {
+ Layout.fillWidth: true
+ Layout.margins: app.margins
+ wrapMode: Text.WordWrap
+ text: root.usersState.value.length === 0 ?
+ qsTr("There are no fingerprints registered with this lock")
+ : qsTr("The following users have valid fingerprints for this lock")
+ }
+ ThinDivider {}
+ ListView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ clip: true
+ model: root.usersState.value
+
+ delegate: MeaListItemDelegate {
+ text: modelData
+ width: parent.width
+ progressive: false
+ iconName: "../images/account.svg"
+ canDelete: true
+ onDeleteClicked: {
+ var actionType = root.deviceClass.actionTypes.findByName("removeUser")
+ var params = []
+ var titleParam = {}
+ titleParam["paramTypeId"] = actionType.paramTypes.findByName("userId").id
+ titleParam["value"] = modelData
+ params.push(titleParam)
+ engine.deviceManager.executeAction(root.device.id, actionType.id, params)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: addUserComponent
+ Page {
+ id: addUserPage
+ header: GuhHeader {
+ text: qsTr("Add a new fingerprint")
+ onBackPressed: pageStack.pop()
+ }
+
+ property bool error: false
+
+ Connections {
+ target: engine.deviceManager
+ onExecuteActionReply: {
+ addUserPage.error = params["deviceError"] !== DeviceManager.DeviceErrorNoError
+ print("Execute action reply:", params);
+ addUserSwipeView.currentIndex++
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ SwipeView {
+ id: addUserSwipeView
+ Layout.fillWidth: true
+ Layout.preferredHeight: 200
+ Layout.alignment: Qt.AlignTop
+ Item {
+ width: addUserSwipeView.width
+ height: addUserSwipeView.height
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: app.margins
+ Label {
+ Layout.fillWidth: true
+ text: qsTr("Username")
+ }
+
+ TextField {
+ id: userIdTextField
+ Layout.fillWidth: true
+ }
+ Button {
+ text: qsTr("Add user")
+ Layout.fillWidth: true
+ onClicked: {
+ var actionType = root.deviceClass.actionTypes.findByName("addUser")
+ var params = []
+ var titleParam = {}
+ titleParam["paramTypeId"] = actionType.paramTypes.findByName("userId").id
+ titleParam["value"] = userIdTextField.displayText
+ params.push(titleParam)
+ engine.deviceManager.executeAction(root.device.id, actionType.id, params)
+ addUserSwipeView.currentIndex++
+ }
+ }
+ }
+ }
+
+ Item {
+ width: addUserSwipeView.width
+ height: addUserSwipeView.height
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: app.margins
+ Label {
+ text: qsTr("Please scan the fingerprint now")
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ Item {
+ width: addUserSwipeView.width
+ height: addUserSwipeView.height
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: app.margins
+ spacing: app.margins * 2
+ Label {
+ Layout.fillWidth: true
+ font.pixelSize: app.largeFont
+ color: app.accentColor
+ text: addUserPage.error ? qsTr("Uh oh") :
+ qsTr("All done!")
+ horizontalAlignment: Text.AlignHCenter
+ }
+ Label {
+ text: addUserPage.error ? qsTr("Fingerprint could not be read.\nPlease try again.") :
+ qsTr("Fingerprint added!")
+ Layout.fillWidth: true
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ }
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("OK")
+ onClicked: pageStack.pop()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: detailsPopup
+ MeaDialog {
+ id: detailsDialog
+ property string timestamp
+ property string notificationTitle
+ property string notificationBody
+ title: qsTr("Notification details")
+ Label {
+ Layout.fillWidth: true
+ text: qsTr("Date sent")
+ font.bold: true
+ }
+
+ Label {
+ Layout.fillWidth: true
+ text: Qt.formatDateTime(detailsDialog.timestamp)
+ }
+ Label {
+ Layout.topMargin: app.margins
+ Layout.fillWidth: true
+ text: qsTr("Title")
+ font.bold: true
+ }
+
+ Label {
+ Layout.fillWidth: true
+ text: detailsDialog.notificationTitle
+ wrapMode: Text.WordWrap
+ }
+ Label {
+ Layout.topMargin: app.margins
+ Layout.fillWidth: true
+ text: qsTr("Text")
+ font.bold: true
+ }
+
+ Label {
+ Layout.fillWidth: true
+ text: detailsDialog.notificationBody
+ wrapMode: Text.WordWrap
+ }
+ }
+ }
+}
diff --git a/nymea-app/ui/devicepages/GenericDeviceStateDetailsPage.qml b/nymea-app/ui/devicepages/GenericDeviceStateDetailsPage.qml
index 72d42b9c..9b65946c 100644
--- a/nymea-app/ui/devicepages/GenericDeviceStateDetailsPage.qml
+++ b/nymea-app/ui/devicepages/GenericDeviceStateDetailsPage.qml
@@ -118,6 +118,7 @@ Page {
property var unitString: deviceClass.stateTypes.getStateType(stateTypeId).unitString
text: unitString === "datetime" ? Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate) : value + " " + unitString
horizontalAlignment: Text.AlignHCenter
+ elide: Text.ElideRight
}
}
diff --git a/nymea-app/ui/devicepages/NotificationsDevicePage.qml b/nymea-app/ui/devicepages/NotificationsDevicePage.qml
index 7a65782b..5ac06f94 100644
--- a/nymea-app/ui/devicepages/NotificationsDevicePage.qml
+++ b/nymea-app/ui/devicepages/NotificationsDevicePage.qml
@@ -79,6 +79,7 @@ DevicePageBase {
logsModel: LogsModel {
deviceId: root.device.id
live: true
+ engine: _engine
Component.onCompleted: update()
typeIds: [root.deviceClass.actionTypes.findByName("notify").id];
diff --git a/nymea-app/ui/images/account.svg b/nymea-app/ui/images/account.svg
new file mode 100644
index 00000000..f432ac97
--- /dev/null
+++ b/nymea-app/ui/images/account.svg
@@ -0,0 +1,188 @@
+
+
+
+
diff --git a/nymea-app/ui/images/contact-new.svg b/nymea-app/ui/images/contact-new.svg
new file mode 100644
index 00000000..67dd7616
--- /dev/null
+++ b/nymea-app/ui/images/contact-new.svg
@@ -0,0 +1,188 @@
+
+
+
+