|
|
|
|
@ -74,6 +74,15 @@ class ChatViewModel : ViewModel() {
|
|
|
|
|
private val _localSockets = MutableStateFlow<List<LocalSocketState>>(emptyList()) |
|
|
|
|
val localSockets: StateFlow<List<LocalSocketState>> = _localSockets |
|
|
|
|
|
|
|
|
|
private val _inviteCandidates = MutableStateFlow<List<InviteCandidate>>(emptyList()) |
|
|
|
|
val inviteCandidates: StateFlow<List<InviteCandidate>> = _inviteCandidates |
|
|
|
|
|
|
|
|
|
private val _inviteLink = MutableStateFlow<String?>(null) |
|
|
|
|
val inviteLink: StateFlow<String?> = _inviteLink |
|
|
|
|
|
|
|
|
|
private val _inviteLinkPending = MutableStateFlow(false) |
|
|
|
|
val inviteLinkPending: StateFlow<Boolean> = _inviteLinkPending |
|
|
|
|
|
|
|
|
|
val audioRecorder = AudioRecorderManager() |
|
|
|
|
|
|
|
|
|
private val _isRecording = MutableStateFlow(false) |
|
|
|
|
@ -335,6 +344,51 @@ class ChatViewModel : ViewModel() {
|
|
|
|
|
LogManager.addLog("INFO", "VM", "admin key received ch=$chId from=0x%016x — now co-owner".format(from)) |
|
|
|
|
if (_currentChannel.value?.id == chId) refreshMembers(chId) |
|
|
|
|
} |
|
|
|
|
30 -> { /* INVITE_CANDIDATES: [ch_id_len:1][ch_id][auto_node_id:8][count:2][node_id:8]* */ |
|
|
|
|
if (data == null || data.size < 11) { LogManager.addLog("WARN", "VM", "INVITE_CANDIDATES too short"); return } |
|
|
|
|
val cl = data[0].toInt() and 0xFF |
|
|
|
|
if (data.size < 1 + cl + 10) return |
|
|
|
|
val chId = String(data, 1, cl) |
|
|
|
|
val buf = java.nio.ByteBuffer.wrap(data, 1 + cl, data.size - 1 - cl).order(java.nio.ByteOrder.LITTLE_ENDIAN) |
|
|
|
|
val autoId = buf.long |
|
|
|
|
val count = buf.short.toInt() and 0xFFFF |
|
|
|
|
val ids = mutableListOf<Long>() |
|
|
|
|
for (i in 0 until count) { |
|
|
|
|
if (buf.remaining() < 8) break |
|
|
|
|
ids.add(buf.long) |
|
|
|
|
} |
|
|
|
|
LogManager.addLog("INFO", "VM", "INVITE_CANDIDATES ch=$chId auto=0x%016x count=%d".format(autoId, count)) |
|
|
|
|
viewModelScope.launch { |
|
|
|
|
val myId = NativeLib.getMyNodeId() |
|
|
|
|
val nameOf = withContext(Dispatchers.IO) { |
|
|
|
|
(repo?.getMembers(chId) ?: emptyList()).associate { it.nodeId to it.name } |
|
|
|
|
} |
|
|
|
|
fun label(id: Long): String = nameOf[id]?.takeIf { it.isNotEmpty() } ?: "0x%016x".format(id) |
|
|
|
|
val list = mutableListOf<InviteCandidate>() |
|
|
|
|
list.add(InviteCandidate(autoId, label(autoId), isAuto = true, isSelf = autoId == myId)) |
|
|
|
|
for (id in ids) { |
|
|
|
|
if (id == autoId) continue |
|
|
|
|
list.add(InviteCandidate(id, label(id), isSelf = id == myId)) |
|
|
|
|
} |
|
|
|
|
_inviteCandidates.value = list |
|
|
|
|
NativeLib.requestInviteLink(chId, autoId) |
|
|
|
|
_inviteLink.value = null |
|
|
|
|
_inviteLinkPending.value = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
29 -> { /* INVITE_LINK_READY: [ch_id_len:1][ch_id][link_len:2][link] — пустой link = ошибка */ |
|
|
|
|
if (data == null || data.size < 4) return |
|
|
|
|
val cl = data[0].toInt() and 0xFF |
|
|
|
|
if (data.size < 1 + cl + 2) return |
|
|
|
|
val chId = String(data, 1, cl) |
|
|
|
|
val buf = java.nio.ByteBuffer.wrap(data, 1 + cl, data.size - 1 - cl).order(java.nio.ByteOrder.LITTLE_ENDIAN) |
|
|
|
|
val linkLen = buf.short.toInt() and 0xFFFF |
|
|
|
|
val link = if (linkLen > 0 && data.size >= 1 + cl + 2 + linkLen) |
|
|
|
|
String(data, 1 + cl + 2, linkLen) else null |
|
|
|
|
LogManager.addLog(if (link != null) "INFO" else "ERROR", "VM", "INVITE_LINK_READY ch=$chId len=$linkLen") |
|
|
|
|
_inviteLink.value = link |
|
|
|
|
_inviteLinkPending.value = false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -354,6 +408,27 @@ class ChatViewModel : ViewModel() {
|
|
|
|
|
if (dbReady && repo != null) refreshChannels() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun requestInvite(channelId: String) { |
|
|
|
|
_inviteCandidates.value = emptyList() |
|
|
|
|
_inviteLink.value = null |
|
|
|
|
_inviteLinkPending.value = true |
|
|
|
|
NativeLib.requestInviteCandidates(channelId) |
|
|
|
|
LogManager.addLog("INFO", "VM", "requestInvite ch=$channelId") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun requestInviteLink(channelId: String, targetNodeId: Long) { |
|
|
|
|
_inviteLink.value = null |
|
|
|
|
_inviteLinkPending.value = true |
|
|
|
|
NativeLib.requestInviteLink(channelId, targetNodeId) |
|
|
|
|
LogManager.addLog("INFO", "VM", "requestInviteLink ch=$channelId node=0x%016x".format(targetNodeId)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun clearInvite() { |
|
|
|
|
_inviteCandidates.value = emptyList() |
|
|
|
|
_inviteLink.value = null |
|
|
|
|
_inviteLinkPending.value = false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun refreshChannels() { |
|
|
|
|
val r = repo ?: return |
|
|
|
|
viewModelScope.launch { |
|
|
|
|
|