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()
 | |
|         }
 | |
|     }
 | |
| }
 | 
