diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f4ca2b898..e79fa8eb2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
@@ -19,6 +20,12 @@
+
+
+
+
+
+
@@ -47,6 +54,8 @@
+
+
+ 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.didUpdatePeerList(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 }
+ }
+ }
+ }
+ }
+ }
+ }
// Only start onboarding process if we're in the initial CHECKING state
// This prevents restarting onboarding on configuration changes
@@ -347,6 +395,12 @@ class MainActivity : OrientationAwareActivity() {
bluetoothStatusManager.logBluetoothStatus()
mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus())
+ val bleRequired = try { com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true) } catch (_: Exception) { true }
+ if (!bleRequired) {
+ // Skip BLE checks entirely when BLE is disabled in debug settings
+ checkLocationAndProceed()
+ return
+ }
when (mainViewModel.bluetoothStatus.value) {
BluetoothStatus.ENABLED -> {
// Bluetooth is enabled, check location services next
@@ -513,8 +567,9 @@ class MainActivity : OrientationAwareActivity() {
else -> BatteryOptimizationStatus.ENABLED
}
+ val bleRequired2 = try { com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true) } catch (_: Exception) { true }
when {
- currentBluetoothStatus != BluetoothStatus.ENABLED -> {
+ bleRequired2 && currentBluetoothStatus != BluetoothStatus.ENABLED -> {
// Bluetooth still disabled, but now we have permissions to enable it
Log.d("MainActivity", "Permissions granted, but Bluetooth still disabled. Showing Bluetooth enable screen.")
mainViewModel.updateBluetoothStatus(currentBluetoothStatus)
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 3de484072..c48ce087d 100644
--- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt
+++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt
@@ -94,6 +94,9 @@ class BluetoothMeshService(private val context: Context) {
} catch (_: Exception) { 0.01 }
}
)
+
+ // Register as shared instance for Wi-Fi Aware transport
+ com.bitchat.android.service.MeshServiceHolder.setGossipManager(gossipSyncManager)
// Wire sync manager delegate
gossipSyncManager.delegate = object : GossipSyncManager.Delegate {
@@ -268,7 +271,8 @@ class BluetoothMeshService(private val context: Context) {
override fun sendPacket(packet: BitchatPacket) {
// Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet)
- connectionManager.broadcastPacket(RoutedPacket(signedPacket))
+ val routed = RoutedPacket(signedPacket)
+ connectionManager.broadcastPacket(routed)
}
override fun relayPacket(routed: RoutedPacket) {
diff --git a/app/src/main/java/com/bitchat/android/onboarding/OnboardingCoordinator.kt b/app/src/main/java/com/bitchat/android/onboarding/OnboardingCoordinator.kt
index 871cc892d..ba4da701f 100644
--- a/app/src/main/java/com/bitchat/android/onboarding/OnboardingCoordinator.kt
+++ b/app/src/main/java/com/bitchat/android/onboarding/OnboardingCoordinator.kt
@@ -209,6 +209,7 @@ class OnboardingCoordinator(
return when {
permission.contains("BLUETOOTH") -> "Bluetooth/Nearby Devices"
permission.contains("LOCATION") -> "Location (for Bluetooth scanning)"
+ permission.contains("NEARBY_WIFI") -> "Nearby Wi‑Fi Devices (for Wi‑Fi Aware)"
permission.contains("NOTIFICATION") -> "Notifications"
else -> permission.substringAfterLast(".")
}
diff --git a/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt b/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt
index 2b84aefa1..c00f35f30 100644
--- a/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt
+++ b/app/src/main/java/com/bitchat/android/onboarding/PermissionExplanationScreen.kt
@@ -11,6 +11,7 @@ import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.Power
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Security
+import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.*
@@ -242,6 +243,7 @@ private fun getPermissionIcon(permissionType: PermissionType): ImageVector {
PermissionType.PRECISE_LOCATION -> Icons.Filled.LocationOn
PermissionType.MICROPHONE -> Icons.Filled.Mic
PermissionType.NOTIFICATIONS -> Icons.Filled.Notifications
+ PermissionType.WIFI_AWARE -> Icons.Filled.Wifi
PermissionType.BATTERY_OPTIMIZATION -> Icons.Filled.Power
PermissionType.OTHER -> Icons.Filled.Settings
}
diff --git a/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt b/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt
index ff0a160fd..aedf85f8e 100644
--- a/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt
+++ b/app/src/main/java/com/bitchat/android/onboarding/PermissionManager.kt
@@ -67,6 +67,11 @@ class PermissionManager(private val context: Context) {
Manifest.permission.ACCESS_FINE_LOCATION
))
+ // Wi‑Fi Aware: Android 13+ requires NEARBY_WIFI_DEVICES runtime permission
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ permissions.add(Manifest.permission.NEARBY_WIFI_DEVICES)
+ }
+
// Notification permission intentionally excluded to keep it optional
return permissions
@@ -177,6 +182,20 @@ class PermissionManager(private val context: Context) {
)
)
+ // Wi‑Fi Aware category (Android 13+)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val wifiAwarePermissions = listOf(Manifest.permission.NEARBY_WIFI_DEVICES)
+ categories.add(
+ PermissionCategory(
+ type = PermissionType.WIFI_AWARE,
+ description = "Enable Wi‑Fi Aware to discover and connect to nearby bitchat users over Wi‑Fi.",
+ permissions = wifiAwarePermissions,
+ isGranted = wifiAwarePermissions.all { isPermissionGranted(it) },
+ systemDescription = "Allow bitchat to discover nearby Wi‑Fi devices"
+ )
+ )
+ }
+
// Notifications category (if applicable)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
categories.add(
@@ -262,6 +281,7 @@ enum class PermissionType(val nameValue: String) {
PRECISE_LOCATION("Precise Location"),
MICROPHONE("Microphone"),
NOTIFICATIONS("Notifications"),
+ WIFI_AWARE("Wi‑Fi Aware"),
BATTERY_OPTIMIZATION("Battery Optimization"),
OTHER("Other")
}
diff --git a/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt b/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt
index d271ab295..71dddb664 100644
--- a/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt
+++ b/app/src/main/java/com/bitchat/android/service/MeshServiceHolder.kt
@@ -9,6 +9,12 @@ import com.bitchat.android.mesh.BluetoothMeshService
*/
object MeshServiceHolder {
private const val TAG = "MeshServiceHolder"
+ @Volatile
+ var sharedGossipSyncManager: com.bitchat.android.sync.GossipSyncManager? = null
+ private set
+
+ fun setGossipManager(mgr: com.bitchat.android.sync.GossipSyncManager) { sharedGossipSyncManager = mgr }
+
@Volatile
var meshService: BluetoothMeshService? = null
private set
diff --git a/app/src/main/java/com/bitchat/android/services/MessageRouter.kt b/app/src/main/java/com/bitchat/android/services/MessageRouter.kt
index 8166487e4..53db2fa61 100644
--- a/app/src/main/java/com/bitchat/android/services/MessageRouter.kt
+++ b/app/src/main/java/com/bitchat/android/services/MessageRouter.kt
@@ -72,9 +72,15 @@ class MessageRouter private constructor(
val hasMesh = mesh.getPeerInfo(toPeerID)?.isConnected == true
val hasEstablished = mesh.hasEstablishedSession(toPeerID)
+ // Check Wi‑Fi Aware availability as a secondary transport
+ val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null }
+ val hasAware = try { aware?.getPeerInfo(toPeerID)?.isConnected == true && aware.hasEstablishedSession(toPeerID) } catch (_: Exception) { false }
if (hasMesh && hasEstablished) {
Log.d(TAG, "Routing PM via mesh to ${toPeerID} msg_id=${messageID.take(8)}…")
mesh.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
+ } else if (hasAware) {
+ Log.d(TAG, "Routing PM via Wi‑Fi Aware to ${toPeerID} msg_id=${messageID.take(8)}…")
+ aware?.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
} else if (canSendViaNostr(toPeerID)) {
Log.d(TAG, "Routing PM via Nostr to ${toPeerID.take(32)}… msg_id=${messageID.take(8)}…")
nostr.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
@@ -83,14 +89,21 @@ class MessageRouter private constructor(
val q = outbox.getOrPut(toPeerID) { mutableListOf() }
q.add(Triple(content, recipientNickname, messageID))
Log.d(TAG, "Initiating noise handshake after queueing PM for ${toPeerID.take(8)}…")
- mesh.initiateNoiseHandshake(toPeerID)
+ if (hasMesh) mesh.initiateNoiseHandshake(toPeerID) else aware?.initiateNoiseHandshake(toPeerID)
}
}
fun sendReadReceipt(receipt: ReadReceipt, toPeerID: String) {
- if ((mesh.getPeerInfo(toPeerID)?.isConnected == true) && mesh.hasEstablishedSession(toPeerID)) {
+ val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null }
+ val viaMesh = (mesh.getPeerInfo(toPeerID)?.isConnected == true) && mesh.hasEstablishedSession(toPeerID)
+ val viaAware = try { aware?.getPeerInfo(toPeerID)?.isConnected == true && aware.hasEstablishedSession(toPeerID) } catch (_: Exception) { false }
+ if (viaMesh) {
Log.d(TAG, "Routing READ via mesh to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}…")
mesh.sendReadReceipt(receipt.originalMessageID, toPeerID, mesh.getPeerNicknames()[toPeerID] ?: mesh.myPeerID)
+ } else if (viaAware) {
+ Log.d(TAG, "Routing READ via Wi‑Fi Aware to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}…")
+ val me = try { aware?.myPeerID } catch (_: Exception) { null }
+ aware?.sendReadReceipt(receipt.originalMessageID, toPeerID, me ?: "")
} else {
Log.d(TAG, "Routing READ via Nostr to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}…")
nostr.sendReadReceipt(receipt, toPeerID)
diff --git a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt
index 1196396d5..223459235 100644
--- a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt
+++ b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt
@@ -441,8 +441,9 @@ class ChatViewModel(
state.getNicknameValue()
)
} else {
- // Default: route via mesh
+ // Default: route via mesh + Wi‑Fi Aware
meshService.sendMessage(messageContent, mentions, channel)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(messageContent, mentions, channel) } catch (_: Exception) {}
}
})
return
@@ -512,19 +513,23 @@ class ChatViewModel(
state.getNicknameValue(),
meshService.myPeerID,
onEncryptedPayload = { encryptedData ->
- // This would need proper mesh service integration
+ // Send encrypted payload announcement over both transports for reachability
meshService.sendMessage(content, mentions, currentChannelValue)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
},
onFallback = {
meshService.sendMessage(content, mentions, currentChannelValue)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
}
)
} else {
meshService.sendMessage(content, mentions, currentChannelValue)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
}
} else {
messageManager.addMessage(message)
meshService.sendMessage(content, mentions, null)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, null) } catch (_: Exception) {}
}
}
}
@@ -647,16 +652,23 @@ class ChatViewModel(
val fingerprints = privateChatManager.getAllPeerFingerprints()
state.setPeerFingerprints(fingerprints)
- val nicknames = meshService.getPeerNicknames()
- state.setPeerNicknames(nicknames)
+ // Merge nicknames from BLE and Wi‑Fi Aware to display names for all peers
+ val bleNick = meshService.getPeerNicknames()
+ val awareNickRaw = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerNicknamesMap() } catch (_: Exception) { null }
+ val mergedNick = if (awareNickRaw != null) bleNick + awareNickRaw.filter { it.value != null }.mapValues { it.value!! }.filterKeys { it !in bleNick || bleNick[it].isNullOrBlank() } else bleNick
+ state.setPeerNicknames(mergedNick)
val rssiValues = meshService.getPeerRSSI()
- state.setPeerRSSI(rssiValues)
+ val awareRssi = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerRSSI() } catch (_: Exception) { null }
+ val mergedRssi = if (awareRssi != null) rssiValues + awareRssi.filterKeys { it !in rssiValues } else rssiValues
+ state.setPeerRSSI(mergedRssi)
// Update directness per peer (driven by PeerManager state)
try {
val directMap = state.getConnectedPeersValue().associateWith { pid ->
- meshService.getPeerInfo(pid)?.isDirectConnection == true
+ val ble = meshService.getPeerInfo(pid)?.isDirectConnection == true
+ val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerInfo(pid)?.isDirectConnection == true } catch (_: Exception) { false }
+ ble || aware
}
state.setPeerDirect(directMap)
} catch (_: Exception) { }
diff --git a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt
index a3def5235..fc134a1ba 100644
--- a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt
+++ b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt
@@ -210,6 +210,7 @@ class MediaSendingManager(
Log.d(TAG, "📤 Calling meshService.sendFilePrivate to $toPeerID")
meshService.sendFilePrivate(toPeerID, filePacket)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFilePrivate(toPeerID, filePacket) } catch (_: Exception) {}
Log.d(TAG, "✅ File send completed successfully")
}
@@ -264,6 +265,7 @@ class MediaSendingManager(
Log.d(TAG, "📤 Calling meshService.sendFileBroadcast")
meshService.sendFileBroadcast(filePacket)
+ try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFileBroadcast(filePacket) } catch (_: Exception) {}
Log.d(TAG, "✅ File broadcast completed successfully")
}
diff --git a/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt b/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt
index a452616ec..6e7d5d224 100644
--- a/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt
+++ b/app/src/main/java/com/bitchat/android/ui/MeshDelegateHandler.kt
@@ -96,7 +96,10 @@ class MeshDelegateHandler(
override fun didUpdatePeerList(peers: List) {
coroutineScope.launch {
- state.setConnectedPeers(peers)
+ // Merge peers from multiple transports to avoid flapping
+ val current = state.getConnectedPeersValue().toMutableSet()
+ current.addAll(peers)
+ state.setConnectedPeers(current.toList())
state.setIsConnected(peers.isNotEmpty())
notificationManager.showActiveUserNotification(peers)
// Flush router outbox for any peers that just connected (and their noiseHex aliases)
diff --git a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt
index 4ab4360af..c504b9f93 100644
--- a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt
+++ b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt
@@ -576,9 +576,26 @@ private fun PeerItem(
tint = Color.Gray
)
} else {
+ val awareConnected by com.bitchat.android.wifiaware.WifiAwareController.connectedPeers.collectAsState()
+ val awareDiscovered by com.bitchat.android.wifiaware.WifiAwareController.discoveredPeers.collectAsState()
+ val isWifiDirect = awareConnected.containsKey(peerID)
+ val isBleDirect = isDirect
+ val icon = when {
+ isWifiDirect -> Icons.Filled.Wifi
+ isBleDirect -> Icons.Outlined.SettingsInputAntenna
+ // Routed: show Route icon; optionally prefer Wi‑Fi Aware if discovered there
+ awareDiscovered.contains(peerID) -> Icons.Filled.WifiTethering
+ else -> Icons.Filled.Route
+ }
+ val cd = when {
+ isWifiDirect -> "Direct Wi‑Fi Aware"
+ isBleDirect -> "Direct Bluetooth"
+ awareDiscovered.contains(peerID) -> "Routed over Wi‑Fi"
+ else -> "Routed"
+ }
Icon(
- imageVector = if (isDirect) Icons.Outlined.SettingsInputAntenna else Icons.Filled.Route,
- contentDescription = if (isDirect) "Direct Bluetooth" else "Routed",
+ imageVector = icon,
+ contentDescription = cd,
modifier = Modifier.size(16.dp),
tint = colorScheme.onSurface.copy(alpha = 0.8f)
)
diff --git a/app/src/main/java/com/bitchat/android/ui/debug/DebugPreferenceManager.kt b/app/src/main/java/com/bitchat/android/ui/debug/DebugPreferenceManager.kt
index 04ad48a2e..2d734c14e 100644
--- a/app/src/main/java/com/bitchat/android/ui/debug/DebugPreferenceManager.kt
+++ b/app/src/main/java/com/bitchat/android/ui/debug/DebugPreferenceManager.kt
@@ -20,7 +20,10 @@ object DebugPreferenceManager {
// GCS keys (no migration/back-compat)
private const val KEY_GCS_MAX_BYTES = "gcs_max_filter_bytes"
private const val KEY_GCS_FPR = "gcs_filter_fpr_percent"
- // Removed: persistent notification toggle is now governed by MeshServicePreferences.isBackgroundEnabled
+ // Transport master toggles
+ private const val KEY_BLE_ENABLED = "ble_enabled"
+ private const val KEY_WIFI_AWARE_ENABLED = "wifi_aware_enabled"
+ private const val KEY_WIFI_AWARE_VERBOSE = "wifi_aware_verbose"
private lateinit var prefs: SharedPreferences
@@ -102,5 +105,25 @@ object DebugPreferenceManager {
if (ready()) prefs.edit().putLong(KEY_GCS_FPR, java.lang.Double.doubleToRawLongBits(value)).apply()
}
- // No longer storing persistent notification in debug prefs.
+ // Transport toggles
+ fun getBleEnabled(default: Boolean = true): Boolean =
+ if (ready()) prefs.getBoolean(KEY_BLE_ENABLED, default) else default
+
+ fun setBleEnabled(value: Boolean) {
+ if (ready()) prefs.edit().putBoolean(KEY_BLE_ENABLED, value).apply()
+ }
+
+ fun getWifiAwareEnabled(default: Boolean = false): Boolean =
+ if (ready()) prefs.getBoolean(KEY_WIFI_AWARE_ENABLED, default) else default
+
+ fun setWifiAwareEnabled(value: Boolean) {
+ if (ready()) prefs.edit().putBoolean(KEY_WIFI_AWARE_ENABLED, value).apply()
+ }
+
+ fun getWifiAwareVerbose(default: Boolean = false): Boolean =
+ if (ready()) prefs.getBoolean(KEY_WIFI_AWARE_VERBOSE, default) else default
+
+ fun setWifiAwareVerbose(value: Boolean) {
+ if (ready()) prefs.edit().putBoolean(KEY_WIFI_AWARE_VERBOSE, value).apply()
+ }
}
diff --git a/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt b/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt
index 77f6ce12a..669331e60 100644
--- a/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt
+++ b/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsManager.kt
@@ -36,6 +36,17 @@ class DebugSettingsManager private constructor() {
private val _packetRelayEnabled = MutableStateFlow(true)
val packetRelayEnabled: StateFlow = _packetRelayEnabled.asStateFlow()
+ // Master transport toggles
+ private val _bleEnabled = MutableStateFlow(true)
+ val bleEnabled: StateFlow = _bleEnabled.asStateFlow()
+
+ private val _wifiAwareEnabled = MutableStateFlow(false)
+ val wifiAwareEnabled: StateFlow = _wifiAwareEnabled.asStateFlow()
+
+ // Master transport toggles
+ private val _wifiAwareVerbose = MutableStateFlow(false)
+ val wifiAwareVerbose: StateFlow = _wifiAwareVerbose.asStateFlow()
+
// Visibility of the debug sheet; gates heavy work
private val _debugSheetVisible = MutableStateFlow(false)
val debugSheetVisible: StateFlow = _debugSheetVisible.asStateFlow()
@@ -59,6 +70,10 @@ class DebugSettingsManager private constructor() {
_maxConnectionsOverall.value = DebugPreferenceManager.getMaxConnectionsOverall(8)
_maxServerConnections.value = DebugPreferenceManager.getMaxConnectionsServer(8)
_maxClientConnections.value = DebugPreferenceManager.getMaxConnectionsClient(8)
+ // Transport toggles
+ _bleEnabled.value = DebugPreferenceManager.getBleEnabled(true)
+ _wifiAwareEnabled.value = DebugPreferenceManager.getWifiAwareEnabled(false)
+ _wifiAwareVerbose.value = DebugPreferenceManager.getWifiAwareVerbose(false)
} catch (_: Exception) {
// Preferences not ready yet; keep defaults. They will be applied on first change.
}
@@ -262,6 +277,27 @@ class DebugSettingsManager private constructor() {
))
}
+ fun setBleEnabled(enabled: Boolean) {
+ DebugPreferenceManager.setBleEnabled(enabled)
+ _bleEnabled.value = enabled
+ addDebugMessage(DebugMessage.SystemMessage(if (enabled) "🟢 BLE enabled" else "🔴 BLE disabled"))
+ }
+
+ fun setWifiAwareEnabled(enabled: Boolean) {
+ DebugPreferenceManager.setWifiAwareEnabled(enabled)
+ _wifiAwareEnabled.value = enabled
+ addDebugMessage(DebugMessage.SystemMessage(if (enabled) "🟢 Wi‑Fi Aware enabled" else "🔴 Wi‑Fi Aware disabled"))
+ try {
+ com.bitchat.android.wifiaware.WifiAwareController.setEnabled(enabled)
+ } catch (_: Exception) { }
+ }
+
+ fun setWifiAwareVerbose(enabled: Boolean) {
+ DebugPreferenceManager.setWifiAwareVerbose(enabled)
+ _wifiAwareVerbose.value = enabled
+ addDebugMessage(DebugMessage.SystemMessage(if (enabled) "🔊 Wi‑Fi Aware verbose logging enabled" else "🔇 Wi‑Fi Aware verbose logging disabled"))
+ }
+
fun setMaxConnectionsOverall(value: Int) {
val clamped = value.coerceIn(1, 32)
DebugPreferenceManager.setMaxConnectionsOverall(clamped)
@@ -319,6 +355,16 @@ class DebugSettingsManager private constructor() {
fun updateConnectedDevices(devices: List) {
_connectedDevices.value = devices
}
+
+ // Wi‑Fi Aware debug collections
+ private val _wifiAwareDiscovered = MutableStateFlow