Handle request bluetooth permission

This commit is contained in:
2026-05-25 10:18:56 +07:00
parent 66fe3be32f
commit 31ea6be8cd
14 changed files with 426 additions and 9 deletions
@@ -4,6 +4,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.digitoolsolutions.app.torquevaultkmp.screens.MainScreen
import com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.BluetoothRequirementWrapper
import com.digitoolsolutions.app.torquevaultkmp.theme.TorqueVaultTheme
import org.koin.compose.viewmodel.koinViewModel
@@ -16,10 +17,12 @@ fun App() {
val isLoggedIn = viewModel.isLoggedIn()
TorqueVaultTheme(darkTheme = isDarkTheme) {
MainScreen(
isDarkTheme = isDarkTheme,
onThemeToggle = { viewModel.toggleTheme(it) },
isLoggedIn = isLoggedIn
)
BluetoothRequirementWrapper {
MainScreen(
isDarkTheme = isDarkTheme,
onThemeToggle = { viewModel.toggleTheme(it) },
isLoggedIn = isLoggedIn
)
}
}
}
@@ -19,6 +19,7 @@ import androidx.navigation.compose.rememberNavController
import com.digitoolsolutions.app.torquevaultkmp.AppViewModel
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.AuthDestination
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.LoginScreen
import com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.BluetoothRequirementWrapper
import com.digitoolsolutions.app.torquevaultkmp.screens.bonded.BondedDestination
import com.digitoolsolutions.app.torquevaultkmp.screens.bonded.BondedScreen
import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeDestination
@@ -109,8 +110,16 @@ fun MainScreen(
modifier = Modifier.padding(innerPadding).fillMaxSize()
) {
composable(AuthDestination.route) { LoginScreen(navController) }
composable(HomeDestination.route) { HomeScreen(navController) }
composable(BondedDestination.route) { BondedScreen(navController) }
composable(HomeDestination.route) {
BluetoothRequirementWrapper {
HomeScreen(navController)
}
}
composable(BondedDestination.route) {
BluetoothRequirementWrapper {
BondedScreen(navController)
}
}
composable(LogDestination.route) { LogScreen(navController) }
composable(SettingDestination.route) {
SettingScreen(navController, isDarkTheme, onThemeToggle)
@@ -0,0 +1,125 @@
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BluetoothDisabled
import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.LifecycleResumeEffect
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
import com.digitoolsolutions.app.torquevaultkmp.utils.HandlePermissionRequest
import org.koin.compose.koinInject
@Composable
fun BluetoothRequirementWrapper(
content: @Composable () -> Unit
) {
val bluetoothManager: BluetoothManager = koinInject()
val isEnabled by bluetoothManager.isBluetoothEnabled.collectAsState()
val hasPermission by bluetoothManager.hasBluetoothPermission.collectAsState()
var showPermissionRequest by remember { mutableStateOf(false) }
if (showPermissionRequest) {
HandlePermissionRequest { _ ->
showPermissionRequest = false
bluetoothManager.checkBluetoothPermission()
}
}
LifecycleResumeEffect(Unit) {
bluetoothManager.checkBluetoothState()
bluetoothManager.checkBluetoothPermission()
onPauseOrDispose { }
}
LaunchedEffect(isEnabled, hasPermission) {
if (!isEnabled && hasPermission) {
bluetoothManager.enableBluetooth()
}
}
if (isEnabled && hasPermission) {
content()
} else {
BluetoothRequestScreen(
isEnabled = isEnabled,
hasPermission = hasPermission,
onEnableBluetooth = { bluetoothManager.enableBluetooth() },
onRequestPermission = {
bluetoothManager.enableBluetooth()
showPermissionRequest = true
},
onOpenSettings = { bluetoothManager.openSettings() }
)
}
}
@Composable
fun BluetoothRequestScreen(
isEnabled: Boolean,
hasPermission: Boolean,
onEnableBluetooth: () -> Unit,
onRequestPermission: () -> Unit,
onOpenSettings: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = if (!hasPermission) Icons.Default.Security else Icons.Default.BluetoothDisabled,
contentDescription = null,
modifier = Modifier.size(80.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = if (!hasPermission) "Bluetooth Permission Required" else "Bluetooth is Disabled",
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = if (!hasPermission)
"This app needs Bluetooth permissions to connect to your devices. Please grant permission."
else "Please enable Bluetooth to use this app's features.",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(32.dp))
if (!hasPermission) {
Button(onClick = onRequestPermission) {
Text("Request Permission")
}
} else if (!isEnabled) {
Button(onClick = onEnableBluetooth) {
Text("Enable Bluetooth")
}
}
}
}
@@ -0,0 +1,15 @@
package com.digitoolsolutions.app.torquevaultkmp.utils
import kotlinx.coroutines.flow.StateFlow
interface BluetoothManager {
val isBluetoothEnabled: StateFlow<Boolean>
val hasBluetoothPermission: StateFlow<Boolean>
fun checkBluetoothState()
fun checkBluetoothPermission()
fun openSettings()
fun enableBluetooth()
}
expect fun createBluetoothManager(): BluetoothManager
@@ -0,0 +1,8 @@
package com.digitoolsolutions.app.torquevaultkmp.utils
import androidx.compose.runtime.Composable
@Composable
expect fun HandlePermissionRequest(
onPermissionResult: (Boolean) -> Unit
)