Add profile/verification UI for room members
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
7
src/gui/Base/HStackView.qml
Normal file
7
src/gui/Base/HStackView.qml
Normal file
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
StackView {
|
||||
}
|
136
src/gui/Pages/Chat/RoomPane/MemberView/DeviceVerification.qml
Normal file
136
src/gui/Pages/Chat/RoomPane/MemberView/DeviceVerification.qml
Normal 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
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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
|
||||
},
|
||||
)
|
||||
}
|
164
src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml
Normal file
164
src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml
Normal 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()
|
||||
}
|
@@ -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()
|
@@ -3,6 +3,7 @@
|
||||
import QtQuick 2.12
|
||||
import "../../../Base"
|
||||
import "../../.."
|
||||
import "MemberView"
|
||||
|
||||
MultiviewPane {
|
||||
id: roomPane
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user