From 472a96766f73b65eaf408d3df216a471c358bfeb Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 9 Oct 2018 00:58:48 +0200 Subject: [PATCH] add grouping and filtering to device lists --- libnymea-app-core/devicesproxy.cpp | 21 +++ libnymea-app-core/devicesproxy.h | 6 + nymea-app/resources.qrc | 4 + nymea-app/ui/EditDevicesPage.qml | 66 ++++--- nymea-app/ui/components/ListFilterInput.qml | 38 ++++ nymea-app/ui/components/ListSectionHeader.qml | 17 ++ nymea-app/ui/delegates/ThingDelegate.qml | 2 +- nymea-app/ui/images/erase.svg | 170 +++++++++++++++++ nymea-app/ui/images/find.svg | 176 ++++++++++++++++++ nymea-app/ui/magic/SelectThingPage.qml | 18 ++ 10 files changed, 490 insertions(+), 28 deletions(-) create mode 100644 nymea-app/ui/components/ListFilterInput.qml create mode 100644 nymea-app/ui/components/ListSectionHeader.qml create mode 100644 nymea-app/ui/images/erase.svg create mode 100644 nymea-app/ui/images/find.svg diff --git a/libnymea-app-core/devicesproxy.cpp b/libnymea-app-core/devicesproxy.cpp index b9e2404d..0e9c7368 100644 --- a/libnymea-app-core/devicesproxy.cpp +++ b/libnymea-app-core/devicesproxy.cpp @@ -131,6 +131,21 @@ void DevicesProxy::setHiddenInterfaces(const QStringList &hiddenInterfaces) } } +QString DevicesProxy::nameFilter() const +{ + return m_nameFilter; +} + +void DevicesProxy::setNameFilter(const QString &nameFilter) +{ + if (m_nameFilter != nameFilter) { + m_nameFilter = nameFilter; + emit nameFilterChanged(); + invalidateFilter(); + countChanged(); + } +} + bool DevicesProxy::filterBatteryCritical() const { return m_filterBatteryCritical; @@ -249,5 +264,11 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa return false; } } + + if (!m_nameFilter.isEmpty()) { + if (!device->name().toLower().contains(m_nameFilter.toLower().trimmed())) { + return false; + } + } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } diff --git a/libnymea-app-core/devicesproxy.h b/libnymea-app-core/devicesproxy.h index df6b4e36..63dfca95 100644 --- a/libnymea-app-core/devicesproxy.h +++ b/libnymea-app-core/devicesproxy.h @@ -40,6 +40,7 @@ class DevicesProxy : public QSortFilterProxyModel Q_PROPERTY(QString filterTagId READ filterTagId WRITE setFilterTagId NOTIFY filterTagIdChanged) Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged) Q_PROPERTY(QStringList hiddenInterfaces READ hiddenInterfaces WRITE setHiddenInterfaces NOTIFY hiddenInterfacesChanged) + Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter NOTIFY nameFilterChanged) // Setting this to true will imply filtering for "battery" interface Q_PROPERTY(bool filterBatteryCritical READ filterBatteryCritical WRITE setFilterBatteryCritical NOTIFY filterBatteryCriticalChanged) @@ -67,6 +68,9 @@ public: QStringList hiddenInterfaces() const; void setHiddenInterfaces(const QStringList &hiddenInterfaces); + QString nameFilter() const; + void setNameFilter(const QString &nameFilter); + bool filterBatteryCritical() const; void setFilterBatteryCritical(bool filterBatteryCritical); @@ -84,6 +88,7 @@ signals: void filterTagIdChanged(); void shownInterfacesChanged(); void hiddenInterfacesChanged(); + void nameFilterChanged(); void filterBatteryCriticalChanged(); void filterDisconnectedChanged(); void groupByInterfaceChanged(); @@ -97,6 +102,7 @@ private: QString m_filterTagId; QStringList m_shownInterfaces; QStringList m_hiddenInterfaces; + QString m_nameFilter; bool m_filterBatteryCritical = false; bool m_filterDisconnected = false; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 4bd2b005..4447fe24 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -264,5 +264,9 @@ ui/images/fingerprint/fingerprint_boxes.json ui/images/fingerprint/fingerprint_segmented.png ui/images/fingerprint.svg + ui/components/ListSectionHeader.qml + ui/images/find.svg + ui/images/erase.svg + ui/components/ListFilterInput.qml diff --git a/nymea-app/ui/EditDevicesPage.qml b/nymea-app/ui/EditDevicesPage.qml index 248698aa..c93a1a1e 100644 --- a/nymea-app/ui/EditDevicesPage.qml +++ b/nymea-app/ui/EditDevicesPage.qml @@ -11,6 +11,13 @@ Page { text: qsTr("Configure Things") onBackPressed: pageStack.pop() + HeaderButton { + imageSource: "../images/find.svg" + color: filterInput.shown ? app.accentColor : keyColor + onClicked: filterInput.shown = !filterInput.shown + + } + HeaderButton { imageSource: "../images/add.svg" onClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) @@ -45,41 +52,46 @@ Page { } } - ListView { + ColumnLayout { anchors.fill: parent - model: DevicesProxy { - id: deviceProxy - engine: _engine - groupByInterface: true - } - section.property: "baseInterface" - section.criteria: ViewSection.FullString - section.delegate: ColumnLayout { - width: parent.width - Label { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - Layout.topMargin: app.margins - text: app.interfaceToString(section) - horizontalAlignment: Text.AlignRight - } - ThinDivider {} + + ListFilterInput { + id: filterInput + Layout.fillWidth: true } - delegate: ThingDelegate { - device: deviceProxy.get(index) - canDelete: true - onClicked: { - pageStack.push(Qt.resolvedUrl("devicepages/ConfigureThingPage.qml"), {device: deviceProxy.get(index)}) + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + model: DevicesProxy { + id: deviceProxy + engine: _engine + groupByInterface: true + nameFilter: filterInput.shown ? filterInput.text : "" } - onDeleteClicked: { - d.deviceToRemove = deviceProxy.get(index); - engine.deviceManager.removeDevice(d.deviceToRemove.id) + section.property: "baseInterface" + section.criteria: ViewSection.FullString + section.delegate: ListSectionHeader { + text: app.interfaceToString(section) + } + + delegate: ThingDelegate { + device: deviceProxy.get(index) + canDelete: true + onClicked: { + pageStack.push(Qt.resolvedUrl("devicepages/ConfigureThingPage.qml"), {device: deviceProxy.get(index)}) + } + onDeleteClicked: { + d.deviceToRemove = deviceProxy.get(index); + engine.deviceManager.removeDevice(d.deviceToRemove.id) + } } } } + EmptyViewPlaceholder { anchors { left: parent.left; right: parent.right; margins: app.margins } anchors.verticalCenter: parent.verticalCenter diff --git a/nymea-app/ui/components/ListFilterInput.qml b/nymea-app/ui/components/ListFilterInput.qml new file mode 100644 index 00000000..9ab825db --- /dev/null +++ b/nymea-app/ui/components/ListFilterInput.qml @@ -0,0 +1,38 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 +import "../components" +import "../delegates" +import Nymea 1.0 + +Item { + id: root + opacity: shown ? 1 : 0 + implicitWidth: searchColumn.implicitWidth + implicitHeight: shown ? searchColumn.implicitHeight : 0 + Behavior on implicitHeight {NumberAnimation { duration: 130; easing.type: Easing.InOutQuad }} + + property bool shown: false + property alias text: searchTextField.displayText + + ColumnLayout { + id: searchColumn + anchors { left: parent.left; bottom: parent.bottom; right: parent.right } + RowLayout { + Layout.margins: app.margins + spacing: app.margins + TextField { + id: searchTextField + Layout.fillWidth: true + } + + HeaderButton { + imageSource: "../images/erase.svg" + onClicked: searchTextField.text = "" + enabled: searchTextField.displayText.length > 0 + color: enabled ? app.accentColor : keyColor + } + } + ThinDivider {} + } +} diff --git a/nymea-app/ui/components/ListSectionHeader.qml b/nymea-app/ui/components/ListSectionHeader.qml new file mode 100644 index 00000000..b1b8461f --- /dev/null +++ b/nymea-app/ui/components/ListSectionHeader.qml @@ -0,0 +1,17 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 + +ColumnLayout { + width: parent.width + property alias text: label.text + Label { + id: label + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.topMargin: app.margins + horizontalAlignment: Text.AlignRight + } + ThinDivider {} +} diff --git a/nymea-app/ui/delegates/ThingDelegate.qml b/nymea-app/ui/delegates/ThingDelegate.qml index 5537794b..d38d43d1 100644 --- a/nymea-app/ui/delegates/ThingDelegate.qml +++ b/nymea-app/ui/delegates/ThingDelegate.qml @@ -8,7 +8,7 @@ MeaListItemDelegate { id: root width: parent.width iconName: deviceClass ? app.interfacesToIcon(deviceClass.interfaces) : "" - text: device.name + text: device ? device.name : "" progressive: true secondaryIconName: batteryCritical ? "../images/battery/battery-010.svg" : "" tertiaryIconName: disconnected ? "../images/dialog-warning-symbolic.svg" : "" diff --git a/nymea-app/ui/images/erase.svg b/nymea-app/ui/images/erase.svg new file mode 100644 index 00000000..821c2080 --- /dev/null +++ b/nymea-app/ui/images/erase.svg @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/find.svg b/nymea-app/ui/images/find.svg new file mode 100644 index 00000000..25270e75 --- /dev/null +++ b/nymea-app/ui/images/find.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index a0b7a2ff..20a700ab 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -25,6 +25,12 @@ Page { qsTr("Select a kind of things") : root.shownInterfaces.length > 0 ? qsTr("Select a %1").arg(app.interfaceToDisplayName(root.shownInterfaces[0])) : qsTr("Select a thing") onBackPressed: root.backPressed() + + HeaderButton { + imageSource: "../images/find.svg" + color: filterInput.shown ? app.accentColor : keyColor + onClicked: filterInput.shown = !filterInput.shown + } } InterfacesProxy { @@ -35,6 +41,8 @@ Page { DevicesProxy { id: devicesProxy engine: _engine + groupByInterface: true + nameFilter: filterInput.shown ? filterInput.text : "" } ColumnLayout { @@ -50,11 +58,21 @@ Page { } ThinDivider { visible: root.allowSelectAny } + ListFilterInput { + id: filterInput + Layout.fillWidth: true + } + ListView { Layout.fillWidth: true Layout.fillHeight: true model: root.selectInterface ? interfacesProxy : devicesProxy clip: true + section.property: "baseInterface" + section.criteria: ViewSection.FullString + section.delegate: ListSectionHeader { + text: app.interfaceToString(section) + } delegate: MeaListItemDelegate { width: parent.width text: root.selectInterface ? model.displayName : model.name