core, ui: correct member attention stat for support chat on opening it; mark support chat read (#6240)

This commit is contained in:
spaced4ndy
2025-09-05 14:00:32 +00:00
committed by GitHub
parent 9705ae15ea
commit ca9b0d4e43
18 changed files with 413 additions and 62 deletions

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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=?)

View File

@@ -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 -> []