Merge account settings Encryption & Sessions page

This commit is contained in:
miruka 2020-09-20 15:41:02 -04:00
parent bb74221e3e
commit 755d954948
7 changed files with 335 additions and 263 deletions

View File

@ -12,15 +12,15 @@ Item {
Item { Item {
id: clipArea id: clipArea
anchors.bottom: parent.bottom anchors.bottom: parent ? parent.bottom : undefined
width: parent.width width: parent ? parent.width : 0
height: 1 height: 1
clip: true clip: true
Rectangle { Rectangle {
id: borderRectangle id: borderRectangle
anchors.bottom: parent.bottom anchors.bottom: parent ? parent.bottom : undefined
width: parent.width width: parent ? parent.width : 0
height: root.height height: root.height
radius: rectangle.radius radius: rectangle.radius
} }

View File

@ -19,12 +19,10 @@ HPage {
header: HTabBar { header: HTabBar {
HTabButton { text: qsTr("Account") } HTabButton { text: qsTr("Account") }
HTabButton { text: qsTr("Encryption") } HTabButton { text: qsTr("Security") }
HTabButton { text: qsTr("Sessions") }
} }
Account { userId: page.userId } Account { userId: page.userId }
Encryption { userId: page.userId } Security { userId: page.userId }
Sessions { userId: page.userId }
} }
} }

View File

@ -30,6 +30,7 @@ HTile {
HCheckBox { HCheckBox {
id: checkBox id: checkBox
activeFocusOnTab: false
checked: view.checked[model.id] || false checked: view.checked[model.id] || false
onClicked: view.toggleCheck(model.index) onClicked: view.toggleCheck(model.index)
} }
@ -64,6 +65,7 @@ HTile {
icon.name: "device-action-menu" icon.name: "device-action-menu"
toolTip.text: qsTr("Rename, verify or sign out") toolTip.text: qsTr("Rename, verify or sign out")
backgroundColor: "transparent" backgroundColor: "transparent"
activeFocusOnTab: false
onClicked: deviceTile.openMenu() onClicked: deviceTile.openMenu()
Layout.fillHeight: true Layout.fillHeight: true

View File

@ -19,7 +19,6 @@ HRowLayout {
HCheckBox { HCheckBox {
id: checkBox id: checkBox
padding: theme.spacing padding: theme.spacing
topPadding: padding * (section === "current" ? 1 : 2)
text: text:
section === "current" ? qsTr("Current session") : section === "current" ? qsTr("Current session") :

View File

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

View 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 } }
}
}
}

View File

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