Merge account settings Encryption & Sessions page
This commit is contained in:
parent
bb74221e3e
commit
755d954948
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,6 @@ HRowLayout {
|
|||
HCheckBox {
|
||||
id: checkBox
|
||||
padding: theme.spacing
|
||||
topPadding: padding * (section === "current" ? 1 : 2)
|
||||
|
||||
text:
|
||||
section === "current" ? qsTr("Current session") :
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
|
||||
// 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 " +
|
||||
"<b>until present time</b> can be saved " +
|
||||
"to a passphrase-protected file.<br><br>" +
|
||||
|
||||
"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
|
||||
}
|
||||
}
|
327
src/gui/Pages/AccountSettings/Security.qml
Normal file
327
src/gui/Pages/AccountSettings/Security.qml
Normal file
|
@ -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 <b>until present time</b> can be exported " +
|
||||
"to a passphrase-protected file.<br><br>" +
|
||||
|
||||
"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 } }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
|
||||
// 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 } }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user