mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 19:23:36 +00:00
core: support chat unread stats (#5844)
This commit is contained in:
1 parent
b9adc7b345
commit
5262b8caca
9 files changed
+300
-77
No files matched your search
+24
@@ -898,6 +898,28 @@ 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)
|
||||
}
|
||||
@@ -1315,6 +1337,8 @@ 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
|
||||
|
||||
+3
-1
@@ -2559,10 +2559,12 @@ object ChatController {
|
||||
if (active(r.user)) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.addChatItem(rhId, cInfo, cItem)
|
||||
// TODO [knocking] increase support chats unread count; move `isActiveReport` checks inside model functions?
|
||||
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)
|
||||
|
||||
+66
-19
@@ -63,6 +63,7 @@ fun ChatView(
|
||||
onComposed: suspend (chatId: String) -> Unit
|
||||
) {
|
||||
val showSearch = rememberSaveable { mutableStateOf(false) }
|
||||
val chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }
|
||||
// They have their own iterator inside for a reason to prevent crash "Reading a state that was created after the snapshot..."
|
||||
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
|
||||
val activeChatInfo = remember { derivedStateOf {
|
||||
@@ -78,7 +79,7 @@ fun ChatView(
|
||||
} }
|
||||
val user = chatModel.currentUser.value
|
||||
val chatInfo = activeChatInfo.value
|
||||
if (chatInfo == null || user == null) {
|
||||
if (chat == null || chatInfo == null || user == null) {
|
||||
LaunchedEffect(Unit) {
|
||||
chatModel.chatId.value = null
|
||||
ModalManager.end.closeModals()
|
||||
@@ -139,6 +140,7 @@ fun ChatView(
|
||||
chatsCtx.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0
|
||||
}
|
||||
}
|
||||
val reportsCount = reportsCount(chatInfo.id)
|
||||
val clipboard = LocalClipboardManager.current
|
||||
CompositionLocalProvider(
|
||||
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
|
||||
@@ -340,7 +342,7 @@ fun ChatView(
|
||||
}
|
||||
}
|
||||
},
|
||||
showGroupReports = {
|
||||
showReportsOrSupportChats = {
|
||||
val info = activeChatInfo.value ?: return@ChatLayout
|
||||
if (ModalManager.end.hasModalsOpen()) {
|
||||
ModalManager.end.closeModals()
|
||||
@@ -348,7 +350,30 @@ fun ChatView(
|
||||
}
|
||||
hideKeyboard(view)
|
||||
scope.launch {
|
||||
showGroupReportsView(staleChatId, scrollToItemId, info)
|
||||
if (reportsCount > 0) {
|
||||
showGroupReportsView(staleChatId, scrollToItemId, info)
|
||||
} else if (info is ChatInfo.Group && info.groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
|
||||
ModalManager.end.showCustomModal { close ->
|
||||
MemberSupportView(
|
||||
chatRh,
|
||||
chat,
|
||||
info.groupInfo,
|
||||
scrollToItemId,
|
||||
close
|
||||
)
|
||||
}
|
||||
} else if (info is ChatInfo.Group) {
|
||||
val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null)
|
||||
val supportChatInfo = ChatInfo.Group(info.groupInfo, groupChatScope = scopeInfo)
|
||||
scope.launch {
|
||||
showMemberSupportChatView(
|
||||
chatModel.chatId,
|
||||
scrollToItemId = scrollToItemId,
|
||||
supportChatInfo,
|
||||
scopeInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
||||
@@ -599,6 +624,9 @@ 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(
|
||||
chatRh,
|
||||
@@ -705,7 +733,7 @@ fun ChatLayout(
|
||||
selectedChatItems: MutableState<Set<Long>?>,
|
||||
back: () -> Unit,
|
||||
info: () -> Unit,
|
||||
showGroupReports: () -> Unit,
|
||||
showReportsOrSupportChats: () -> Unit,
|
||||
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||
loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
|
||||
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
||||
@@ -856,9 +884,10 @@ fun ChatLayout(
|
||||
}
|
||||
}
|
||||
val reportsCount = reportsCount(chatInfo?.id)
|
||||
val supportChatsUnreadCount = supportChatsUnreadCount(chatInfo?.id)
|
||||
if (oneHandUI.value && chatBottomBar.value) {
|
||||
if (chatsCtx.secondaryContextFilter == null && reportsCount > 0) { // TODO [knocking] support chats unread count toolbar
|
||||
ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports)
|
||||
if (chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportChatsUnreadCount > 0)) {
|
||||
SupportChatsCountToolbar(reportsCount, supportChatsUnreadCount, withStatusBar = true, showReportsOrSupportChats)
|
||||
} else {
|
||||
StatusBarBackground()
|
||||
}
|
||||
@@ -914,8 +943,8 @@ fun ChatLayout(
|
||||
SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
|
||||
}
|
||||
}
|
||||
if (reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) {
|
||||
ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports)
|
||||
if ((reportsCount > 0 || supportChatsUnreadCount > 0) && (!oneHandUI.value || !chatBottomBar.value)) {
|
||||
SupportChatsCountToolbar(reportsCount, supportChatsUnreadCount, withStatusBar = false, showReportsOrSupportChats)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1147,10 +1176,11 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReportedCountToolbar(
|
||||
private fun SupportChatsCountToolbar(
|
||||
reportsCount: Int,
|
||||
supportChatsUnreadCount: Int,
|
||||
withStatusBar: Boolean,
|
||||
showGroupReports: () -> Unit
|
||||
showReportsOrSupportChats: () -> Unit
|
||||
) {
|
||||
Box {
|
||||
val statusBarPadding = if (withStatusBar) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else 0.dp
|
||||
@@ -1159,18 +1189,25 @@ private fun ReportedCountToolbar(
|
||||
.fillMaxWidth()
|
||||
.height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding)
|
||||
.background(MaterialTheme.colors.background)
|
||||
.clickable(onClick = showGroupReports)
|
||||
.clickable(onClick = showReportsOrSupportChats)
|
||||
.padding(top = statusBarPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
|
||||
val iconColor = if (reportsCount == 0) MaterialTheme.colors.primary else MaterialTheme.colors.error
|
||||
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = iconColor)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
if (reportsCount == 1) {
|
||||
stringResource(MR.strings.group_reports_active_one)
|
||||
if (supportChatsUnreadCount == 0) {
|
||||
if (reportsCount == 1) {
|
||||
stringResource(MR.strings.group_reports_active_one)
|
||||
} else {
|
||||
stringResource(MR.strings.group_reports_active).format(reportsCount)
|
||||
}
|
||||
} else if (reportsCount == 0) {
|
||||
stringResource(MR.strings.group_new_support_messages).format(supportChatsUnreadCount)
|
||||
} else {
|
||||
stringResource(MR.strings.group_reports_active).format(reportsCount)
|
||||
String.format(generalGetString(MR.strings.group_reports_active_new_support_messages), reportsCount, supportChatsUnreadCount)
|
||||
},
|
||||
style = MaterialTheme.typography.button
|
||||
)
|
||||
@@ -1255,9 +1292,10 @@ fun BoxScope.ChatItemsList(
|
||||
}
|
||||
val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } }
|
||||
val reportsCount = reportsCount(chatInfo.id)
|
||||
val supportChatsUnreadCount = supportChatsUnreadCount(chatInfo.id)
|
||||
val topPaddingToContent = topPaddingToContent(
|
||||
chatView = chatsCtx.secondaryContextFilter == null,
|
||||
additionalTopBar = chatsCtx.secondaryContextFilter == null && reportsCount > 0 // TODO [knocking] && support chats unread count > 0 ?
|
||||
additionalTopBar = chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportChatsUnreadCount > 0)
|
||||
)
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() })
|
||||
val numberOfBottomAppBars = numberOfBottomAppBars()
|
||||
@@ -1597,7 +1635,7 @@ fun BoxScope.ChatItemsList(
|
||||
),
|
||||
reverseLayout = true,
|
||||
additionalBarOffset = composeViewHeight,
|
||||
additionalTopBar = rememberUpdatedState(chatsCtx.secondaryContextFilter == null && reportsCount > 0), // TODO [knocking] && support chats unread count > 0 ?
|
||||
additionalTopBar = rememberUpdatedState(chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportChatsUnreadCount > 0)),
|
||||
chatBottomBar = remember { appPrefs.chatBottomBar.state }
|
||||
) {
|
||||
val mergedItemsValue = mergedItems.value
|
||||
@@ -2282,6 +2320,15 @@ fun reportsCount(staleChatId: String?): Int {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun supportChatsUnreadCount(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
|
||||
}
|
||||
}
|
||||
|
||||
private fun reversedChatItemsStatic(chatsCtx: ChatModel.ChatsContext): List<ChatItem> =
|
||||
chatsCtx.chatItems.value.asReversed()
|
||||
|
||||
@@ -2980,7 +3027,7 @@ fun PreviewChatLayout() {
|
||||
selectedChatItems = remember { mutableStateOf(setOf()) },
|
||||
back = {},
|
||||
info = {},
|
||||
showGroupReports = {},
|
||||
showReportsOrSupportChats = {},
|
||||
showMemberInfo = { _, _ -> },
|
||||
loadMessages = { _, _, _ -> },
|
||||
deleteMessage = { _, _ -> },
|
||||
@@ -3058,7 +3105,7 @@ fun PreviewGroupChatLayout() {
|
||||
selectedChatItems = remember { mutableStateOf(setOf()) },
|
||||
back = {},
|
||||
info = {},
|
||||
showGroupReports = {},
|
||||
showReportsOrSupportChats = {},
|
||||
showMemberInfo = { _, _ -> },
|
||||
loadMessages = { _, _, _ -> },
|
||||
deleteMessage = { _, _ -> },
|
||||
|
||||
-1
@@ -27,7 +27,6 @@ import chat.simplex.common.views.chat.*
|
||||
import chat.simplex.common.views.chat.item.ItemAction
|
||||
import chat.simplex.common.views.chatlist.setGroupMembers
|
||||
import chat.simplex.common.views.chatlist.unreadCountStr
|
||||
import chat.simplex.common.views.newchat.AddContactLearnMore
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
+14
@@ -364,6 +364,8 @@ fun ChatPreviewView(
|
||||
progressView()
|
||||
} else if (chat.chatStats.reportsCount > 0) {
|
||||
GroupReportsIcon()
|
||||
} else if (chat.chatStats.supportChatsUnreadCount > 0) {
|
||||
GroupSupportUnreadIcon()
|
||||
} else {
|
||||
IncognitoIcon(chat.chatInfo.incognito)
|
||||
}
|
||||
@@ -559,6 +561,18 @@ fun GroupReportsIcon() {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GroupSupportUnreadIcon() {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_flag),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.size(21.sp.toDp())
|
||||
.offset(x = 2.sp.toDp())
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun groupInvitationPreviewText(currentUserProfileDisplayName: String?, groupInfo: GroupInfo): String {
|
||||
return if (groupInfo.membership.memberIncognito)
|
||||
|
||||
@@ -465,6 +465,8 @@
|
||||
<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_reports_active_new_support_messages">%1$d reports, %2$d new support messages</string>
|
||||
|
||||
<!-- ShareListView.kt -->
|
||||
<string name="share_message">Share message…</string>
|
||||
|
||||
Reference in new issue
Block a user