feat: ban/unban/kick system and ephemeral notifications

This commit is contained in:
Mykhailo Prakhov 2024-03-01 12:24:09 +01:00 committed by Jonathan Rainville
parent b92974ffff
commit 9b8c6aa673
10 changed files with 184 additions and 84 deletions

View File

@ -23,7 +23,10 @@ type
CommunityTokenPermissionDeleted, CommunityTokenPermissionDeleted,
CommunityTokenPermissionCreationFailed, CommunityTokenPermissionCreationFailed,
CommunityTokenPermissionUpdateFailed, CommunityTokenPermissionUpdateFailed,
CommunityTokenPermissionDeletionFailed CommunityTokenPermissionDeletionFailed,
CommunityMemberKicked,
CommunityMemberBanned,
CommunityMemberUnbanned
NotificationDetails* = object NotificationDetails* = object
notificationType*: NotificationType # the default value is `UnknownNotification` notificationType*: NotificationType # the default value is `UnknownNotification`

View File

@ -88,6 +88,10 @@ QtObject:
signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", 2) signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", 2)
signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", 2) signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", 2)
signalConnect(singletonInstance.globalEvents, "showCommunityMemberKickedNotification(QString, QString, QString)", self, "onShowCommunityMemberKickedNotification(QString, QString, QString)", 2)
signalConnect(singletonInstance.globalEvents, "showCommunityMemberBannedNotification(QString, QString, QString)", self, "onShowCommunityMemberBannedNotification(QString, QString, QString)", 2)
signalConnect(singletonInstance.globalEvents, "showCommunityMemberUnbannedNotification(QString, QString, QString)", self, "onShowCommunityMemberUnbannedNotification(QString, QString, QString)", 2)
self.notificationSetUp = true self.notificationSetUp = true
proc init*(self: NotificationsManager) = proc init*(self: NotificationsManager) =
@ -205,6 +209,18 @@ QtObject:
proc onMeMentionedIconBadgeNotification(self: NotificationsManager, allMentions: int) {.slot.} = proc onMeMentionedIconBadgeNotification(self: NotificationsManager, allMentions: int) {.slot.} =
self.osNotification.showIconBadgeNotification(allMentions) self.osNotification.showIconBadgeNotification(allMentions)
proc onShowCommunityMemberKickedNotification*(self: NotificationsManager, title: string, message: string, sectionId: string) {.slot.} =
let details = NotificationDetails(notificationType: NotificationType.CommunityMemberKicked, sectionId: sectionId, isCommunitySection: true)
self.processNotification(title, message, details)
proc onShowCommunityMemberBannedNotification*(self: NotificationsManager, title: string, message: string, sectionId: string) {.slot.} =
let details = NotificationDetails(notificationType: NotificationType.CommunityMemberBanned, sectionId: sectionId, isCommunitySection: true)
self.processNotification(title, message, details)
proc onShowCommunityMemberUnbannedNotification*(self: NotificationsManager, title: string, message: string, sectionId: string) {.slot.} =
let details = NotificationDetails(notificationType: NotificationType.CommunityMemberUnbanned, sectionId: sectionId, isCommunitySection: true)
self.processNotification(title, message, details)
proc notificationCheck(self: NotificationsManager, title: string, message: string, details: NotificationDetails, proc notificationCheck(self: NotificationsManager, title: string, message: string, details: NotificationDetails,
notificationWay: string) = notificationWay: string) =
var data = NotificationArgs(title: title, message: message, details: details) var data = NotificationArgs(title: title, message: message, details: details)

View File

@ -50,3 +50,9 @@ QtObject:
sectionId: string) {.signal.} sectionId: string) {.signal.}
proc meMentionedIconBadgeNotification*(self: GlobalEvents, allMentions: int) {.signal.} proc meMentionedIconBadgeNotification*(self: GlobalEvents, allMentions: int) {.signal.}
proc showCommunityMemberKickedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.}
proc showCommunityMemberBannedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.}
proc showCommunityMemberUnbannedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.}

View File

@ -6,6 +6,14 @@ type
Success Success
Danger Danger
type EphemeralActionType* {.pure} = enum
None = 0
NavigateToCommunityAdmin
OpenFinaliseOwnershipPopup
OpenSendModalPopup
ViewTransactionDetails
OpenFirstCommunityTokenPopup
type type
Item* = object Item* = object
id: int64 id: int64
@ -19,7 +27,7 @@ type
loading: bool loading: bool
ephNotifType: EphemeralNotificationType ephNotifType: EphemeralNotificationType
url: string url: string
actionType: int actionType: EphemeralActionType
actionData: string actionData: string
details: NotificationDetails details: NotificationDetails
@ -33,7 +41,7 @@ proc initItem*(id: int64,
loading = false, loading = false,
ephNotifType = EphemeralNotificationType.Default, ephNotifType = EphemeralNotificationType.Default,
url = "", url = "",
actionType = 0, # It means, no action enabled actionType = EphemeralActionType.None, # It means, no action enabled
actionData = "", actionData = "",
details: NotificationDetails): Item = details: NotificationDetails): Item =
result = Item() result = Item()
@ -85,7 +93,7 @@ proc ephNotifType*(self: Item): EphemeralNotificationType =
proc url*(self: Item): string = proc url*(self: Item): string =
self.url self.url
proc actionType*(self: Item): int = proc actionType*(self: Item): EphemeralActionType =
self.actionType self.actionType
proc actionData*(self: Item): string = proc actionData*(self: Item): string =

