2024-10-02 12:28:20 +00:00
import QtQuick 2.15
import QtQuick . Layouts 1.15
import QtQuick . Controls 2.15
2022-06-28 15:03:10 +00:00
2024-06-13 17:21:04 +00:00
import StatusQ 0.1
2022-06-28 15:03:10 +00:00
import StatusQ . Core 0.1
import StatusQ . Core . Theme 0.1
2024-09-27 17:28:27 +00:00
import StatusQ . Core . Utils 0.1
2022-06-28 15:03:10 +00:00
import StatusQ . Controls 0.1
import StatusQ . Components 0.1
import StatusQ . Popups 0.1
2023-08-07 13:35:14 +00:00
import shared . controls 1.0
2024-10-22 12:39:42 +00:00
import shared . controls . chat 1.0
import shared . stores 1.0 as SharedStores
import shared . views . chat 1.0
import utils 1.0
2022-06-28 15:03:10 +00:00
2024-05-22 08:13:39 +00:00
import AppLayouts . Chat . stores 1.0
2023-06-23 06:17:04 +00:00
import AppLayouts . Communities . layouts 1.0
2022-06-28 15:03:10 +00:00
2024-06-13 17:21:04 +00:00
import SortFilterProxyModel 0.2
2022-06-28 15:03:10 +00:00
Item {
id: root
2024-11-27 15:33:25 +00:00
property string placeholderText: qsTr ( "Search by member name or chat key" )
2022-06-28 15:03:10 +00:00
property var model
2024-05-22 08:13:39 +00:00
property RootStore rootStore
2024-10-22 12:39:42 +00:00
property SharedStores . UtilsStore utilsStore
2023-08-21 19:07:40 +00:00
property int memberRole: Constants . memberRole . none
readonly property bool isOwner: memberRole === Constants . memberRole . owner
readonly property bool isTokenMaster: memberRole === Constants . memberRole . tokenMaster
2022-06-28 15:03:10 +00:00
signal kickUserClicked ( string id , string name )
signal banUserClicked ( string id , string name )
signal unbanUserClicked ( string id )
2024-03-20 10:50:10 +00:00
signal viewMemberMessagesClicked ( string pubKey , string displayName )
2022-06-28 15:03:10 +00:00
2022-08-04 07:49:41 +00:00
signal acceptRequestToJoin ( string id )
signal declineRequestToJoin ( string id )
2022-06-28 15:03:10 +00:00
enum TabType {
AllMembers ,
2022-08-04 07:49:41 +00:00
BannedMembers ,
PendingRequests ,
DeclinedRequests
2022-06-28 15:03:10 +00:00
}
2023-06-26 11:48:45 +00:00
property int panelType: MembersTabPanel . TabType . AllMembers
2022-06-28 15:03:10 +00:00
ColumnLayout {
anchors.fill: parent
2022-08-08 15:56:57 +00:00
spacing: 30
2022-06-28 15:03:10 +00:00
2023-08-17 08:55:28 +00:00
SearchBox {
2022-06-28 15:03:10 +00:00
id: memberSearch
2022-09-22 22:18:15 +00:00
Layout.preferredWidth: 400
2022-08-08 15:56:57 +00:00
Layout.leftMargin: 12
2022-06-28 15:03:10 +00:00
placeholderText: root . placeholderText
2024-11-27 15:33:25 +00:00
enabled: ! ! root . model && ! root . model . ModelCount . empty
2022-06-28 15:03:10 +00:00
}
2023-08-17 08:55:28 +00:00
StatusListView {
2022-06-28 15:03:10 +00:00
id: membersList
2022-10-25 19:08:15 +00:00
objectName: "CommunityMembersTabPanel_MembersListViews"
2022-06-28 15:03:10 +00:00
Layout.fillWidth: true
Layout.fillHeight: true
2024-06-13 17:21:04 +00:00
model: SortFilterProxyModel {
2024-11-27 15:33:25 +00:00
id: filteredModel
2024-06-13 17:21:04 +00:00
sourceModel: root . model
2024-11-27 15:33:25 +00:00
function searchPredicate ( ensName , displayName , aliasName ) {
const lowerCaseSearchString = memberSearch . text . toLowerCase ( )
const secondaryName = ProfileUtils . displayName ( "" , ensName , displayName , aliasName )
return secondaryName . toLowerCase ( ) . includes ( lowerCaseSearchString )
}
2024-06-13 17:21:04 +00:00
sorters : [
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt . CaseInsensitive
}
]
2024-09-27 17:28:27 +00:00
filters: AnyOf {
2024-11-27 15:33:25 +00:00
enabled: memberSearch . text !== ""
// substring search for either nickname or the other primary/secondary display name
2024-09-27 17:28:27 +00:00
SearchFilter {
roleName: "localNickname"
searchPhrase: memberSearch . text
}
2024-11-27 15:33:25 +00:00
FastExpressionFilter {
expression: {
memberSearch . text
return filteredModel . searchPredicate ( model . ensName , model . displayName , model . alias )
}
expectedRoles: [ "ensName" , "displayName" , "alias" ]
2024-09-27 17:28:27 +00:00
}
2024-11-27 15:33:25 +00:00
// exact search for the full key
ValueFilter {
roleName: "compressedPubKey"
value: memberSearch . text
2024-09-27 17:28:27 +00:00
}
}
2024-06-13 17:21:04 +00:00
}
2023-08-17 08:55:28 +00:00
spacing: 0
2022-06-28 15:03:10 +00:00
delegate: StatusMemberListItem {
id: memberItem
2023-08-17 09:01:43 +00:00
// Buttons visibility conditions:
// 1. Tab based buttons - only visible when the tab is selected
// a. All members tab
// - Kick; - Kick pending
// - Ban; - Ban pending
// b. Pending requests tab
// - Accept; - Accept pending
// - Reject; - Reject pending
// c. Rejected members tab
// - Accept; - Accept pending
// d. Banned members tab
// - Unban
// 2. Pending states - buttons in pending states are always visible in their specific tab. Other buttons are disabled if the request is in pending state
// - Accept button is visible when the user is hovered or when the request is in accepted pending state. This condition can be overriden by the ctaAllowed property
// - Reject button is visible when the user is hovered or when the request is in rejected pending state. This condition can be overriden by the ctaAllowed property
// - Kick and ban buttons are visible when the user is hovered or when the request is in kick or ban pending state. This condition can be overriden by the ctaAllowed property
// 3. Other conditions - buttons are visible when the user is hovered and is not himself or other privileged user
2023-12-04 19:14:13 +00:00
// 4. All members tab, member in AwaitingAddress state - buttons is not visible, sandwatch icon is shown
2023-08-17 09:01:43 +00:00
/// Helpers ///
// Tab based buttons
readonly property bool tabIsShowingKickBanButtons: root . panelType === MembersTabPanel . TabType . AllMembers
readonly property bool tabIsShowingUnbanButton: root . panelType === MembersTabPanel . TabType . BannedMembers
readonly property bool tabIsShowingRejectButton: root . panelType === MembersTabPanel . TabType . PendingRequests
readonly property bool tabIsShowingAcceptButton: root . panelType === MembersTabPanel . TabType . PendingRequests ||
root . panelType === MembersTabPanel . TabType . DeclinedRequests
2024-03-20 10:50:10 +00:00
readonly property bool tabIsShowingViewMessagesButton: model . membershipRequestState !== Constants . CommunityMembershipRequestState . BannedWithAllMessagesDelete &&
( root . panelType === MembersTabPanel . TabType . AllMembers ||
root . panelType === MembersTabPanel . TabType . BannedMembers )
2023-08-17 09:01:43 +00:00
// Request states
readonly property bool isPending: model . membershipRequestState === Constants . CommunityMembershipRequestState . Pending
readonly property bool isAccepted: model . membershipRequestState === Constants . CommunityMembershipRequestState . Accepted
readonly property bool isRejected: model . membershipRequestState === Constants . CommunityMembershipRequestState . Rejected
readonly property bool isRejectedPending: model . membershipRequestState === Constants . CommunityMembershipRequestState . RejectedPending
readonly property bool isAcceptedPending: model . membershipRequestState === Constants . CommunityMembershipRequestState . AcceptedPending
readonly property bool isBanPending: model . membershipRequestState === Constants . CommunityMembershipRequestState . BannedPending
2023-10-04 21:41:51 +00:00
readonly property bool isUnbanPending: model . membershipRequestState === Constants . CommunityMembershipRequestState . UnbannedPending
2023-08-17 09:01:43 +00:00
readonly property bool isKickPending: model . membershipRequestState === Constants . CommunityMembershipRequestState . KickedPending
2024-03-19 15:17:02 +00:00
readonly property bool isBanned: model . membershipRequestState === Constants . CommunityMembershipRequestState . Banned ||
model . membershipRequestState === Constants . CommunityMembershipRequestState . BannedWithAllMessagesDelete
2023-08-17 09:01:43 +00:00
readonly property bool isKicked: model . membershipRequestState === Constants . CommunityMembershipRequestState . Kicked
// TODO: Connect to backend when available
// The admin that initited the pending state can change the state. Actions are not visible for other admins
2023-10-04 21:41:51 +00:00
readonly property bool ctaAllowed: ! isRejectedPending && ! isAcceptedPending && ! isBanPending && ! isUnbanPending && ! isKickPending
2023-08-17 09:01:43 +00:00
2024-09-06 13:11:47 +00:00
readonly property bool isHovered: memberItem . hovered
2023-08-21 19:07:40 +00:00
readonly property bool canBeBanned: {
2024-11-07 14:29:28 +00:00
if ( model . isCurrentUser )
2023-08-21 19:07:40 +00:00
return false
2024-11-07 14:29:28 +00:00
2023-08-21 19:07:40 +00:00
switch ( model . memberRole ) {
// Owner can't be banned
case Constants.memberRole.owner: return false
// TokenMaster can only be banned by owner
case Constants.memberRole.tokenMaster: return root . isOwner
// Admin can only be banned by owner and tokenMaster
case Constants.memberRole.admin: return root . isOwner || root . isTokenMaster
// All normal members can be banned by all privileged users
default: return true
}
}
2023-08-17 09:01:43 +00:00
readonly property bool showOnHover: isHovered && ctaAllowed
2024-11-07 14:29:28 +00:00
readonly property bool canDeleteMessages: model . isCurrentUser || model . memberRole !== Constants . memberRole . owner
2023-08-17 09:01:43 +00:00
/// Button visibility ///
2023-08-21 10:57:24 +00:00
readonly property bool acceptButtonVisible: tabIsShowingAcceptButton && ( isPending || isRejected || isRejectedPending || isAcceptedPending ) && showOnHover
readonly property bool rejectButtonVisible: tabIsShowingRejectButton && ( isPending || isRejectedPending || isAcceptedPending ) && showOnHover
2023-08-17 09:01:43 +00:00
readonly property bool acceptPendingButtonVisible: tabIsShowingAcceptButton && isAcceptedPending
readonly property bool rejectPendingButtonVisible: tabIsShowingRejectButton && isRejectedPending
readonly property bool kickButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned
readonly property bool banButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned
readonly property bool kickPendingButtonVisible: tabIsShowingKickBanButtons && isKickPending
readonly property bool banPendingButtonVisible: tabIsShowingKickBanButtons && isBanPending
readonly property bool unbanButtonVisible: tabIsShowingUnbanButton && isBanned && showOnHover
2024-03-20 10:50:10 +00:00
readonly property bool viewMessagesButtonVisible: tabIsShowingViewMessagesButton && showOnHover
readonly property bool messagesDeletedTextVisible: showOnHover &&
model . membershipRequestState === Constants . CommunityMembershipRequestState . BannedWithAllMessagesDelete
2022-06-28 15:03:10 +00:00
2023-08-21 10:57:24 +00:00
/// Pending states ///
2023-10-04 21:41:51 +00:00
readonly property bool isPendingState: isAcceptedPending || isRejectedPending || isBanPending || isUnbanPending || isKickPending
2023-08-21 10:57:24 +00:00
readonly property string pendingStateText: isAcceptedPending ? qsTr ( "Accept pending..." ) :
isRejectedPending ? qsTr ( "Reject pending..." ) :
isBanPending ? qsTr ( "Ban pending..." ) :
2023-10-04 21:41:51 +00:00
isUnbanPending ? qsTr ( "Unban pending..." ) :
2023-08-21 10:57:24 +00:00
isKickPending ? qsTr ( "Kick pending..." ) : ""
2023-12-04 19:14:13 +00:00
isAwaitingAddress: model . membershipRequestState === Constants . CommunityMembershipRequestState . AwaitingAddress
2022-08-04 07:49:41 +00:00
rightPadding: 75
2022-08-08 15:56:57 +00:00
leftPadding: 12
2022-06-28 15:03:10 +00:00
components: [
2023-08-21 10:57:24 +00:00
StatusBaseText {
id: pendingText
width: Math . max ( implicitWidth , d . pendingTextMaxWidth )
onImplicitWidthChanged: {
d . pendingTextMaxWidth = Math . max ( implicitWidth , d . pendingTextMaxWidth )
}
visible: ! ! text && isPendingState
2024-10-15 19:26:12 +00:00
rightPadding: isKickPending || isBanPending || isUnbanPending ? 0 : Theme . bigPadding
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-21 10:57:24 +00:00
text: pendingStateText
color: Theme . palette . baseColor1
StatusToolTip {
text: qsTr ( "Waiting for owner node to come online" )
visible: hoverHandler . hovered
}
HoverHandler {
id: hoverHandler
enabled: pendingText . visible
2023-08-08 08:41:59 +00:00
}
2022-06-28 15:03:10 +00:00
} ,
2024-03-20 10:50:10 +00:00
StatusBaseText {
text: qsTr ( "Messages deleted" )
color: Theme . palette . baseColor1
anchors.verticalCenter: parent . verticalCenter
visible: messagesDeletedTextVisible
} ,
StatusButton {
id: viewMessages
anchors.verticalCenter: parent . verticalCenter
objectName: "MemberListItem_ViewMessages"
text: qsTr ( "View Messages" )
visible: viewMessagesButtonVisible
size: StatusBaseButton . Size . Small
2024-10-02 12:28:20 +00:00
onClicked: root . viewMemberMessagesClicked ( model . pubKey , memberItem . title )
2024-03-20 10:50:10 +00:00
} ,
2023-08-21 10:57:24 +00:00
StatusButton {
id: kickButton
anchors.verticalCenter: parent . verticalCenter
objectName: "MemberListItem_KickButton"
text: qsTr ( "Kick" )
visible: kickButtonVisible
type: StatusBaseButton . Type . Danger
size: StatusBaseButton . Size . Small
onClicked: root . kickUserClicked ( model . pubKey , memberItem . title )
} ,
2022-06-28 15:03:10 +00:00
2023-08-21 10:57:24 +00:00
StatusButton {
2023-08-08 08:41:59 +00:00
id: banButton
2024-07-30 05:44:35 +00:00
objectName: "MemberListItem_BanButton"
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-21 10:57:24 +00:00
visible: banButtonVisible
text: qsTr ( "Ban" )
type: StatusBaseButton . Type . Danger
size: StatusBaseButton . Size . Small
onClicked: root . banUserClicked ( model . pubKey , memberItem . title )
2022-06-28 15:03:10 +00:00
} ,
StatusButton {
2024-07-30 05:44:35 +00:00
objectName: "MemberListItem_UnbanButton"
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-17 09:01:43 +00:00
visible: unbanButtonVisible
2022-06-28 15:03:10 +00:00
text: qsTr ( "Unban" )
2024-03-20 10:50:10 +00:00
type: StatusBaseButton . Type . Danger
size: StatusBaseButton . Size . Small
2022-06-28 15:03:10 +00:00
onClicked: root . unbanUserClicked ( model . pubKey )
2022-08-04 07:49:41 +00:00
} ,
2023-08-21 10:57:24 +00:00
StatusButton {
2023-08-07 13:35:14 +00:00
id: acceptButton
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-21 10:57:24 +00:00
opacity: acceptButtonVisible
text: qsTr ( "Accept" )
2024-02-19 13:32:58 +00:00
type: StatusBaseButton . Type . Success
2023-08-21 10:57:24 +00:00
icon.name: "checkmark-circle"
icon.color: enabled ? Theme.palette.successColor1 : disabledTextColor
loading: model . requestToJoinLoading
enabled: ! acceptPendingButtonVisible
onClicked: root . acceptRequestToJoin ( model . requestToJoinId )
2023-03-21 11:21:23 +00:00
} ,
2023-08-21 10:57:24 +00:00
StatusButton {
2023-08-07 13:35:14 +00:00
id: rejectButton
2023-08-21 10:57:24 +00:00
opacity: rejectButtonVisible
text: qsTr ( "Reject" )
type: StatusBaseButton . Type . Danger
icon.name: "close-circle"
2024-10-15 19:26:12 +00:00
icon.color: enabled ? Theme.palette.dangerColor1 : disabledTextColor
2023-08-21 10:57:24 +00:00
enabled: ! rejectPendingButtonVisible
onClicked: root . declineRequestToJoin ( model . requestToJoinId )
2022-06-28 15:03:10 +00:00
}
]
2024-09-20 17:56:22 +00:00
readonly property string title: model . preferredDisplayName
2022-06-28 15:03:10 +00:00
width: membersList . width
color: "transparent"
2023-04-19 16:48:57 +00:00
pubKey: model . isEnsVerified ? "" : Utils . getElidedCompressedPk ( model . pubKey )
2022-06-28 15:03:10 +00:00
nickName: model . localNickname
2022-12-05 09:56:44 +00:00
userName: ProfileUtils . displayName ( "" , model . ensName , model . displayName , model . alias )
2022-06-28 15:03:10 +00:00
status: model . onlineStatus
2024-09-06 13:11:47 +00:00
icon.color: Utils . colorForColorId ( model . colorId )
icon.name: model . icon
icon.width: 40
icon.height: 40
2023-01-10 11:29:24 +00:00
ringSettings.ringSpecModel: model . colorHash
2024-09-06 13:11:47 +00:00
badge.visible: ( root . panelType === MembersTabPanel . TabType . AllMembers )
2022-06-28 15:03:10 +00:00
2023-04-06 14:23:19 +00:00
onClicked: {
2024-11-07 14:29:28 +00:00
if ( mouse . button === Qt . RightButton ) {
const profileType = Utils . getProfileType ( model . isCurrentUser , false , model . isBlocked )
const contactType = Utils . getContactType ( model . contactRequest , model . isContact )
2024-09-06 15:55:44 +00:00
2024-11-07 14:29:28 +00:00
const params = {
profileType , contactType ,
2024-11-07 10:43:06 +00:00
pubKey: model . pubKey ,
compressedPubKey: model . compressedPubKey ,
emojiHash: root . utilsStore . getEmojiHash ( model . pubKey ) ,
2024-11-07 14:29:28 +00:00
colorHash: model . colorHash ,
colorId: model . colorId ,
2024-09-23 20:22:35 +00:00
displayName: memberItem . title || model . displayName ,
2024-11-07 14:29:28 +00:00
userIcon: model . icon ,
trustStatus: model . trustStatus ,
onlineStatus: model . onlineStatus ,
ensVerified: model . isEnsVerified ,
hasLocalNickname: ! ! model . localNickname
}
Global . openMenu ( memberContextMenuComponent , this , params )
} else if ( mouse . button === Qt . LeftButton ) {
2023-05-19 16:07:50 +00:00
Global . openProfilePopup ( model . pubKey )
2023-04-06 14:23:19 +00:00
}
}
2022-06-28 15:03:10 +00:00
}
}
}
2023-05-19 16:07:50 +00:00
Component {
id: memberContextMenuComponent
ProfileContextMenu {
id: memberContextMenuView
2024-11-27 15:33:25 +00:00
required property string pubKey
2024-11-07 10:43:06 +00:00
2024-11-27 15:33:25 +00:00
onOpenProfileClicked: Global . openProfilePopup ( pubKey , null )
2023-05-19 16:07:50 +00:00
onCreateOneToOneChat: {
Global . changeAppSectionBySectionType ( Constants . appSection . chat )
2024-11-27 15:33:25 +00:00
root . rootStore . chatCommunitySectionModule . createOneToOneChat ( "" , pubKey , "" )
2024-09-06 15:55:44 +00:00
}
2024-11-27 15:33:25 +00:00
onReviewContactRequest: Global . openReviewContactRequestPopup ( pubKey , null )
onSendContactRequest: Global . openContactRequestPopup ( pubKey , null )
onEditNickname: Global . openNicknamePopupRequested ( pubKey , null )
onRemoveNickname: root . rootStore . contactsStore . changeContactNickname ( pubKey , "" , displayName , true )
onUnblockContact: Global . unblockContactRequested ( pubKey )
onMarkAsUntrusted: Global . markAsUntrustedRequested ( pubKey )
onRemoveTrustStatus: root . rootStore . contactsStore . removeTrustStatus ( pubKey )
onRemoveContact: Global . removeContactRequested ( pubKey )
onBlockContact: Global . blockContactRequested ( pubKey )
onMarkAsTrusted: Global . openMarkAsIDVerifiedPopup ( pubKey , null )
onRemoveTrustedMark: Global . openRemoveIDVerificationDialog ( pubKey , null )
2024-10-02 12:54:11 +00:00
onClosed: destroy ( )
2023-05-19 16:07:50 +00:00
}
}
2023-08-21 10:57:24 +00:00
QtObject {
id: d
// This is used to calculate the max width of the pending text
// so that the text aligned on all rows (the text might be different on each row)
property real pendingTextMaxWidth: 0
}
onPanelTypeChanged: { d . pendingTextMaxWidth = 0 }
2022-06-28 15:03:10 +00:00
}