Recover stale Android chat ids
This commit is contained in:
@@ -22,8 +22,8 @@ android {
|
||||
applicationId = "xyz.kusoft.qmax"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 47
|
||||
versionName = "0.1.46"
|
||||
versionCode = 48
|
||||
versionName = "0.1.47"
|
||||
|
||||
buildConfigField("String", "QMAX_DEFAULT_SERVER_URL", "\"https://qmax.kusoft.xyz\"")
|
||||
buildConfigField("String", "QMAX_DEFAULT_PAIRING_CODE", "\"qmax-MxRq4h2HQBEIFs6k\"")
|
||||
|
||||
@@ -20,6 +20,7 @@ import xyz.kusoft.qmax.core.model.MaxBridgeStatusDto
|
||||
import xyz.kusoft.qmax.core.model.MessageDeletedDto
|
||||
import xyz.kusoft.qmax.core.model.MessageDto
|
||||
import xyz.kusoft.qmax.core.model.QMaxSession
|
||||
import xyz.kusoft.qmax.core.network.QMaxHttpException
|
||||
import xyz.kusoft.qmax.core.realtime.QMaxRealtimeClient
|
||||
import java.io.File
|
||||
|
||||
@@ -378,9 +379,16 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
|
||||
if (cached.isNotEmpty() && state.value.selectedChat?.id == chat.id && state.value.messages.isEmpty()) {
|
||||
state.value = state.value.copy(messages = cached)
|
||||
}
|
||||
val fresh = withTimeout(MessageSyncTimeoutMs) {
|
||||
val fresh = try {
|
||||
withTimeout(MessageSyncTimeoutMs) {
|
||||
repository.messages(session, chat.id)
|
||||
}
|
||||
} catch (error: QMaxHttpException) {
|
||||
if (error.code == 404 && recoverMissingChat(session, chat)) {
|
||||
return@launchLoading
|
||||
}
|
||||
throw error
|
||||
}
|
||||
if (state.value.selectedChat?.id == chat.id) {
|
||||
val selected = state.value.selectedChat?.copy(unreadCount = 0)
|
||||
selected?.let(::rememberChatRead)
|
||||
@@ -393,6 +401,61 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun recoverMissingChat(session: QMaxSession, staleChat: ChatDto): Boolean {
|
||||
val freshChats = runCatching {
|
||||
withTimeout(ChatListTimeoutMs) {
|
||||
repository.chats(session)
|
||||
}
|
||||
}.getOrElse {
|
||||
return false
|
||||
}
|
||||
|
||||
val orderedFresh = normalizeChats(freshChats)
|
||||
val replacement = findReplacementChat(staleChat, orderedFresh)
|
||||
if (replacement == null) {
|
||||
state.value = state.value.copy(
|
||||
selectedChat = null,
|
||||
chats = orderedFresh,
|
||||
messages = emptyList(),
|
||||
error = null
|
||||
)
|
||||
repository.cacheChats(session, orderedFresh)
|
||||
return true
|
||||
}
|
||||
|
||||
val openedChat = replacement.copy(unreadCount = 0)
|
||||
rememberChatRead(openedChat)
|
||||
val chats = normalizeChats(
|
||||
orderedFresh.map { chat ->
|
||||
if (chat.id == openedChat.id) chat.copy(unreadCount = 0) else chat
|
||||
},
|
||||
selectedChatId = openedChat.id
|
||||
)
|
||||
state.value = state.value.copy(
|
||||
selectedChat = openedChat,
|
||||
chats = chats,
|
||||
messages = emptyList(),
|
||||
chatPresenceText = null,
|
||||
error = null
|
||||
)
|
||||
repository.cacheChats(session, chats)
|
||||
syncChatRead(openedChat.id)
|
||||
realtime.joinChat(openedChat.id)
|
||||
|
||||
val freshMessages = withTimeout(MessageSyncTimeoutMs) {
|
||||
repository.messages(session, openedChat.id)
|
||||
}
|
||||
if (state.value.selectedChat?.id == openedChat.id) {
|
||||
state.value = state.value.copy(
|
||||
selectedChat = state.value.selectedChat?.copy(unreadCount = 0),
|
||||
chats = normalizeChats(state.value.chats, selectedChatId = openedChat.id),
|
||||
messages = freshMessages
|
||||
)
|
||||
persistCurrentChats()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun sendMessage() = sendComposer(emptyList())
|
||||
|
||||
fun sendComposer(attachmentUris: List<Uri>, onSuccess: () -> Unit = {}) = launchLoading(false) {
|
||||
@@ -731,6 +794,33 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReplacementChat(staleChat: ChatDto, freshChats: List<ChatDto>): ChatDto? {
|
||||
staleChat.externalId?.takeIf { it.isNotBlank() }?.let { externalId ->
|
||||
freshChats.firstOrNull { it.externalId == externalId }?.let { return it }
|
||||
}
|
||||
|
||||
val titleMatches = freshChats.filter { it.title.equals(staleChat.title, ignoreCase = true) }
|
||||
if (titleMatches.size == 1) {
|
||||
return titleMatches.first()
|
||||
}
|
||||
|
||||
val staleAvatar = staleChat.avatarUrl.orEmpty()
|
||||
if (staleAvatar.isNotBlank()) {
|
||||
titleMatches.firstOrNull { it.avatarUrl.orEmpty() == staleAvatar }?.let { return it }
|
||||
val staleFingerprint = avatarFingerprint(staleAvatar)
|
||||
titleMatches.firstOrNull { avatarFingerprint(it.avatarUrl.orEmpty()) == staleFingerprint }?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun avatarFingerprint(value: String): String {
|
||||
if (value.isBlank()) return ""
|
||||
return runCatching {
|
||||
android.net.Uri.parse(value).getQueryParameter("r")
|
||||
}.getOrNull()?.takeIf { it.isNotBlank() } ?: value
|
||||
}
|
||||
|
||||
private suspend fun refreshPresence(chatId: String) {
|
||||
val session = state.value.session ?: return
|
||||
val presence = runCatching {
|
||||
|
||||
Reference in New Issue
Block a user