diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index f5289878..5b42d0e2 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -111,6 +111,13 @@ class BluetoothMeshService(private val context: Context) { return signPacketBeforeBroadcast(packet) } } + + // Inject dynamic direct connection check into PeerManager + // Matches iOS logic: checks if we have an active hardware mapping for this peer + peerManager.isPeerDirectlyConnected = { peerID -> + connectionManager.addressPeerMap.containsValue(peerID) + } + Log.d(TAG, "Delegates set up; GossipSyncManager initialized") } @@ -478,8 +485,8 @@ class BluetoothMeshService(private val context: Context) { connectionManager.addressPeerMap[deviceAddress] = pid Log.d(TAG, "Mapped device $deviceAddress to peer $pid (TTL=${routed.packet.ttl})") - // Mark as directly connected - try { peerManager.setDirectConnection(pid, true) } catch (_: Exception) { } + // Mark as directly connected - refresh UI state + try { peerManager.refreshPeerList() } catch (_: Exception) { } // Initial sync for this direct peer try { gossipSyncManager.scheduleInitialSyncToPeer(pid, 1_000) } catch (_: Exception) { } @@ -584,8 +591,8 @@ class BluetoothMeshService(private val context: Context) { if (peer != null) { val stillMapped = connectionManager.addressPeerMap.values.any { it == peer } if (!stillMapped) { - // Peer might still be reachable indirectly; mark as not-direct - try { peerManager.setDirectConnection(peer, false) } catch (_: Exception) { } + // Peer might still be reachable indirectly; refresh to update state + try { peerManager.refreshPeerList() } catch (_: Exception) { } } // Verbose debug: device disconnected try { diff --git a/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt b/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt index d4c84c03..536a9b2a 100644 --- a/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/PeerManager.kt @@ -86,6 +86,9 @@ class PeerManager { // Delegate for callbacks var delegate: PeerManagerDelegate? = null + // Callback to check if a peer is directly connected (injected by BluetoothMeshService) + var isPeerDirectlyConnected: ((String) -> Boolean)? = null + // Coroutines private val managerScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -145,10 +148,18 @@ class PeerManager { } /** - * Get peer info + * Get peer info with dynamic direct connection status */ fun getPeerInfo(peerID: String): PeerInfo? { - return peers[peerID] + return peers[peerID]?.let { info -> + // Dynamically check direct connection status from ConnectionManager + val isDirect = isPeerDirectlyConnected?.invoke(peerID) ?: false + if (info.isDirectConnection != isDirect) { + info.copy(isDirectConnection = isDirect) + } else { + info + } + } } /** @@ -159,28 +170,21 @@ class PeerManager { } /** - * Get all verified peers + * Get all verified peers with dynamic direct connection status */ fun getVerifiedPeers(): Map { - return peers.filterValues { it.isVerifiedNickname } + return peers.filterValues { it.isVerifiedNickname }.mapValues { (_, info) -> + val isDirect = isPeerDirectlyConnected?.invoke(info.id) ?: false + if (info.isDirectConnection != isDirect) info.copy(isDirectConnection = isDirect) else info + } } /** - * Set whether a peer is directly connected over Bluetooth. - * Triggers a peer list update to refresh UI badges. + * Force a peer list update notification. + * Call this when connection state changes to refresh UI badges. */ - fun setDirectConnection(peerID: String, isDirect: Boolean) { - peers[peerID]?.let { existing -> - if (existing.isDirectConnection != isDirect) { - peers[peerID] = existing.copy(isDirectConnection = isDirect) - notifyPeerListUpdate() - // NEW: notify UI state (if available via delegate path) about directness change - try { - // Best-effort: delegate path flows up to ChatViewModel via didUpdatePeerList - // No direct reference to UI layer here by design. - } catch (_: Exception) { } - } - } + fun refreshPeerList() { + notifyPeerListUpdate() } // MARK: - Legacy Methods (maintained for compatibility) @@ -364,7 +368,11 @@ class PeerManager { return buildString { appendLine("=== Peer Manager Debug Info ===") appendLine("Active Peers: ${activeIds.size}") - peers.forEach { (peerID, info) -> + peers.forEach { (peerID, storedInfo) -> + // Use dynamic direct status for debug log accuracy + val isDirect = isPeerDirectlyConnected?.invoke(peerID) ?: false + val info = if (storedInfo.isDirectConnection != isDirect) storedInfo.copy(isDirectConnection = isDirect) else storedInfo + val timeSince = (now - info.lastSeen) / 1000 val rssi = peerRSSI[peerID]?.let { "${it} dBm" } ?: "No RSSI" val deviceAddress = addressPeerMap?.entries?.find { it.value == peerID }?.key