Match private conversation confirmations before opening the requested peer
This commit is contained in:
1 parent
69ae8b57cb
commit
321a2c3ed7
3 files changed
+75
-20
No files matched your search
@@ -330,6 +330,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// После смерти процесса ViewModel уже новый; сохранённый экран может потерять свою беседу или звонок.
|
||||
LaunchedEffect(screen) {
|
||||
if (screen != Screen.MemberList && screen != Screen.Dm) vm.cancelDmStart()
|
||||
inputScreen = screen.name
|
||||
LogManager.addLog("INFO", "MAIN", "UI screen=$screen")
|
||||
val unavailable = when (screen) {
|
||||
@@ -382,7 +383,8 @@ class MainActivity : AppCompatActivity() {
|
||||
vm.dmOpen.collect { convId ->
|
||||
if (convId != null) {
|
||||
vm.consumeDmOpen()
|
||||
screen = Screen.Dm
|
||||
if (screen == Screen.MemberList && vm.currentDm.value?.convId == convId) screen = Screen.Dm
|
||||
else LogManager.addLog("DEBUG", "MAIN", "Ignoring stale DM navigation conv=$convId screen=$screen")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -462,8 +464,14 @@ class MainActivity : AppCompatActivity() {
|
||||
isStuck = stuck,
|
||||
cpuLoad = cpuLoad,
|
||||
onCpuLoadVisible = vm::setCpuLoadVisible,
|
||||
onSelectChannel = { ch -> vm.selectChannel(ch); screen = Screen.Chat },
|
||||
onSelectDm = { dm -> vm.openDm(dm.convId, dm.peerNodeId, dm.peerName, dm.generation); screen = Screen.Dm },
|
||||
onSelectChannel = { ch ->
|
||||
vm.selectChannel(ch)
|
||||
if (vm.currentChannel.value?.id == ch.id) screen = Screen.Chat
|
||||
},
|
||||
onSelectDm = { dm ->
|
||||
vm.openDm(dm.convId, dm.peerNodeId, dm.peerName, dm.generation)
|
||||
if (vm.currentDm.value?.convId == dm.convId) screen = Screen.Dm
|
||||
},
|
||||
onCreateChannel = { name -> vm.createChannel(name, "channel_1") },
|
||||
onJoinChannel = { showJoinDialog = true },
|
||||
onInviteClick = showInvite,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.utun.chat.data
|
||||
|
||||
/** Одна ожидаемая навигация: ID принятой worker команды фиксируется до DM_CONV_UPDATED.
|
||||
* Чужое событие сохраняет ожидание. Новый запрос/отмена лишает старый timeout права менять состояние. */
|
||||
class PendingDmOpen {
|
||||
private data class Request(val token: Long, val conversation: String, val peer: Long, val name: String)
|
||||
private var sequence = 0L
|
||||
private var request: Request? = null
|
||||
val pending: Boolean get() = request != null
|
||||
|
||||
fun begin(conversation: String, peer: Long, name: String): Long {
|
||||
require(conversation.toULongOrNull()?.let { it != 0UL } == true && peer != 0L)
|
||||
val token = ++sequence
|
||||
request = Request(token, conversation, peer, name)
|
||||
return token
|
||||
}
|
||||
fun isPending(token: Long): Boolean = request?.token == token
|
||||
fun cancel() { request = null }
|
||||
fun complete(conversation: String, generation: Long): DmConversation? {
|
||||
val current = request ?: return null
|
||||
if (current.conversation != conversation || generation <= 0) return null
|
||||
request = null
|
||||
return DmConversation(convId = conversation, peerNodeId = current.peer, peerName = current.name, generation = generation)
|
||||
}
|
||||
}
|
||||
@@ -349,8 +349,15 @@ class ChatViewModel : ViewModel() {
|
||||
private val _dmOpen = MutableStateFlow<String?>(null)
|
||||
val dmOpen: StateFlow<String?> = _dmOpen
|
||||
|
||||
/* peer-данные начатой беседы — для автооткрытия по событию DM_CONV_UPDATED. */
|
||||
private var pendingStartPeer: Pair<Long, String>? = null
|
||||
private val pendingDmOpen = PendingDmOpen()
|
||||
private var dmStartTimeout: kotlinx.coroutines.Job? = null
|
||||
private val _dmStartFailed = MutableStateFlow(false)
|
||||
val dmStartFailed: StateFlow<Boolean> = _dmStartFailed
|
||||
fun dismissDmStartError() { _dmStartFailed.value = false }
|
||||
|
||||
fun cancelDmStart() {
|
||||
pendingDmOpen.cancel(); dmStartTimeout?.cancel(); dmStartTimeout = null
|
||||
}
|
||||
|
||||
val audioRecorder = AudioRecorderManager()
|
||||
|
||||
@@ -505,6 +512,8 @@ class ChatViewModel : ViewModel() {
|
||||
|
||||
private fun handleEvent(type: Int, data: ByteArray?) {
|
||||
if (type == 13) {
|
||||
if (pendingDmOpen.pending) { _dmStartFailed.value = true; LogManager.addLog("WARN", "PM", "Conversation start interrupted by core stop") }
|
||||
cancelDmStart()
|
||||
cpuLoadPending = false; _cpuLoad.value = 0
|
||||
dmPresenceRefreshJob?.cancel()
|
||||
_dmCallAvailable.value = false
|
||||
@@ -895,16 +904,11 @@ class ChatViewModel : ViewModel() {
|
||||
refreshDmMessages()
|
||||
}
|
||||
}
|
||||
val pend = pendingStartPeer
|
||||
if (pend != null) {
|
||||
pendingStartPeer = null
|
||||
_currentDm.value = DmConversation(convId = convId, peerNodeId = pend.first, peerName = pend.second,
|
||||
generation = generation)
|
||||
_dmCallAvailable.value = false
|
||||
refreshDmCallRoute()
|
||||
refreshDmMessages()
|
||||
_dmOpen.value = convId
|
||||
LogManager.addLog("INFO", "VM", "DM auto-open conv=$convId")
|
||||
pendingDmOpen.complete(convId, generation)?.let { started ->
|
||||
dmStartTimeout?.cancel(); dmStartTimeout = null
|
||||
openDm(started.convId, started.peerNodeId, started.peerName, started.generation)
|
||||
if (_currentDm.value?.convId == convId) _dmOpen.value = convId
|
||||
LogManager.addLog("INFO", "VM", "DM start confirmed conv=$convId peer=${started.peerNodeId}")
|
||||
}
|
||||
}
|
||||
32 -> { /* DM_MSG_RECEIVED: [conv_id_len:1][conv_id:var][author_node_id:8] */
|
||||
@@ -1149,7 +1153,10 @@ class ChatViewModel : ViewModel() {
|
||||
_inviteCandidates.value = emptyList()
|
||||
_inviteLink.value = null
|
||||
_inviteLinkPending.value = true
|
||||
NativeLib.requestInviteCandidates(channelId)
|
||||
if (!NativeLib.requestInviteCandidates(channelId)) {
|
||||
_inviteLinkPending.value = false
|
||||
LogManager.addLog("ERROR", "INVITE", "Candidate request rejected ch=$channelId")
|
||||
}
|
||||
LogManager.addLog("INFO", "VM", "requestInvite ch=$channelId")
|
||||
}
|
||||
|
||||
@@ -1157,7 +1164,10 @@ class ChatViewModel : ViewModel() {
|
||||
_inviteLink.value = null
|
||||
_inviteLinkPending.value = true
|
||||
inviteChannelId = channelId
|
||||
NativeLib.requestInviteLink(channelId, targetNodeId, ++inviteRequestId)
|
||||
if (!NativeLib.requestInviteLink(channelId, targetNodeId, ++inviteRequestId)) {
|
||||
_inviteLinkPending.value = false
|
||||
LogManager.addLog("ERROR", "INVITE", "Link request rejected ch=$channelId target=$targetNodeId")
|
||||
}
|
||||
LogManager.addLog("INFO", "VM", "requestInviteLink ch=$channelId node=0x%016x".format(targetNodeId))
|
||||
}
|
||||
|
||||
@@ -1331,14 +1341,25 @@ class ChatViewModel : ViewModel() {
|
||||
fun startDm(channelId: String, nodeId: Long, name: String) {
|
||||
val r = repo ?: return
|
||||
if (nodeId == 0L || nodeId == NativeLib.getMyNodeId()) return
|
||||
LogManager.addLog("INFO", "VM", "startDm ch=$channelId peer=0x%016x name=$name".format(nodeId))
|
||||
pendingStartPeer = nodeId to name
|
||||
r.dmStart(nodeId, channelId, name)
|
||||
cancelDmStart(); _dmStartFailed.value = false
|
||||
try {
|
||||
val conversation = checkNotNull(r.dmStart(nodeId, channelId, name)) { "Conversation start rejected" }
|
||||
val token = pendingDmOpen.begin(conversation, nodeId, name)
|
||||
LogManager.addLog("INFO", "PM", "Conversation start accepted channel=$channelId peer=$nodeId conv=$conversation")
|
||||
dmStartTimeout = viewModelScope.launch {
|
||||
delay(10_000)
|
||||
if (pendingDmOpen.isPending(token)) {
|
||||
pendingDmOpen.cancel(); _dmStartFailed.value = true
|
||||
LogManager.addLog("ERROR", "PM", "Conversation start confirmation timed out conv=$conversation peer=$nodeId")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) { _dmStartFailed.value = true; LogManager.addLog("ERROR", "PM", "Conversation start failed peer=$nodeId: $e") }
|
||||
}
|
||||
|
||||
fun consumeDmOpen() { _dmOpen.value = null }
|
||||
|
||||
fun openDm(convId: String, peerNodeId: Long, peerName: String, generation: Long) {
|
||||
cancelDmStart()
|
||||
if (repo == null) return
|
||||
_editing.value = null
|
||||
val dm = DmConversation(convId = convId, peerNodeId = peerNodeId, peerName = peerName, generation = generation)
|
||||
@@ -1902,6 +1923,7 @@ class ChatViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun clearState() {
|
||||
cancelDmStart(); _dmStartFailed.value = false; _dmOpen.value = null
|
||||
++channelRefreshRevision; ++messageRefreshRevision; ++jumpRevision
|
||||
memberDetailJob?.cancel(); memberDetailTarget = null; _selectedMember.value = null
|
||||
dismissPmInvite(); _linkJoin.value = null; _joinedChannel.value = null
|
||||
|
||||
Reference in new issue
Block a user