305 lines
8.9 KiB
QML
305 lines
8.9 KiB
QML
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
import QtQuick 2.12
|
|
import QtQuick.Layouts 1.12
|
|
import "../../../.."
|
|
import "../../../../Base"
|
|
import "../../../../Base/Buttons"
|
|
import "../../../../PythonBridge"
|
|
|
|
HListView {
|
|
id: root
|
|
|
|
property string userId
|
|
property string roomId
|
|
property int ownPowerLevel
|
|
property int canSetPowerLevels
|
|
property QtObject member // RoomMember model item
|
|
property HStackView stackView
|
|
|
|
property bool powerLevelFieldFocused: false
|
|
|
|
property Future setPowerFuture: null
|
|
property Future getPresenceFuture: null
|
|
|
|
function loadDevices() {
|
|
py.callClientCoro(userId, "member_devices", [member.id], devices => {
|
|
root.model.clear()
|
|
|
|
for (const device of devices)
|
|
root.model.append(device)
|
|
})
|
|
}
|
|
|
|
|
|
clip: true
|
|
bottomMargin: theme.spacing
|
|
model: ListModel {}
|
|
delegate: MemberDeviceDelegate {
|
|
width: root.width
|
|
userId: root.userId
|
|
deviceOwner: member.id
|
|
deviceOwnerDisplayName: member.display_name
|
|
stackView: root.stackView
|
|
|
|
onTrustSet: trust => root.loadDevices()
|
|
}
|
|
|
|
section.property: "type"
|
|
section.delegate: MemberDeviceSection {
|
|
width: root.width
|
|
}
|
|
|
|
header: HColumnLayout {
|
|
x: theme.spacing
|
|
width: root.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.maximumWidth: 256 * theme.uiScale
|
|
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: root.stackView.pop()
|
|
}
|
|
}
|
|
|
|
HColumnLayout { // no spacing between these children
|
|
HLabel {
|
|
wrapMode: HLabel.Wrap
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
color: utils.nameColor(member.display_name || member.id)
|
|
text: member.display_name.trim() || member.id
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
HLabel {
|
|
wrapMode: HLabel.Wrap
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
color: theme.colors.dimText
|
|
text: member.id
|
|
visible: member.display_name.trim() !== ""
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
HColumnLayout {
|
|
HLabel {
|
|
wrapMode: HLabel.Wrap
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
|
|
text:
|
|
member.presence === "online" ? qsTr("Online") :
|
|
member.presence === "unavailable" ? qsTr("Unavailable") :
|
|
member.presence === "invisible" ? qsTr("Invisible") :
|
|
qsTr("Offline / Unknown")
|
|
|
|
color:
|
|
member.presence === "online" ?
|
|
theme.colors.positiveText :
|
|
|
|
member.presence === "unavailable" ?
|
|
theme.colors.warningText :
|
|
|
|
theme.colors.halfDimText
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
HLabel {
|
|
wrapMode: HLabel.Wrap
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
visible: ! member.currently_active && text !== ""
|
|
color: theme.colors.dimText
|
|
|
|
Timer {
|
|
repeat: true
|
|
triggeredOnStart: true
|
|
|
|
running:
|
|
! member.currently_active &&
|
|
member.last_active_at > new Date(1)
|
|
|
|
interval:
|
|
new Date() - member.last_active_at < 60000 ?
|
|
1000 :
|
|
60000
|
|
|
|
onTriggered: parent.text = Qt.binding(() =>
|
|
qsTr("Last seen %1 ago").arg(utils.formatRelativeTime(
|
|
new Date() - member.last_active_at, false,
|
|
))
|
|
)
|
|
}
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
HLabel {
|
|
wrapMode: HLabel.Wrap
|
|
text: member.status_msg.trim()
|
|
visible: text !== ""
|
|
horizontalAlignment: lineCount > 1 ? Qt.AlignLeft : Qt.AlignHCenter
|
|
color: theme.colors.halfDimText
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
HLabeledItem {
|
|
id: powerLevel
|
|
elementsOpacity: item.field.opacity
|
|
enabled:
|
|
root.canSetPowerLevels &&
|
|
(
|
|
root.ownPowerLevel > member.power_level ||
|
|
(root.ownPowerLevel === 100 && member.id === userId)
|
|
)
|
|
|
|
label.text: qsTr("Power level:")
|
|
label.horizontalAlignment: Qt.AlignHCenter
|
|
|
|
errorLabel.horizontalAlignment: Qt.AlignHCenter
|
|
errorLabel.text:
|
|
! item.changed ?
|
|
"" :
|
|
|
|
item.fieldOverMaximum && root.userId === member.id ?
|
|
qsTr("Can't set your own level higher") :
|
|
|
|
item.fieldOverMaximum ?
|
|
qsTr("Can't set level higher than your own") :
|
|
|
|
item.uncappedLevel === root.ownPowerLevel ?
|
|
qsTr("You won't be able to demote this user") :
|
|
|
|
item.uncappedLevel <
|
|
root.ownPowerLevel && root.userId === member.id ?
|
|
qsTr("You won't be able to regain power") :
|
|
|
|
""
|
|
|
|
errorLabel.color:
|
|
item.uncappedLevel === root.ownPowerLevel ||
|
|
(
|
|
item.uncappedLevel <
|
|
root.ownPowerLevel && root.userId === member.id
|
|
) ?
|
|
theme.colors.warningText :
|
|
theme.colors.errorText
|
|
|
|
Layout.preferredWidth: parent.width
|
|
|
|
PowerLevelControl {
|
|
width: parent.width
|
|
defaultLevel: member.power_level
|
|
maximumLevel: root.ownPowerLevel
|
|
rowSpacing: powerLevel.spacing
|
|
|
|
onAccepted: applyButton.clicked()
|
|
onFieldFocusedChanged:
|
|
root.powerLevelFieldFocused = fieldFocused
|
|
Component.onCompleted: forceActiveFocus()
|
|
|
|
}
|
|
}
|
|
|
|
AutoDirectionLayout {
|
|
visible: scale > 0
|
|
id: buttonsLayout
|
|
scale: powerLevel.item.changed ? 1 : 0
|
|
rowSpacing: powerLevel.spacing
|
|
|
|
Layout.preferredWidth: parent.width
|
|
Layout.preferredHeight: implicitHeight * scale
|
|
Layout.topMargin: -theme.spacing
|
|
|
|
Behavior on scale { HNumberAnimation {} }
|
|
|
|
HSpacer {}
|
|
|
|
ApplyButton {
|
|
id: applyButton
|
|
enabled: ! powerLevel.item.fieldOverMaximum
|
|
loading: setPowerFuture !== null
|
|
onClicked: {
|
|
setPowerFuture = py.callClientCoro(
|
|
userId,
|
|
"room_set_member_power",
|
|
[roomId, member.id, powerLevel.item.level],
|
|
() => { setPowerFuture = null }
|
|
)
|
|
}
|
|
|
|
Layout.fillWidth: false
|
|
Layout.alignment: Qt.AlignCenter
|
|
}
|
|
|
|
CancelButton {
|
|
onClicked: {
|
|
setPowerFuture.cancel()
|
|
setPowerFuture = null
|
|
powerLevel.item.reset()
|
|
}
|
|
|
|
Layout.fillWidth: false
|
|
Layout.alignment: Qt.AlignCenter
|
|
}
|
|
|
|
HSpacer {}
|
|
}
|
|
|
|
Item {
|
|
// This item is just to have some spacing at the bottom of header
|
|
visible: root.count > 0
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
loadDevices()
|
|
|
|
if (member.presence === "offline" &&
|
|
member.last_active_at < new Date(1))
|
|
{
|
|
getPresenceFuture =
|
|
py.callClientCoro(userId, "get_offline_presence", [member.id])
|
|
}
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
if (setPowerFuture) setPowerFuture.cancel()
|
|
if (getPresenceFuture) getPresenceFuture.cancel()
|
|
}
|
|
|
|
Keys.onEnterPressed: Keys.onReturnPressed(event)
|
|
Keys.onReturnPressed: if (! root.powerLevelFieldFocused && currentItem) {
|
|
currentItem.leftClicked()
|
|
currentItem.clicked()
|
|
}
|
|
Keys.onEscapePressed: stackView.pop()
|
|
|
|
Connections {
|
|
target: py.eventHandlers
|
|
|
|
function onDeviceUpdateSignal(forAccount) {
|
|
if (forAccount === root.userId) root.loadDevices()
|
|
}
|
|
}
|
|
}
|