core: member requires attention counter (#5881)

This commit is contained in:
spaced4ndy
2025-05-09 15:36:06 +00:00
committed by GitHub
parent 2a9df3e10b
commit 79991e84c9
33 changed files with 530 additions and 494 deletions
+3
View File
@@ -727,6 +727,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case contactDeleted(user: UserRef, contact: Contact)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case groupDeletedUser(user: UserRef, groupInfo: GroupInfo)
case itemsReadForChat(user: UserRef, chatInfo: ChatInfo)
case chatCleared(user: UserRef, chatInfo: ChatInfo)
case userProfileNoChange(user: User)
case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary)
@@ -765,6 +766,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case .contactDeleted: "contactDeleted"
case .contactConnectionDeleted: "contactConnectionDeleted"
case .groupDeletedUser: "groupDeletedUser"
case .itemsReadForChat: "itemsReadForChat"
case .chatCleared: "chatCleared"
case .userProfileNoChange: "userProfileNoChange"
case .userProfileUpdated: "userProfileUpdated"
@@ -797,6 +799,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case let .contactDeleted(u, contact): return withUser(u, String(describing: contact))
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
case let .itemsReadForChat(u, chatInfo): return withUser(u, String(describing: chatInfo))
case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo))
case .userProfileNoChange: return noDetails
case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile))
+14 -17
View File
@@ -564,6 +564,8 @@ final class ChatModel: ObservableObject {
// }
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
// updates membersRequireAttention
updateChatInfo(cInfo)
// mark chat non deleted
if case let .direct(contact) = cInfo, contact.chatDeleted {
var updatedContact = contact
@@ -1034,23 +1036,6 @@ final class ChatModel: ObservableObject {
return unread
}
func increaseGroupSupportChatsUnreadCounter(_ chatId: ChatId) {
changeGroupSupportChatsUnreadCounter(chatId, 1)
}
func decreaseGroupSupportChatsUnreadCounter(_ chatId: ChatId, by: Int = 1) {
changeGroupSupportChatsUnreadCounter(chatId, -by)
}
private func changeGroupSupportChatsUnreadCounter(_ chatId: ChatId, _ by: Int = 0) {
if by == 0 { return }
if let i = getChatIndex(chatId) {
let chat = chats[i]
chat.chatStats.supportChatsUnreadCount = max(0, chat.chatStats.supportChatsUnreadCount + by)
}
}
func increaseGroupReportsCounter(_ chatId: ChatId) {
changeGroupReportsCounter(chatId, 1)
}
@@ -1296,6 +1281,18 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
var supportUnreadCount: Int {
switch chatInfo {
case let .group(groupInfo, _):
if groupInfo.canModerate {
return groupInfo.membersRequireAttention
} else {
return groupInfo.membership.supportChat?.unread ?? 0
}
default: return 0
}
}
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}
+9 -8
View File
@@ -1252,8 +1252,10 @@ func apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?) async throws
try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: scope))
}
func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws {
try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, scope: scope, itemIds: itemIds))
func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ChatInfo {
let r: ChatResponse1 = try await chatSendCmd(.apiChatItemsRead(type: type, id: id, scope: scope, itemIds: itemIds))
if case let .itemsReadForChat(_, updatedChatInfo) = r { return updatedChatInfo }
throw r.unexpected
}
func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws {
@@ -1587,8 +1589,9 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async {
func apiMarkChatItemsRead(_ im: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
do {
try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds)
let updatedChatInfo = try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds)
await MainActor.run {
ChatModel.shared.updateChatInfo(updatedChatInfo)
ChatModel.shared.markChatItemsRead(im, cInfo, itemIds, mentionsRead)
}
} catch {
@@ -1637,9 +1640,9 @@ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult {
}
}
func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember {
func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> (GroupInfo, GroupMember) {
let r: ChatResponse2 = try await chatSendCmd(.apiAcceptMember(groupId: groupId, groupMemberId: groupMemberId, memberRole: memberRole))
if case let .memberAccepted(_, _, member) = r { return member }
if case let .memberAccepted(_, groupInfo, member) = r { return (groupInfo, member) }
throw r.unexpected
}
@@ -2131,9 +2134,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
if cItem.isActiveReport {
m.increaseGroupReportsCounter(cInfo.id)
}
if cInfo.groupChatScope() != nil && cItem.isRcvNew && cInfo.ntfsEnabled(chatItem: cItem) {
m.increaseGroupSupportChatsUnreadCounter(cInfo.id)
}
} else if cItem.isRcvNew && cInfo.ntfsEnabled(chatItem: cItem) {
m.increaseUnreadCounter(user: user)
}
@@ -2252,6 +2252,7 @@ func processReceivedMsg(_ res: ChatEvent) async {
if active(user) {
await MainActor.run {
_ = m.upsertGroupMember(groupInfo, member)
m.updateGroup(groupInfo)
}
}
case let .deletedMemberUser(user, groupInfo, member, withMessages): // TODO update user member
@@ -35,7 +35,7 @@ struct ChatInfoToolbar: View {
.frame(width: 14, height: 14)
.symbolRenderingMode(.palette)
.foregroundStyle(.white, .red)
} else if chat.chatStats.supportChatsUnreadCount > 0 {
} else if chat.supportUnreadCount > 0 {
Image(systemName: "flag.circle.fill")
.resizable()
.scaledToFit()
@@ -1339,17 +1339,11 @@ struct ChatView: View {
if !itemIds.isEmpty {
waitToMarkRead {
await apiMarkChatItemsRead(im, chat.chatInfo, itemIds, mentionsRead: unreadMentions)
if (im.secondaryIMFilter != nil) {
m.decreaseGroupSupportChatsUnreadCounter(chat.chatInfo.id, by: itemIds.count )
}
}
}
} else if chatItem.isRcvNew {
waitToMarkRead {
await apiMarkChatItemsRead(im, chat.chatInfo, [chatItem.id], mentionsRead: chatItem.meta.userMention ? 1 : 0)
if (im.secondaryIMFilter != nil) {
m.decreaseGroupSupportChatsUnreadCounter(chat.chatInfo.id)
}
}
}
}
@@ -73,9 +73,10 @@ func showAcceptMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismis
func acceptMember(_ groupInfo: GroupInfo, _ member: GroupMember, _ role: GroupMemberRole, dismiss: DismissAction? = nil) {
Task {
do {
let acceptedMember = try await apiAcceptMember(groupInfo.groupId, member.groupMemberId, role)
let (gInfo, acceptedMember) = try await apiAcceptMember(groupInfo.groupId, member.groupMemberId, role)
await MainActor.run {
_ = ChatModel.shared.upsertGroupMember(groupInfo, acceptedMember)
ChatModel.shared.updateGroup(gInfo)
dismiss?()
}
} catch let error {
@@ -579,7 +579,7 @@ struct GroupChatInfoView: View {
} label: {
Label(
"Chats with members",
systemImage: chat.chatStats.supportChatsUnreadCount > 0 ? "flag.fill" : "flag"
systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag"
)
}
}
@@ -445,7 +445,7 @@ struct ChatPreviewView: View {
ProgressView()
} else if chat.chatStats.reportsCount > 0 {
groupReportsIcon(size: size * 0.8)
} else if chat.chatStats.supportChatsUnreadCount > 0 {
} else if chat.supportUnreadCount > 0 {
GroupSupportUnreadIcon(size: size * 0.8)
} else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
+2 -4
View File
@@ -1610,14 +1610,12 @@ public struct ChatStats: Decodable, Hashable {
unreadCount: Int = 0,
unreadMentions: Int = 0,
reportsCount: Int = 0,
supportChatsUnreadCount: Int = 0,
minUnreadItemId: Int64 = 0,
unreadChat: Bool = false
) {
self.unreadCount = unreadCount
self.unreadMentions = unreadMentions
self.reportsCount = reportsCount
self.supportChatsUnreadCount = supportChatsUnreadCount
self.minUnreadItemId = minUnreadItemId
self.unreadChat = unreadChat
}
@@ -1626,8 +1624,6 @@ public struct ChatStats: Decodable, Hashable {
public var unreadMentions: Int = 0
// actual only via getChats() and getChat(.initial), otherwise, zero
public var reportsCount: Int = 0
// actual only via getChats() and getChat(.initial), otherwise, zero
public var supportChatsUnreadCount: Int = 0
public var minUnreadItemId: Int64 = 0
// actual only via getChats(), otherwise, false
public var unreadChat: Bool = false
@@ -2029,6 +2025,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
var updatedAt: Date
var chatTs: Date?
public var uiThemes: ThemeModeOverrides?
public var membersRequireAttention: Int
public var id: ChatId { get { "#\(groupId)" } }
public var apiId: Int64 { get { groupId } }
@@ -2066,6 +2063,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
chatSettings: ChatSettings.defaults,
createdAt: .now,
updatedAt: .now,
membersRequireAttention: 0,
chatTags: [],
localAlias: ""
)
@@ -429,6 +429,8 @@ object ChatModel {
}
suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
// updates membersRequireAttention
updateChatInfo(rhId, cInfo)
// mark chat non deleted
if (cInfo is ChatInfo.Direct && cInfo.chatDeleted) {
val updatedContact = cInfo.contact.copy(chatDeleted = false)
@@ -903,28 +905,6 @@ object ChatModel {
}
}
fun increaseGroupSupportChatsUnreadCounter(rhId: Long?, chatId: ChatId) {
changeGroupSupportChatsUnreadCounter(rhId, chatId, 1)
}
fun decreaseGroupSupportChatsUnreadCounter(rhId: Long?, chatId: ChatId, by: Int = 1) {
changeGroupSupportChatsUnreadCounter(rhId, chatId, -by)
}
private fun changeGroupSupportChatsUnreadCounter(rhId: Long?, chatId: ChatId, by: Int = 0) {
if (by == 0) return
val i = getChatIndex(rhId, chatId)
if (i >= 0) {
val chat = chats.value[i]
chats[i] = chat.copy(
chatStats = chat.chatStats.copy(
supportChatsUnreadCount = (chat.chatStats.supportChatsUnreadCount + by).coerceAtLeast(0),
)
)
}
}
fun increaseGroupReportsCounter(rhId: Long?, chatId: ChatId) {
changeGroupReportsCounter(rhId, chatId, 1)
}
@@ -1329,6 +1309,16 @@ data class Chat(
val id: String get() = chatInfo.id
val supportUnreadCount: Int get() = when (chatInfo) {
is ChatInfo.Group ->
if (chatInfo.groupInfo.canModerate) {
chatInfo.groupInfo.membersRequireAttention
} else {
chatInfo.groupInfo.membership.supportChat?.unread ?: 0
}
else -> 0
}
fun groupFeatureEnabled(feature: GroupFeature): Boolean =
if (chatInfo is ChatInfo.Group) {
chatInfo.groupInfo.groupFeatureEnabled(feature)
@@ -1342,8 +1332,6 @@ data class Chat(
val unreadMentions: Int = 0,
// actual only via getChats() and getChat(.initial), otherwise, zero
val reportsCount: Int = 0,
// actual only via getChats() and getChat(.initial), otherwise, zero
val supportChatsUnreadCount: Int = 0,
val minUnreadItemId: Long = 0,
// actual only via getChats(), otherwise, false
val unreadChat: Boolean = false
@@ -1853,6 +1841,7 @@ data class GroupInfo (
override val updatedAt: Instant,
val chatTs: Instant?,
val uiThemes: ThemeModeOverrides? = null,
val membersRequireAttention: Int,
val chatTags: List<Long>,
val chatItemTTL: Long?,
override val localAlias: String,
@@ -1915,6 +1904,7 @@ data class GroupInfo (
updatedAt = Clock.System.now(),
chatTs = Clock.System.now(),
uiThemes = null,
membersRequireAttention = 0,
chatTags = emptyList(),
localAlias = "",
chatItemTTL = null
@@ -1669,11 +1669,11 @@ object ChatController {
return false
}
suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List<Long>): Boolean {
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.result is CR.CmdOk) return true
if (r is API.Result && r.res is CR.ItemsReadForChat) return r.res.chatInfo
Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}")
return false
return null
}
suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean {
@@ -1902,9 +1902,9 @@ object ChatController {
}
}
suspend fun apiAcceptMember(rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole): GroupMember? {
suspend fun apiAcceptMember(rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole): Pair<GroupInfo, GroupMember>? {
val r = sendCmd(rh, CC.ApiAcceptMember(groupId, groupMemberId, memberRole))
if (r is API.Result && r.res is CR.MemberAccepted) return r.res.member
if (r is API.Result && r.res is CR.MemberAccepted) return r.res.groupInfo to r.res.member
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiAcceptMember", generalGetString(MR.strings.error_accepting_member), r)
}
@@ -2430,9 +2430,6 @@ object ChatController {
if (cItem.isActiveReport) {
chatModel.chatsContext.increaseGroupReportsCounter(rhId, cInfo.id)
}
if (cInfo.groupChatScope() != null && cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) {
chatModel.chatsContext.increaseGroupSupportChatsUnreadCounter(rhId, cInfo.id)
}
}
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem)
@@ -2586,6 +2583,7 @@ object ChatController {
if (active(r.user)) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
}
is CR.DeletedMemberUser -> // TODO update user member
@@ -5796,6 +5794,7 @@ sealed class CR {
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactDeletedByContact") class ContactDeletedByContact(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("itemsReadForChat") class ItemsReadForChat(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("chatCleared") class ChatCleared(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("userProfileNoChange") class UserProfileNoChange(val user: User): CR()
@Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val user: User, val fromProfile: Profile, val toProfile: Profile, val updateSummary: UserProfileUpdateSummary): CR()
@@ -5976,6 +5975,7 @@ sealed class CR {
is ContactAlreadyExists -> "contactAlreadyExists"
is ContactDeleted -> "contactDeleted"
is ContactDeletedByContact -> "contactDeletedByContact"
is ItemsReadForChat -> "itemsReadForChat"
is ChatCleared -> "chatCleared"
is UserProfileNoChange -> "userProfileNoChange"
is UserProfileUpdated -> "userProfileUpdated"
@@ -6146,6 +6146,7 @@ sealed class CR {
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
is ContactDeleted -> withUser(user, json.encodeToString(contact))
is ContactDeletedByContact -> withUser(user, json.encodeToString(contact))
is ItemsReadForChat -> withUser(user, json.encodeToString(chatInfo))
is ChatCleared -> withUser(user, json.encodeToString(chatInfo))
is UserProfileNoChange -> withUser(user, noDetails())
is UserProfileUpdated -> withUser(user, json.encodeToString(toProfile))
@@ -633,17 +633,17 @@ fun ChatView(
withBGApi {
withContext(Dispatchers.Main) {
chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds)
if (chatsCtx.secondaryContextFilter != null) {
chatModel.chatsContext.decreaseGroupSupportChatsUnreadCounter(chatRh, chatInfo.id)
}
ntfManager.cancelNotificationsForChat(chatInfo.id)
chatModel.controller.apiChatItemsRead(
val updatedChatInfo = chatModel.controller.apiChatItemsRead(
chatRh,
chatInfo.chatType,
chatInfo.apiId,
chatInfo.groupChatScope(),
itemsIds
)
if (updatedChatInfo != null) {
chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo)
}
}
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.value?.markChatItemsRead(chatRh, chatInfo.id, itemsIds)
@@ -894,10 +894,10 @@ fun ChatLayout(
}
}
val reportsCount = reportsCount(chatInfo?.id)
val supportChatsUnreadCount = supportChatsUnreadCount(chatInfo?.id)
val supportUnreadCount = supportUnreadCount(chatInfo?.id)
if (oneHandUI.value && chatBottomBar.value) {
if (chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportChatsUnreadCount > 0)) {
SupportChatsCountToolbar(reportsCount, supportChatsUnreadCount, withStatusBar = true, showReports, showSupportChats)
if (chatInfo != null && chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0)) {
SupportChatsCountToolbar(chatInfo, reportsCount, supportUnreadCount, withStatusBar = true, showReports, showSupportChats)
} else {
StatusBarBackground()
}
@@ -953,8 +953,8 @@ fun ChatLayout(
SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
}
}
if ((reportsCount > 0 || supportChatsUnreadCount > 0) && (!oneHandUI.value || !chatBottomBar.value)) {
SupportChatsCountToolbar(reportsCount, supportChatsUnreadCount, withStatusBar = false, showReports, showSupportChats)
if (chatInfo != null && (reportsCount > 0 || supportUnreadCount > 0) && (!oneHandUI.value || !chatBottomBar.value)) {
SupportChatsCountToolbar(chatInfo, reportsCount, supportUnreadCount, withStatusBar = false, showReports, showSupportChats)
}
}
}
@@ -1187,8 +1187,9 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
@Composable
private fun SupportChatsCountToolbar(
chatInfo: ChatInfo,
reportsCount: Int,
supportChatsUnreadCount: Int,
supportUnreadCount: Int,
withStatusBar: Boolean,
showReports: () -> Unit,
showSupportChats: () -> Unit
@@ -1225,7 +1226,7 @@ private fun SupportChatsCountToolbar(
}
}
if (supportChatsUnreadCount > 0) {
if (supportUnreadCount > 0) {
Row(
Modifier
.fillMaxWidth()
@@ -1240,10 +1241,13 @@ private fun SupportChatsCountToolbar(
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.primary)
Spacer(Modifier.width(4.dp))
Text(
if (appPlatform.isAndroid)
stringResource(MR.strings.group_new_support_messages_short).format(supportChatsUnreadCount)
if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate)
if (appPlatform.isAndroid)
stringResource(MR.strings.group_new_support_chats_short).format(supportUnreadCount)
else
stringResource(MR.strings.group_new_support_chats).format(supportUnreadCount)
else
stringResource(MR.strings.group_new_support_messages).format(supportChatsUnreadCount),
stringResource(MR.strings.group_new_support_messages).format(supportUnreadCount),
style = MaterialTheme.typography.button
)
}
@@ -1329,10 +1333,10 @@ fun BoxScope.ChatItemsList(
}
val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } }
val reportsCount = reportsCount(chatInfo.id)
val supportChatsUnreadCount = supportChatsUnreadCount(chatInfo.id)
val supportUnreadCount = supportUnreadCount(chatInfo.id)
val topPaddingToContent = topPaddingToContent(
chatView = chatsCtx.secondaryContextFilter == null,
additionalTopBar = chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportChatsUnreadCount > 0)
additionalTopBar = chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0)
)
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() })
val numberOfBottomAppBars = numberOfBottomAppBars()
@@ -1672,7 +1676,7 @@ fun BoxScope.ChatItemsList(
),
reverseLayout = true,
additionalBarOffset = composeViewHeight,
additionalTopBar = rememberUpdatedState(chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportChatsUnreadCount > 0)),
additionalTopBar = rememberUpdatedState(chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0)),
chatBottomBar = remember { appPrefs.chatBottomBar.state }
) {
val mergedItemsValue = mergedItems.value
@@ -2358,11 +2362,11 @@ fun reportsCount(staleChatId: String?): Int {
}
@Composable
fun supportChatsUnreadCount(staleChatId: String?): Int {
fun supportUnreadCount(staleChatId: String?): Int {
return if (staleChatId?.startsWith("#") != true) {
0
} else {
remember(staleChatId) { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId }?.chatStats } }.value?.supportChatsUnreadCount ?: 0
remember(staleChatId) { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId } } }.value?.supportUnreadCount ?: 0
}
}
@@ -102,13 +102,14 @@ fun acceptMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
private fun acceptMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, role: GroupMemberRole, close: (() -> Unit)?) {
withBGApi {
val acceptedMember = chatModel.controller.apiAcceptMember(rhId, groupInfo.groupId, member.groupMemberId, role)
if (acceptedMember != null) {
val r = chatModel.controller.apiAcceptMember(rhId, groupInfo.groupId, member.groupMemberId, role)
if (r != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, acceptedMember)
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, r.second)
chatModel.chatsContext.updateGroup(rhId, r.first)
}
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, acceptedMember)
chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, r.second)
}
}
close?.invoke()
@@ -741,10 +741,10 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
@Composable
private fun MemberSupportButton(chat: Chat, onClick: () -> Unit) {
SettingsActionItem(
painterResource(if (chat.chatStats.supportChatsUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
stringResource(MR.strings.member_support),
click = onClick,
iconColor = (if (chat.chatStats.supportChatsUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary)
iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary)
)
}
@@ -364,7 +364,7 @@ fun ChatPreviewView(
progressView()
} else if (chat.chatStats.reportsCount > 0) {
GroupReportsIcon()
} else if (chat.chatStats.supportChatsUnreadCount > 0) {
} else if (chat.supportUnreadCount > 0) {
GroupSupportUnreadIcon()
} else {
IncognitoIcon(chat.chatInfo.incognito)
@@ -465,8 +465,9 @@
<string name="group_reports_active_one">1 report</string>
<string name="group_reports_active">%d reports</string>
<string name="group_reports_member_reports">Member reports</string>
<string name="group_new_support_messages">%d new support messages</string>
<string name="group_new_support_messages_short">%d messages</string>
<string name="group_new_support_messages">%d messages</string>
<string name="group_new_support_chats">%d chats with members</string>
<string name="group_new_support_chats_short">%d chats</string>
<!-- ShareListView.kt -->
<string name="share_message">Share message…</string>
+1
View File
@@ -676,6 +676,7 @@ data ChatResponse
| CRSentConfirmation {user :: User, connection :: PendingContactConnection}
| CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile}
| CRSentInvitationToContact {user :: User, contact :: Contact, customUserProfile :: Maybe Profile}
| CRItemsReadForChat {user :: User, chatInfo :: AChatInfo}
| CRContactDeleted {user :: User, contact :: Contact}
| CRChatCleared {user :: User, chatInfo :: AChatInfo}
| CRUserContactLinkCreated {user :: User, connLinkContact :: CreatedLinkContact}
+20 -10
View File
@@ -1017,22 +1017,26 @@ processChatCommand' vr = \case
CTContactConnection -> throwCmdError "not supported"
APIChatItemsRead chatRef@(ChatRef cType chatId scope) itemIds -> withUser $ \_ -> case cType of
CTDirect -> do
user <- withFastStore $ \db -> getUserByContactId db chatId
(user, ct) <- withFastStore $ \db -> do
user <- getUserByContactId db chatId
ct <- getContact db vr user chatId
pure (user, ct)
timedItems <- withFastStore' $ \db -> do
timedItems <- updateDirectChatItemsReadList db user chatId itemIds
setDirectChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
ok user
pure $ CRItemsReadForChat user (AChatInfo SCTDirect $ DirectChat ct)
CTGroup -> do
(user, gInfo) <- withFastStore $ \db -> do
user <- getUserByGroupId db chatId
gInfo <- getGroupInfo db vr user chatId
pure (user, gInfo)
timedItems <- withFastStore' $ \db -> do
timedItems <- updateGroupChatItemsReadList db user gInfo scope itemIds
setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
(timedItems, gInfo') <- withFastStore $ \db -> do
(timedItems, gInfo') <- updateGroupChatItemsReadList db vr user gInfo scope itemIds
timedItems' <- liftIO $ setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
pure (timedItems', gInfo')
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
ok user
pure $ CRItemsReadForChat user (AChatInfo SCTGroup $ GroupChat gInfo' Nothing)
CTLocal -> throwCmdError "not supported"
CTContactRequest -> throwCmdError "not supported"
CTContactConnection -> throwCmdError "not supported"
@@ -2054,8 +2058,11 @@ processChatCommand' vr = \case
void $ sendDirectMemberMessage mConn msg groupId
introduceToRemaining vr user gInfo m {memberRole = role}
when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo m
m' <- withFastStore' $ \db -> updateGroupMemberAccepted db user m GSMemConnected role
pure $ CRMemberAccepted user gInfo m'
(m', gInfo') <- withFastStore' $ \db -> do
m' <- updateGroupMemberAccepted db user m GSMemConnected role
gInfo' <- updateGroupMembersRequireAttention db user gInfo m m'
pure (m', gInfo')
pure $ CRMemberAccepted user gInfo' m'
Nothing -> throwChatError CEGroupMemberNotActive
GSMemPendingReview -> do
let scope = Just $ GCSMemberSupport $ Just (groupMemberId' m)
@@ -2063,8 +2070,11 @@ processChatCommand' vr = \case
let rcpModMs' = filter memberCurrent modMs
msg = XGrpLinkAcpt GAAccepted role (memberId' m)
void $ sendGroupMessage user gInfo scope ([m] <> rcpModMs') msg
m' <- withFastStore' $ \db -> updateGroupMemberAccepted db user m newMemberStatus role
pure $ CRMemberAccepted user gInfo m'
(m', gInfo') <- withFastStore' $ \db -> do
m' <- updateGroupMemberAccepted db user m newMemberStatus role
gInfo' <- updateGroupMembersRequireAttention db user gInfo m m'
pure (m', gInfo')
pure $ CRMemberAccepted user gInfo' m'
where
newMemberStatus = case memberConn m of
Just c | connReady c -> GSMemConnected
+18 -11
View File
@@ -2032,8 +2032,9 @@ saveSndChatItems ::
CM [Either ChatError (ChatItem c 'MDSnd)]
saveSndChatItems user cd itemsData itemTimed live = do
createdAt <- liftIO getCurrentTime
vr <- chatVersionRange
when (contactChatDeleted cd || any (\NewSndChatItemData {content} -> ciRequiresAttention content) (rights itemsData)) $
withStore' (\db -> updateChatTsStats db user cd createdAt Nothing)
void $ withStore' (\db -> updateChatTsStats db vr user cd createdAt Nothing)
lift $ withStoreBatch (\db -> map (bindRight $ createItem db createdAt) itemsData)
where
createItem :: DB.Connection -> UTCTime -> NewSndChatItemData c -> IO (Either ChatError (ChatItem c 'MDSnd))
@@ -2045,19 +2046,20 @@ saveSndChatItems user cd itemsData itemTimed live = do
CDGroupSnd g _scope | not (null itemMentions) -> createGroupCIMentions db g ci itemMentions
_ -> pure ci
saveRcvChatItemNoParse :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv)
saveRcvChatItemNoParse :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv, ChatInfo c)
saveRcvChatItemNoParse user cd msg brokerTs = saveRcvChatItem user cd msg brokerTs . ciContentNoParse
saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> CM (ChatItem c 'MDRcv)
saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> CM (ChatItem c 'MDRcv, ChatInfo c)
saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} brokerTs content =
saveRcvChatItem' user cd msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
ciContentNoParse :: CIContent 'MDRcv -> (CIContent 'MDRcv, (Text, Maybe MarkdownList))
ciContentNoParse content = (content, (ciContentToText content, Nothing))
saveRcvChatItem' :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> Map MemberName MsgMention -> CM (ChatItem c 'MDRcv)
saveRcvChatItem' :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> Map MemberName MsgMention -> CM (ChatItem c 'MDRcv, ChatInfo c)
saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, forwardedByMember} sharedMsgId_ brokerTs (content, (t, ft_)) ciFile itemTimed live mentions = do
createdAt <- liftIO getCurrentTime
vr <- chatVersionRange
withStore' $ \db -> do
(mentions' :: Map MemberName CIMention, userMention) <- case cd of
CDGroupRcv g@GroupInfo {membership} _scope _m -> do
@@ -2068,13 +2070,16 @@ saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, forwardedByMember} shared
userMention' = userReply || any (\CIMention {memberId} -> sameMemberId memberId membership) mentions'
in pure (mentions', userMention')
CDDirectRcv _ -> pure (M.empty, False)
when (ciRequiresAttention content || contactChatDeleted cd) $ updateChatTsStats db user cd createdAt (chatStatsCounts userMention)
cInfo' <- if (ciRequiresAttention content || contactChatDeleted cd)
then updateChatTsStats db vr user cd createdAt (chatStatsCounts userMention)
else pure $ toChatInfo cd
(ciId, quotedItem, itemForwarded) <- createNewRcvChatItem db user cd msg sharedMsgId_ content itemTimed live userMention brokerTs createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
let ci = mkChatItem_ cd ciId content (t, ft_) ciFile quotedItem sharedMsgId_ itemForwarded itemTimed live userMention brokerTs forwardedByMember createdAt
case cd of
ci' <- case cd of
CDGroupRcv g _scope _m | not (null mentions') -> createGroupCIMentions db g ci mentions'
_ -> pure ci
pure (ci', cInfo')
where
chatStatsCounts :: Bool -> Maybe (Int, MemberAttention, Int)
chatStatsCounts userMention = case cd of
@@ -2297,12 +2302,13 @@ createInternalItemsForChats ::
createInternalItemsForChats user itemTs_ dirsCIContents = do
createdAt <- liftIO getCurrentTime
let itemTs = fromMaybe createdAt itemTs_
void . withStoreBatch' $ \db -> map (uncurry $ updateChat db createdAt) dirsCIContents
vr <- chatVersionRange'
void . withStoreBatch' $ \db -> map (uncurry $ updateChat db vr createdAt) dirsCIContents
withStoreBatch' $ \db -> concatMap (uncurry $ createACIs db itemTs createdAt) dirsCIContents
where
updateChat :: DB.Connection -> UTCTime -> ChatDirection c d -> [CIContent d] -> IO ()
updateChat db createdAt cd contents
| any ciRequiresAttention contents || contactChatDeleted cd = updateChatTsStats db user cd createdAt chatStatsCounts
updateChat :: DB.Connection -> VersionRangeChat -> UTCTime -> ChatDirection c d -> [CIContent d] -> IO ()
updateChat db vr createdAt cd contents
| any ciRequiresAttention contents || contactChatDeleted cd = void $ updateChatTsStats db vr user cd createdAt chatStatsCounts
| otherwise = pure ()
where
chatStatsCounts :: Maybe (Int, MemberAttention, Int)
@@ -2331,7 +2337,8 @@ createLocalChatItems ::
UTCTime ->
CM [ChatItem 'CTLocal 'MDSnd]
createLocalChatItems user cd itemsData createdAt = do
withStore' $ \db -> updateChatTsStats db user cd createdAt Nothing
vr <- chatVersionRange
void $ withStore' $ \db -> updateChatTsStats db vr user cd createdAt Nothing
(errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) $ L.toList itemsData)
unless (null errs) $ toView $ CEvtChatErrors errs
pure items
+90 -74
View File
@@ -784,29 +784,33 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
memberConnectedChatItem gInfo' scopeInfo m'
unless (memberPending membership) $ maybeCreateGroupDescrLocal gInfo' m'
GCInviteeMember -> do
mStatus <-
(gInfo', mStatus) <-
if not (memberPending m)
then withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected $> GSMemConnected
else pure $ memberStatus m
(gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
memberConnectedChatItem gInfo' scopeInfo m'
then do
mStatus <- withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected $> GSMemConnected
pure (gInfo, mStatus)
else do
gInfo' <- withStore' $ \db -> increaseGroupMembersRequireAttention db user gInfo
pure (gInfo', memberStatus m)
(gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo' m
memberConnectedChatItem gInfo'' scopeInfo m'
case scopeInfo of
Just (GCSIMemberSupport _) -> do
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m') (CIRcvGroupEvent RGENewMemberPendingReview) Nothing
createInternalChatItem user (CDGroupRcv gInfo'' scopeInfo m') (CIRcvGroupEvent RGENewMemberPendingReview) Nothing
_ -> pure ()
toView $ CEvtJoinedGroupMember user gInfo' m' {memberStatus = mStatus}
toView $ CEvtJoinedGroupMember user gInfo'' m' {memberStatus = mStatus}
let Connection {viaUserContactLink} = conn
when (isJust viaUserContactLink && isNothing (memberContactId m')) $ sendXGrpLinkMem gInfo'
when (isJust viaUserContactLink && isNothing (memberContactId m')) $ sendXGrpLinkMem gInfo''
when (connChatVersion < batchSend2Version) sendGroupAutoReply
case mStatus of
GSMemPendingApproval -> pure ()
GSMemPendingReview -> introduceToModerators vr user gInfo' m'
GSMemPendingReview -> introduceToModerators vr user gInfo'' m'
_ -> do
introduceToAll vr user gInfo' m'
when (groupFeatureAllowed SGFHistory gInfo') $ sendHistory user gInfo' m'
introduceToAll vr user gInfo'' m'
when (groupFeatureAllowed SGFHistory gInfo'') $ sendHistory user gInfo'' m'
where
sendXGrpLinkMem gInfo' = do
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo'
sendXGrpLinkMem gInfo'' = do
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo''
profileToSend = profileToSendOnAccept user profileMode True
void $ sendDirectMemberMessage conn (XGrpLinkMem profileToSend) groupId
_ -> do
@@ -1466,9 +1470,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
where
brokerTs = metaBrokerTs msgMeta
newChatItem content ciFile_ timed_ live = do
ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content ciFile_ timed_ live M.empty
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content ciFile_ timed_ live M.empty
reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}]
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci {reactions}]
autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> CM ()
autoAcceptFile = mapM_ $ \(ft, CIFile {fileSize}) -> do
@@ -1534,11 +1538,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- Chat item and update message which created it will have different sharedMsgId in this case...
let timed_ = rcvContactCITimed ct ttl
ts = ciContentTexts content
ci <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateDirectChatItem' db user contactId ci content True live Nothing Nothing
toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci')
toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci')
where
brokerTs = metaBrokerTs msgMeta
content = CIRcvMsgContent mc
@@ -1658,27 +1662,26 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| groupFeatureAllowed SGFFullDelete gInfo = do
(gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
-- ignores member role when blocked by admin
ci <- saveRcvCI gInfo' scopeInfo m' (ciContentNoParse CIRcvBlocked) Nothing timed' False M.empty
(ci, cInfo) <- saveRcvCI gInfo' scopeInfo m' (ciContentNoParse CIRcvBlocked) Nothing timed' False M.empty
ci' <- withStore' $ \db -> updateGroupCIBlockedByAdmin db user gInfo' ci brokerTs
groupMsgToView gInfo' scopeInfo ci'
groupMsgToView cInfo ci'
| otherwise = do
file_ <- processFileInv
ci <- createNonLive file_
(ci, cInfo) <- createNonLive file_
ci' <- withStore' $ \db -> markGroupCIBlockedByAdmin db user gInfo ci
(gInfo', _m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
groupMsgToView gInfo' scopeInfo ci'
groupMsgToView cInfo ci'
applyModeration CIModeration {moderatorMember = moderator@GroupMember {memberRole = moderatorRole}, moderatedAt}
| moderatorRole < GRModerator || moderatorRole < memberRole =
createContentItem
| groupFeatureMemberAllowed SGFFullDelete moderator gInfo = do
(gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
ci <- saveRcvCI gInfo' scopeInfo m' (ciContentNoParse CIRcvModerated) Nothing timed' False M.empty
(ci, cInfo) <- saveRcvCI gInfo' scopeInfo m' (ciContentNoParse CIRcvModerated) Nothing timed' False M.empty
ci' <- withStore' $ \db -> updateGroupChatItemModerated db user gInfo' ci moderator moderatedAt
groupMsgToView gInfo' scopeInfo ci'
groupMsgToView cInfo ci'
| otherwise = do
(gInfo', _m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
file_ <- processFileInv
ci <- createNonLive file_
(ci, _cInfo) <- createNonLive file_
deletions <- markGroupCIsDeleted user gInfo' scopeInfo [CChatItem SMDRcv ci] (Just moderator) moderatedAt
toView $ CEvtChatItemsDeleted user deletions False False
createNonLive file_ = do
@@ -1693,10 +1696,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
newChatItem ciContent ciFile_ timed_ live = do
let mentions' = if showMessages (memberSettings m) then mentions else []
(gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
ci <- saveRcvCI gInfo' scopeInfo m' ciContent ciFile_ timed_ live mentions'
(ci, cInfo) <- saveRcvCI gInfo' scopeInfo m' ciContent ciFile_ timed_ live mentions'
ci' <- blockedMember m' ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo' ci
reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getGroupCIReactions db gInfo' memberId sharedMsgId) sharedMsgId_
groupMsgToView gInfo' scopeInfo ci' {reactions}
groupMsgToView cInfo ci' {reactions}
groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> Map MemberName MsgMention -> Maybe MsgScope -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM ()
groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc mentions msgScope_ msg@RcvMessage {msgId} brokerTs ttl_ live_
@@ -1710,12 +1713,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let timed_ = rcvGroupCITimed gInfo ttl_
mentions' = if showMessages (memberSettings m) then mentions else []
(gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
ci <- saveRcvChatItem' user (CDGroupRcv gInfo' scopeInfo m') msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions'
(ci, cInfo) <- saveRcvChatItem' user (CDGroupRcv gInfo' scopeInfo m') msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions'
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
ci' <- updateGroupChatItem db user groupId ci content True live Nothing
blockedMember m' ci' $ markGroupChatItemBlocked db user gInfo' ci'
toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo' scopeInfo) ci')
toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci')
where
content = CIRcvMsgContent mc
ts@(_, ft_) = msgContentTexts mc
@@ -1802,8 +1805,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol}
content = ciContentNoParse $ CIRcvMsgContent $ MCFile ""
ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
where
brokerTs = metaBrokerTs msgMeta
@@ -1816,9 +1819,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol}
content = ciContentNoParse $ CIRcvMsgContent $ MCFile ""
ci <- saveRcvChatItem' user (CDGroupRcv gInfo Nothing m) msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
(ci, cInfo) <- saveRcvChatItem' user (CDGroupRcv gInfo Nothing m) msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
ci' <- blockedMember m ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo ci
groupMsgToView gInfo Nothing ci'
groupMsgToView cInfo ci'
blockedMember :: Monad m' => GroupMember -> ChatItem c d -> m' (ChatItem c d) -> m' (ChatItem c d)
blockedMember m ci blockedCI
@@ -1969,9 +1972,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
_ -> messageError "x.file.acpt.inv: member connection is not active"
else messageError "x.file.acpt.inv: fileName is different from expected"
groupMsgToView :: forall d. MsgDirectionI d => GroupInfo -> Maybe GroupChatScopeInfo -> ChatItem 'CTGroup d -> CM ()
groupMsgToView gInfo chatScopeInfo ci = do
toView $ CEvtNewChatItems user [AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo chatScopeInfo) ci]
groupMsgToView :: forall d. MsgDirectionI d => ChatInfo 'CTGroup -> ChatItem 'CTGroup d -> CM ()
groupMsgToView cInfo ci = do
toView $ CEvtNewChatItems user [AChatItem SCTGroup (msgDirection @d) cInfo ci]
processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM ()
processGroupInvitation ct inv msg msgMeta = do
@@ -1996,9 +1999,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct)
else do
let content = CIRcvGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole
ci <- saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs content
(ci, cInfo) <- saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs content
withStore' $ \db -> setGroupInvitationChatItemId db user groupId (chatItemId' ci)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
toView $ CEvtReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole}
where
brokerTs = metaBrokerTs msgMeta
@@ -2024,8 +2027,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
forM_ contactConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted
activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted}
let ct'' = ct' {activeConn = activeConn'} :: Contact
ci <- saveRcvChatItemNoParse user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct'') ci]
(ci, cInfo) <- saveRcvChatItemNoParse user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
toView $ CEvtContactDeletedByContact user ct''
else do
contactConns <- withStore' $ \db -> getContactConnections db vr userId c
@@ -2096,13 +2099,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case
Left _ -> messageError "x.grp.link.acpt error: referenced member does not exist"
Right referencedMember -> do
referencedMember' <- withFastStore' $ \db -> updateGroupMemberAccepted db user referencedMember (newMemberStatus referencedMember) role
(referencedMember', gInfo') <- withFastStore' $ \db -> do
referencedMember' <- updateGroupMemberAccepted db user referencedMember (newMemberStatus referencedMember) role
gInfo' <- updateGroupMembersRequireAttention db user gInfo referencedMember referencedMember'
pure (referencedMember', gInfo')
when (memberCategory referencedMember == GCInviteeMember) $ introduceToRemainingMembers referencedMember'
let scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Just referencedMember'}
gEvent = RGEMemberAccepted (groupMemberId' referencedMember') (fromLocalProfile $ memberProfile referencedMember')
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo scopeInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView gInfo scopeInfo ci
toView $ CEvtMemberAcceptedByOther user gInfo m referencedMember'
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView cInfo ci
toView $ CEvtMemberAcceptedByOther user gInfo' m referencedMember'
where
newMemberStatus refMem = case memberConn refMem of
Just c | connReady c -> GSMemConnected
@@ -2112,8 +2118,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
GAAccepted -> do
membership' <- withStore' $ \db -> updateGroupMemberAccepted db user membership GSMemConnected role
let scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo scopeInfo m) msg brokerTs (CIRcvGroupEvent RGEUserAccepted)
groupMsgToView gInfo scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo scopeInfo m) msg brokerTs (CIRcvGroupEvent RGEUserAccepted)
groupMsgToView cInfo ci
toView $ CEvtUserJoinedGroup user gInfo {membership = membership'} m
let cd = CDGroupRcv gInfo Nothing m
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
@@ -2265,7 +2271,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
then do
g <- asks random
dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing
ci <- saveCallItem CISCallPending
(ci, cInfo) <- saveCallItem CISCallPending
callUUID <- UUID.toText <$> liftIO V4.nextRandom
let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> (snd <$> dhKeyPair))
callState = CallInvitationReceived {peerCallType = callType, localDhPubKey = fst <$> dhKeyPair, sharedKey}
@@ -2278,15 +2284,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
call_ <- atomically (TM.lookupInsert contactId call' calls)
forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing
toView $ CEvtCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callUUID, callTs = chatItemTs' ci}
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
else featureRejected CFCalls
where
brokerTs = metaBrokerTs msgMeta
saveCallItem status = saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs (CIRcvCall status 0)
featureRejected f = do
let content = ciContentNoParse $ CIRcvChatFeatureRejected f
ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
-- to party initiating call
xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> CM ()
@@ -2457,27 +2463,37 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
unless (sameMemberId memId $ membership gInfo) $
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
Right unknownMember@GroupMember {memberStatus = GSMemUnknown} -> do
updatedMember <- withStore $ \db -> updateUnknownMemberAnnounced db vr user m unknownMember memInfo initialStatus
toView $ CEvtUnknownMemberAnnounced user gInfo m unknownMember updatedMember
memberAnnouncedToView updatedMember
(updatedMember, gInfo') <- withStore $ \db -> do
updatedMember <- updateUnknownMemberAnnounced db vr user m unknownMember memInfo initialStatus
gInfo' <- if memberPending updatedMember
then liftIO $ increaseGroupMembersRequireAttention db user gInfo
else pure gInfo
pure (updatedMember, gInfo')
toView $ CEvtUnknownMemberAnnounced user gInfo' m unknownMember updatedMember
memberAnnouncedToView updatedMember gInfo'
Right _ -> messageError "x.grp.mem.new error: member already exists"
Left _ -> do
newMember <- withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember initialStatus
memberAnnouncedToView newMember
(newMember, gInfo') <- withStore $ \db -> do
newMember <- createNewGroupMember db user gInfo m memInfo GCPostMember initialStatus
gInfo' <- if memberPending newMember
then liftIO $ increaseGroupMembersRequireAttention db user gInfo
else pure gInfo
pure (newMember, gInfo')
memberAnnouncedToView newMember gInfo'
where
initialStatus = case msgScope_ of
Just (MSMember _) -> GSMemPendingReview
_ -> GSMemAnnounced
memberAnnouncedToView announcedMember@GroupMember {groupMemberId, memberProfile} = do
memberAnnouncedToView announcedMember@GroupMember {groupMemberId, memberProfile} gInfo' = do
(announcedMember', scopeInfo) <- getMemNewChatScope announcedMember
let event = RGEMemberAdded groupMemberId (fromLocalProfile memberProfile)
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo scopeInfo m) msg brokerTs (CIRcvGroupEvent event)
groupMsgToView gInfo scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m) msg brokerTs (CIRcvGroupEvent event)
groupMsgToView cInfo ci
case scopeInfo of
Just (GCSIMemberSupport _) -> do
createInternalChatItem user (CDGroupRcv gInfo scopeInfo m) (CIRcvGroupEvent RGENewMemberPendingReview) (Just brokerTs)
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m) (CIRcvGroupEvent RGENewMemberPendingReview) (Just brokerTs)
_ -> pure ()
toView $ CEvtJoinedGroupMemberConnecting user gInfo m announcedMember'
toView $ CEvtJoinedGroupMemberConnecting user gInfo' m announcedMember'
getMemNewChatScope announcedMember = case msgScope_ of
Nothing -> pure (announcedMember, Nothing)
Just (MSMember _) -> do
@@ -2571,8 +2587,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise = do
withStore' $ \db -> updateGroupMemberRole db user member memRole
(gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo' m
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView gInfo'' scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView cInfo ci
toView CEvtMemberRole {user, groupInfo = gInfo'', byMember = m', member = member {memberRole = memRole}, fromRole, toRole = memRole}
checkHostRole :: GroupMember -> GroupMemberRole -> CM ()
@@ -2600,8 +2616,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toggleNtf bm' (not blocked)
let ciContent = CIRcvGroupEvent $ RGEMemberBlocked bmId (fromLocalProfile bmp) blocked
(gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs ciContent
groupMsgToView gInfo' scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs ciContent
groupMsgToView cInfo ci
toView CEvtMemberBlockedForAll {user, groupInfo = gInfo', byMember = m', member = bm, blocked}
Left (SEGroupMemberNotFoundByMemberId _) -> do
bm <- createUnknownMember gInfo memId
@@ -2685,8 +2701,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise = a
deleteMemberItem gEvent = do
(gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView gInfo' scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
groupMsgToView cInfo ci
deleteMessages :: MsgDirectionI d => GroupMember -> SMsgDirection d -> CM ()
deleteMessages delMem msgDir
| groupFeatureMemberAllowed SGFFullDelete m gInfo = deleteGroupMemberCIs user gInfo delMem m msgDir
@@ -2698,8 +2714,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- member record is not deleted to allow creation of "member left" chat item
withStore' $ \db -> updateGroupMemberStatus db userId m GSMemLeft
(gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEMemberLeft)
groupMsgToView gInfo' scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEMemberLeft)
groupMsgToView cInfo ci
toView $ CEvtLeftMember user gInfo' m' {memberStatus = GSMemLeft}
xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM ()
@@ -2712,8 +2728,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- member records are not deleted to keep history
deleteMembersConnections user ms
(gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo m
ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEGroupDeleted)
groupMsgToView gInfo'' scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEGroupDeleted)
groupMsgToView cInfo ci
toView $ CEvtGroupDeleted user gInfo'' {membership = membership {memberStatus = GSMemGroupDeleted}} m'
xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM ()
@@ -2726,8 +2742,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtGroupUpdated user g g'' (Just m')
let cd = CDGroupRcv g'' scopeInfo m'
unless (sameGroupProfileInfo p p') $ do
ci <- saveRcvChatItemNoParse user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
groupMsgToView g'' scopeInfo ci
(ci, cInfo) <- saveRcvChatItemNoParse user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
groupMsgToView cInfo ci
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g''
Just _ -> updateGroupPrefs_ g m $ fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
@@ -2787,8 +2803,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
createInternalChatItem user (CDGroupRcv g' scopeInfo m'') (CIRcvGroupEvent RGEMemberCreatedContact) Nothing
toView $ CEvtNewMemberContactReceivedInv user mCt' g' m''
forM_ mContent_ $ \mc -> do
ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc, msgContentTexts mc)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat mCt') ci]
(ci, cInfo) <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc, msgContentTexts mc)
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
securityCodeChanged :: Contact -> CM ()
securityCodeChanged ct = do
+1 -2
View File
@@ -350,14 +350,13 @@ data ChatStats = ChatStats
{ unreadCount :: Int, -- returned both in /_get chat initial API and in /_get chats API
unreadMentions :: Int, -- returned both in /_get chat initial API and in /_get chats API
reportsCount :: Int, -- returned both in /_get chat initial API and in /_get chats API
supportChatsUnreadCount :: Int, -- returned both in /_get chat initial API and in /_get chats API
minUnreadItemId :: ChatItemId,
unreadChat :: Bool
}
deriving (Show)
emptyChatStats :: ChatStats
emptyChatStats = ChatStats 0 0 0 0 0 False
emptyChatStats = ChatStats 0 0 0 0 False
data NavigationInfo = NavigationInfo
{ afterUnread :: Int,
+2 -1
View File
@@ -137,7 +137,8 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+43 -5
View File
@@ -80,6 +80,8 @@ module Simplex.Chat.Store.Groups
updateGroupMemberStatus,
updateGroupMemberStatusById,
updateGroupMemberAccepted,
updateGroupMembersRequireAttention,
increaseGroupMembersRequireAttention,
createNewGroupMember,
checkGroupMemberHasItems,
deleteGroupMember,
@@ -279,7 +281,8 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -362,7 +365,8 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
customData = Nothing
customData = Nothing,
membersRequireAttention = 0
}
-- | creates a new group record for the group the current user was invited to, or returns an existing one
@@ -432,7 +436,8 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
customData = Nothing
customData = Nothing,
membersRequireAttention = 0
},
groupMemberId
)
@@ -768,7 +773,8 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
@@ -1224,6 +1230,37 @@ updateGroupMemberAccepted db User {userId} m@GroupMember {groupMemberId} status
(status, role, currentTs, userId, groupMemberId)
pure m {memberStatus = status, memberRole = role, updatedAt = currentTs}
updateGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> IO GroupInfo
updateGroupMembersRequireAttention db user@User {userId} g@GroupInfo {groupId, membersRequireAttention} member member'
| nowRequires && not didRequire =
increaseGroupMembersRequireAttention db user g
| not nowRequires && didRequire = do
DB.execute
db
[sql|
UPDATE groups
SET members_require_attention = members_require_attention - 1
WHERE user_id = ? AND group_id = ?
|]
(userId, groupId)
pure g {membersRequireAttention = membersRequireAttention - 1}
| otherwise = pure g
where
didRequire = gmRequiresAttention member
nowRequires = gmRequiresAttention member'
increaseGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> IO GroupInfo
increaseGroupMembersRequireAttention db User {userId} g@GroupInfo {groupId, membersRequireAttention} = do
DB.execute
db
[sql|
UPDATE groups
SET members_require_attention = members_require_attention + 1
WHERE user_id = ? AND group_id = ?
|]
(userId, groupId)
pure g {membersRequireAttention = membersRequireAttention + 1}
-- | add new member with profile
createNewGroupMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
createNewGroupMember db user gInfo invitingMember memInfo@MemberInfo {profile} memCategory memStatus = do
@@ -1561,7 +1598,8 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+150 -97
View File
@@ -369,56 +369,103 @@ type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bo
data MemberAttention = MAInc Int | MAReset
deriving (Show)
updateChatTsStats :: DB.Connection -> User -> ChatDirection c d -> UTCTime -> Maybe (Int, MemberAttention, Int) -> IO ()
updateChatTsStats db User {userId} chatDirection chatTs chatStats_ = case toChatInfo chatDirection of
DirectChat Contact {contactId} ->
updateChatTsStats :: DB.Connection -> VersionRangeChat -> User -> ChatDirection c d -> UTCTime -> Maybe (Int, MemberAttention, Int) -> IO (ChatInfo c)
updateChatTsStats db vr user@User {userId} chatDirection chatTs chatStats_ = case toChatInfo chatDirection of
DirectChat ct@Contact {contactId} -> do
DB.execute
db
"UPDATE contacts SET chat_ts = ?, chat_deleted = 0 WHERE user_id = ? AND contact_id = ?"
(chatTs, userId, contactId)
GroupChat GroupInfo {groupId, membership} scopeInfo -> do
pure $ DirectChat ct {chatTs = Just chatTs}
GroupChat g@GroupInfo {groupId} Nothing -> do
DB.execute
db
"UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?"
(chatTs, userId, groupId)
forM_ scopeInfo $ \GCSIMemberSupport {groupMember_} -> do
let gmId = groupMemberId' $ fromMaybe membership groupMember_
case chatStats_ of
Nothing ->
DB.execute
db
"UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?"
(chatTs, gmId)
Just (unread, MAInc unanswered, mentions) ->
DB.execute
db
[sql|
UPDATE group_members
SET support_chat_ts = ?,
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 + ?
WHERE group_member_id = ?
|]
(chatTs, unread, unanswered, mentions, gmId)
Just (unread, MAReset, mentions) ->
DB.execute
db
[sql|
UPDATE group_members
SET support_chat_ts = ?,
support_chat_items_unread = support_chat_items_unread + ?,
support_chat_items_member_attention = 0,
support_chat_items_mentions = support_chat_items_mentions + ?
WHERE group_member_id = ?
|]
(chatTs, unread, mentions, gmId)
LocalChat NoteFolder {noteFolderId} ->
pure $ GroupChat g {chatTs = Just chatTs} Nothing
GroupChat g@GroupInfo {groupId, membership, membersRequireAttention} (Just GCSIMemberSupport {groupMember_}) ->
case groupMember_ of
Nothing -> do
membership' <- updateGMStats membership
DB.execute
db
"UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?"
(chatTs, userId, groupId)
pure $ GroupChat g {membership = membership', chatTs = Just chatTs} (Just $ GCSIMemberSupport Nothing)
Just member -> do
member' <- updateGMStats member
let didRequire = gmRequiresAttention member
nowRequires = gmRequiresAttention member'
if
| nowRequires && not didRequire -> do
DB.execute
db
[sql|
UPDATE groups
SET chat_ts = ?,
members_require_attention = members_require_attention + 1
WHERE user_id = ? AND group_id = ?
|]
(chatTs, userId, groupId)
pure $ GroupChat g {membersRequireAttention = membersRequireAttention + 1, chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
| not nowRequires && didRequire -> do
DB.execute
db
[sql|
UPDATE groups
SET chat_ts = ?,
members_require_attention = members_require_attention - 1
WHERE user_id = ? AND group_id = ?
|]
(chatTs, userId, groupId)
pure $ GroupChat g {membersRequireAttention = membersRequireAttention - 1, chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
| otherwise -> do
DB.execute
db
"UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?"
(chatTs, userId, groupId)
pure $ GroupChat g {chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
where
updateGMStats m@GroupMember {groupMemberId} = do
case chatStats_ of
Nothing ->
DB.execute
db
"UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?"
(chatTs, groupMemberId)
Just (unread, MAInc unanswered, mentions) ->
DB.execute
db
[sql|
UPDATE group_members
SET support_chat_ts = ?,
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 + ?
WHERE group_member_id = ?
|]
(chatTs, unread, unanswered, mentions, groupMemberId)
Just (unread, MAReset, mentions) ->
DB.execute
db
[sql|
UPDATE group_members
SET support_chat_ts = ?,
support_chat_items_unread = support_chat_items_unread + ?,
support_chat_items_member_attention = 0,
support_chat_items_mentions = support_chat_items_mentions + ?
WHERE group_member_id = ?
|]
(chatTs, unread, mentions, groupMemberId)
m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
LocalChat nf@NoteFolder {noteFolderId} -> do
DB.execute
db
"UPDATE note_folders SET chat_ts = ? WHERE user_id = ? AND note_folder_id = ?"
(chatTs, userId, noteFolderId)
_ -> pure ()
pure $ LocalChat nf {chatTs = chatTs}
cInfo -> pure cInfo
setSupportChatTs :: DB.Connection -> GroupMemberId -> UTCTime -> IO ()
setSupportChatTs db groupMemberId chatTs =
@@ -636,13 +683,13 @@ type ChatStatsRow = (Int, ChatItemId, BoolInt)
toChatStats :: ChatStatsRow -> ChatStats
toChatStats (unreadCount, minUnreadItemId, BI unreadChat) =
ChatStats {unreadCount, unreadMentions = 0, reportsCount = 0, supportChatsUnreadCount = 0, minUnreadItemId, unreadChat}
ChatStats {unreadCount, unreadMentions = 0, reportsCount = 0, minUnreadItemId, unreadChat}
type GroupStatsRow = (Int, Int, Int, Int, ChatItemId, BoolInt)
type GroupStatsRow = (Int, Int, Int, ChatItemId, BoolInt)
toGroupStats :: GroupStatsRow -> ChatStats
toGroupStats (unreadCount, unreadMentions, reportsCount, supportChatsUnreadCount, minUnreadItemId, BI unreadChat) =
ChatStats {unreadCount, unreadMentions, reportsCount, supportChatsUnreadCount, minUnreadItemId, unreadChat}
toGroupStats (unreadCount, unreadMentions, reportsCount, minUnreadItemId, BI unreadChat) =
ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat}
findDirectChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
findDirectChatPreviews_ db User {userId} pagination clq =
@@ -763,7 +810,6 @@ findGroupChatPreviews_ db User {userId} pagination clq =
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -780,16 +826,8 @@ findGroupChatPreviews_ db User {userId} pagination clq =
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
|]
baseParams = (userId, userId, CISRcvNew, userId, MCReport_, BI False, userId, CISRcvNew)
baseParams = (userId, userId, CISRcvNew, userId, MCReport_, BI False)
getPreviews = case clq of
CLQFilters {favorite = False, unread = False} -> do
let q = baseQuery <> " WHERE g.user_id = ?"
@@ -1494,16 +1532,14 @@ getGroupChatInitial_ db user g scopeInfo_ contentFilter count = do
where
getStats minUnreadItemId (unreadCount, unreadMentions) = do
reportsCount <- getGroupReportsCount_ db user g False
supportChatsUnreadCount <- getGroupSupportChatsUnreadCount_ db user g
pure ChatStats {unreadCount, unreadMentions, reportsCount, supportChatsUnreadCount, minUnreadItemId, unreadChat = False}
pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False}
getGroupStats_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> ExceptT StoreError IO ChatStats
getGroupStats_ db user g scopeInfo_ = do
minUnreadItemId <- fromMaybe 0 <$> getGroupMinUnreadId_ db user g scopeInfo_ Nothing
(unreadCount, unreadMentions) <- getGroupUnreadCount_ db user g scopeInfo_ Nothing
reportsCount <- liftIO $ getGroupReportsCount_ db user g False
supportChatsUnreadCount <- liftIO $ getGroupSupportChatsUnreadCount_ db user g
pure ChatStats {unreadCount, unreadMentions, reportsCount, supportChatsUnreadCount, minUnreadItemId, unreadChat = False}
pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False}
getGroupMinUnreadId_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Maybe ChatItemId)
getGroupMinUnreadId_ db user g scopeInfo_ contentFilter =
@@ -1527,14 +1563,6 @@ getGroupReportsCount_ db User {userId} GroupInfo {groupId} archived =
"SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0"
(userId, groupId, MCReport_, BI archived)
getGroupSupportChatsUnreadCount_ :: DB.Connection -> User -> GroupInfo -> IO Int
getGroupSupportChatsUnreadCount_ db User {userId} GroupInfo {groupId} =
fromOnly . head
<$> DB.query
db
"SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NOT NULL AND item_status = ?"
(userId, groupId, CISRcvNew)
queryUnreadGroupItems :: FromRow r => DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> Query -> Query -> ExceptT StoreError IO [r]
queryUnreadGroupItems db User {userId} GroupInfo {groupId} scopeInfo_ contentFilter baseQuery orderLimit =
case (scopeInfo_, contentFilter) of
@@ -1954,14 +1982,14 @@ getGroupUnreadTimedItems db User {userId} groupId =
|]
(userId, groupId, CISRcvNew)
updateGroupChatItemsReadList :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScope -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)]
updateGroupChatItemsReadList db User {userId} GroupInfo {groupId, membership} scope itemIds = do
currentTs <- getCurrentTime
updateGroupChatItemsReadList :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScope -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo)
updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId, membership, membersRequireAttention} scope itemIds = do
currentTs <- liftIO getCurrentTime
-- Possible improvement is to differentiate retrieval queries for each scope,
-- but we rely on UI to not pass item IDs from incorrect scope.
readItemsData <- catMaybes . L.toList <$> mapM (getUpdateGroupItem currentTs) itemIds
updateChatStats readItemsData
pure $ timedItems readItemsData
readItemsData <- liftIO $ catMaybes . L.toList <$> mapM (getUpdateGroupItem currentTs) itemIds
g' <- updateChatStats readItemsData
pure (timedItems readItemsData, g')
where
getUpdateGroupItem :: UTCTime -> ChatItemId -> IO (Maybe (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt))
getUpdateGroupItem currentTs itemId =
@@ -1974,36 +2002,61 @@ updateGroupChatItemsReadList db User {userId} GroupInfo {groupId, membership} sc
RETURNING chat_item_id, timed_ttl, timed_delete_at, group_member_id, user_mention
|]
(CISRcvRead, currentTs, userId, groupId, CISRcvNew, itemId)
updateChatStats :: [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> IO ()
updateChatStats :: [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> ExceptT StoreError IO GroupInfo
updateChatStats readItemsData = case scope of
Nothing -> pure ()
Just GCSMemberSupport {groupMemberId_} -> do
let unread = length readItemsData
(unanswered, mentions) = decStats
gmId = fromMaybe (groupMemberId' membership) groupMemberId_
DB.execute
db
[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 - ?
WHERE group_member_id = ?
|]
(unread, unanswered, mentions, gmId)
Nothing -> pure g
Just GCSMemberSupport {groupMemberId_} -> case groupMemberId_ of
Nothing -> do
membership' <- updateGMStats membership
pure g {membership = membership'}
Just groupMemberId -> do
member <- getGroupMemberById db vr user groupMemberId
member' <- updateGMStats member
let didRequire = gmRequiresAttention member
nowRequires = gmRequiresAttention member'
if (not nowRequires && didRequire)
then do
liftIO $
DB.execute
db
[sql|
UPDATE groups
SET members_require_attention = members_require_attention - 1
WHERE user_id = ? AND group_id = ?
|]
(userId, groupId)
pure g {membersRequireAttention = membersRequireAttention - 1}
else
pure g
where
decStats :: (Int, Int)
decStats = foldl' countItem (0, 0) readItemsData
updateGMStats GroupMember {groupMemberId} = do
let unread = length readItemsData
(unanswered, mentions) = decStats
liftIO $
DB.execute
db
[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 - ?
WHERE group_member_id = ?
|]
(unread, unanswered, mentions, groupMemberId)
getGroupMemberById db vr user groupMemberId
where
countItem :: (Int, Int) -> (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt) -> (Int, Int)
countItem (!unanswered, !mentions) (_, _, _, itemGMId_, userMention_) =
let unanswered' = case (groupMemberId_, itemGMId_) of
(Just scopeGMId, Just itemGMId) | itemGMId == scopeGMId -> unanswered + 1
_ -> unanswered
mentions' = case userMention_ of
Just (BI True) -> mentions + 1
_ -> mentions
in (unanswered', mentions')
decStats :: (Int, Int)
decStats = foldl' countItem (0, 0) readItemsData
where
countItem :: (Int, Int) -> (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt) -> (Int, Int)
countItem (!unanswered, !mentions) (_, _, _, itemGMId_, userMention_) =
let unanswered' = case (groupMemberId_, itemGMId_) of
(Just scopeGMId, Just itemGMId) | itemGMId == scopeGMId -> unanswered + 1
_ -> unanswered
mentions' = case userMention_ of
Just (BI True) -> mentions + 1
_ -> mentions
in (unanswered', mentions')
timedItems :: [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> [(ChatItemId, Int)]
timedItems = foldl' addTimedItem []
where
@@ -17,6 +17,8 @@ ALTER TABLE group_members ADD COLUMN support_chat_items_unread BIGINT NOT NULL D
ALTER TABLE group_members ADD COLUMN support_chat_items_member_attention BIGINT NOT NULL DEFAULT 0;
ALTER TABLE group_members ADD COLUMN support_chat_items_mentions BIGINT NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN members_require_attention BIGINT NOT NULL DEFAULT 0;
ALTER TABLE chat_items ADD COLUMN group_scope_tag TEXT;
ALTER TABLE chat_items ADD COLUMN group_scope_group_member_id BIGINT REFERENCES group_members(group_member_id) ON DELETE CASCADE;
@@ -53,6 +55,8 @@ DROP INDEX idx_chat_items_group_scope_group_member_id;
ALTER TABLE chat_items DROP COLUMN group_scope_tag;
ALTER TABLE chat_items DROP COLUMN group_scope_group_member_id;
ALTER TABLE groups DROP COLUMN members_require_attention;
ALTER TABLE group_members DROP COLUMN support_chat_ts;
ALTER TABLE group_members DROP COLUMN support_chat_items_unread;
ALTER TABLE group_members DROP COLUMN support_chat_items_member_attention;
@@ -15,6 +15,8 @@ ALTER TABLE group_members ADD COLUMN support_chat_items_unread INTEGER NOT NULL
ALTER TABLE group_members ADD COLUMN support_chat_items_member_attention INTEGER NOT NULL DEFAULT 0;
ALTER TABLE group_members ADD COLUMN support_chat_items_mentions INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN members_require_attention INTEGER NOT NULL DEFAULT 0;
ALTER TABLE chat_items ADD COLUMN group_scope_tag TEXT;
ALTER TABLE chat_items ADD COLUMN group_scope_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE CASCADE;
@@ -50,6 +52,8 @@ DROP INDEX idx_chat_items_group_scope_group_member_id;
ALTER TABLE chat_items DROP COLUMN group_scope_tag;
ALTER TABLE chat_items DROP COLUMN group_scope_group_member_id;
ALTER TABLE groups DROP COLUMN members_require_attention;
ALTER TABLE group_members DROP COLUMN support_chat_ts;
ALTER TABLE group_members DROP COLUMN support_chat_items_unread;
ALTER TABLE group_members DROP COLUMN support_chat_items_member_attention;
@@ -1,3 +1,39 @@
Query:
UPDATE groups
SET members_require_attention = members_require_attention - 1
WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups 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 - ?
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE groups
SET chat_ts = ?,
members_require_attention = members_require_attention + 1
WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE groups
SET chat_ts = ?,
members_require_attention = members_require_attention - 1
WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
INSERT INTO group_members
( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
@@ -36,7 +72,8 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -81,6 +118,28 @@ Query:
Plan:
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_members
SET support_chat_ts = ?,
support_chat_items_unread = support_chat_items_unread + ?,
support_chat_items_member_attention = 0,
support_chat_items_mentions = support_chat_items_mentions + ?
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_members
SET support_chat_ts = ?,
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 + ?
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
INSERT INTO contact_requests
(user_contact_link_id, agent_invitation_id, peer_chat_min_version, peer_chat_max_version, contact_profile_id, local_display_name, user_id,
@@ -621,38 +680,6 @@ Query:
Plan:
SEARCH chat_items 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 - ?
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_members
SET support_chat_ts = ?,
support_chat_items_unread = support_chat_items_unread + ?,
support_chat_items_member_attention = 0,
support_chat_items_mentions = support_chat_items_mentions + ?
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_members
SET support_chat_ts = ?,
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 + ?
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
DELETE FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ?
@@ -840,7 +867,8 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -887,7 +915,8 @@ Query:
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
@@ -1270,16 +1299,6 @@ Query:
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_members
SET support_chat_items_unread = 0,
support_chat_items_member_attention = 0,
support_chat_items_mentions = 0
WHERE group_member_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_profiles
SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ?
@@ -1294,6 +1313,14 @@ SEARCH group_profiles USING INTEGER PRIMARY KEY (rowid=?)
LIST SUBQUERY 1
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE groups
SET members_require_attention = members_require_attention - 1
WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE user_contact_links
SET auto_accept = ?, business_address = ?, auto_accept_incognito = ?, auto_reply_msg_content = ?
@@ -1948,7 +1975,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -1965,14 +1991,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
WHERE g.user_id = ?
@@ -1988,13 +2006,10 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH gp USING INTEGER PRIMARY KEY (rowid=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2012,7 +2027,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2029,14 +2043,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND (g.favorite = 1
@@ -2047,12 +2053,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2070,7 +2073,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2087,14 +2089,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
@@ -2104,12 +2098,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts<?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2127,7 +2118,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2144,14 +2134,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
@@ -2161,12 +2143,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2184,7 +2163,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2201,14 +2179,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND (g.unread_chat = 1 OR ChatStats.UnreadCount > 0)
@@ -2218,12 +2188,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2241,7 +2208,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2258,14 +2224,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND g.favorite = 1
@@ -2275,12 +2233,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts<?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2298,7 +2253,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2315,14 +2269,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND g.favorite = 1
@@ -2332,12 +2278,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2355,7 +2298,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2372,14 +2314,6 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ?
AND g.favorite = 1
@@ -2389,12 +2323,9 @@ MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2412,7 +2343,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2429,26 +2359,15 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ? AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts<?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2466,7 +2385,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2483,26 +2401,15 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ? AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?
Plan:
MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -2520,7 +2427,6 @@ Query:
COALESCE(ChatStats.UnreadCount, 0),
COALESCE(ChatStats.UnreadMentions, 0),
COALESCE(ReportCount.Count, 0),
COALESCE(SupportChatsUnreadCount.Count, 0),
COALESCE(ChatStats.MinUnread, 0),
g.unread_chat
FROM groups g
@@ -2537,26 +2443,15 @@ Query:
AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
GROUP BY group_id
) ReportCount ON ReportCount.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS Count
FROM chat_items
WHERE user_id = ? AND group_id IS NOT NULL
AND group_scope_tag IS NOT NULL
AND item_status = ?
GROUP BY group_id
) SupportChatsUnreadCount ON SupportChatsUnreadCount.group_id = g.group_id
WHERE g.user_id = ? ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
MATERIALIZE SupportChatsUnreadCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH SupportChatsUnreadCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
@@ -4526,6 +4421,14 @@ Query:
Plan:
SEARCH group_snd_item_statuses USING INDEX idx_group_snd_item_statuses_chat_item_id_group_member_id (chat_item_id=? AND group_member_id=?)
Query:
UPDATE groups
SET members_require_attention = members_require_attention + 1
WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE groups
SET via_group_link_uri_hash = (SELECT via_contact_uri_hash FROM connections WHERE connection_id = ?)
@@ -4625,7 +4528,8 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -4648,7 +4552,8 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -5638,10 +5543,6 @@ Query: SELECT COUNT(1) FROM chat_item_versions WHERE chat_item_id = ?
Plan:
SEARCH chat_item_versions USING COVERING INDEX idx_chat_item_versions_chat_item_id (chat_item_id=?)
Query: SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NOT NULL AND item_status = ?
Plan:
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id=? AND group_scope_tag>?)
Query: SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
Plan:
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id=? AND msg_content_tag=? AND item_deleted=? AND item_sent=?)
@@ -134,7 +134,8 @@ CREATE TABLE groups(
business_xcontact_id BLOB NULL,
customer_member_id BLOB NULL,
chat_item_ttl INTEGER,
local_alias TEXT DEFAULT '', -- received
local_alias TEXT DEFAULT '',
members_require_attention INTEGER NOT NULL DEFAULT 0, -- received
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
+5 -4
View File
@@ -579,18 +579,18 @@ safeDeleteLDN db User {userId} localDisplayName = do
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64, Int) :. GroupMemberRow
type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64)
toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) =
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL, membersRequireAttention) :. userMemberRow) =
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission}
businessChat = toBusinessChatInfo businessRow
in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData}
in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData, membersRequireAttention}
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences) :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions)) =
@@ -622,7 +622,8 @@ groupInfoQuery =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+9 -1
View File
@@ -421,7 +421,8 @@ data GroupInfo = GroupInfo
chatTags :: [ChatTagId],
chatItemTTL :: Maybe Int64,
uiThemes :: Maybe UIThemeEntityOverrides,
customData :: Maybe CustomData
customData :: Maybe CustomData,
membersRequireAttention :: Int
}
deriving (Eq, Show)
@@ -854,6 +855,13 @@ data GroupSupportChat = GroupSupportChat
}
deriving (Eq, Show)
gmRequiresAttention :: GroupMember -> Bool
gmRequiresAttention m@GroupMember {supportChat} =
memberPending m || maybe False supportChatAttention supportChat
where
supportChatAttention GroupSupportChat {memberAttention, mentions} =
memberAttention > 0 || mentions > 0
data GroupMemberRef = GroupMemberRef {groupMemberId :: Int64, profile :: Profile}
deriving (Eq, Show)
+1
View File
@@ -192,6 +192,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
CRSentConfirmation u _ -> ttyUser u ["confirmation sent!"]
CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
CRSentInvitationToContact u _c customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
CRItemsReadForChat u chatId -> ttyUser u ["items read for chat"]
CRContactDeleted u c -> ttyUser u [ttyContact' c <> ": contact is deleted"]
CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo
CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c
+2 -2
View File
@@ -372,7 +372,7 @@ testMarkReadDirect = testChat2 aliceProfile bobProfile $ \alice bob -> do
bob ##> "/last_item_id"
i :: ChatItemId <- read <$> getTermLine bob
let itemIds = intercalate "," $ map show [i - 3 .. i]
bob #$> ("/_read chat items @2 " <> itemIds, id, "ok")
bob #$> ("/_read chat items @2 " <> itemIds, id, "items read for chat")
testChatPaginationInitial :: HasCallStack => TestParams -> IO ()
testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice bob -> do
@@ -391,7 +391,7 @@ testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice
-- Read next 2 items
let itemIds = intercalate "," $ map itemId [1 .. 2]
bob #$> ("/_read chat items @2 " <> itemIds, id, "ok")
bob #$> ("/_read chat items @2 " <> itemIds, id, "items read for chat")
bob #$> ("/_get chat @2 initial=2", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")])
-- Read all items
+4 -4
View File
@@ -371,7 +371,7 @@ testMarkReadGroup = testChat2 aliceProfile bobProfile $ \alice bob -> do
bob ##> "/last_item_id"
i :: ChatItemId <- read <$> getTermLine bob
let itemIds = intercalate "," $ map show [i - 3 .. i]
bob #$> ("/_read chat items #1 " <> itemIds, id, "ok")
bob #$> ("/_read chat items #1 " <> itemIds, id, "items read for chat")
testChatPaginationInitial :: HasCallStack => TestParams -> IO ()
testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice bob -> do
@@ -392,7 +392,7 @@ testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice
-- Read next 2 items
let itemIds = intercalate "," $ map groupItemId [1 .. 2]
bob #$> ("/_read chat items #1 " <> itemIds, id, "ok")
bob #$> ("/_read chat items #1 " <> itemIds, id, "items read for chat")
bob #$> ("/_get chat #1 initial=2", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")])
-- Read all items
@@ -7072,12 +7072,12 @@ testScopedSupportUnreadStats =
bob ##> "/member support chats #team"
bob <## "support: unread: 3, require attention: 0, mentions: 0"
alice #$> ("/_read chat items #1(_support:2) " <> aliceMentionedByDanItemId, id, "ok")
alice #$> ("/_read chat items #1(_support:2) " <> aliceMentionedByDanItemId, id, "items read for chat")
alice ##> "/member support chats #team"
alice <## "bob (Bob) (id 2): unread: 4, require attention: 2, mentions: 1"
alice #$> ("/_read chat items #1(_support:2) " <> aliceMentionedByBobItemId, id, "ok")
alice #$> ("/_read chat items #1(_support:2) " <> aliceMentionedByBobItemId, id, "items read for chat")
alice ##> "/member support chats #team"
alice <## "bob (Bob) (id 2): unread: 3, require attention: 1, mentions: 0"