View File

@ -84,7 +84,7 @@ QtObject:
of ModelRole.Url: of ModelRole.Url:
result = newQVariant(item.url) result = newQVariant(item.url)
of ModelRole.ActionType: of ModelRole.ActionType:
result = newQVariant(item.actionType) result = newQVariant(item.actionType.int)
of ModelRole.ActionData: of ModelRole.ActionData:
result = newQVariant(item.actionData) result = newQVariant(item.actionData)

View File

@ -822,6 +822,9 @@ method setActiveSection*[T](self: Module[T], item: SectionItem, skipSavingInSett
method setActiveSectionById*[T](self: Module[T], id: string) = method setActiveSectionById*[T](self: Module[T], id: string) =
let item = self.view.model().getItemById(id) let item = self.view.model().getItemById(id)
if item.isEmpty():
discard self.communitiesModule.spectateCommunity(id)
else:
self.setActiveSection(item) self.setActiveSection(item)
proc notifySubModulesAboutChange[T](self: Module[T], sectionId: string) = proc notifySubModulesAboutChange[T](self: Module[T], sectionId: string) =
@ -1231,10 +1234,37 @@ method onAcceptRequestToJoinSuccess*[T](self: Module[T], communityId: string, me
item.updatePendingRequestLoadingState(memberKey, false) item.updatePendingRequestLoadingState(memberKey, false)
method onMembershipStatusUpdated*[T](self: Module[T], communityId: string, memberPubkey: string, status: MembershipRequestState) = method onMembershipStatusUpdated*[T](self: Module[T], communityId: string, memberPubkey: string, status: MembershipRequestState) =
let myPublicKey = singletonInstance.userProfile.getPubKey()
let communityDto = self.controller.getCommunityById(communityId)
if myPublicKey == memberPubkey:
case status:
of MembershipRequestState.Banned:
singletonInstance.globalEvents.showCommunityMemberBannedNotification(fmt "You've been banned from {communityDto.name}", "", communityId)
of MembershipRequestState.Kicked:
singletonInstance.globalEvents.showCommunityMemberKickedNotification(fmt "You were kicked from {communityDto.name}", "", communityId)
of MembershipRequestState.Unbanned:
singletonInstance.globalEvents.showCommunityMemberUnbannedNotification(fmt "You were unbanned from {communityDto.name}", "", communityId)
else:
discard
elif communityDto.isControlNode:
let (contactName, _, _) = self.controller.getContactNameAndImage(memberPubkey)
let item = self.view.model().getItemById(communityId) let item = self.view.model().getItemById(communityId)
if item.id != "": if item.id != "":
item.updateMembershipStatus(memberPubkey, status) item.updateMembershipStatus(memberPubkey, status)
case status:
of MembershipRequestState.Banned:
self.displayEphemeralNotification(fmt "{contactName} was banned from {communityDto.name}", "" , "checkmark-circle", false, EphemeralNotificationType.Success.int, "")
of MembershipRequestState.Kicked:
self.displayEphemeralNotification(fmt "{contactName} was kicked from {communityDto.name}", "" , "checkmark-circle", false, EphemeralNotificationType.Success.int, "")
of MembershipRequestState.Unbanned:
self.displayEphemeralNotification(fmt "{contactName} unbanned from {communityDto.name}", "" , "checkmark-circle", false, EphemeralNotificationType.Success.int, "")
else:
discard
method calculateProfileSectionHasNotification*[T](self: Module[T]): bool = method calculateProfileSectionHasNotification*[T](self: Module[T]): bool =
return not self.controller.isMnemonicBackedUp() return not self.controller.isMnemonicBackedUp()
@ -1280,7 +1310,7 @@ method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle
finalEphNotifType = EphemeralNotificationType.Danger finalEphNotifType = EphemeralNotificationType.Danger
let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, "", icon, "", let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, "", icon, "",
loading, finalEphNotifType, url, 0, "", details) loading, finalEphNotifType, url, EphemeralActionType.None, "", details)
self.view.ephemeralNotificationModel().addItem(item) self.view.ephemeralNotificationModel().addItem(item)
# TO UNIFY with the one above. # TO UNIFY with the one above.
@ -1296,7 +1326,7 @@ method displayEphemeralWithActionNotification*[T](self: Module[T], title: string
finalEphNotifType = EphemeralNotificationType.Danger finalEphNotifType = EphemeralNotificationType.Danger
let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, "", icon, iconColor, let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, "", icon, iconColor,
loading, finalEphNotifType, "", actionType, actionData, details) loading, finalEphNotifType, "", EphemeralActionType(actionType), actionData, details)
self.view.ephemeralNotificationModel().addItem(item) self.view.ephemeralNotificationModel().addItem(item)
# TO UNIFY with the one above. # TO UNIFY with the one above.
@ -1313,11 +1343,11 @@ method displayEphemeralImageWithActionNotification*[T](self: Module[T], title: s
let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, image, "", "", false, let item = ephemeral_notification_item.initItem(id, title, TOAST_MESSAGE_VISIBILITY_DURATION_IN_MS, subTitle, image, "", "", false,
finalEphNotifType, "", actionType, actionData, details) finalEphNotifType, "", EphemeralActionType(actionType), actionData, details)
self.view.ephemeralNotificationModel().addItem(item) self.view.ephemeralNotificationModel().addItem(item)
method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle: string, details: NotificationDetails) = method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle: string, details: NotificationDetails) =
if(details.notificationType == NotificationType.NewMessage or if details.notificationType == NotificationType.NewMessage or
details.notificationType == NotificationType.NewMessageWithPersonalMention or details.notificationType == NotificationType.NewMessageWithPersonalMention or
details.notificationType == NotificationType.CommunityTokenPermissionCreated or details.notificationType == NotificationType.CommunityTokenPermissionCreated or
details.notificationType == NotificationType.CommunityTokenPermissionUpdated or details.notificationType == NotificationType.CommunityTokenPermissionUpdated or
@ -1325,17 +1355,26 @@ method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle
details.notificationType == NotificationType.CommunityTokenPermissionCreationFailed or details.notificationType == NotificationType.CommunityTokenPermissionCreationFailed or
details.notificationType == NotificationType.CommunityTokenPermissionUpdateFailed or details.notificationType == NotificationType.CommunityTokenPermissionUpdateFailed or
details.notificationType == NotificationType.CommunityTokenPermissionDeletionFailed or details.notificationType == NotificationType.CommunityTokenPermissionDeletionFailed or
details.notificationType == NotificationType.NewMessageWithGlobalMention): details.notificationType == NotificationType.NewMessageWithGlobalMention:
self.displayEphemeralNotification(title, subTitle, "", false, EphemeralNotificationType.Default.int, "", details) self.displayEphemeralNotification(title, subTitle, "", false, EphemeralNotificationType.Default.int, "", details)
elif(details.notificationType == NotificationType.NewContactRequest or elif details.notificationType == NotificationType.NewContactRequest or
details.notificationType == NotificationType.IdentityVerificationRequest or details.notificationType == NotificationType.IdentityVerificationRequest or
details.notificationType == NotificationType.ContactRemoved): details.notificationType == NotificationType.ContactRemoved:
self.displayEphemeralNotification(title, subTitle, "contact", false, EphemeralNotificationType.Default.int, "", details) self.displayEphemeralNotification(title, subTitle, "contact", false, EphemeralNotificationType.Default.int, "", details)
elif(details.notificationType == NotificationType.AcceptedContactRequest): elif details.notificationType == NotificationType.AcceptedContactRequest:
self.displayEphemeralNotification(title, subTitle, "checkmark-circle", false, EphemeralNotificationType.Success.int, "", details) self.displayEphemeralNotification(title, subTitle, "checkmark-circle", false, EphemeralNotificationType.Success.int, "", details)
elif details.notificationType == NotificationType.CommunityMemberKicked:
self.displayEphemeralNotification(title, subTitle, "communities", false, EphemeralNotificationType.Danger.int, "", details)
elif details.notificationType == NotificationType.CommunityMemberBanned:
self.displayEphemeralNotification(title, subTitle, "communities", false, EphemeralNotificationType.Danger.int, "", details)
elif details.notificationType == NotificationType.CommunityMemberUnbanned:
self.displayEphemeralWithActionNotification(title, "Visit community" , "communities", "", false, EphemeralNotificationType.Success.int, EphemeralActionType.NavigateToCommunityAdmin.int, details.sectionId)
method removeEphemeralNotification*[T](self: Module[T], id: int64) = method removeEphemeralNotification*[T](self: Module[T], id: int64) =
self.view.ephemeralNotificationModel().removeItemWithId(id) self.view.ephemeralNotificationModel().removeItemWithId(id)

