diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index a45d384638..8cdac06333 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2569,7 +2569,7 @@ data class ChatItem ( else -> CIMergeCategory.RcvGroupEvent } is CIContent.SndGroupEventContent -> when (content.sndGroupEvent) { - is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft -> null + is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft, is SndGroupEvent.UserPendingReview -> null else -> CIMergeCategory.SndGroupEvent } else -> { @@ -4349,6 +4349,7 @@ sealed class SndGroupEvent() { @Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): SndGroupEvent() @Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent() @Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): SndGroupEvent() + @Serializable @SerialName("userPendingReview") class UserPendingReview(): SndGroupEvent() val text: String get() = when (this) { is MemberRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_member_role), profile.profileViewName, role.text) @@ -4361,6 +4362,7 @@ sealed class SndGroupEvent() { is MemberDeleted -> String.format(generalGetString(MR.strings.snd_group_event_member_deleted), profile.profileViewName) is UserLeft -> generalGetString(MR.strings.snd_group_event_user_left) is GroupUpdated -> generalGetString(MR.strings.snd_group_event_group_profile_updated) + is UserPendingReview -> generalGetString(MR.strings.snd_group_event_user_pending_review) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 3ea75a74a7..1f4615d5fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -636,6 +636,15 @@ fun ChatItemView( CIEventView(eventItemViewText(reversedChatItems)) } + @Composable fun UserPendingReviewEventItemView() { + Text( + buildAnnotatedString { + withStyle(chatEventStyle.copy(fontWeight = FontWeight.Bold)) { append(cItem.content.text) } + }, + Modifier.padding(horizontal = 6.dp, vertical = 6.dp) + ) + } + @Composable fun DeletedItem() { MarkedDeletedItemView(chatsCtx, cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp) @@ -717,7 +726,10 @@ fun ChatItemView( MsgContentItemDropdownMenu() } is CIContent.SndGroupEventContent -> { - EventItemView() + when (c.sndGroupEvent) { + is SndGroupEvent.UserPendingReview -> UserPendingReviewEventItemView() + else -> EventItemView() + } MsgContentItemDropdownMenu() } is CIContent.RcvConnEventContent -> { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index a3f7ae9dfd..d9f789a202 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1594,6 +1594,7 @@ you removed %1$s you left group profile updated + Please wait for group moderators to review your request to join the group. %s connected %s and %s connected diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 13e58eb36d..66cc5c1767 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -967,10 +967,17 @@ profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ExistingIncognito lp -> fromLocalProfile lp introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM () -introduceToModerators vr user gInfo m = do +introduceToModerators vr user gInfo@GroupInfo {groupId} m = do + when (maxVersion (memberChatVRange m) < groupKnockingVersion) $ sendPendingReviewMessage modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo let rcpModMs = filter memberCurrent modMs introduceMember vr user gInfo m rcpModMs (Just $ MSMember $ memberId' m) + where + sendPendingReviewMessage = case memberConn m of + Just conn -> do + let event = XMsgNew $ MCSimple $ extMsgContent (MCText pendingReviewMessage) Nothing + sendGroupMemberMessages user conn [event] groupId + Nothing -> pure () introduceToAll :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM () introduceToAll vr user gInfo m = do diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 41d7bdad72..35b6a0b822 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -796,9 +796,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (connChatVersion < batchSend2Version) sendGroupAutoReply case mStatus of GSMemPendingApproval -> pure () - -- edge case: reviews were turned off mid connection; - -- options: proceed to review as declared, or introduce to group and send XGrpLinkAcpt; - -- choosing first option for simplicity, same edge case for approval is also not considered GSMemPendingReview -> introduceToModerators vr user gInfo' m' _ -> do introduceToAll vr user gInfo' m' @@ -2470,7 +2467,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure (announcedMember', Just scopeInfo) xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> CM () - xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do + xGrpMemIntro gInfo@GroupInfo {membership, chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do case memberCategory m of GCHostMember -> withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case @@ -2481,6 +2478,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Nothing -> messageError "x.grp.mem.intro: member chat version range incompatible" Just (ChatVersionRange mcvr) | maxVersion mcvr >= groupDirectInvVersion -> do + memCount <- withStore' $ \db -> getGroupMembersCount db user gInfo + -- only create SGEUserPendingReview item on the first introduction - when only 2 members are user and host + when (memberPending membership && memCount == 2) $ do + (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m + createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndGroupEvent SGEUserPendingReview) Nothing subMode <- chatReadVar subscriptionMode -- [async agent commands] commands should be asynchronous, continuation is to send XGrpMemInv - have to remember one has completed and process on second groupConnIds <- createConn subMode diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 886639aa07..9e93db39b5 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -341,6 +341,12 @@ sndGroupEventToText = \case SGEMemberDeleted _ p -> "removed " <> profileToText p SGEUserLeft -> "left" SGEGroupUpdated _ -> "group profile updated" + SGEUserPendingReview -> "please wait for group moderators to review your request to join the group" + +-- used to send to members with old version +pendingReviewMessage :: Text +pendingReviewMessage = + "Please wait for group moderators to review your request to join the group." rcvConnEventToText :: RcvConnEvent -> Text rcvConnEventToText = \case diff --git a/src/Simplex/Chat/Messages/CIContent/Events.hs b/src/Simplex/Chat/Messages/CIContent/Events.hs index ca076e5b8a..36b3a6ebdd 100644 --- a/src/Simplex/Chat/Messages/CIContent/Events.hs +++ b/src/Simplex/Chat/Messages/CIContent/Events.hs @@ -40,6 +40,7 @@ data SndGroupEvent | SGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRUserDeletedMembers | SGEUserLeft -- CRLeftMemberUser | SGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated + | SGEUserPendingReview deriving (Show) data RcvConnEvent diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 24f598a7e6..2e82b1dc27 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -56,6 +56,7 @@ module Simplex.Chat.Store.Groups getGroupMembers, getGroupModerators, getGroupMembersForExpiration, + getGroupMembersCount, getGroupCurrentMembersCount, deleteGroupChatItems, deleteGroupMembers, @@ -944,6 +945,11 @@ toContactMember :: VersionRangeChat -> User -> (GroupMemberRow :. MaybeConnectio toContactMember vr User {userContactId} (memberRow :. connRow) = (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection vr connRow} +getGroupMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int +getGroupMembersCount db User {userId} GroupInfo {groupId} = + fromOnly . head + <$> DB.query db "SELECT COUNT(1) FROM group_members WHERE group_id = ? AND user_id = ?" (groupId, userId) + getGroupCurrentMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int getGroupCurrentMembersCount db User {userId} GroupInfo {groupId} = do statuses :: [GroupMemberStatus] <- diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 3b63f346d5..743bfc53bf 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -5650,6 +5650,10 @@ Query: SELECT COUNT(1) FROM contacts WHERE user_id = ? AND chat_item_ttl > 0 Plan: SEARCH contacts USING INDEX idx_contacts_chat_ts (user_id=?) +Query: SELECT COUNT(1) FROM group_members WHERE group_id = ? AND user_id = ? +Plan: +SEARCH group_members USING COVERING INDEX idx_group_members_group_id (user_id=? AND group_id=?) + Query: SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0 Plan: SEARCH groups USING INDEX idx_groups_chat_ts (user_id=?)