diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index 0ba81c88..2496018f 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -39,6 +39,8 @@ import com.bitchat.android.ui.ChatScreen import com.bitchat.android.ui.ChatViewModel import com.bitchat.android.ui.OrientationAwareActivity import com.bitchat.android.ui.theme.BitchatTheme +import com.bitchat.android.wifiaware.WifiAwareController +import com.bitchat.android.wifiaware.WifiAwareMeshDelegate import com.bitchat.android.nostr.PoWPreferenceManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -62,6 +64,37 @@ class MainActivity : OrientationAwareActivity() { } } } + + private val wifiAwareDelegate by lazy { + object : WifiAwareMeshDelegate { + override fun didReceiveMessage(message: com.bitchat.android.model.BitchatMessage) { + chatViewModel.didReceiveMessage(message) + } + override fun didUpdatePeerList(peers: List) { + chatViewModel.onWifiPeersUpdated(peers) + } + override fun didReceiveChannelLeave(channel: String, fromPeer: String) { + chatViewModel.didReceiveChannelLeave(channel, fromPeer) + } + override fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String) { + chatViewModel.didReceiveDeliveryAck(messageID, recipientPeerID) + } + override fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) { + chatViewModel.didReceiveReadReceipt(messageID, recipientPeerID) + } + override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? { + return chatViewModel.decryptChannelMessage(encryptedContent, channel) + } + override fun getNickname(): String? { + return chatViewModel.getNickname() + } + override fun isFavorite(peerID: String): Boolean { + return try { + com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(peerID)?.isMutual == true + } catch (_: Exception) { false } + } + } + } private val forceFinishReceiver = object : android.content.BroadcastReceiver() { override fun onReceive(context: android.content.Context, intent: android.content.Intent) { @@ -166,44 +199,10 @@ class MainActivity : OrientationAwareActivity() { // Bridge Wi‑Fi Aware callbacks into ChatViewModel (reusing BLE delegate methods) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - com.bitchat.android.wifiaware.WifiAwareController.running.collect { running -> - val svc = com.bitchat.android.wifiaware.WifiAwareController.getService() - if (running && svc != null) { - svc.delegate = object : com.bitchat.android.wifiaware.WifiAwareMeshDelegate { - override fun didReceiveMessage(message: com.bitchat.android.model.BitchatMessage) { - if (message.isPrivate) { - message.senderPeerID?.let { pid -> com.bitchat.android.services.AppStateStore.addPrivateMessage(pid, message) } - } else if (message.channel != null) { - com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel, message) - } else { - com.bitchat.android.services.AppStateStore.addPublicMessage(message) - } - chatViewModel.didReceiveMessage(message) - } - override fun didUpdatePeerList(peers: List) { - chatViewModel.onWifiPeersUpdated(peers) - } - override fun didReceiveChannelLeave(channel: String, fromPeer: String) { - chatViewModel.didReceiveChannelLeave(channel, fromPeer) - } - override fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String) { - chatViewModel.didReceiveDeliveryAck(messageID, recipientPeerID) - } - override fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) { - chatViewModel.didReceiveReadReceipt(messageID, recipientPeerID) - } - override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? { - return chatViewModel.decryptChannelMessage(encryptedContent, channel) - } - override fun getNickname(): String? { - return chatViewModel.getNickname() - } - override fun isFavorite(peerID: String): Boolean { - return try { - com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(peerID)?.isMutual == true - } catch (_: Exception) { false } - } - } + WifiAwareController.running.collect { running -> + val svc = WifiAwareController.getService() + if (running && svc != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + svc.delegate = wifiAwareDelegate } } } @@ -761,6 +760,7 @@ class MainActivity : OrientationAwareActivity() { if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { // Reattach mesh delegate to new ChatViewModel instance after Activity recreation try { meshService.delegate = chatViewModel } catch (_: Exception) { } + try { WifiAwareController.getService()?.delegate = wifiAwareDelegate } catch (_: Exception) { } // Set app foreground state meshService.connectionManager.setAppBackgroundState(false) chatViewModel.setAppBackgroundState(false) @@ -798,6 +798,7 @@ class MainActivity : OrientationAwareActivity() { chatViewModel.setAppBackgroundState(true) // Detach UI delegate so the foreground service can own DM notifications while UI is closed try { meshService.delegate = null } catch (_: Exception) { } + try { WifiAwareController.getService()?.delegate = null } catch (_: Exception) { } } } diff --git a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt index a8d4dda0..ae9724dd 100644 --- a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt +++ b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt @@ -17,6 +17,7 @@ import com.bitchat.android.mesh.MeshService import com.bitchat.android.mesh.MeshTransport import com.bitchat.android.mesh.PeerInfo import com.bitchat.android.model.BitchatFilePacket +import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.RoutedPacket import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.service.TransportBridgeService @@ -67,6 +68,13 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor private val wifiTransport = WifiAwareTransport() private lateinit var meshCore: MeshCore + // Service-level notification manager for background (no-UI) DMs + private val serviceNotificationManager = com.bitchat.android.ui.NotificationManager( + context.applicationContext, + androidx.core.app.NotificationManagerCompat.from(context.applicationContext), + com.bitchat.android.util.NotificationIntervalManager() + ) + // Wi-Fi Aware transport private val awareManager = context.getSystemService(WifiAwareManager::class.java) private var wifiAwareSession: WifiAwareSession? = null @@ -95,6 +103,9 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor private val lastTimestamps = ConcurrentHashMap() init { + // Ensure BluetoothMeshService is initialized so we share its GossipSyncManager + // This avoids race conditions and ensures a single gossip source/delegate + com.bitchat.android.service.MeshServiceHolder.getOrCreate(context) val shared = com.bitchat.android.service.MeshServiceHolder.sharedGossipSyncManager meshCore = MeshCore( context = context.applicationContext, @@ -110,6 +121,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor override fun gcsTargetFpr(): Double = 0.01 }, hooks = MeshCore.Hooks( + onMessageReceived = { message -> handleMessageReceived(message) }, onAnnounceProcessed = { routed, _ -> routed.peerID?.let { pid -> try { meshCore.gossipSyncManager.scheduleInitialSyncToPeer(pid, 1_000) } catch (_: Exception) { } @@ -125,6 +137,35 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor ) } + private fun handleMessageReceived(message: BitchatMessage) { + try { + when { + message.isPrivate -> { + val peer = message.senderPeerID ?: "" + if (peer.isNotEmpty()) com.bitchat.android.services.AppStateStore.addPrivateMessage(peer, message) + } + message.channel != null -> { + com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel!!, message) + } + else -> { + com.bitchat.android.services.AppStateStore.addPublicMessage(message) + } + } + } catch (_: Exception) { } + + if (delegate == null && message.isPrivate) { + try { + val senderPeerID = message.senderPeerID + if (senderPeerID != null) { + val nick = try { meshCore.getPeerNickname(senderPeerID) } catch (_: Exception) { null } ?: senderPeerID + val preview = com.bitchat.android.ui.NotificationTextUtils.buildPrivateMessagePreview(message) + serviceNotificationManager.setAppBackgroundState(true) + serviceNotificationManager.showPrivateMessageNotification(senderPeerID, nick, preview) + } + } catch (_: Exception) { } + } + } + /** * Broadcasts raw bytes to currently connected peer. */