Keep malformed events and deleted database state out of Android UI
This commit is contained in:
1 parent
c5879042fb
commit
4a5a8cf057
2 files changed
+50
-53
No files matched your search
+5
-1
@@ -98,6 +98,7 @@ fun ProfileAvatarControls() {
|
||||
var pickedAccount by rememberSaveable { mutableStateOf(0L) }
|
||||
var pickedServer by rememberSaveable { mutableStateOf(0L) }
|
||||
var cropUri by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var pickerFailed by remember { mutableStateOf(false) }
|
||||
val state = edits["profile"] ?: AvatarEditState.IDLE
|
||||
LaunchedEffect(servers) {
|
||||
if (servers.none { it.nodeId == selectedServer }) selectedServer = servers.firstOrNull()?.nodeId ?: 0L
|
||||
@@ -128,12 +129,15 @@ fun ProfileAvatarControls() {
|
||||
}
|
||||
Row {
|
||||
TextButton(enabled = selectedServer != 0L && state != AvatarEditState.WORKING, onClick = {
|
||||
pickedAccount = repository.accountId; pickedServer = selectedServer; picker.launch("image/*")
|
||||
pickedAccount = repository.accountId; pickedServer = selectedServer
|
||||
try { pickerFailed = false; picker.launch("image/*") }
|
||||
catch (e: Exception) { pickerFailed = true; LogManager.addLog("ERROR", "Avatar", "Profile image picker failed: $e") }
|
||||
}) { Text(stringResource(R.string.avatar_upload)) }
|
||||
TextButton(enabled = repository.accountId != 0L && state != AvatarEditState.WORKING, onClick = {
|
||||
repository.editProfile(0, null, repository.accountId)
|
||||
}) { Text(stringResource(R.string.avatar_remove)) }
|
||||
}
|
||||
if (pickerFailed) Text(stringResource(R.string.avatar_edit_failed), color = MaterialTheme.colorScheme.error)
|
||||
when (state) {
|
||||
AvatarEditState.WORKING -> LinearProgressIndicator(Modifier.fillMaxWidth())
|
||||
AvatarEditState.ERROR -> Text(stringResource(R.string.avatar_edit_failed), color = MaterialTheme.colorScheme.error)
|
||||
|
||||
@@ -804,18 +804,8 @@ class ChatViewModel : ViewModel() {
|
||||
} else refreshSelectedMemberDetail(channel, node)
|
||||
}
|
||||
27 -> { /* CONNECTING_NODES: [ch_id_len:1][ch_id:var][count:2][node_id:8]* */
|
||||
if (data == null || data.size < 3) 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 count = buf.getShort().toInt() and 0xFFFF
|
||||
val set = HashSet<Long>()
|
||||
for (i in 0 until count) {
|
||||
if (buf.position() + 8 > data.size) break
|
||||
set.add(buf.getLong())
|
||||
}
|
||||
_connecting.value = _connecting.value + (chId to set)
|
||||
val snapshot = ChannelEventFrames.nodes(data)
|
||||
_connecting.value = _connecting.value + (snapshot.channel to snapshot.ids.toSet())
|
||||
}
|
||||
28 -> { /* ADMIN_KEY_RECEIVED: [ch_id_len:1][ch_id:var][from_node_id:8] */
|
||||
if (data == null || data.size < 10) return
|
||||
@@ -828,50 +818,39 @@ class ChatViewModel : ViewModel() {
|
||||
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 snapshot = ChannelEventFrames.nodes(data, candidates = true)
|
||||
val chId = snapshot.channel
|
||||
if (inviteChannelId != chId) return
|
||||
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))
|
||||
val autoId = snapshot.automatic
|
||||
val ids = snapshot.ids
|
||||
LogManager.addLog("INFO", "VM", "INVITE_CANDIDATES ch=$chId auto=0x%016x count=%d".format(autoId, ids.size))
|
||||
viewModelScope.launch {
|
||||
val myId = NativeLib.getMyNodeId()
|
||||
val nameOf = withContext(Dispatchers.IO) {
|
||||
(repo?.getMembers(chId) ?: emptyList()).associate { it.nodeId to it.name }
|
||||
try {
|
||||
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))
|
||||
}
|
||||
if (inviteChannelId != chId) return@launch
|
||||
_inviteCandidates.value = list
|
||||
requestInviteLink(chId, autoId)
|
||||
} catch (e: kotlinx.coroutines.CancellationException) { throw e
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Invite", "Candidate names unavailable channel=$chId: $e")
|
||||
}
|
||||
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))
|
||||
}
|
||||
if (inviteChannelId != chId) return@launch
|
||||
_inviteCandidates.value = list
|
||||
if (inviteChannelId == chId) requestInviteLink(chId, autoId)
|
||||
}
|
||||
}
|
||||
29 -> { /* [ch_id_len:1][ch_id][request_id:8][link_len:2][link] */
|
||||
if (data == null || data.size < 11) 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 requestId = buf.long
|
||||
if (chId != inviteChannelId || requestId != inviteRequestId) return
|
||||
val linkLen = buf.short.toInt() and 0xFFFF
|
||||
val link = if (linkLen > 0 && buf.remaining() >= linkLen)
|
||||
String(data, 1 + cl + 10, linkLen) else null
|
||||
LogManager.addLog(if (link != null) "INFO" else "ERROR", "VM", "INVITE_LINK_READY ch=$chId len=$linkLen")
|
||||
_inviteLink.value = link
|
||||
val result = ChannelEventFrames.invite(data)
|
||||
if (result.channel != inviteChannelId || result.request != inviteRequestId) return
|
||||
LogManager.addLog(if (result.link != null) "INFO" else "ERROR", "VM", "INVITE_LINK_READY ch=${result.channel} bytes=${data?.size}")
|
||||
_inviteLink.value = result.link
|
||||
_inviteLinkPending.value = false
|
||||
}
|
||||
31 -> { /* CHANNEL_DELETED: [ch_id_len:1][ch_id:var] */
|
||||
@@ -1921,12 +1900,23 @@ class ChatViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Main, после подтверждённого join worker: убрать все ссылки на удаляемую БД и завершить её ожидающие отправки. */
|
||||
fun clearState() {
|
||||
cancelDmStart(); _dmStartFailed.value = false; _dmOpen.value = null
|
||||
++channelRefreshRevision; ++messageRefreshRevision; ++jumpRevision
|
||||
++editRevision; ++replyRevision
|
||||
for (id in submissions.keys.toList()) submissionResult(id, false)
|
||||
_editing.value = null; _replies.value = emptyMap(); _replyOriginals.value = emptyMap(); cameraReply = null
|
||||
_historyAnchor.value = ""; _messageJump.value = null; pendingLatestJump = null; _originalMissing.value = false
|
||||
feedbackCache.clear(); _reactionCatalog.value = emptyList()
|
||||
memberOwnerJob?.cancel(); _members.value = emptyList(); _memberLinks.value = MemberLinks()
|
||||
memberDetailJob?.cancel(); memberDetailTarget = null; _selectedMember.value = null
|
||||
dismissPmInvite(); _linkJoin.value = null; _joinedChannel.value = null
|
||||
_attachmentPreparations.value = emptyMap()
|
||||
dismissPmInvite(); _linkJoin.value = null; _joinedChannel.value = null; _serverLink.value = null
|
||||
inviteChannelId = null; _inviteCandidates.value = emptyList(); _inviteLink.value = null; _inviteLinkPending.value = false
|
||||
_connecting.value = emptyMap(); dmPresenceRefreshJob?.cancel(); _dmCallAvailable.value = false
|
||||
_currentDm.value = null; _dmConversations.value = emptyList(); _dmMessages.value = emptyList()
|
||||
dmDeleteRequests.clear(); _dmDeleting.value = emptySet(); _dmDeleteFailed.value = false
|
||||
_attachmentPreparations.value = emptyMap(); discardedPreparationJobs.clear()
|
||||
repo?.close()
|
||||
repo = null
|
||||
dbReady = false
|
||||
@@ -1937,6 +1927,9 @@ class ChatViewModel : ViewModel() {
|
||||
_generatingKeys.value = false
|
||||
_localSockets.value = emptyList()
|
||||
_channelOwner.value = false
|
||||
_playbackState.value = PlaybackState(); realPlaybackElapsedMs = 0
|
||||
_radioActive.value = false; _radioTalking.value = null; _radioTalkState.value = RadioTalkState()
|
||||
LogManager.addLog("INFO", "VM", "Database state cleared: conversations, drafts, members, history and pending submissions")
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
||||
Reference in new issue
Block a user