From 755d954948ff8c0eddff47d9b7692a4f569a8228 Mon Sep 17 00:00:00 2001 From: miruka Date: Sun, 20 Sep 2020 15:41:02 -0400 Subject: [PATCH] Merge account settings Encryption & Sessions page --- src/gui/Base/HRectangleBottomBorder.qml | 8 +- .../Pages/AccountSettings/AccountSettings.qml | 6 +- .../Pages/AccountSettings/DeviceDelegate.qml | 2 + .../Pages/AccountSettings/DeviceSection.qml | 1 - src/gui/Pages/AccountSettings/Encryption.qml | 60 ---- src/gui/Pages/AccountSettings/Security.qml | 327 ++++++++++++++++++ src/gui/Pages/AccountSettings/Sessions.qml | 194 ----------- 7 files changed, 335 insertions(+), 263 deletions(-) delete mode 100644 src/gui/Pages/AccountSettings/Encryption.qml create mode 100644 src/gui/Pages/AccountSettings/Security.qml delete mode 100644 src/gui/Pages/AccountSettings/Sessions.qml diff --git a/src/gui/Base/HRectangleBottomBorder.qml b/src/gui/Base/HRectangleBottomBorder.qml index 9fcb9d06..92008c1d 100644 --- a/src/gui/Base/HRectangleBottomBorder.qml +++ b/src/gui/Base/HRectangleBottomBorder.qml @@ -12,15 +12,15 @@ Item { Item { id: clipArea - anchors.bottom: parent.bottom - width: parent.width + anchors.bottom: parent ? parent.bottom : undefined + width: parent ? parent.width : 0 height: 1 clip: true Rectangle { id: borderRectangle - anchors.bottom: parent.bottom - width: parent.width + anchors.bottom: parent ? parent.bottom : undefined + width: parent ? parent.width : 0 height: root.height radius: rectangle.radius } diff --git a/src/gui/Pages/AccountSettings/AccountSettings.qml b/src/gui/Pages/AccountSettings/AccountSettings.qml index acfd0b31..b1c19c86 100644 --- a/src/gui/Pages/AccountSettings/AccountSettings.qml +++ b/src/gui/Pages/AccountSettings/AccountSettings.qml @@ -19,12 +19,10 @@ HPage { header: HTabBar { HTabButton { text: qsTr("Account") } - HTabButton { text: qsTr("Encryption") } - HTabButton { text: qsTr("Sessions") } + HTabButton { text: qsTr("Security") } } Account { userId: page.userId } - Encryption { userId: page.userId } - Sessions { userId: page.userId } + Security { userId: page.userId } } } diff --git a/src/gui/Pages/AccountSettings/DeviceDelegate.qml b/src/gui/Pages/AccountSettings/DeviceDelegate.qml index af2fada4..1677d4ae 100644 --- a/src/gui/Pages/AccountSettings/DeviceDelegate.qml +++ b/src/gui/Pages/AccountSettings/DeviceDelegate.qml @@ -30,6 +30,7 @@ HTile { HCheckBox { id: checkBox + activeFocusOnTab: false checked: view.checked[model.id] || false onClicked: view.toggleCheck(model.index) } @@ -64,6 +65,7 @@ HTile { icon.name: "device-action-menu" toolTip.text: qsTr("Rename, verify or sign out") backgroundColor: "transparent" + activeFocusOnTab: false onClicked: deviceTile.openMenu() Layout.fillHeight: true diff --git a/src/gui/Pages/AccountSettings/DeviceSection.qml b/src/gui/Pages/AccountSettings/DeviceSection.qml index 3528752b..a2a8d759 100644 --- a/src/gui/Pages/AccountSettings/DeviceSection.qml +++ b/src/gui/Pages/AccountSettings/DeviceSection.qml @@ -19,7 +19,6 @@ HRowLayout { HCheckBox { id: checkBox padding: theme.spacing - topPadding: padding * (section === "current" ? 1 : 2) text: section === "current" ? qsTr("Current session") : diff --git a/src/gui/Pages/AccountSettings/Encryption.qml b/src/gui/Pages/AccountSettings/Encryption.qml deleted file mode 100644 index 80b6dc62..00000000 --- a/src/gui/Pages/AccountSettings/Encryption.qml +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright Mirage authors & contributors -// SPDX-License-Identifier: LGPL-3.0-or-later - -import QtQuick 2.12 -import QtQuick.Layouts 1.12 -import "../../Base" -import "../../Base/Buttons" - -HFlickableColumnPage { - id: page - - property string userId - - function takeFocus() { exportButton.forceActiveFocus() } - - footer: AutoDirectionLayout { - GroupButton { - id: exportButton - text: qsTr("Export") - icon.name: "export-keys" - - onClicked: utils.makeObject( - "Dialogs/ExportKeys.qml", - page, - { userId: page.userId }, - obj => { - loading = Qt.binding(() => obj.exporting) - obj.dialog.open() - } - ) - } - - GroupButton { - text: qsTr("Import") - icon.name: "import-keys" - - onClicked: utils.makeObject( - "Dialogs/ImportKeys.qml", - page, - { userId: page.userId }, - obj => { obj.dialog.open() } - ) - } - } - - HLabel { - wrapMode: HLabel.Wrap - text: qsTr( - "The decryption keys for messages received in encrypted rooms " + - "until present time can be saved " + - "to a passphrase-protected file.

" + - - "You can then import this file on any Matrix account or " + - "client, to be able to decrypt these messages again." - ) - textFormat: Text.StyledText - - Layout.fillWidth: true - } -} diff --git a/src/gui/Pages/AccountSettings/Security.qml b/src/gui/Pages/AccountSettings/Security.qml new file mode 100644 index 00000000..785d8328 --- /dev/null +++ b/src/gui/Pages/AccountSettings/Security.qml @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import "../.." +import "../../Base" +import "../../Base/Buttons" +import "../../PythonBridge" +import "../../ShortcutBundles" + +HColumnPage { + id: page + + property string userId + + property bool enableFlickShortcuts: + SwipeView ? SwipeView.isCurrentItem : true + + property Future loadFuture: null + + function takeFocus() { + deviceList.headerItem.exportButton.forceActiveFocus() + } + + function loadDevices() { + loadFuture = py.callClientCoro(userId, "devices_info", [], devices => { + deviceList.uncheckAll() + deviceList.model.clear() + + for (const device of devices) + deviceList.model.append(device) + + loadFuture = null + deviceList.sectionItemCounts = getSectionItemCounts() + }) + } + + function renameDevice(index, name) { + const device = deviceList.model.get(index) + + device.display_name = name + + py.callClientCoro(userId, "rename_device", [device.id, name], ok => { + if (! ok) deviceList.model.remove(index) // 404 happened + }) + } + + function deleteDevices(...indice) { + if (indice.length === 1 && indice[0] === 0) { + window.makePopup("Popups/SignOutPopup.qml", {userId: page.userId}) + return + } + + const deviceIds = [] + let deleteOwnDevice = false + + for (const i of indice.sort()) { + i === 0 ? + deleteOwnDevice = true : + deviceIds.push(deviceList.model.get(i).id) + } + + window.makePopup( + "Popups/DeleteDevicesPopup.qml", + { + userId: page.userId, + deviceIds, + deletedCallback: () => { + deleteOwnDevice ? + window.makePopup( + "Popups/SignOutPopup.qml", { userId: page.userId }, + ) : + page.loadDevices() + }, + }, + ) + } + + function getSectionItemCounts() { + const counts = {} + + for (let i = 0; i < deviceList.model.count; i++) { + const section = deviceList.model.get(i).type + section in counts ? counts[section] += 1 : counts[section] = 1 + } + + return counts + } + + + enabled: ModelStore.get("accounts").find(userId).presence !== "offline" + contentHeight: Math.min( + window.height, + Math.max( + deviceList.contentHeight + deviceList.bottomMargin, + busyIndicatorLoader.height + theme.spacing * 2, + ) + ) + + Keys.forwardTo: [deviceList] + + HListView { + id: deviceList + + // Don't bind directly to getSectionItemCounts(), laggy with big list + property var sectionItemCounts: ({}) + + bottomMargin: theme.spacing + clip: true + keyNavigationEnabled: false + + model: ListModel {} + + header: HColumnLayout { + readonly property alias exportButton: exportButton + readonly property alias signOutCheckedButton: signOutCheckedButton + + spacing: theme.spacing + x: spacing + width: deviceList.width - x * 2 + + HLabel { + text: qsTr("Decryption keys") + font.pixelSize: theme.fontSize.big + wrapMode: HLabel.Wrap + topPadding: parent.spacing + + Layout.fillWidth: true + } + + HLabel { + text: qsTr( + "The decryption keys for messages received in encrypted " + + "rooms until present time can be exported " + + "to a passphrase-protected file.

" + + + "You can then import this file on any Matrix account or " + + "application, in order to decrypt these messages again." + ) + textFormat: Text.StyledText + wrapMode: HLabel.Wrap + + Layout.fillWidth: true + } + + AutoDirectionLayout { + GroupButton { + id: exportButton + text: qsTr("Export") + icon.name: "export-keys" + + onClicked: utils.makeObject( + "Dialogs/ExportKeys.qml", + page, + { userId: page.userId }, + obj => { + loading = Qt.binding(() => obj.exporting) + obj.dialog.open() + } + ) + + Keys.onBacktabPressed: { + deviceList.currentIndex = deviceList.count - 1 + listController.forceActiveFocus() + } + } + + GroupButton { + text: qsTr("Import") + icon.name: "import-keys" + + onClicked: utils.makeObject( + "Dialogs/ImportKeys.qml", + page, + { userId: page.userId }, + obj => { obj.dialog.open() } + ) + } + } + + HLabel { + text: qsTr("Sessions") + font.pixelSize: theme.fontSize.big + wrapMode: HLabel.Wrap + topPadding: parent.spacing / 2 + + Layout.fillWidth: true + } + + HLabel { + text: qsTr( + "New sessions are created the first time you sign in " + + "from a different device or application." + ) + wrapMode: HLabel.Wrap + + Layout.fillWidth: true + } + + AutoDirectionLayout { + GroupButton { + id: refreshButton + text: qsTr("Refresh") + icon.name: "device-refresh-list" + onClicked: page.loadDevices() + } + + NegativeButton { + id: signOutCheckedButton + enabled: deviceList.model.count > 0 + text: + deviceList.selectedCount === 0 ? + qsTr("Sign out others") : + qsTr("Sign out checked") + + icon.name: "device-delete-checked" + onClicked: + deviceList.selectedCount ? + page.deleteDevices(...deviceList.checkedIndice) : + page.deleteDevices( + ...utils.range(1, deviceList.count - 1), + ) + + Keys.onTabPressed: { + deviceList.currentIndex = 0 + listController.forceActiveFocus() + } + } + } + } + + delegate: DeviceDelegate { + width: deviceList.width + view: deviceList + userId: page.userId + onVerified: page.loadDevices() + onBlacklisted: page.loadDevices() + onRenameRequest: name => page.renameDevice(model.index, name) + onDeleteRequest: page.deleteDevices(model.index) + } + + section.property: "type" + section.delegate: DeviceSection { + width: deviceList.width + view: deviceList + } + + Component.onCompleted: page.loadDevices() + + Layout.fillWidth: true + Layout.fillHeight: true + + Keys.onEscapePressed: uncheckAll() + Keys.onSpacePressed: if (currentItem) toggleCheck(currentIndex) + Keys.onEnterPressed: if (currentItem) currentItem.openMenu(false) + Keys.onReturnPressed: Keys.onEnterPressed(event) + Keys.onMenuPressed: Keys.onEnterPressed(event) + + Keys.onUpPressed: { + deviceList.currentIndex = deviceList.count - 1 + listController.forceActiveFocus() + } + + Keys.onDownPressed: { + deviceList.currentIndex = 0 + listController.forceActiveFocus() + } + + Item { + id: listController + + Keys.onBacktabPressed: { + if (parent.currentIndex === 0) { + parent.currentIndex = -1 + parent.headerItem.signOutCheckedButton.forceActiveFocus() + return + } + parent.decrementCurrentIndex() + forceActiveFocus() + } + + Keys.onTabPressed: { + if (parent.currentIndex === parent.count - 1) { + utils.flickToTop(deviceList) + parent.currentIndex = -1 + parent.headerItem.exportButton.forceActiveFocus() + return + } + parent.incrementCurrentIndex() + forceActiveFocus() + } + + Keys.onUpPressed: ev => Keys.onBacktabPressed(ev) + Keys.onDownPressed: ev => Keys.onTabPressed(ev) + } + + HShortcut { + sequences: window.settings.keys.refreshDevices + onActivated: refreshButton.clicked() + } + + HShortcut { + sequences: window.settings.keys.signOutCheckedOrAllDevices + onActivated: signOutCheckedButton.clicked() + } + + FlickShortcuts { + flickable: deviceList + active: + ! mainUI.debugConsole.visible && page.enableFlickShortcuts + } + + HLoader { + id: busyIndicatorLoader + anchors.centerIn: parent + width: 96 * theme.uiScale + height: width + + source: "../../Base/HBusyIndicator.qml" + active: page.loadFuture + opacity: active ? 1 : 0 + + Behavior on opacity { HNumberAnimation { factor: 2 } } + } + } +} diff --git a/src/gui/Pages/AccountSettings/Sessions.qml b/src/gui/Pages/AccountSettings/Sessions.qml deleted file mode 100644 index 20b7fe46..00000000 --- a/src/gui/Pages/AccountSettings/Sessions.qml +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright Mirage authors & contributors -// SPDX-License-Identifier: LGPL-3.0-or-later - -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import "../.." -import "../../Base" -import "../../Base/Buttons" -import "../../ShortcutBundles" - -HColumnPage { - id: page - - property string userId - - property bool enableFlickShortcuts: - SwipeView ? SwipeView.isCurrentItem : true - - property string loadFutureId: "" - - function takeFocus() {} // TODO - - function loadDevices() { - loadFutureId = py.callClientCoro(userId, "devices_info",[],devices => { - deviceList.uncheckAll() - deviceList.model.clear() - - for (const device of devices) - deviceList.model.append(device) - - loadFutureId = "" - deviceList.sectionItemCounts = getSectionItemCounts() - - if (page.enabled && ! deviceList.currentItem) - deviceList.currentIndex = 0 - }) - } - - function renameDevice(index, name) { - const device = deviceList.model.get(index) - - device.display_name = name - - py.callClientCoro(userId, "rename_device", [device.id, name], ok => { - if (! ok) deviceList.model.remove(index) // 404 happened - }) - } - - function deleteDevices(...indice) { - if (indice.length === 1 && indice[0] === 0) { - window.makePopup("Popups/SignOutPopup.qml", {userId: page.userId}) - return - } - - const deviceIds = [] - let deleteOwnDevice = false - - for (const i of indice.sort()) { - i === 0 ? - deleteOwnDevice = true : - deviceIds.push(deviceList.model.get(i).id) - } - - window.makePopup( - "Popups/DeleteDevicesPopup.qml", - { - userId: page.userId, - deviceIds, - deletedCallback: () => { - deleteOwnDevice ? - window.makePopup( - "Popups/SignOutPopup.qml", { userId: page.userId }, - ) : - page.loadDevices() - }, - }, - ) - } - - function getSectionItemCounts() { - const counts = {} - - for (let i = 0; i < deviceList.model.count; i++) { - const section = deviceList.model.get(i).type - section in counts ? counts[section] += 1 : counts[section] = 1 - } - - return counts - } - - enabled: ModelStore.get("accounts").find(userId).presence !== "offline" - contentHeight: Math.min( - window.height, - Math.max( - deviceList.contentHeight + deviceList.bottomMargin, - busyIndicatorLoader.height + theme.spacing * 2, - ) - ) - - footer: AutoDirectionLayout { - GroupButton { - id: refreshButton - text: qsTr("Refresh") - icon.name: "device-refresh-list" - onClicked: page.loadDevices() - } - - NegativeButton { - id: signOutCheckedButton - enabled: deviceList.model.count > 0 - text: - deviceList.selectedCount === 0 ? - qsTr("Sign out all") : - qsTr("Sign out checked") - - icon.name: "device-delete-checked" - onClicked: - deviceList.selectedCount ? - page.deleteDevices(...deviceList.checkedIndice) : - page.deleteDevices(...utils.range(1, deviceList.count - 1)) - } - } - - Keys.forwardTo: [deviceList] - - HListView { - id: deviceList - - // Don't bind directly to getSectionItemCounts(), laggy with big list - property var sectionItemCounts: ({}) - - bottomMargin: theme.spacing - clip: true - model: ListModel {} - delegate: DeviceDelegate { - width: deviceList.width - view: deviceList - userId: page.userId - onVerified: page.loadDevices() - onBlacklisted: page.loadDevices() - onRenameRequest: name => page.renameDevice(model.index, name) - onDeleteRequest: page.deleteDevices(model.index) - } - - section.property: "type" - section.delegate: DeviceSection { - width: deviceList.width - view: deviceList - } - - Component.onCompleted: page.loadDevices() - - Layout.fillWidth: true - Layout.fillHeight: true - - Keys.onEscapePressed: uncheckAll() - Keys.onTabPressed: incrementCurrentIndex() - Keys.onBacktabPressed: decrementCurrentIndex() - Keys.onSpacePressed: if (currentItem) toggleCheck(currentIndex) - Keys.onEnterPressed: if (currentItem) currentItem.openMenu(false) - Keys.onReturnPressed: Keys.onEnterPressed(event) - Keys.onMenuPressed: Keys.onEnterPressed(event) - - HShortcut { - sequences: window.settings.Keys.Sessions.refresh - onActivated: refreshButton.clicked() - } - - HShortcut { - sequences: window.settings.Keys.Sessions.sign_out_checked_or_all - onActivated: signOutCheckedButton.clicked() - } - - FlickShortcuts { - flickable: deviceList - active: - ! mainUI.debugConsole.visible && page.enableFlickShortcuts - } - - HLoader { - id: busyIndicatorLoader - anchors.centerIn: parent - width: 96 * theme.uiScale - height: width - - source: "../../Base/HBusyIndicator.qml" - active: page.loadFutureId - opacity: active ? 1 : 0 - - Behavior on opacity { HNumberAnimation { factor: 2 } } - } - } -}