View File

@ -71,6 +71,7 @@ type MembershipRequestState* {.pure} = enum
UnbannedPending = 9, UnbannedPending = 9,
KickedPending = 10, KickedPending = 10,
AwaitingAddress = 11, AwaitingAddress = 11,
Unbanned = 12,
type type
ContractTransactionStatus* {.pure.} = enum ContractTransactionStatus* {.pure.} = enum

View File

@ -41,6 +41,8 @@ type
BanPending, BanPending,
UnbanPending, UnbanPending,
KickPending KickPending
Unbanned,
Kicked
type CommunityMembershipRequestDto* = object type CommunityMembershipRequestDto* = object
id*: string id*: string
@ -479,6 +481,7 @@ proc toMembershipRequestState*(state: CommunityMemberPendingBanOrKick): Membersh
return MembershipRequestState.UnbannedPending return MembershipRequestState.UnbannedPending
of CommunityMemberPendingBanOrKick.KickPending: of CommunityMemberPendingBanOrKick.KickPending:
return MembershipRequestState.KickedPending return MembershipRequestState.KickedPending
else:
return MembershipRequestState.None return MembershipRequestState.None
proc toCommunitySettingsDto*(jsonObj: JsonNode): CommunitySettingsDto = proc toCommunitySettingsDto*(jsonObj: JsonNode): CommunitySettingsDto =

