mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 00:25:20 +00:00
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:
@@ -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()
|
||||||
@@ -132,7 +132,29 @@ class MainActivity : ComponentActivity() {
|
|||||||
val isBluetoothLoading by mainViewModel.isBluetoothLoading.collectAsState()
|
val isBluetoothLoading by mainViewModel.isBluetoothLoading.collectAsState()
|
||||||
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()
|
||||||
@@ -209,7 +231,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
// Let ChatViewModel handle navigation state
|
// Let ChatViewModel handle navigation state
|
||||||
val handled = chatViewModel.handleBackPressed()
|
val handled = chatViewModel.handleBackPressed()
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// If ChatViewModel doesn't handle it, disable this callback
|
// If ChatViewModel doesn't handle it, disable this callback
|
||||||
// and let the system handle it (which will exit the app)
|
// and let the system handle it (which will exit the app)
|
||||||
this.isEnabled = false
|
this.isEnabled = false
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
@@ -217,10 +239,9 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +589,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
handleOnboardingFailed("Some permissions were revoked. Please grant all permissions to continue.")
|
handleOnboardingFailed("Some permissions were revoked. Please grant all permissions to continue.")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up mesh service delegate and start services
|
// Set up mesh service delegate and start services
|
||||||
meshService.delegate = chatViewModel
|
meshService.delegate = chatViewModel
|
||||||
meshService.startServices()
|
meshService.startServices()
|
||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user