Handle request bluetooth permission
This commit is contained in:
+3
@@ -5,10 +5,13 @@ import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||
import com.digitoolsolutions.app.torquevaultkmp.AppStoragePlatform
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.db.AppDatabase
|
||||
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
||||
import com.digitoolsolutions.app.torquevaultkmp.utils.IosBluetoothManager
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val platformModule = module {
|
||||
single<AppStorage> { AppStoragePlatform() }
|
||||
single<BluetoothManager> { IosBluetoothManager() }
|
||||
single<SqlDriver> {
|
||||
NativeSqliteDriver(
|
||||
schema = AppDatabase.Schema,
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.utils
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import platform.CoreBluetooth.*
|
||||
import platform.Foundation.NSURL
|
||||
import platform.UIKit.*
|
||||
import platform.darwin.NSObject
|
||||
import platform.darwin.dispatch_async
|
||||
import platform.darwin.dispatch_get_main_queue
|
||||
|
||||
class IosBluetoothManager : BluetoothManager {
|
||||
private val _isBluetoothEnabled = MutableStateFlow(false)
|
||||
override val isBluetoothEnabled: StateFlow<Boolean> = _isBluetoothEnabled.asStateFlow()
|
||||
|
||||
private val _hasBluetoothPermission = MutableStateFlow(false)
|
||||
override val hasBluetoothPermission: StateFlow<Boolean> = _hasBluetoothPermission.asStateFlow()
|
||||
|
||||
// Strong reference to delegate to prevent garbage collection
|
||||
private val centralManagerDelegate = object : NSObject(), CBCentralManagerDelegateProtocol {
|
||||
override fun centralManagerDidUpdateState(central: CBCentralManager) {
|
||||
_isBluetoothEnabled.value = central.state == CBCentralManagerStatePoweredOn
|
||||
checkBluetoothPermission()
|
||||
}
|
||||
}
|
||||
|
||||
private val centralManager: CBCentralManager by lazy {
|
||||
val options = mapOf<Any?, Any?>(CBCentralManagerOptionShowPowerAlertKey to true)
|
||||
CBCentralManager(delegate = centralManagerDelegate, queue = null, options = options)
|
||||
}
|
||||
|
||||
init {
|
||||
// Trigger lazy initialization
|
||||
checkBluetoothState()
|
||||
checkBluetoothPermission()
|
||||
}
|
||||
|
||||
override fun checkBluetoothState() {
|
||||
_isBluetoothEnabled.value = centralManager.state == CBCentralManagerStatePoweredOn
|
||||
}
|
||||
|
||||
override fun checkBluetoothPermission() {
|
||||
val auth = CBManager.authorization
|
||||
_hasBluetoothPermission.value = auth == CBManagerAuthorizationAllowedAlways
|
||||
}
|
||||
|
||||
override fun openSettings() {
|
||||
val url = NSURL.URLWithString(UIApplicationOpenSettingsURLString)
|
||||
if (url != null) {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
if (UIApplication.sharedApplication.canOpenURL(url)) {
|
||||
UIApplication.sharedApplication.openURL(url, mapOf<Any?, Any?>(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableBluetooth() {
|
||||
val auth = CBManager.authorization
|
||||
if (auth == CBManagerAuthorizationDenied || auth == CBManagerAuthorizationRestricted) {
|
||||
openSettings()
|
||||
return
|
||||
}
|
||||
|
||||
val state = centralManager.state
|
||||
if (state == CBCentralManagerStatePoweredOff) {
|
||||
// Attempt to open Bluetooth settings directly via App-Prefs (best effort)
|
||||
val bluetoothUrl = NSURL.URLWithString("App-Prefs:root=Bluetooth")
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
val app = UIApplication.sharedApplication
|
||||
if (bluetoothUrl != null && app.canOpenURL(bluetoothUrl)) {
|
||||
app.openURL(bluetoothUrl, mapOf<Any?, Any?>(), null)
|
||||
} else {
|
||||
// Fallback: Trigger the system's "Bluetooth is Off" power alert
|
||||
// This alert has a "Settings" button that goes to the right place.
|
||||
centralManager.scanForPeripheralsWithServices(null, null)
|
||||
centralManager.stopScan()
|
||||
}
|
||||
}
|
||||
} else if (auth == CBManagerAuthorizationNotDetermined) {
|
||||
// Accessing the state or scanning will trigger the system prompt
|
||||
centralManager.scanForPeripheralsWithServices(null, null)
|
||||
centralManager.stopScan()
|
||||
checkBluetoothState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actual fun createBluetoothManager(): BluetoothManager = IosBluetoothManager()
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.utils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
actual fun HandlePermissionRequest(
|
||||
onPermissionResult: (Boolean) -> Unit
|
||||
) {
|
||||
val bluetoothManager: BluetoothManager = koinInject()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Trigger enableBluetooth which on iOS will touch the CBCentralManager
|
||||
// and show the permission prompt if it's currently "Not Determined"
|
||||
bluetoothManager.enableBluetooth()
|
||||
|
||||
// We call the result immediately, but the StateFlows in BluetoothManager
|
||||
// will update asynchronously via the CBCentralManagerDelegate
|
||||
onPermissionResult(bluetoothManager.hasBluetoothPermission.value)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user