Add Real-Time Bluetooth State Monitoring to OnboardingFlowScreen (#55)

* Add bluetooth monitor function to onboarding flow screen

* Adds function that checks bluetooth status in real time

* Add bluetooth monitor function to onboarding flow screen

* Refactor

* Comment button to manually check BT state

* Original commits

* feat(ui): Remove auto-trigger for Bluetooth enable prompt

* feat(ui): Remove auto-trigger for Bluetooth enable prompt

* feat(ui): Remove BluetoothStatusBanner. Pending approval on feature, will add in a new PR

* Refactor and fix conflicts

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
Eben Justice
2025-08-05 23:49:04 +02:00
committed by GitHub
co-authored by callebtc
parent 335f914f32
commit a0d1c6f77c
3 changed files with 104 additions and 24 deletions
@@ -11,11 +11,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModelProvider import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import com.bitchat.android.mesh.BluetoothMeshService import com.bitchat.android.mesh.BluetoothMeshService
@@ -60,16 +62,13 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Initialize core mesh service first
meshService = BluetoothMeshService(this)
// Initialize permission management // Initialize permission management
permissionManager = PermissionManager(this) permissionManager = PermissionManager(this)
// Initialize core mesh service first
meshService = BluetoothMeshService(this)
bluetoothStatusManager = BluetoothStatusManager( bluetoothStatusManager = BluetoothStatusManager(
activity = this, activity = this,
context = this, context = this,
@@ -124,6 +123,7 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
private fun OnboardingFlowScreen() { private fun OnboardingFlowScreen() {
val context = LocalContext.current
val onboardingState by mainViewModel.onboardingState.collectAsState() val onboardingState by mainViewModel.onboardingState.collectAsState()
val bluetoothStatus by mainViewModel.bluetoothStatus.collectAsState() val bluetoothStatus by mainViewModel.bluetoothStatus.collectAsState()
val locationStatus by mainViewModel.locationStatus.collectAsState() val locationStatus by mainViewModel.locationStatus.collectAsState()
@@ -133,6 +133,28 @@ class MainActivity : ComponentActivity() {
val isLocationLoading by mainViewModel.isLocationLoading.collectAsState() val isLocationLoading by mainViewModel.isLocationLoading.collectAsState()
val isBatteryOptimizationLoading by mainViewModel.isBatteryOptimizationLoading.collectAsState() val isBatteryOptimizationLoading by mainViewModel.isBatteryOptimizationLoading.collectAsState()
DisposableEffect(context, bluetoothStatusManager) {
val receiver = bluetoothStatusManager.monitorBluetoothState(
context = context,
bluetoothStatusManager = bluetoothStatusManager,
onBluetoothStateChanged = { status ->
if (status == BluetoothStatus.ENABLED && onboardingState == OnboardingState.BLUETOOTH_CHECK) {
checkBluetoothAndProceed()
}
}
)
onDispose {
try {
context.unregisterReceiver(receiver)
Log.d("BluetoothStatusUI", "BroadcastReceiver unregistered")
} catch (e: IllegalStateException) {
Log.w("BluetoothStatusUI", "Receiver was not registered")
}
}
}
when (onboardingState) { when (onboardingState) {
OnboardingState.CHECKING -> { OnboardingState.CHECKING -> {
InitializingScreen() InitializingScreen()
@@ -220,7 +242,6 @@ class MainActivity : ComponentActivity() {
// Add the callback - this will be automatically removed when the activity is destroyed // Add the callback - this will be automatically removed when the activity is destroyed
onBackPressedDispatcher.addCallback(this, backCallback) onBackPressedDispatcher.addCallback(this, backCallback)
ChatScreen(viewModel = chatViewModel) ChatScreen(viewModel = chatViewModel)
} }
@@ -626,7 +647,7 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
// Only set background state if app is fully initialized // Only set background state if app is fully initialized
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
@@ -661,6 +682,7 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
@@ -144,18 +144,20 @@ private fun BluetoothDisabledContent(
) )
} }
OutlinedButton( //Since we are automatically checking bluetooth state -- commented
onClick = onRetry,
modifier = Modifier.fillMaxWidth() // OutlinedButton(
) { // onClick = onRetry,
Text( // modifier = Modifier.fillMaxWidth()
text = "Check Again", // ) {
style = MaterialTheme.typography.bodyMedium.copy( // Text(
fontFamily = FontFamily.Monospace // text = "Check Again",
), // style = MaterialTheme.typography.bodyMedium.copy(
modifier = Modifier.padding(vertical = 4.dp) // fontFamily = FontFamily.Monospace
) // ),
} // modifier = Modifier.padding(vertical = 4.dp)
// )
// }
} }
} }
} }
@@ -2,12 +2,15 @@ package com.bitchat.android.onboarding
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
/** /**
* Manages Bluetooth enable/disable state and user prompts * Manages Bluetooth enable/disable state and user prompts
@@ -203,6 +206,59 @@ class BluetoothStatusManager(
fun logBluetoothStatus() { fun logBluetoothStatus() {
Log.d(TAG, getDiagnostics()) Log.d(TAG, getDiagnostics())
} }
/**
* Monitors Bluetooth state changes in real-time and invokes the provided callback.
* Returns the BroadcastReceiver for manual un-registration.
*/
fun monitorBluetoothState(
context: Context,
bluetoothStatusManager: BluetoothStatusManager,
onBluetoothStateChanged: (BluetoothStatus) -> Unit
): BroadcastReceiver {
Log.d(TAG, "Starting Bluetooth State Monitoring")
if (!bluetoothStatusManager.isBluetoothSupported()) {
Log.e(TAG, "Bluetooth is not supported")
onBluetoothStateChanged(BluetoothStatus.NOT_SUPPORTED)
bluetoothStatusManager.handleBluetoothStatus(BluetoothStatus.NOT_SUPPORTED)
return object : BroadcastReceiver() { override fun onReceive(p0: Context?, p1: Intent?) {}
}
}
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
BluetoothAdapter.STATE_ON -> {
Log.d(TAG, "Bluetooth turned ON")
onBluetoothStateChanged(BluetoothStatus.ENABLED)
bluetoothStatusManager.handleBluetoothStatus(BluetoothStatus.ENABLED)
}
BluetoothAdapter.STATE_OFF -> {
Log.d(TAG, "Bluetooth turned OFF")
onBluetoothStateChanged(BluetoothStatus.DISABLED)
bluetoothStatusManager.onBluetoothDisabled("User has turned off their Blue")
}
BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_OFF -> {
Log.d(TAG, "Bluetooth state transitioning: ${bluetoothStatusManager.getAdapterStateName(intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR))}")
}
}
}
}
//Register the receiver
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
ContextCompat.registerReceiver(context, receiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
//Check initial Bluetooth state
val initialStatus = bluetoothStatusManager.checkBluetoothStatus()
onBluetoothStateChanged(initialStatus)
return receiver
}
} }
/** /**