Browse Source

Track Android PM invitations and joining from message links in ViewModel

master
evgeny 5 days ago
parent
commit
e5a87bd043
  1. 99
      tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

99
tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

@ -25,6 +25,19 @@ import java.io.FileOutputStream
private val IMAGE_EXTENSIONS = setOf("jpg", "jpeg", "png", "gif", "bmp", "webp")
class ChatViewModel : ViewModel() {
data class PmInviteState(val peer: Long, val name: String, val source: String, val groups: List<Channel>,
val groupId: String = "", val requestId: Long = 0, val pending: Boolean = false, val errorRes: Int = 0)
private val _pmInvite = MutableStateFlow<PmInviteState?>(null)
val pmInvite: StateFlow<PmInviteState?> = _pmInvite
private var nextPmInviteId = 0L
data class LinkJoinState(val link: String, val channelId: Long, val nodeId: Long,
val pending: Boolean = true, val result: Int? = null)
private val _linkJoin = MutableStateFlow<LinkJoinState?>(null)
val linkJoin: StateFlow<LinkJoinState?> = _linkJoin
private val _joinedChannel = MutableStateFlow<Channel?>(null)
val joinedChannel: StateFlow<Channel?> = _joinedChannel
data class AttachmentPreparation(val jobs: Set<String> = emptySet(), val failed: Boolean = false)
private val _attachmentPreparations = MutableStateFlow<Map<String, AttachmentPreparation>>(emptyMap())
val attachmentPreparations: StateFlow<Map<String, AttachmentPreparation>> = _attachmentPreparations
@ -317,6 +330,18 @@ class ChatViewModel : ViewModel() {
val nodeId = buf.getLong()
val result = buf.getInt()
val chId = buf.getLong()
val joining = _linkJoin.value
if (joining != null && joining.pending && joining.channelId == chId && joining.nodeId == nodeId) {
_linkJoin.value = joining.copy(pending = false, result = result)
if (result == 0) viewModelScope.launch {
val channels = withContext(Dispatchers.IO) { repo?.getChannels().orEmpty() }
_channels.value = channels
val channel = channels.find { it.id.toULongOrNull()?.toLong() == chId }
if (_linkJoin.value?.link == joining.link && channel != null) {
_linkJoin.value = null; _joinedChannel.value = channel
} else if (channel == null) LogManager.addLog("ERROR", "JOIN", "joined channel missing ch=$chId")
}
}
LogManager.addLog(
if (result == 0) "INFO" else "ERROR",
"JOIN", "connect to 0x%016x ch=$chId result=$result".format(nodeId))
@ -326,6 +351,26 @@ class ChatViewModel : ViewModel() {
4 -> { if (repo != null) { LogManager.addLog("INFO", "VM", "CHANNEL_UPDATED — refreshChannels"); refreshChannels() } else { LogManager.addLog("WARN", "VM", "CHANNEL_UPDATED skipped — repo is NULL") } }
12 -> LogManager.addLog("INFO", "VM", "service started")
13 -> { _attachmentPreparations.value = emptyMap(); LogManager.addLog("INFO", "VM", "service stopped") }
48 -> { /* [request_id:8][result:4][peer:8][conv:8] */
if (data == null || data.size != 28) { LogManager.addLog("ERROR", "INVITE", "invalid PM invite result"); return }
val buf = java.nio.ByteBuffer.wrap(data).order(java.nio.ByteOrder.LITTLE_ENDIAN)
val request = buf.long; val result = buf.int; val peer = buf.long; val conv = buf.long.toULong().toString()
val state = _pmInvite.value ?: return
if (!state.pending || state.requestId != request || state.peer != peer) return
LogManager.addLog(if (result == 0) "INFO" else "ERROR", "INVITE", "PM invite result request=$request peer=$peer result=$result")
if (result == 0) {
_pmInvite.value = null
openDm(conv, peer, state.name); _dmOpen.value = conv; refreshDmConversations()
} else {
val error = when (result) {
-1 -> R.string.invite_pm_start_failed
-3 -> R.string.invite_pm_send_failed
-5 -> R.string.invite_pm_busy
else -> R.string.invite_build_failed
}
_pmInvite.value = state.copy(pending = false, errorRes = error)
}
}
47 -> {
if (data == null || data.size < 17 || data.size != 17 + (data[0].toInt() and 0xff)) {
LogManager.addLog("WARN", "Call", "invalid PM route snapshot size=${data?.size}"); return
@ -919,6 +964,59 @@ class ChatViewModel : ViewModel() {
r.dmSend(dm.convId, text)
}
fun showPmInvite(peer: Long, name: String, source: String = "") {
if (peer == 0L || peer == NativeLib.getMyNodeId()) { LogManager.addLog("ERROR", "INVITE", "invalid PM recipient"); return }
if (_pmInvite.value != null) return
val groups = _channels.value.filter { NativeLib.isChannelMember(it.id, NativeLib.getMyNodeId()) }
_pmInvite.value = PmInviteState(peer, name, source, groups)
LogManager.addLog("INFO", "INVITE", "PM group picker peer=$peer source=$source groups=${groups.size}")
}
fun selectPmInviteGroup(groupId: String) {
_pmInvite.value?.let { if (!it.pending) _pmInvite.value = it.copy(groupId = groupId, errorRes = 0) }
}
fun sendPmInvite() {
val state = _pmInvite.value ?: return
if (state.pending) return
val channel = state.groupId.toULongOrNull()?.toLong() ?: return
val request = ++nextPmInviteId
_pmInvite.value = state.copy(requestId = request, pending = true, errorRes = 0)
if (!NativeLib.dmInvite(request, state.peer, state.source, channel, state.name)) {
_pmInvite.value = state.copy(errorRes = R.string.invite_core_unavailable)
}
}
fun dismissPmInvite() {
val state = _pmInvite.value ?: return
_pmInvite.value = null
if (state.pending) NativeLib.dmInvite(state.requestId, state.peer, state.source, 0, state.name, cancel = true)
LogManager.addLog("INFO", "INVITE", "PM group picker closed request=${state.requestId} pending=${state.pending}")
}
fun openInviteLink(link: String) {
if (_linkJoin.value?.pending == true) return
val invite = InviteLink.decode(link)
if (invite == null) {
LogManager.addLog("ERROR", "JOIN", "invalid message invite link")
_linkJoin.value = LinkJoinState(link, 0, 0, pending = false, result = -1); return
}
val existing = _channels.value.find { it.id.toULongOrNull()?.toLong() == invite.channelId }
if (existing != null && NativeLib.isChannelMember(existing.id, NativeLib.getMyNodeId())) {
_linkJoin.value = null; _joinedChannel.value = existing; return
}
if (repo == null || !NativeLib.isReady()) {
LogManager.addLog("ERROR", "JOIN", "core unavailable for message invite")
_linkJoin.value = LinkJoinState(link, invite.channelId, invite.nodeId, pending = false, result = -7); return
}
_linkJoin.value = LinkJoinState(link, invite.channelId, invite.nodeId)
val addresses = invite.serializeAddrs()
joinChannel(invite.channelId, invite.nodeId, invite.joinKey, invite.pubkey, addresses, invite.addrs.size)
}
fun dismissLinkJoin() { _linkJoin.value = null }
fun consumeJoinedChannel() { _joinedChannel.value = null }
fun sendDmFile(convId: String, path: String, name: String) {
if (repo != null) NativeLib.dmSendFile(convId, path, name)
}
@ -1288,6 +1386,7 @@ class ChatViewModel : ViewModel() {
}
fun clearState() {
dismissPmInvite(); _linkJoin.value = null; _joinedChannel.value = null
_attachmentPreparations.value = emptyMap()
repo?.close()
repo = null

Loading…
Cancel
Save