Add profile/verification UI for room members

This commit is contained in:
miruka
2020-07-08 11:33:05 -04:00
parent 4ccb774411
commit 9b43bef935
12 changed files with 452 additions and 31 deletions

View File

@@ -8,9 +8,10 @@ Button {
id: button
enabled: ! button.loading
spacing: theme.spacing
topPadding: padded ? spacing / (circle ? 1.75 : 2) : 0
topPadding:
padded ? spacing * (circle ? (iconItem.small ? 1.5 : 1.8) : 0.5) : 0
bottomPadding: topPadding
leftPadding: padded ? spacing / (circle ? 1.5 : 1) : 0
leftPadding: padded ? spacing : 0
rightPadding: leftPadding
icon.color: theme.icons.colorize
@@ -31,7 +32,7 @@ Button {
background: HButtonBackground {
button: button
buttonTheme: theme.controls.button
radius: circle ? height : enableRadius ? theme.radius : 0
radius: circle ? height / 2 : enableRadius ? theme.radius : 0
color: backgroundColor
}

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Controls 2.12
StackView {
}

View File

@@ -0,0 +1,136 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../../../Base"
import "../../../../Base/ButtonLayout"
HFlickableColumnPage {
id: page
property string userId
property string deviceOwner
property string deviceOwnerDisplayName
property string deviceId
property string deviceName
property string ed25519Key
property HStackView stackView
footer: ButtonLayout {
ApplyButton {
text: qsTr("They're the same")
icon.name: "device-verified"
onClicked: {
loading = true
py.callClientCoro(
userId,
"verify_device_id",
[deviceOwner, deviceId],
() => {
loading = false
page.verified()
}
)
}
}
CancelButton {
text: qsTr("They differ")
icon.name: "device-blacklisted"
onClicked: {
loading = true
py.callClientCoro(
userId,
"blacklist_device_id",
[deviceOwner, deviceId],
() => {
loading = false
page.blacklisted()
}
)
}
}
CancelButton {
id: cancelButton
onClicked: stackView.pop()
Component.onCompleted: forceActiveFocus()
}
}
onKeyboardCancel: stackView.pop()
HRowLayout {
HButton {
id: closeButton
circle: true
icon.name: "close-view"
iconItem.small: true
onClicked: page.stackView.pop()
Layout.rightMargin: theme.spacing
}
HLabel {
text: qsTr("Verification")
font.bold: true
elide: HLabel.ElideRight
horizontalAlignment: Qt.AlignHCenter
Layout.fillWidth: true
}
Item {
Layout.preferredWidth: closeButton.width
}
}
HLabel {
wrapMode: HLabel.Wrap
textFormat: HLabel.StyledText
text: qsTr(
"Does %1 sees the same info in their session's account settings?"
).arg(utils.coloredNameHtml(deviceOwnerDisplayName, deviceOwner))
Layout.fillWidth: true
}
HTextArea {
function formatInfo(info, value) {
return (
`<p style="line-height: 115%">` +
info +
`<br><span style="font-family: ${theme.fontFamily.mono}">` +
value +
`</span></p>`
)
}
readOnly: true
wrapMode: HSelectableLabel.Wrap
textFormat: Qt.RichText
text: (
formatInfo(qsTr("Session name: "), page.deviceName) +
formatInfo(qsTr("Session ID: "), page.deviceId) +
formatInfo(qsTr("Session key: "), "<b>"+page.ed25519Key+"</b>")
)
Layout.fillWidth: true
}
HLabel {
wrapMode: HLabel.Wrap
text:
qsTr(
"If you already know this user, exchange these info by using" +
" a trusted contact method, such as email or a phone call."
)
Layout.fillWidth: true
}
}

View File

@@ -2,9 +2,9 @@
import QtQuick 2.12
import Clipboard 0.1
import "../../../Base"
import "../../../Base/HTile"
import "../../../Popups"
import "../../../../Base"
import "../../../../Base/HTile"
import "../../../../Popups"
HTile {
id: member

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../../../Base"
import "../../../../Base/ButtonLayout"
import "../../../../Base/HTile"
HTile {
id: deviceTile
property string userId
property string deviceOwner
property string deviceOwnerDisplayName
property HStackView stackView
signal trustChanged()
backgroundColor: "transparent"
rightPadding: theme.spacing / 2
compact: false
contentItem: ContentRow {
tile: deviceTile
spacing: 0
HColumnLayout {
HRowLayout {
spacing: theme.spacing
TitleLabel {
text: model.display_name || qsTr("Unnamed")
}
}
SubtitleLabel {
tile: deviceTile
font.family: theme.fontFamily.mono
text: model.id
}
}
HIcon {
svgName: "device-action-menu"
Layout.fillHeight: true
}
}
onClicked: stackView.push(
"DeviceVerification.qml",
{
userId: deviceTile.userId,
deviceOwner: deviceTile.deviceOwner,
deviceOwnerDisplayName: deviceTile.deviceOwnerDisplayName,
deviceId: model.id,
deviceName: model.display_name,
ed25519Key: model.ed25519_key,
stackView: deviceTile.stackView
},
)
}

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../../.."
import "../../../../Base"
HListView {
id: profile
property string userId
property string roomId
property QtObject member // RoomMember model item
property HStackView stackView
function loadDevices() {
py.callClientCoro(userId, "member_devices", [member.id], devices => {
profile.model.clear()
for (const device of devices)
profile.model.append(device)
})
}
clip: true
bottomMargin: theme.spacing
model: ListModel {}
delegate: MemberDeviceDelegate {
width: profile.width
userId: profile.userId
deviceOwner: member.id
deviceOwnerDisplayName: member.display_name
stackView: profile.stackView
}
section.property: "type"
section.delegate: RowLayout {
width: profile.width
spacing: theme.spacing / 2
HIcon {
svgName: "device-" + section
colorize:
section === "verified" ? theme.colors.positiveText :
section === "blacklisted" ? theme.colors.errorText :
theme.colors.warningText
Layout.preferredHeight: dimension
Layout.leftMargin: theme.spacing / 2
}
HLabel {
wrapMode: HLabel.Wrap
verticalAlignment: Qt.AlignVCenter
text:
section === "unset" ? qsTr("Unverified sessions") :
section === "verified" ? qsTr("Verified sessions") :
section === "ignored" ? qsTr("Ignored sessions") :
qsTr("Blacklisted sessions")
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: theme.spacing
Layout.bottomMargin: theme.spacing
Layout.rightMargin: theme.spacing / 2
}
}
header: HColumnLayout {
x: theme.spacing
width: profile.width - x * 2
spacing: theme.spacing * 1.5
HUserAvatar {
userId: member.id
displayName: member.display_name
mxc: member.avatar_url
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.preferredHeight: width
Layout.topMargin: theme.spacing
HButton {
x: -theme.spacing * 0.75
y: x
z: 999
circle: true
icon.name: "close-view"
iconItem.small: true
onClicked: profile.stackView.pop()
}
}
HLabel {
textFormat: HLabel.StyledText
wrapMode: HLabel.Wrap
horizontalAlignment: Qt.AlignHCenter
text:
utils.coloredNameHtml(member.display_name, member.user_id) +
(member.display_name.trim() ?
`<br><font color="${theme.colors.dimText}">${member.id}</font>` :
"")
Layout.fillWidth: true
Layout.bottomMargin: theme.spacing
}
// TODO
// HColumnLayout {
// spacing: theme.spacing / 2
// HLabel {
// text: qsTr("Power level:")
// wrapMode: HLabel.Wrap
// horizontalAlignment: Qt.AlignHCenter
// Layout.fillWidth: true
// }
// HRowLayout {
// spacing: theme.spacing
// HSpacer {}
// Row {
// HButton {
// text: qsTr("Default")
// checked: levelBox.value >= 0 && levelBox.value < 50
// onClicked: levelBox.value = 0
// }
// HButton {
// text: qsTr("Moderator")
// checked: levelBox.value >= 50 && levelBox.value < 100
// onClicked: levelBox.value = 50
// }
// HButton {
// text: qsTr("Admin")
// checked: levelBox.value === 100
// onClicked: levelBox.value = 100
// }
// }
// HSpinBox {
// id: levelBox
// from: -999
// to: 100
// defaultValue: member.power_level
// }
// HSpacer {}
// }
// }
}
Component.onCompleted: loadDevices()
Keys.onEscapePressed: stackView.pop()
}

View File

@@ -2,33 +2,45 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../.."
import "../../../Base"
import "../../../.."
import "../../../../Base"
HColumnLayout {
readonly property alias keybindFocusItem: filterField
readonly property var modelSyncId:
[chat.userId, chat.roomId, "filtered_members"]
HListView {
id: memberList
clip: true
HStackView {
id: stackView
model: ModelStore.get(modelSyncId)
background: Rectangle {
color: theme.chat.roomPane.listView.background
}
delegate: MemberDelegate {
id: member
width: memberList.width
initialItem: HListView {
id: memberList
clip: true
model: ModelStore.get(modelSyncId)
delegate: MemberDelegate {
id: member
width: memberList.width
onLeftClicked: stackView.push(
"MemberProfile.qml",
{
userId: chat.userId,
roomId: chat.roomId,
member: model,
stackView: stackView,
},
)
}
}
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
z: -100
color: theme.chat.roomPane.listView.background
}
}
Rectangle {
@@ -58,8 +70,10 @@ HColumnLayout {
// declared normally
Component.onCompleted: placeholderText = qsTr("Filter members")
onTextChanged:
onTextChanged: {
stackView.pop(stackView.initialItem)
py.callCoro("set_substring_filter", [modelSyncId, text])
}
Keys.onEscapePressed: {
roomPane.toggleFocus()

View File

@@ -3,6 +3,7 @@
import QtQuick 2.12
import "../../../Base"
import "../../.."
import "MemberView"
MultiviewPane {
id: roomPane

View File

@@ -49,8 +49,8 @@ QtObject {
function makePopup(urlComponent, properties={}, callback=null,
autoDestruct=true) {
makeObject(urlComponent, window, properties, (popup) => {
autoDestruct=true, parent=window) {
makeObject(urlComponent, parent, properties, (popup) => {
popup.open()
if (autoDestruct) popup.closed.connect(() => { popup.destroy() })
if (callback) callback(popup)