View File

@ -731,11 +731,31 @@ QtObject:
self.events.emit(SIGNAL_COMMUNITY_JOINED, CommunityArgs(community: community, fromUserAction: false)) self.events.emit(SIGNAL_COMMUNITY_JOINED, CommunityArgs(community: community, fromUserAction: false))
self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community])) self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community]))
if wasJoined and not community.joined and not community.isMember: if wasJoined and not community.joined and not community.isMember:
if not community.spectated: # If we were kicked due to ownership change - we will stay in a spectate mode
self.events.emit(SIGNAL_COMMUNITY_LEFT, CommunityIdArgs(communityId: community.id)) if community.spectated:
else:
self.events.emit(SIGNAL_COMMUNITY_KICKED, CommunityArgs(community: community)) self.events.emit(SIGNAL_COMMUNITY_KICKED, CommunityArgs(community: community))
else:
# We were kicked or banned, leave the community
self.events.emit(SIGNAL_COMMUNITY_LEFT, CommunityIdArgs(communityId: community.id))
var status = MembershipRequestState.Kicked
if community.pendingAndBannedMembers.hasKey(myPublicKey) and
community.pendingAndBannedMembers[myPublicKey] == CommunityMemberPendingBanOrKick.Banned:
status = MembershipRequestState.Banned
self.events.emit(SIGNAL_COMMUNITY_MEMBER_STATUS_CHANGED, CommunityMemberStatusUpdatedArgs(
communityId: community.id,
memberPubkey: myPublicKey,
status: status))
# Check if we were unbanned
if not wasJoined and not community.joined and prevCommunity.pendingAndBannedMembers.hasKey(myPublicKey) and not
community.pendingAndBannedMembers.hasKey(myPublicKey):
self.events.emit(SIGNAL_COMMUNITY_MEMBER_STATUS_CHANGED, CommunityMemberStatusUpdatedArgs(
communityId: community.id,
memberPubkey: myPublicKey,
status: MembershipRequestState.Unbanned))
except Exception as e: except Exception as e:
error "Error handling community updates", msg = e.msg error "Error handling community updates", msg = e.msg
@ -2133,10 +2153,13 @@ QtObject:
var status: MembershipRequestState = MembershipRequestState.None var status: MembershipRequestState = MembershipRequestState.None
if community.pendingAndBannedMembers.hasKey(memberPubkey): if community.pendingAndBannedMembers.hasKey(memberPubkey):
status = community.pendingAndBannedMembers[memberPubkey].toMembershipRequestState() status = community.pendingAndBannedMembers[memberPubkey].toMembershipRequestState()
else:
for member in community.members: if status == MembershipRequestState.None:
if member.id == memberPubkey: let prevCommunity = self.communities[community.id]
status = MembershipRequestState.Accepted if prevCommunity.pendingAndBannedMembers.hasKey(memberPubkey):
status = MembershipRequestState.Unbanned
elif len(prevCommunity.members) > len(community.members):
status = MembershipRequestState.Kicked
self.events.emit(SIGNAL_COMMUNITY_MEMBER_STATUS_CHANGED, CommunityMemberStatusUpdatedArgs( self.events.emit(SIGNAL_COMMUNITY_MEMBER_STATUS_CHANGED, CommunityMemberStatusUpdatedArgs(
communityId: community.id, communityId: community.id,

View File

@ -1229,7 +1229,8 @@ QtObject {
BannedPending, BannedPending,
UnbannedPending, UnbannedPending,
KickedPending, KickedPending,
AwaitingAddress AwaitingAddress,
Unbanned
} }
readonly property QtObject walletAccountColors: QtObject { readonly property QtObject walletAccountColors: QtObject {