mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-27 06:25:48 +00:00
core, ui: correct member attention stat for support chat on opening it; mark support chat read (#6240)
This commit is contained in:
@@ -912,6 +912,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
|
||||
case leftMemberUser(user: UserRef, groupInfo: GroupInfo)
|
||||
case groupMembers(user: UserRef, group: SimpleXChat.Group)
|
||||
case memberAccepted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
|
||||
case memberSupportChatRead(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
|
||||
case memberSupportChatDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
|
||||
case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole)
|
||||
case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool)
|
||||
@@ -961,6 +962,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
|
||||
case .leftMemberUser: "leftMemberUser"
|
||||
case .groupMembers: "groupMembers"
|
||||
case .memberAccepted: "memberAccepted"
|
||||
case .memberSupportChatRead: "memberSupportChatRead"
|
||||
case .memberSupportChatDeleted: "memberSupportChatDeleted"
|
||||
case .membersRoleUser: "membersRoleUser"
|
||||
case .membersBlockedForAllUser: "membersBlockedForAllUser"
|
||||
@@ -1006,6 +1008,7 @@ enum ChatResponse2: Decodable, ChatAPIResult {
|
||||
case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .groupMembers(u, group): return withUser(u, String(describing: group))
|
||||
case let .memberAccepted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .memberSupportChatRead(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .memberSupportChatDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)")
|
||||
case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)")
|
||||
|
||||
@@ -1391,8 +1391,14 @@ func apiRejectContactRequest(contactReqId: Int64) async throws -> Contact? {
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?) async throws {
|
||||
try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: scope))
|
||||
func apiChatRead(type: ChatType, id: Int64) async throws {
|
||||
try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: nil))
|
||||
}
|
||||
|
||||
func apiSupportChatRead(type: ChatType, id: Int64, scope: GroupChatScope) async throws -> (GroupInfo, GroupMember) {
|
||||
let r: ChatResponse2 = try await chatSendCmd(.apiChatRead(type: type, id: id, scope: scope))
|
||||
if case let .memberSupportChatRead(_, groupInfo, member) = r { return (groupInfo, member) }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ChatInfo {
|
||||
@@ -1729,7 +1735,7 @@ func markChatRead(_ im: ItemsModel, _ chat: Chat) async {
|
||||
do {
|
||||
if chat.chatStats.unreadCount > 0 {
|
||||
let cInfo = chat.chatInfo
|
||||
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope())
|
||||
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
|
||||
await MainActor.run {
|
||||
withAnimation { ChatModel.shared.markAllChatItemsRead(im, cInfo) }
|
||||
}
|
||||
@@ -1754,6 +1760,20 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async {
|
||||
}
|
||||
}
|
||||
|
||||
func markSupportChatRead(_ groupInfo: GroupInfo, _ member: GroupMember) async {
|
||||
do {
|
||||
if member.supportChatNotRead {
|
||||
let (updatedGroupInfo, updatedMember) = try await apiSupportChatRead(type: .group, id: groupInfo.apiId, scope: .memberSupport(groupMemberId_: member.groupMemberId))
|
||||
await MainActor.run {
|
||||
_ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember)
|
||||
ChatModel.shared.updateGroup(updatedGroupInfo)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("markSupportChatRead apiChatRead error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
|
||||
func apiMarkChatItemsRead(_ im: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
|
||||
do {
|
||||
let updatedChatInfo = try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds)
|
||||
|
||||
@@ -92,6 +92,16 @@ struct MemberSupportView: View {
|
||||
.frame(width: 1, height: 1)
|
||||
.hidden()
|
||||
}
|
||||
.if(!memberWithChat.wrapped.memberPending && memberWithChat.wrapped.supportChatNotRead) { v in
|
||||
v.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
Button {
|
||||
Task { await markSupportChatRead(groupInfo, memberWithChat.wrapped) }
|
||||
} label: {
|
||||
Label("Read", systemImage: "checkmark")
|
||||
}
|
||||
.tint(theme.colors.primary)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if memberWithChat.wrapped.memberPending {
|
||||
Button {
|
||||
|
||||
@@ -2661,6 +2661,15 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
|
||||
memberRole >= .moderator && versionRange.maxVersion >= REPORTS_VERSION
|
||||
}
|
||||
|
||||
public var supportChatNotRead: Bool {
|
||||
if let supportChat = supportChat,
|
||||
supportChat.memberAttention > 0 || supportChat.mentions > 0 || supportChat.unread > 0 {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
public var versionRange: VersionRange {
|
||||
if let activeConn {
|
||||
activeConn.peerChatVRange
|
||||
|
||||
@@ -2355,6 +2355,12 @@ data class GroupMember (
|
||||
&& !memberPending
|
||||
}
|
||||
|
||||
val supportChatNotRead: Boolean get() =
|
||||
if (supportChat != null)
|
||||
supportChat.memberAttention > 0 || supportChat.mentions > 0 || supportChat.unread > 0
|
||||
else
|
||||
false
|
||||
|
||||
val versionRange: VersionRange = activeConn?.peerChatVRange ?: memberChatVRange
|
||||
|
||||
val memberIncognito = memberProfile.profileId != memberContactProfileId
|
||||
|
||||
+12
-2
@@ -1849,13 +1849,20 @@ object ChatController {
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?): Boolean {
|
||||
val r = sendCmd(rh, CC.ApiChatRead(type, id, scope))
|
||||
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean {
|
||||
val r = sendCmd(rh, CC.ApiChatRead(type, id, scope = null))
|
||||
if (r.result is CR.CmdOk) return true
|
||||
Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}")
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun apiSupportChatRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope): Pair<GroupInfo, GroupMember>? {
|
||||
val r = sendCmd(rh, CC.ApiChatRead(type, id, scope))
|
||||
if (r is API.Result && r.res is CR.MemberSupportChatRead) return r.res.groupInfo to r.res.member
|
||||
apiErrorAlert("apiSupportChatRead", generalGetString(MR.strings.error_marking_member_support_chat_read), r)
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List<Long>): ChatInfo? {
|
||||
val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, scope, itemIds))
|
||||
if (r is API.Result && r.res is CR.ItemsReadForChat) return r.res.chatInfo
|
||||
@@ -6211,6 +6218,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("groupDeletedUser") class GroupDeletedUser(val user: UserRef, val groupInfo: GroupInfo): CR()
|
||||
@Serializable @SerialName("joinedGroupMemberConnecting") class JoinedGroupMemberConnecting(val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("memberAccepted") class MemberAccepted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("memberSupportChatRead") class MemberSupportChatRead(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("memberSupportChatDeleted") class MemberSupportChatDeleted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("memberAcceptedByOther") class MemberAcceptedByOther(val user: UserRef, val groupInfo: GroupInfo, val acceptingMember: GroupMember, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("memberRole") class MemberRole(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
|
||||
@@ -6395,6 +6403,7 @@ sealed class CR {
|
||||
is GroupDeletedUser -> "groupDeletedUser"
|
||||
is JoinedGroupMemberConnecting -> "joinedGroupMemberConnecting"
|
||||
is MemberAccepted -> "memberAccepted"
|
||||
is MemberSupportChatRead -> "memberSupportChatRead"
|
||||
is MemberSupportChatDeleted -> "memberSupportChatDeleted"
|
||||
is MemberAcceptedByOther -> "memberAcceptedByOther"
|
||||
is MemberRole -> "memberRole"
|
||||
@@ -6572,6 +6581,7 @@ sealed class CR {
|
||||
is GroupDeletedUser -> withUser(user, json.encodeToString(groupInfo))
|
||||
is JoinedGroupMemberConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nmember: $member")
|
||||
is MemberAccepted -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
|
||||
is MemberSupportChatRead -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
|
||||
is MemberSupportChatDeleted -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
|
||||
is MemberAcceptedByOther -> withUser(user, "groupInfo: $groupInfo\nacceptingMember: $acceptingMember\nmember: $member")
|
||||
is MemberRole -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole")
|
||||
|
||||
+1
-2
@@ -707,8 +707,7 @@ fun ChatView(
|
||||
chatModel.controller.apiChatRead(
|
||||
chatRh,
|
||||
chatInfo.chatType,
|
||||
chatInfo.apiId,
|
||||
chatInfo.groupChatScope()
|
||||
chatInfo.apiId
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
+25
@@ -270,6 +270,12 @@ private fun DropDownMenuForSupportChat(rhId: Long?, member: GroupMember, groupIn
|
||||
showMenu.value = false
|
||||
})
|
||||
} else {
|
||||
if (member.supportChatNotRead) {
|
||||
ItemAction(stringResource(MR.strings.mark_read), painterResource(MR.images.ic_check), color = MaterialTheme.colors.primary, onClick = {
|
||||
markSupportChatRead(rhId, groupInfo, member)
|
||||
showMenu.value = false
|
||||
})
|
||||
}
|
||||
ItemAction(stringResource(MR.strings.delete_member_support_chat_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = {
|
||||
deleteMemberSupportChatDialog(rhId, groupInfo, member)
|
||||
showMenu.value = false
|
||||
@@ -300,3 +306,22 @@ private fun deleteMemberSupportChat(rhId: Long?, groupInfo: GroupInfo, member: G
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun markSupportChatRead(rhId: Long?, groupInfo: GroupInfo, member: GroupMember) {
|
||||
withBGApi {
|
||||
if (member.supportChatNotRead) {
|
||||
val r = chatModel.controller.apiSupportChatRead(
|
||||
rh = rhId,
|
||||
type = ChatType.Group,
|
||||
id = groupInfo.apiId,
|
||||
scope = GroupChatScope.MemberSupport(member.groupMemberId)
|
||||
)
|
||||
if (r != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second)
|
||||
chatModel.chatsContext.updateGroup(rhId, r.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -648,8 +648,7 @@ fun markChatRead(c: Chat) {
|
||||
chatModel.controller.apiChatRead(
|
||||
chat.remoteHostId,
|
||||
chat.chatInfo.chatType,
|
||||
chat.chatInfo.apiId,
|
||||
chat.chatInfo.groupChatScope()
|
||||
chat.chatInfo.apiId
|
||||
)
|
||||
chat = chatModel.getChat(chat.id) ?: return@withApi
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
<string name="error_adding_members">Error adding member(s)</string>
|
||||
<string name="error_joining_group">Error joining group</string>
|
||||
<string name="error_accepting_member">Error accepting member</string>
|
||||
<string name="error_marking_member_support_chat_read">Error marking chat with member as read</string>
|
||||
<string name="error_deleting_member_support_chat">Error deleting chat with member</string>
|
||||
<string name="cannot_receive_file">Cannot receive file</string>
|
||||
<string name="sender_cancelled_file_transfer">Sender cancelled file transfer.</string>
|
||||
|
||||
@@ -162,6 +162,7 @@ undocumentedResponses =
|
||||
"CRGroupUserChanged",
|
||||
"CRItemsReadForChat",
|
||||
"CRJoinedGroupMember",
|
||||
"CRMemberSupportChatRead",
|
||||
"CRMemberSupportChatDeleted",
|
||||
"CRMemberSupportChats",
|
||||
"CRNetworkConfig",
|
||||
|
||||
@@ -726,6 +726,7 @@ data ChatResponse
|
||||
| CRNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]}
|
||||
| CRJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRMemberAccepted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRMemberSupportChatRead {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRMemberSupportChatDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| CRMembersRoleUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], toRole :: GroupMemberRole}
|
||||
| CRMembersBlockedForAllUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], blocked :: Bool}
|
||||
|
||||
@@ -517,13 +517,36 @@ processChatCommand vr nm = \case
|
||||
pure $ CRApiChat user (AChat SCTDirect directChat) navInfo
|
||||
CTGroup -> do
|
||||
(groupChat, navInfo) <- withFastStore (\db -> getGroupChat db vr user cId scope_ contentFilter pagination search)
|
||||
pure $ CRApiChat user (AChat SCTGroup groupChat) navInfo
|
||||
groupChat' <- checkSupportChatAttention user groupChat
|
||||
pure $ CRApiChat user (AChat SCTGroup groupChat') navInfo
|
||||
CTLocal -> do
|
||||
when (isJust contentFilter) $ throwCmdError "content filter not supported"
|
||||
(localChat, navInfo) <- withFastStore (\db -> getLocalChat db user cId pagination search)
|
||||
pure $ CRApiChat user (AChat SCTLocal localChat) navInfo
|
||||
CTContactRequest -> throwCmdError "not implemented"
|
||||
CTContactConnection -> throwCmdError "not supported"
|
||||
where
|
||||
checkSupportChatAttention :: User -> Chat 'CTGroup -> CM (Chat 'CTGroup)
|
||||
checkSupportChatAttention user groupChat@Chat {chatInfo, chatItems} =
|
||||
case chatInfo of
|
||||
GroupChat gInfo (Just GCSIMemberSupport {groupMember_ = Just scopeMem@GroupMember {supportChat = Just suppChat}}) -> do
|
||||
case correctedMemAttention (groupMemberId' scopeMem) suppChat chatItems of
|
||||
Just newMemAttention -> do
|
||||
(gInfo', scopeMem') <-
|
||||
withFastStore' $ \db -> setSupportChatMemberAttention db vr user gInfo scopeMem newMemAttention
|
||||
pure $ groupChat {chatInfo = GroupChat gInfo' (Just $ GCSIMemberSupport (Just scopeMem'))}
|
||||
Nothing -> pure groupChat
|
||||
_ -> pure groupChat
|
||||
where
|
||||
correctedMemAttention :: GroupMemberId -> GroupSupportChat -> [CChatItem 'CTGroup] -> Maybe Int64
|
||||
correctedMemAttention scopeGMId GroupSupportChat {memberAttention} items =
|
||||
let numNewFromMember = fromIntegral . length . takeWhile newFromMember $ reverse items
|
||||
in if numNewFromMember == memberAttention then Nothing else Just numNewFromMember
|
||||
where
|
||||
newFromMember :: CChatItem 'CTGroup -> Bool
|
||||
newFromMember (CChatItem _ ChatItem {chatDir = CIGroupRcv m, meta = CIMeta {itemStatus = CISRcvNew}}) =
|
||||
groupMemberId' m == scopeGMId
|
||||
newFromMember _ = False
|
||||
APIGetChatItems pagination search -> withUser $ \user -> do
|
||||
chatItems <- withFastStore $ \db -> getAllChatItems db vr user pagination search
|
||||
pure $ CRChatItems user Nothing chatItems
|
||||
@@ -998,7 +1021,7 @@ processChatCommand vr nm = \case
|
||||
pure $ prefix <> formattedDate <> ext
|
||||
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
|
||||
UserRead -> withUser $ \User {userId} -> processChatCommand vr nm $ APIUserRead userId
|
||||
APIChatRead chatRef@(ChatRef cType chatId scope) -> withUser $ \_ -> case cType of
|
||||
APIChatRead chatRef@(ChatRef cType chatId scope_) -> withUser $ \_ -> case cType of
|
||||
CTDirect -> do
|
||||
user <- withFastStore $ \db -> getUserByContactId db chatId
|
||||
ts <- liftIO getCurrentTime
|
||||
@@ -1014,12 +1037,23 @@ processChatCommand vr nm = \case
|
||||
gInfo <- getGroupInfo db vr user chatId
|
||||
pure (user, gInfo)
|
||||
ts <- liftIO getCurrentTime
|
||||
timedItems <- withFastStore' $ \db -> do
|
||||
timedItems <- getGroupUnreadTimedItems db user chatId
|
||||
updateGroupChatItemsRead db user gInfo scope
|
||||
setGroupChatItemsDeleteAt db user chatId timedItems ts
|
||||
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
|
||||
ok user
|
||||
case scope_ of
|
||||
Nothing -> do
|
||||
timedItems <- withFastStore' $ \db -> do
|
||||
timedItems <- getGroupUnreadTimedItems db user chatId Nothing
|
||||
updateGroupChatItemsRead db user gInfo
|
||||
setGroupChatItemsDeleteAt db user chatId timedItems ts
|
||||
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
|
||||
ok user
|
||||
Just scope -> do
|
||||
scopeInfo <- getChatScopeInfo vr user scope
|
||||
(gInfo', m', timedItems) <- withFastStore' $ \db -> do
|
||||
timedItems <- getGroupUnreadTimedItems db user chatId (Just scope)
|
||||
(gInfo', m') <- updateSupportChatItemsRead db vr user gInfo scopeInfo
|
||||
timedItems' <- setGroupChatItemsDeleteAt db user chatId timedItems ts
|
||||
pure (gInfo', m', timedItems')
|
||||
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
|
||||
pure $ CRMemberSupportChatRead user gInfo' m'
|
||||
CTLocal -> do
|
||||
user <- withFastStore $ \db -> getUserByNoteFolderId db chatId
|
||||
withFastStore' $ \db -> updateLocalChatItemsRead db user chatId
|
||||
|
||||
@@ -1495,13 +1495,21 @@ decreaseGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> IO
|
||||
decreaseGroupMembersRequireAttention db User {userId} g@GroupInfo {groupId, membersRequireAttention} = do
|
||||
DB.execute
|
||||
db
|
||||
#if defined(dbPostgres)
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET members_require_attention = members_require_attention - 1
|
||||
SET members_require_attention = GREATEST(0, members_require_attention - 1)
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|]
|
||||
#else
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET members_require_attention = MAX(0, members_require_attention - 1)
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|]
|
||||
#endif
|
||||
(userId, groupId)
|
||||
pure g {membersRequireAttention = membersRequireAttention - 1}
|
||||
pure g {membersRequireAttention = max 0 (membersRequireAttention - 1)}
|
||||
|
||||
increaseGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> IO GroupInfo
|
||||
increaseGroupMembersRequireAttention db User {userId} g@GroupInfo {groupId, membersRequireAttention} = do
|
||||
|
||||
@@ -38,6 +38,7 @@ module Simplex.Chat.Store.Messages
|
||||
MemberAttention (..),
|
||||
updateChatTsStats,
|
||||
setSupportChatTs,
|
||||
setSupportChatMemberAttention,
|
||||
createNewSndChatItem,
|
||||
createNewRcvChatItem,
|
||||
createNewChatItemNoMsg,
|
||||
@@ -79,6 +80,7 @@ module Simplex.Chat.Store.Messages
|
||||
setDirectChatItemRead,
|
||||
setDirectChatItemsDeleteAt,
|
||||
updateGroupChatItemsRead,
|
||||
updateSupportChatItemsRead,
|
||||
getGroupUnreadTimedItems,
|
||||
updateGroupChatItemsReadList,
|
||||
updateGroupScopeUnreadStats,
|
||||
@@ -423,14 +425,23 @@ updateChatTsStats db vr user@User {userId} chatDirection chatTs chatStats_ = cas
|
||||
| not nowRequires && didRequire -> do
|
||||
DB.execute
|
||||
db
|
||||
#if defined(dbPostgres)
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET chat_ts = ?,
|
||||
members_require_attention = members_require_attention - 1
|
||||
members_require_attention = GREATEST(0, members_require_attention - 1)
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|]
|
||||
#else
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET chat_ts = ?,
|
||||
members_require_attention = MAX(0, members_require_attention - 1)
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|]
|
||||
#endif
|
||||
(chatTs, userId, groupId)
|
||||
pure $ GroupChat g {membersRequireAttention = membersRequireAttention - 1, chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
|
||||
pure $ GroupChat g {membersRequireAttention = max 0 (membersRequireAttention - 1), chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
|
||||
| otherwise -> do
|
||||
DB.execute
|
||||
db
|
||||
@@ -496,6 +507,21 @@ setSupportChatTs :: DB.Connection -> GroupMemberId -> UTCTime -> IO ()
|
||||
setSupportChatTs db groupMemberId chatTs =
|
||||
DB.execute db "UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?" (chatTs, groupMemberId)
|
||||
|
||||
setSupportChatMemberAttention :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> Int64 -> IO (GroupInfo, GroupMember)
|
||||
setSupportChatMemberAttention db vr user g m memberAttention = do
|
||||
m' <- updateGMAttention m
|
||||
g' <- updateGroupMembersRequireAttention db user g m m'
|
||||
pure (g', m')
|
||||
where
|
||||
updateGMAttention m@GroupMember {groupMemberId} = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
"UPDATE group_members SET support_chat_items_member_attention = ?, updated_at = ? WHERE group_member_id = ?"
|
||||
(memberAttention, currentTs, groupMemberId' m)
|
||||
m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
|
||||
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
|
||||
|
||||
createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> UTCTime -> IO ChatItemId
|
||||
createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciContent quotedItem itemForwarded timed live createdAt =
|
||||
createNewChatItem_ db user chatDirection False createdByMsgId (Just sharedMsgId) ciContent quoteRow itemForwarded timed live False createdAt Nothing createdAt
|
||||
@@ -2010,20 +2036,46 @@ setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM i
|
||||
(deleteAt, userId, contactId, chatItemId)
|
||||
pure (chatItemId, deleteAt)
|
||||
|
||||
updateGroupChatItemsRead :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScope -> IO ()
|
||||
updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope = do
|
||||
updateGroupChatItemsRead :: DB.Connection -> User -> GroupInfo -> IO ()
|
||||
updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ? AND item_status = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, userId, groupId, CISRcvNew)
|
||||
case scope of
|
||||
Nothing -> pure ()
|
||||
Just GCSMemberSupport {groupMemberId_} -> do
|
||||
let gmId = fromMaybe (groupMemberId' membership) groupMemberId_
|
||||
|
||||
updateSupportChatItemsRead :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScopeInfo -> IO (GroupInfo, GroupMember)
|
||||
updateSupportChatItemsRead db vr user@User {userId} g@GroupInfo {groupId, membership} scopeInfo = do
|
||||
currentTs <- getCurrentTime
|
||||
case scopeInfo of
|
||||
GCSIMemberSupport {groupMember_} -> do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ?
|
||||
AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, userId, groupId, GCSTMemberSupport_, groupMemberId' <$> groupMember_, CISRcvNew)
|
||||
case groupMember_ of
|
||||
Nothing -> do
|
||||
membership' <- updateGMStats membership
|
||||
pure (g {membership = membership'}, membership')
|
||||
Just member -> do
|
||||
member' <- updateGMStats member
|
||||
let didRequire = gmRequiresAttention member
|
||||
nowRequires = gmRequiresAttention member'
|
||||
if (not nowRequires && didRequire)
|
||||
then (,member') <$> decreaseGroupMembersRequireAttention db user g
|
||||
else pure (g, member')
|
||||
where
|
||||
updateGMStats m@GroupMember {groupMemberId} = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -2033,18 +2085,34 @@ updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope
|
||||
support_chat_items_mentions = 0
|
||||
WHERE group_member_id = ?
|
||||
|]
|
||||
(Only gmId)
|
||||
(Only groupMemberId)
|
||||
m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
|
||||
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
|
||||
|
||||
getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> IO [(ChatItemId, Int)]
|
||||
getGroupUnreadTimedItems db User {userId} groupId =
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, groupId, CISRcvNew)
|
||||
getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> Maybe GroupChatScope -> IO [(ChatItemId, Int)]
|
||||
getGroupUnreadTimedItems db User {userId} groupId scope =
|
||||
case scope of
|
||||
Nothing ->
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, groupId, CISRcvNew)
|
||||
Just GCSMemberSupport {groupMemberId_} ->
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ?
|
||||
AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, groupId, GCSTMemberSupport_, groupMemberId_, CISRcvNew)
|
||||
|
||||
updateGroupChatItemsReadList :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo)
|
||||
updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId} scopeInfo_ itemIds = do
|
||||
@@ -2110,14 +2178,25 @@ updateGroupScopeUnreadStats db vr user g@GroupInfo {membership} scopeInfo (unrea
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
#if defined(dbPostgres)
|
||||
[sql|
|
||||
UPDATE group_members
|
||||
SET support_chat_items_unread = support_chat_items_unread - ?,
|
||||
support_chat_items_member_attention = support_chat_items_member_attention - ?,
|
||||
support_chat_items_mentions = support_chat_items_mentions - ?,
|
||||
SET support_chat_items_unread = GREATEST(0, support_chat_items_unread - ?),
|
||||
support_chat_items_member_attention = GREATEST(0, support_chat_items_member_attention - ?),
|
||||
support_chat_items_mentions = GREATEST(0, support_chat_items_mentions - ?),
|
||||
updated_at = ?
|
||||
WHERE group_member_id = ?
|
||||
|]
|
||||
#else
|
||||
[sql|
|
||||
UPDATE group_members
|
||||
SET support_chat_items_unread = MAX(0, support_chat_items_unread - ?),
|
||||
support_chat_items_member_attention = MAX(0, support_chat_items_member_attention - ?),
|
||||
support_chat_items_mentions = MAX(0, support_chat_items_mentions - ?),
|
||||
updated_at = ?
|
||||
WHERE group_member_id = ?
|
||||
|]
|
||||
#endif
|
||||
(unread, unanswered, mentions, currentTs, groupMemberId)
|
||||
m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
|
||||
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
|
||||
|
||||
@@ -25,7 +25,7 @@ SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET chat_ts = ?,
|
||||
members_require_attention = members_require_attention + 1
|
||||
members_require_attention = MAX(0, members_require_attention - 1)
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|
||||
Plan:
|
||||
@@ -34,7 +34,7 @@ SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET chat_ts = ?,
|
||||
members_require_attention = members_require_attention - 1
|
||||
members_require_attention = members_require_attention + 1
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|
||||
Plan:
|
||||
@@ -1189,6 +1189,25 @@ Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_ts (user_id=?)
|
||||
USE TEMP B-TREE FOR ORDER BY
|
||||
|
||||
Query:
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ?
|
||||
AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_scope_stats_all (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
SELECT chat_item_moderation_id, moderator_member_id, created_by_msg_id, moderated_at
|
||||
FROM chat_item_moderations
|
||||
@@ -1431,6 +1450,15 @@ SEARCH r USING INDEX idx_received_probes_user_id (user_id=?)
|
||||
SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
|
||||
SEARCH g USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
|
||||
|
||||
Query:
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ?
|
||||
AND item_status = ?
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_scope_stats_all (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
UPDATE connections SET via_contact_uri = NULL, via_contact_uri_hash = NULL, xcontact_id = NULL
|
||||
WHERE user_id = ? AND via_group_link = 1 AND contact_id IN (
|
||||
@@ -1482,9 +1510,9 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE group_members
|
||||
SET support_chat_items_unread = support_chat_items_unread - ?,
|
||||
support_chat_items_member_attention = support_chat_items_member_attention - ?,
|
||||
support_chat_items_mentions = support_chat_items_mentions - ?,
|
||||
SET support_chat_items_unread = MAX(0, support_chat_items_unread - ?),
|
||||
support_chat_items_member_attention = MAX(0, support_chat_items_member_attention - ?),
|
||||
support_chat_items_mentions = MAX(0, support_chat_items_mentions - ?),
|
||||
updated_at = ?
|
||||
WHERE group_member_id = ?
|
||||
|
||||
@@ -4267,14 +4295,6 @@ Query:
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_contacts (user_id=? AND contact_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
SELECT group_snd_item_status, COUNT(1)
|
||||
FROM group_snd_item_statuses
|
||||
@@ -4361,7 +4381,8 @@ SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE chat_items SET item_status = ?, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ? AND item_status = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND item_status = ?
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?)
|
||||
@@ -4623,7 +4644,7 @@ SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET members_require_attention = members_require_attention + 1
|
||||
SET members_require_attention = MAX(0, members_require_attention - 1)
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|
||||
Plan:
|
||||
@@ -4631,7 +4652,7 @@ SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET members_require_attention = members_require_attention - 1
|
||||
SET members_require_attention = members_require_attention + 1
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
|
||||
Plan:
|
||||
@@ -6277,6 +6298,14 @@ Query: UPDATE group_members SET member_role = ? WHERE user_id = ? AND group_memb
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE group_members SET support_chat_items_member_attention = ?, updated_at = ? WHERE group_member_id = ?
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE group_members SET support_chat_items_member_attention=100 WHERE group_member_id=?
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
@@ -6317,6 +6346,10 @@ Query: UPDATE groups SET local_display_name = ?, updated_at = ? WHERE user_id =
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE groups SET members_require_attention=1 WHERE group_id=?
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE groups SET request_shared_msg_id = ? WHERE group_id = ?
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -231,6 +231,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else []
|
||||
CRJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m
|
||||
CRMemberAccepted u g m -> ttyUser u $ viewMemberAccepted g m
|
||||
CRMemberSupportChatRead u g m -> ttyUser u $ viewSupportChatRead g m
|
||||
CRMemberSupportChatDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " support chat deleted"]
|
||||
CRMembersRoleUser u g members r' -> ttyUser u $ viewMemberRoleUserChanged g members r'
|
||||
CRMembersBlockedForAllUser u g members blocked -> ttyUser u $ viewMembersBlockedForAllUser g members blocked
|
||||
@@ -1229,6 +1230,11 @@ viewMemberAccepted g m@GroupMember {memberStatus} = case memberStatus of
|
||||
GSMemPendingReview -> [ttyGroup' g <> ": " <> ttyMember m <> " accepted and pending review (will introduce moderators)"]
|
||||
_ -> [ttyGroup' g <> ": " <> ttyMember m <> " accepted"]
|
||||
|
||||
viewSupportChatRead :: GroupInfo -> GroupMember -> [StyledString]
|
||||
viewSupportChatRead g@GroupInfo {membership = GroupMember {groupMemberId = membershipId}} m
|
||||
| groupMemberId' m == membershipId = [ttyGroup' g <> ": support chat read"]
|
||||
| otherwise = [ttyGroup' g <> ": " <> ttyMember m <> " support chat read"]
|
||||
|
||||
viewMemberAcceptedByOther :: GroupInfo -> GroupMember -> GroupMember -> [StyledString]
|
||||
viewMemberAcceptedByOther g acceptingMember m@GroupMember {memberCategory, memberStatus} = case memberCategory of
|
||||
GCUserMember -> case memberStatus of
|
||||
@@ -1324,8 +1330,12 @@ viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filt
|
||||
| otherwise = []
|
||||
|
||||
viewMemberSupportChats :: GroupInfo -> [GroupMember] -> [StyledString]
|
||||
viewMemberSupportChats GroupInfo {membership} ms = support <> map groupMember ms
|
||||
viewMemberSupportChats GroupInfo {membership = membership@GroupMember {memberRole = membershipRole}, membersRequireAttention} ms =
|
||||
memsAttention <> support <> map groupMember ms
|
||||
where
|
||||
memsAttention
|
||||
| membershipRole >= GRModerator = ["members require attention: " <> sShow membersRequireAttention]
|
||||
| otherwise = []
|
||||
support = case supportChat membership of
|
||||
Just sc -> ["support: " <> chatStats sc]
|
||||
Nothing -> []
|
||||
|
||||
+106
-3
@@ -18,6 +18,7 @@ import Control.Concurrent.Async (concurrently_)
|
||||
import Control.Monad (forM_, void, when)
|
||||
import Data.Bifunctor (second)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Int (Int64)
|
||||
import Data.List (intercalate, isInfixOf)
|
||||
import qualified Data.Map.Strict as M
|
||||
import qualified Data.Text as T
|
||||
@@ -219,6 +220,7 @@ chatGroupTests = do
|
||||
it "should send messages to admins and members" testSupportCLISendCommand
|
||||
it "should correctly maintain unread stats for support chats on reading chat items" testScopedSupportUnreadStatsOnRead
|
||||
it "should correctly maintain unread stats for support chats on deleting chat items" testScopedSupportUnreadStatsOnDelete
|
||||
it "should correct member attention stat for support chat on opening it" testScopedSupportUnreadStatsCorrectOnOpen
|
||||
|
||||
testGroupCheckMessages :: HasCallStack => TestParams -> IO ()
|
||||
testGroupCheckMessages =
|
||||
@@ -7432,8 +7434,10 @@ testScopedSupportManyModerators =
|
||||
cath #$> ("/_get chat #1(_support:3) count=100", chat, [])
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 0"
|
||||
dan <## "bob (Bob) (id 3): unread: 0, require attention: 0, mentions: 0"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 0, require attention: 0, mentions: 0"
|
||||
@@ -7888,8 +7892,10 @@ testScopedSupportUnreadStatsOnRead =
|
||||
dan <# "#team (support: bob) alice> 3"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 0"
|
||||
dan <## "bob (Bob) (id 3): unread: 1, require attention: 0, mentions: 0"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 1, require attention: 0, mentions: 0"
|
||||
@@ -7899,8 +7905,10 @@ testScopedSupportUnreadStatsOnRead =
|
||||
[alice, dan] *<# "#team (support: bob) bob> 4"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 1, require attention: 1, mentions: 0"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 1"
|
||||
dan <## "bob (Bob) (id 3): unread: 2, require attention: 1, mentions: 0"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 1, require attention: 0, mentions: 0"
|
||||
@@ -7913,9 +7921,11 @@ testScopedSupportUnreadStatsOnRead =
|
||||
bob <# "#team (support) dan> 5"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 2, require attention: 0, mentions: 0"
|
||||
-- In test "answering" doesn't reset unanswered, but in UI items would be marked read on opening chat
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 1"
|
||||
dan <## "bob (Bob) (id 3): unread: 2, require attention: 1, mentions: 0"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 2, require attention: 0, mentions: 0"
|
||||
@@ -7928,8 +7938,10 @@ testScopedSupportUnreadStatsOnRead =
|
||||
bob <# "#team (support) dan> @alice 6"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 3, require attention: 0, mentions: 1"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 1"
|
||||
dan <## "bob (Bob) (id 3): unread: 2, require attention: 1, mentions: 0"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 3, require attention: 0, mentions: 0"
|
||||
@@ -7944,8 +7956,10 @@ testScopedSupportUnreadStatsOnRead =
|
||||
dan <# "#team (support: bob) bob> @alice 7"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 4, require attention: 1, mentions: 2"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 1"
|
||||
dan <## "bob (Bob) (id 3): unread: 3, require attention: 2, mentions: 0"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 3, require attention: 0, mentions: 0"
|
||||
@@ -7958,8 +7972,10 @@ testScopedSupportUnreadStatsOnRead =
|
||||
dan <# "#team (support: bob) bob!> @dan 8"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 5, require attention: 2, mentions: 2"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 1"
|
||||
dan <## "bob (Bob) (id 3): unread: 4, require attention: 3, mentions: 1"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 3, require attention: 0, mentions: 0"
|
||||
@@ -7967,11 +7983,13 @@ testScopedSupportUnreadStatsOnRead =
|
||||
alice #$> ("/_read chat items #1(_support:2) " <> aliceMentionedByDanItemId, id, "items read for chat")
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 4, require attention: 2, mentions: 1"
|
||||
|
||||
alice #$> ("/_read chat items #1(_support:2) " <> aliceMentionedByBobItemId, id, "items read for chat")
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 3, require attention: 1, mentions: 0"
|
||||
|
||||
threadDelay 1000000
|
||||
@@ -7982,23 +8000,30 @@ testScopedSupportUnreadStatsOnRead =
|
||||
bob <# "#team (support) dan!> @bob 9"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 4, require attention: 0, mentions: 0"
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 1"
|
||||
dan <## "bob (Bob) (id 3): unread: 4, require attention: 3, mentions: 1"
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 4, require attention: 0, mentions: 1"
|
||||
|
||||
alice #$> ("/_read chat #1(_support:2)", id, "ok")
|
||||
alice ##> "/_read chat #1(_support:2)"
|
||||
alice <## "#team: bob support chat read"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
|
||||
dan #$> ("/_read chat #1(_support:3)", id, "ok")
|
||||
dan ##> "/_read chat #1(_support:3)"
|
||||
dan <## "#team: bob support chat read"
|
||||
|
||||
dan ##> "/member support chats #team"
|
||||
dan <## "members require attention: 0"
|
||||
dan <## "bob (Bob) (id 3): unread: 0, require attention: 0, mentions: 0"
|
||||
|
||||
bob #$> ("/_read chat #1(_support)", id, "ok")
|
||||
bob ##> "/_read chat #1(_support)"
|
||||
bob <## "#team: support chat read"
|
||||
|
||||
bob ##> "/member support chats #team"
|
||||
bob <## "support: unread: 0, require attention: 0, mentions: 0"
|
||||
@@ -8029,12 +8054,90 @@ testScopedSupportUnreadStatsOnDelete =
|
||||
msgIdBob <- lastItemId bob
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 1, require attention: 1, mentions: 0"
|
||||
|
||||
bob #$> ("/_delete item #1(_support) " <> msgIdBob <> " broadcast", id, "message deleted")
|
||||
alice <# "#team (support: bob) bob> [deleted] 1"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
where
|
||||
opts =
|
||||
testOpts
|
||||
{ markRead = False
|
||||
}
|
||||
|
||||
testScopedSupportUnreadStatsCorrectOnOpen :: HasCallStack => TestParams -> IO ()
|
||||
testScopedSupportUnreadStatsCorrectOnOpen =
|
||||
testChatOpts2 opts aliceProfile bobProfile $ \alice bob -> do
|
||||
createGroup2 "team" alice bob
|
||||
|
||||
bob #> "#team (support) 1"
|
||||
alice <# "#team (support: bob) bob> 1"
|
||||
|
||||
bob #> "#team (support) 2"
|
||||
alice <# "#team (support: bob) bob> 2"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 2, require attention: 2, mentions: 0"
|
||||
|
||||
alice ##> "/_read chat #1(_support:2)"
|
||||
alice <## "#team: bob support chat read"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
|
||||
bob #> "#team (support) 3"
|
||||
alice <# "#team (support: bob) bob> 3"
|
||||
|
||||
bob #> "#team (support) 4"
|
||||
alice <# "#team (support: bob) bob> 4"
|
||||
|
||||
bob #> "#team (support) 5"
|
||||
alice <# "#team (support: bob) bob> 5"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 3, require attention: 3, mentions: 0"
|
||||
|
||||
-- opening chat should correct group_members.support_chat_items_member_attention value if it got out of sync
|
||||
void $ withCCTransaction alice $ \db ->
|
||||
DB.execute db "UPDATE group_members SET support_chat_items_member_attention=100 WHERE group_member_id=?" (Only (2 :: Int64))
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 3, require attention: 100, mentions: 0"
|
||||
|
||||
alice #$> ("/_get chat #1(_support:2) count=100", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")])
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 3, require attention: 3, mentions: 0"
|
||||
|
||||
alice ##> "/_read chat #1(_support:2)"
|
||||
alice <## "#team: bob support chat read"
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
|
||||
-- opening chat should also correct groups.members_require_attention value if corrected member no longer requires attention
|
||||
void $ withCCTransaction alice $ \db -> do
|
||||
DB.execute db "UPDATE group_members SET support_chat_items_member_attention=100 WHERE group_member_id=?" (Only (2 :: Int64))
|
||||
DB.execute db "UPDATE groups SET members_require_attention=1 WHERE group_id=?" (Only (1 :: Int64))
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 1"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 100, mentions: 0"
|
||||
|
||||
alice #$> ("/_get chat #1(_support:2) count=100", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")])
|
||||
|
||||
alice ##> "/member support chats #team"
|
||||
alice <## "members require attention: 0"
|
||||
alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0"
|
||||
where
|
||||
opts =
|
||||
|
||||
Reference in New Issue
Block a user