ui: fix marking chat read (don't use range api) (#5257)

This commit is contained in:
spaced4ndy
2024-11-27 18:30:39 +04:00
committed by GitHub
parent 15fae29e5b
commit 9fa968a593
7 changed files with 63 additions and 73 deletions
@@ -333,9 +333,8 @@ object ChatModel {
chatItems = arrayListOf(newPreviewItem),
chatStats =
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId
increaseUnreadCounter(rhId, currentUser.value!!)
chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, minUnreadItemId = minUnreadId)
chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1)
}
else
chat.chatStats
@@ -514,23 +513,19 @@ object ChatModel {
}
}
fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, itemIds: List<Long>? = null) {
val cInfo = chatInfo
val markedRead = markItemsReadInCurrentChat(chatInfo, range)
val markedRead = markItemsReadInCurrentChat(chatInfo, itemIds)
// update preview
val chatIdx = getChatIndex(remoteHostId, cInfo.id)
if (chatIdx >= 0) {
val chat = chats[chatIdx]
val lastId = chat.chatItems.lastOrNull()?.id
if (lastId != null) {
val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0
val unreadCount = if (itemIds != null) chat.chatStats.unreadCount - markedRead else 0
decreaseUnreadCounter(remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
chats[chatIdx] = chat.copy(
chatStats = chat.chatStats.copy(
unreadCount = unreadCount,
// Can't use minUnreadItemId currently since chat items can have unread items between read items
//minUnreadItemId = if (range != null) kotlin.math.max(chat.chatStats.minUnreadItemId, range.to + 1) else lastId + 1
)
chatStats = chat.chatStats.copy(unreadCount = unreadCount)
)
}
}
@@ -642,21 +637,17 @@ object ChatModel {
}
}
private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, range: CC.ItemRange? = null): Int {
private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, itemIds: List<Long>? = null): Int {
val cInfo = chatInfo
var markedRead = 0
if (chatId.value == cInfo.id) {
val items = chatItems.value
var i = items.lastIndex
val itemIdsFromRange = if (range != null) {
(range.from .. range.to).toMutableSet()
} else {
mutableSetOf()
}
val itemIdsFromRange = itemIds?.toMutableSet() ?: mutableSetOf()
val markedReadIds = mutableSetOf<Long>()
while (i >= 0) {
val item = items[i]
if (item.meta.itemStatus is CIStatus.RcvNew && (range == null || itemIdsFromRange.contains(item.id))) {
if (item.meta.itemStatus is CIStatus.RcvNew && (itemIds == null || itemIdsFromRange.contains(item.id))) {
val newItem = item.withStatus(CIStatus.RcvRead())
items[i] = newItem
if (newItem.meta.itemLive != true && newItem.meta.itemTimed?.ttl != null) {
@@ -666,7 +657,7 @@ object ChatModel {
}
markedReadIds.add(item.id)
markedRead++
if (range != null) {
if (itemIds != null) {
itemIdsFromRange.remove(item.id)
// already set all needed items as read, can finish the loop
if (itemIdsFromRange.isEmpty()) break
@@ -674,7 +665,7 @@ object ChatModel {
}
i--
}
chatItemsChangesListener?.read(if (range != null) markedReadIds else null, items)
chatItemsChangesListener?.read(if (itemIds != null) markedReadIds else null, items)
}
return markedRead
}
@@ -1599,8 +1599,8 @@ object ChatController {
return null
}
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long, range: CC.ItemRange): Boolean {
val r = sendCmd(rh, CC.ApiChatRead(type, id, range))
suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean {
val r = sendCmd(rh, CC.ApiChatRead(type, id))
if (r is CR.CmdOk) return true
Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}")
return false
@@ -3172,7 +3172,7 @@ sealed class CC {
class ApiGetNetworkStatuses(): CC()
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
class ApiRejectContact(val contactReqId: Long): CC()
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
class ApiChatRead(val type: ChatType, val id: Long): CC()
class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List<Long>): CC()
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC()
@@ -3338,7 +3338,7 @@ sealed class CC {
is ApiEndCall -> "/_call end @${contact.apiId}"
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
is ApiGetNetworkStatuses -> "/_network_statuses"
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
is ApiChatRead -> "/_read chat ${chatRef(type, id)}"
is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}"
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
is ReceiveFile ->
@@ -490,19 +490,35 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
},
addMembers = { groupInfo -> addGroupMembers(view = view, groupInfo = groupInfo, rhId = chatRh, close = { ModalManager.end.closeModals() }) },
openGroupLink = { groupInfo -> openGroupLink(view = view, groupInfo = groupInfo, rhId = chatRh, close = { ModalManager.end.closeModals() }) },
markRead = { range, unreadCountAfter ->
markItemsRead = { itemsIds ->
withBGApi {
withChats {
// It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace
withContext(Dispatchers.Main) {
markChatItemsRead(chatRh, chatInfo, range, unreadCountAfter)
markChatItemsRead(chatRh, chatInfo, itemsIds)
}
ntfManager.cancelNotificationsForChat(chatInfo.id)
chatModel.controller.apiChatItemsRead(
chatRh,
chatInfo.chatType,
chatInfo.apiId,
itemsIds
)
}
}
},
markChatRead = {
withBGApi {
withChats {
// It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace
withContext(Dispatchers.Main) {
markChatItemsRead(chatRh, chatInfo)
}
ntfManager.cancelNotificationsForChat(chatInfo.id)
chatModel.controller.apiChatRead(
chatRh,
chatInfo.chatType,
chatInfo.apiId,
range
chatInfo.apiId
)
}
}
@@ -602,7 +618,8 @@ fun ChatLayout(
showItemDetails: (ChatInfo, ChatItem) -> Unit,
addMembers: (GroupInfo) -> Unit,
openGroupLink: (GroupInfo) -> Unit,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
markItemsRead: (List<Long>) -> Unit,
markChatRead: () -> Unit,
changeNtfsState: (Boolean, currentValue: MutableState<Boolean>) -> Unit,
onSearchValueChanged: (String) -> Unit,
onComposed: suspend (chatId: String) -> Unit,
@@ -649,7 +666,7 @@ fun ChatLayout(
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadMessages, deleteMessage, deleteMessages,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
)
}
}
@@ -937,7 +954,8 @@ fun BoxScope.ChatItemsList(
findModelMember: (String) -> GroupMember?,
setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit,
showItemDetails: (ChatInfo, ChatItem) -> Unit,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
markItemsRead: (List<Long>) -> Unit,
markChatRead: () -> Unit,
onComposed: suspend (chatId: String) -> Unit,
developerTools: Boolean,
showViaProxy: Boolean
@@ -1284,15 +1302,15 @@ fun BoxScope.ChatItemsList(
DateSeparator(last.meta.itemTs)
}
if (item.isRcvNew) {
val (itemIdStart, itemIdEnd) = when (merged) {
is MergedItem.Single -> merged.item.item.id to merged.item.item.id
is MergedItem.Grouped -> merged.items.last().item.id to merged.items.first().item.id
val itemIds = when (merged) {
is MergedItem.Single -> listOf(merged.item.item.id)
is MergedItem.Grouped -> merged.items.map { it.item.id }
}
MarkItemsReadAfterDelay(keyForItem(item), itemIdStart, itemIdEnd, finishedInitialComposition, chatInfo.id, listState, markRead)
MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead)
}
}
}
FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markRead, listState)
FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markChatRead, listState)
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), mergedItems, listState)
LaunchedEffect(Unit) {
@@ -1385,7 +1403,7 @@ fun BoxScope.FloatingButtons(
remoteHostId: Long?,
chatInfo: ChatInfo,
searchValue: State<String>,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
markChatRead: () -> Unit,
listState: State<LazyListState>
) {
val scope = rememberCoroutineScope()
@@ -1453,12 +1471,7 @@ fun BoxScope.FloatingButtons(
generalGetString(MR.strings.mark_read),
painterResource(MR.images.ic_check),
onClick = {
val chat = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id } ?: return@ItemAction
val minUnreadItemId = chat.chatStats.minUnreadItemId
markRead(
CC.ItemRange(minUnreadItemId, chat.chatItems.lastOrNull()?.id ?: return@ItemAction),
0
)
markChatRead()
showDropDown.value = false
})
}
@@ -1779,12 +1792,11 @@ private fun DateSeparator(date: Instant) {
@Composable
private fun MarkItemsReadAfterDelay(
itemKey: String,
itemIdStart: Long,
itemIdEnd: Long,
itemIds: List<Long>,
finishedInitialComposition: State<Boolean>,
chatId: ChatId,
listState: State<LazyListState>,
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit
markItemsRead: (List<Long>) -> Unit
) {
// items can be "visible" in terms of LazyColumn but hidden behind compose view/appBar. So don't count such item as visible and not mark read
val itemIsPartiallyAboveCompose = remember { derivedStateOf {
@@ -1795,11 +1807,11 @@ private fun MarkItemsReadAfterDelay(
false
}
} }
LaunchedEffect(itemIsPartiallyAboveCompose.value, itemIdStart, itemIdEnd, finishedInitialComposition.value, chatId) {
LaunchedEffect(itemIsPartiallyAboveCompose.value, itemIds, finishedInitialComposition.value, chatId) {
if (chatId != ChatModel.chatId.value || !itemIsPartiallyAboveCompose.value || !finishedInitialComposition.value) return@LaunchedEffect
delay(600L)
markRead(CC.ItemRange(itemIdStart, itemIdEnd), null)
markItemsRead(itemIds)
}
}
@@ -2406,7 +2418,8 @@ fun PreviewChatLayout() {
showItemDetails = { _, _ -> },
addMembers = { _ -> },
openGroupLink = {},
markRead = { _, _ -> },
markItemsRead = { _ -> },
markChatRead = {},
changeNtfsState = { _, _ -> },
onSearchValueChanged = {},
onComposed = {},
@@ -2478,7 +2491,8 @@ fun PreviewGroupChatLayout() {
showItemDetails = { _, _ -> },
addMembers = { _ -> },
openGroupLink = {},
markRead = { _, _ -> },
markItemsRead = { _ -> },
markChatRead = {},
changeNtfsState = { _, _ -> },
onSearchValueChanged = {},
onComposed = {},
@@ -537,15 +537,13 @@ fun markChatRead(c: Chat, chatModel: ChatModel) {
var chat = c
withApi {
if (chat.chatStats.unreadCount > 0) {
val minUnreadItemId = chat.chatStats.minUnreadItemId
withChats {
markChatItemsRead(chat.remoteHostId, chat.chatInfo)
}
chatModel.controller.apiChatRead(
chat.remoteHostId,
chat.chatInfo.chatType,
chat.chatInfo.apiId,
CC.ItemRange(minUnreadItemId, chat.chatItems.last().id)
chat.chatInfo.apiId
)
chat = chatModel.getChat(chat.id) ?: return@withApi
}