mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 04:05:21 +00:00
update DM queue thread-safety and responsive UI indicators
This commit is contained in:
@@ -188,6 +188,12 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
// SecurityManager delegate for key exchange notifications
|
// SecurityManager delegate for key exchange notifications
|
||||||
securityManager.delegate = object : SecurityManagerDelegate {
|
securityManager.delegate = object : SecurityManagerDelegate {
|
||||||
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) {
|
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray) {
|
||||||
|
// Immediate notification for UI updates
|
||||||
|
delegate?.onSessionEstablished(peerID)
|
||||||
|
|
||||||
|
// Flush router outbox immediately
|
||||||
|
com.bitchat.android.services.MessageRouter.tryGetInstance()?.onSessionEstablished(peerID)
|
||||||
|
|
||||||
// Send announcement and cached messages after key exchange
|
// Send announcement and cached messages after key exchange
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
Log.d(TAG, "Key exchange completed with $peerID; sending follow-ups")
|
Log.d(TAG, "Key exchange completed with $peerID; sending follow-ups")
|
||||||
@@ -1418,5 +1424,6 @@ interface BluetoothMeshDelegate {
|
|||||||
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
|
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
|
||||||
fun getNickname(): String?
|
fun getNickname(): String?
|
||||||
fun isFavorite(peerID: String): Boolean
|
fun isFavorite(peerID: String): Boolean
|
||||||
// registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager
|
fun onSessionEstablished(peerID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import com.bitchat.android.mesh.BluetoothMeshService
|
|||||||
import com.bitchat.android.model.ReadReceipt
|
import com.bitchat.android.model.ReadReceipt
|
||||||
import com.bitchat.android.nostr.NostrTransport
|
import com.bitchat.android.nostr.NostrTransport
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes messages between BLE mesh and Nostr transports, matching iOS behavior.
|
* Routes messages between BLE mesh and Nostr transports, matching iOS behavior.
|
||||||
*/
|
*/
|
||||||
@@ -39,7 +42,8 @@ class MessageRouter private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Outbox: peerID -> queued (content, nickname, messageID)
|
// Outbox: peerID -> queued (content, nickname, messageID)
|
||||||
private val outbox = mutableMapOf<String, MutableList<Triple<String, String, String>>>()
|
// Thread-safe map with synchronized lists
|
||||||
|
private val outbox = ConcurrentHashMap<String, MutableList<Triple<String, String, String>>>()
|
||||||
|
|
||||||
// Listener for favorites changes to flush outbox when npub mapping appears/changes
|
// Listener for favorites changes to flush outbox when npub mapping appears/changes
|
||||||
private val favoriteListener = object: com.bitchat.android.favorites.FavoritesChangeListener {
|
private val favoriteListener = object: com.bitchat.android.favorites.FavoritesChangeListener {
|
||||||
@@ -80,7 +84,7 @@ class MessageRouter private constructor(
|
|||||||
nostr.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
|
nostr.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Queued PM for ${toPeerID} (no mesh, no Nostr mapping) msg_id=${messageID.take(8)}…")
|
Log.d(TAG, "Queued PM for ${toPeerID} (no mesh, no Nostr mapping) msg_id=${messageID.take(8)}…")
|
||||||
val q = outbox.getOrPut(toPeerID) { mutableListOf() }
|
val q = outbox.getOrPut(toPeerID) { Collections.synchronizedList(mutableListOf()) }
|
||||||
q.add(Triple(content, recipientNickname, messageID))
|
q.add(Triple(content, recipientNickname, messageID))
|
||||||
Log.d(TAG, "Initiating noise handshake after queueing PM for ${toPeerID.take(8)}…")
|
Log.d(TAG, "Initiating noise handshake after queueing PM for ${toPeerID.take(8)}…")
|
||||||
mesh.initiateNoiseHandshake(toPeerID)
|
mesh.initiateNoiseHandshake(toPeerID)
|
||||||
@@ -126,30 +130,34 @@ class MessageRouter private constructor(
|
|||||||
// Flush any queued messages for a specific peerID
|
// Flush any queued messages for a specific peerID
|
||||||
fun flushOutboxFor(peerID: String) {
|
fun flushOutboxFor(peerID: String) {
|
||||||
val queued = outbox[peerID] ?: return
|
val queued = outbox[peerID] ?: return
|
||||||
if (queued.isEmpty()) return
|
|
||||||
Log.d(TAG, "Flushing outbox for ${peerID.take(8)}… count=${queued.size}")
|
synchronized(queued) {
|
||||||
val iterator = queued.iterator()
|
if (queued.isEmpty()) return
|
||||||
while (iterator.hasNext()) {
|
Log.d(TAG, "Flushing outbox for ${peerID.take(8)}… count=${queued.size}")
|
||||||
val (content, nickname, messageID) = iterator.next()
|
val iterator = queued.iterator()
|
||||||
var hasMesh = mesh.getPeerInfo(peerID)?.isConnected == true && mesh.hasEstablishedSession(peerID)
|
while (iterator.hasNext()) {
|
||||||
// If this is a noiseHex key, see if there is a connected mesh peer for this identity
|
val (content, nickname, messageID) = iterator.next()
|
||||||
if (!hasMesh && peerID.length == 64 && peerID.matches(Regex("^[0-9a-fA-F]+$"))) {
|
var hasMesh = mesh.getPeerInfo(peerID)?.isConnected == true && mesh.hasEstablishedSession(peerID)
|
||||||
val meshPeer = resolveMeshPeerForNoiseHex(peerID)
|
// If this is a noiseHex key, see if there is a connected mesh peer for this identity
|
||||||
if (meshPeer != null && mesh.getPeerInfo(meshPeer)?.isConnected == true && mesh.hasEstablishedSession(meshPeer)) {
|
if (!hasMesh && peerID.length == 64 && peerID.matches(Regex("^[0-9a-fA-F]+$"))) {
|
||||||
mesh.sendPrivateMessage(content, meshPeer, nickname, messageID)
|
val meshPeer = resolveMeshPeerForNoiseHex(peerID)
|
||||||
|
if (meshPeer != null && mesh.getPeerInfo(meshPeer)?.isConnected == true && mesh.hasEstablishedSession(meshPeer)) {
|
||||||
|
mesh.sendPrivateMessage(content, meshPeer, nickname, messageID)
|
||||||
|
iterator.remove()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val canNostr = canSendViaNostr(peerID)
|
||||||
|
if (hasMesh) {
|
||||||
|
mesh.sendPrivateMessage(content, peerID, nickname, messageID)
|
||||||
|
iterator.remove()
|
||||||
|
} else if (canNostr) {
|
||||||
|
nostr.sendPrivateMessage(content, peerID, nickname, messageID)
|
||||||
iterator.remove()
|
iterator.remove()
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val canNostr = canSendViaNostr(peerID)
|
|
||||||
if (hasMesh) {
|
|
||||||
mesh.sendPrivateMessage(content, peerID, nickname, messageID)
|
|
||||||
iterator.remove()
|
|
||||||
} else if (canNostr) {
|
|
||||||
nostr.sendPrivateMessage(content, peerID, nickname, messageID)
|
|
||||||
iterator.remove()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Cleanup empty lists (safe-ish with ConcurrentHashMap, worst case we create a new one later)
|
||||||
if (queued.isEmpty()) {
|
if (queued.isEmpty()) {
|
||||||
outbox.remove(peerID)
|
outbox.remove(peerID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -880,6 +880,10 @@ class ChatViewModel(
|
|||||||
return meshDelegateHandler.isFavorite(peerID)
|
return meshDelegateHandler.isFavorite(peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSessionEstablished(peerID: String) {
|
||||||
|
meshDelegateHandler.onSessionEstablished(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
// registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager
|
// registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager
|
||||||
|
|
||||||
// MARK: - Emergency Clear
|
// MARK: - Emergency Clear
|
||||||
|
|||||||
@@ -235,6 +235,15 @@ class MeshDelegateHandler(
|
|||||||
return privateChatManager.isFavorite(peerID)
|
return privateChatManager.isFavorite(peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSessionEstablished(peerID: String) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
val sessionStates = state.getPeerSessionStatesValue().toMutableMap()
|
||||||
|
sessionStates[peerID] = "established"
|
||||||
|
state.setPeerSessionStates(sessionStates)
|
||||||
|
android.util.Log.d("MeshDelegateHandler", "UI: Updated session state to 'established' for $peerID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for mentions in mesh messages and trigger notifications
|
* Check for mentions in mesh messages and trigger notifications
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user