Implement preconditions for app usage (BLE support, permissions, bluetooth and network condition)
This commit is contained in:
@@ -89,7 +89,7 @@ android {
|
|||||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||||
versionCode = 2
|
versionCode = 2
|
||||||
versionName = "1.0.1-2"
|
versionName = "1.0.2"
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- REQUIRED BLE FOR APP -->
|
||||||
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<!-- Android 6.0–11 -->
|
<!-- Android 6.0–11 -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
||||||
<!-- Android 12+ -->
|
<!-- Android 12+ -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"
|
android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
tools:targetApi="31" />
|
android:usesPermissionFlags="neverForLocation"
|
||||||
|
tools:targetApi="31"
|
||||||
|
/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:targetApi="31"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:targetApi="31"/>
|
||||||
|
|
||||||
<!-- Allow access http: android:usesCleartextTraffic="true" -->
|
<!-- Allow access http: android:usesCleartextTraffic="true" -->
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ package com.digitoolsolutions.app.torquevaultkmp
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
|
||||||
class AndroidPlatform : Platform {
|
class AndroidPlatform : Platform {
|
||||||
override val name: String = "Android ${Build.VERSION.SDK_INT}"
|
override val name: String = "Android"
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getPlatform(): Platform = AndroidPlatform()
|
actual fun getPlatform(): Platform = AndroidPlatform()
|
||||||
+3
@@ -6,7 +6,9 @@ import com.digitoolsolutions.app.torquevaultkmp.AppStoragePlatform
|
|||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.db.AppDatabase
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.db.AppDatabase
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.AndroidBluetoothManager
|
import com.digitoolsolutions.app.torquevaultkmp.utils.AndroidBluetoothManager
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.AndroidNetworkMonitor
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.NetworkMonitor
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandler
|
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandler
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandlerPlatform
|
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandlerPlatform
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
@@ -23,4 +25,5 @@ actual val platformModule = module {
|
|||||||
name = "app.db"
|
name = "app.db"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
single<NetworkMonitor> { AndroidNetworkMonitor(androidContext()) }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -50,11 +50,13 @@ class AndroidBluetoothManager(private val context: Context) : BluetoothManager {
|
|||||||
|
|
||||||
override fun checkBluetoothPermission() {
|
override fun checkBluetoothPermission() {
|
||||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
/* For android 12+ */
|
||||||
listOf(
|
listOf(
|
||||||
Manifest.permission.BLUETOOTH_SCAN,
|
Manifest.permission.BLUETOOTH_SCAN,
|
||||||
Manifest.permission.BLUETOOTH_CONNECT
|
Manifest.permission.BLUETOOTH_CONNECT
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
/* For android 11- */
|
||||||
listOf(
|
listOf(
|
||||||
Manifest.permission.BLUETOOTH,
|
Manifest.permission.BLUETOOTH,
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package com.digitoolsolutions.app.torquevaultkmp.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkRequest
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class AndroidNetworkMonitor(private val context: Context): NetworkMonitor {
|
||||||
|
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
private val _status = MutableStateFlow(NetworkStatus.Unavailable)
|
||||||
|
override val status: StateFlow<NetworkStatus> = _status.asStateFlow()
|
||||||
|
private val callback = object : ConnectivityManager.NetworkCallback() {
|
||||||
|
override fun onAvailable(network: Network) {
|
||||||
|
_status.value = NetworkStatus.Available
|
||||||
|
}
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
_status.value = NetworkStatus.Unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun start() {
|
||||||
|
val request = NetworkRequest.Builder()
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
|
.build()
|
||||||
|
connectivityManager.registerNetworkCallback(request, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
connectivityManager.unregisterNetworkCallback(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-3
@@ -7,6 +7,7 @@ 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.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
@@ -23,7 +24,7 @@ class PermissionHandlerPlatform(private val context: Context): PermissionHandler
|
|||||||
@SuppressLint("ContextCastToActivity")
|
@SuppressLint("ContextCastToActivity")
|
||||||
@Composable
|
@Composable
|
||||||
override fun HandlePermissionRequest (
|
override fun HandlePermissionRequest (
|
||||||
onPermissionResult: (Boolean, Boolean) -> Unit,
|
onPermissionResult: (Boolean) -> Unit,
|
||||||
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
val permissions = if (isPreAndroid12()) {
|
val permissions = if (isPreAndroid12()) {
|
||||||
@@ -43,7 +44,7 @@ class PermissionHandlerPlatform(private val context: Context): PermissionHandler
|
|||||||
val launcher = rememberLauncherForActivityResult(
|
val launcher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
) { result ->
|
) { result ->
|
||||||
val allGranted = result.values.all { it }
|
// val allGranted = result.values.all { it }
|
||||||
val permanentlyDenied = if (isPreAndroid12()) {
|
val permanentlyDenied = if (isPreAndroid12()) {
|
||||||
/** For android 6.0 - 11 */
|
/** For android 6.0 - 11 */
|
||||||
val deniedLocation = result[Manifest.permission.ACCESS_FINE_LOCATION] == false
|
val deniedLocation = result[Manifest.permission.ACCESS_FINE_LOCATION] == false
|
||||||
@@ -67,7 +68,7 @@ class PermissionHandlerPlatform(private val context: Context): PermissionHandler
|
|||||||
} ?: false
|
} ?: false
|
||||||
permDeniedScan || permDeniedConnect
|
permDeniedScan || permDeniedConnect
|
||||||
}
|
}
|
||||||
onPermissionResult(allGranted, permanentlyDenied)
|
onPermissionResult(permanentlyDenied)
|
||||||
}
|
}
|
||||||
content {
|
content {
|
||||||
launcher.launch(permissions)
|
launcher.launch(permissions)
|
||||||
@@ -111,4 +112,5 @@ class PermissionHandlerPlatform(private val context: Context): PermissionHandler
|
|||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
override val hasBleFeature: Boolean = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
|
||||||
}
|
}
|
||||||
@@ -72,4 +72,6 @@
|
|||||||
<string name="location_disabled_title">LOCATION DISABLED</string>
|
<string name="location_disabled_title">LOCATION DISABLED</string>
|
||||||
<string name="location_disabled_info">Location is turned off. Most Android phones require it in order to scan for Bluetooth LE devices.\n
|
<string name="location_disabled_info">Location is turned off. Most Android phones require it in order to scan for Bluetooth LE devices.\n
|
||||||
Click the button below to enable Location.</string>
|
Click the button below to enable Location.</string>
|
||||||
|
<string name="network_not_available_title">NO NETWORK CONNECTION</string>
|
||||||
|
<string name="network_not_available_info">Please check Wi-Fi or mobile data settings to continue.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -4,22 +4,26 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.MainScreen
|
import com.digitoolsolutions.app.torquevaultkmp.screens.MainScreen
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.BluetoothRequirementWrapper
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.RequirementWrapper
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.scanner.ScannerViewModel
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.theme.TorqueVaultTheme
|
import com.digitoolsolutions.app.torquevaultkmp.theme.TorqueVaultTheme
|
||||||
import io.github.aakira.napier.Napier
|
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App(
|
fun App(
|
||||||
viewModel: AppViewModel = koinViewModel()
|
viewModel: AppViewModel = koinViewModel(),
|
||||||
|
scannerViewModel: ScannerViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
val savedTheme = viewModel.isDarkTheme.value
|
val savedTheme = viewModel.isDarkTheme.value
|
||||||
val isDarkTheme = savedTheme ?: isSystemInDarkTheme()
|
val isDarkTheme = savedTheme ?: isSystemInDarkTheme()
|
||||||
val isLoggedIn = viewModel.isLoggedIn()
|
val isLoggedIn = viewModel.isLoggedIn()
|
||||||
|
|
||||||
TorqueVaultTheme(darkTheme = isDarkTheme) {
|
TorqueVaultTheme(darkTheme = isDarkTheme) {
|
||||||
BluetoothRequirementWrapper {
|
RequirementWrapper(
|
||||||
|
onReady = { scannerViewModel.startScan() },
|
||||||
|
onPause = { scannerViewModel.stopScan() }
|
||||||
|
) {
|
||||||
MainScreen(
|
MainScreen(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
onThemeToggle = {
|
onThemeToggle = {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp
|
|
||||||
|
|
||||||
class Greeting {
|
|
||||||
private val platform = getPlatform()
|
|
||||||
|
|
||||||
fun greet(): String {
|
|
||||||
return "Hello, ${platform.name}!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-6
@@ -41,6 +41,7 @@ import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingDestinat
|
|||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingScreen
|
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingScreen
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.koin.compose.koinInject
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
import torquevaultkmp.composeapp.generated.resources.Res
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
import torquevaultkmp.composeapp.generated.resources.btn_login
|
import torquevaultkmp.composeapp.generated.resources.btn_login
|
||||||
@@ -52,8 +53,7 @@ fun MainScreen(
|
|||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onThemeToggle: (Boolean) -> Unit,
|
onThemeToggle: (Boolean) -> Unit,
|
||||||
isLoggedIn: Boolean,
|
isLoggedIn: Boolean,
|
||||||
viewModel: AppViewModel = koinViewModel(),
|
viewModel: AppViewModel = koinInject()
|
||||||
scannerViewModel: ScannerViewModel = koinViewModel(),
|
|
||||||
) {
|
) {
|
||||||
val destinations = listOf(
|
val destinations = listOf(
|
||||||
HomeDestination,
|
HomeDestination,
|
||||||
@@ -88,10 +88,6 @@ fun MainScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
scannerViewModel.startScan()
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ class AuthViewModel(
|
|||||||
private val _loginState = MutableStateFlow<LoginUiState>(LoginUiState.Idle)
|
private val _loginState = MutableStateFlow<LoginUiState>(LoginUiState.Idle)
|
||||||
val loginState = _loginState.asStateFlow()
|
val loginState = _loginState.asStateFlow()
|
||||||
|
|
||||||
private val _serverUrl = MutableStateFlow(tokenManager.getBaseUrl() ?: "")
|
private val _serverUrl = MutableStateFlow(tokenManager.getBaseUrl() ?: "http://digitoolsolutions.synology.me:3000")
|
||||||
val serverUrl = _serverUrl.asStateFlow()
|
val serverUrl = _serverUrl.asStateFlow()
|
||||||
|
|
||||||
fun onChangeServerUrl(url: String) {
|
fun onChangeServerUrl(url: String) {
|
||||||
|
|||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
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.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views.BluetoothDisabledView
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views.LocationDisabledView
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views.LocationPermissionRequiredView
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandler
|
|
||||||
import org.koin.compose.koinInject
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BluetoothRequirementWrapper(
|
|
||||||
content: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
val bluetoothManager: BluetoothManager = koinInject()
|
|
||||||
val permissionHandler: PermissionHandler = koinInject()
|
|
||||||
val isEnabled by bluetoothManager.isBluetoothEnabled.collectAsState()
|
|
||||||
val hasPermission by bluetoothManager.hasBluetoothPermission.collectAsState()
|
|
||||||
var hasPermanentlyDenied by remember { mutableStateOf(false) }
|
|
||||||
val isLocationEnabled by permissionHandler.isLocationEnabled.collectAsState()
|
|
||||||
var showPermissionRequest by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (showPermissionRequest) {
|
|
||||||
permissionHandler.HandlePermissionRequest(
|
|
||||||
{ allGranted, denied ->
|
|
||||||
showPermissionRequest = false
|
|
||||||
bluetoothManager.checkBluetoothPermission()
|
|
||||||
hasPermanentlyDenied = denied
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
requestPermissions ->
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
requestPermissions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LifecycleResumeEffect(Unit) {
|
|
||||||
bluetoothManager.checkBluetoothState()
|
|
||||||
bluetoothManager.checkBluetoothPermission()
|
|
||||||
permissionHandler.checkLocationState()
|
|
||||||
onPauseOrDispose { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MainScreen */
|
|
||||||
content()
|
|
||||||
/* Overlay content */
|
|
||||||
if (!isEnabled || !hasPermission || !isLocationEnabled) {
|
|
||||||
BluetoothRequestScreen(
|
|
||||||
isEnabled = isEnabled,
|
|
||||||
hasPermission = hasPermission,
|
|
||||||
hasPermanentlyDenied = hasPermanentlyDenied,
|
|
||||||
isLocationEnabled = isLocationEnabled,
|
|
||||||
onEnableBluetooth = { bluetoothManager.enableBluetooth() },
|
|
||||||
onEnableLocation = { permissionHandler.enableLocation() },
|
|
||||||
onRequestPermission = { showPermissionRequest = true },
|
|
||||||
onOpenSettings = { bluetoothManager.openSettings() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BluetoothRequestScreen(
|
|
||||||
isEnabled: Boolean,
|
|
||||||
hasPermission: Boolean,
|
|
||||||
hasPermanentlyDenied: Boolean,
|
|
||||||
isLocationEnabled: Boolean,
|
|
||||||
onEnableBluetooth: () -> Unit,
|
|
||||||
onEnableLocation: () -> Unit,
|
|
||||||
onRequestPermission: () -> Unit,
|
|
||||||
onOpenSettings: () -> Unit
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.padding(12.dp)
|
|
||||||
){
|
|
||||||
when {
|
|
||||||
!isEnabled -> BluetoothDisabledView(onEnable = onEnableBluetooth)
|
|
||||||
!hasPermission -> LocationPermissionRequiredView(
|
|
||||||
hasPermission = hasPermission,
|
|
||||||
hasPermanentlyDenied = hasPermanentlyDenied,
|
|
||||||
onRequestPermission = onRequestPermission,
|
|
||||||
onOpenSetting = onOpenSettings
|
|
||||||
)
|
|
||||||
!isLocationEnabled -> LocationDisabledView(onEnable = onEnableLocation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -66,7 +66,7 @@ fun UartCommunicationScreen(
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.Rounded.DeleteForever,
|
Icons.Rounded.DeleteForever,
|
||||||
contentDescription = "Clear history",
|
contentDescription = "Clear history",
|
||||||
tint = Color.Red
|
tint = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.getPlatform
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views.BluetoothDisabledView
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views.BluetoothNotAvailableView
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views.BluetoothPermissionRequiredView
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views.LocationDisabledView
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views.LocationPermissionRequiredView
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views.NetworkNotAvailableView
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.NetworkMonitor
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.NetworkStatus
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandler
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
|
data class Requirements(
|
||||||
|
val hasBleFeature: Boolean,
|
||||||
|
val isEnabled: Boolean,
|
||||||
|
val hasPermission: Boolean,
|
||||||
|
val hasPermanentlyDenied: Boolean,
|
||||||
|
val isLocationEnabled: Boolean,
|
||||||
|
val networkAvailable: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class RequirementState {
|
||||||
|
object BleUnsupported : RequirementState()
|
||||||
|
object BluetoothDisabled : RequirementState()
|
||||||
|
data class BluetoothPermissionRequired(val permanentlyDenied: Boolean) : RequirementState()
|
||||||
|
data class LocationPermissionRequired(val permanentlyDenied: Boolean) : RequirementState()
|
||||||
|
object LocationDisabled : RequirementState()
|
||||||
|
object NetworkUnavailable : RequirementState()
|
||||||
|
object Ready : RequirementState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Requirements.toState(): RequirementState {
|
||||||
|
return when {
|
||||||
|
!hasBleFeature -> RequirementState.BleUnsupported
|
||||||
|
!hasPermission -> {
|
||||||
|
if (getPlatform().name == "IOS") {
|
||||||
|
RequirementState.BluetoothPermissionRequired(hasPermanentlyDenied)
|
||||||
|
} else {
|
||||||
|
RequirementState.LocationPermissionRequired(hasPermanentlyDenied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
!isEnabled -> RequirementState.BluetoothDisabled
|
||||||
|
!isLocationEnabled -> RequirementState.LocationDisabled
|
||||||
|
!networkAvailable -> RequirementState.NetworkUnavailable
|
||||||
|
else -> RequirementState.Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun RequirementWrapper(
|
||||||
|
onReady: () -> Unit,
|
||||||
|
onPause: () -> Unit,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val bluetoothManager: BluetoothManager = koinInject()
|
||||||
|
val permissionHandler: PermissionHandler = koinInject()
|
||||||
|
val networkMonitor: NetworkMonitor = koinInject()
|
||||||
|
val hasBleFeature = permissionHandler.hasBleFeature
|
||||||
|
val isEnabled by bluetoothManager.isBluetoothEnabled.collectAsState()
|
||||||
|
val hasPermission by bluetoothManager.hasBluetoothPermission.collectAsState()
|
||||||
|
var hasPermanentlyDenied by remember { mutableStateOf(false) }
|
||||||
|
val isLocationEnabled by permissionHandler.isLocationEnabled.collectAsState()
|
||||||
|
var showPermissionRequest by remember { mutableStateOf(false) }
|
||||||
|
val networkStatus by networkMonitor.status.collectAsState()
|
||||||
|
|
||||||
|
if (showPermissionRequest) {
|
||||||
|
permissionHandler.HandlePermissionRequest(
|
||||||
|
{ denied ->
|
||||||
|
showPermissionRequest = false
|
||||||
|
bluetoothManager.checkBluetoothPermission()
|
||||||
|
hasPermanentlyDenied = denied
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
requestPermissions ->
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
requestPermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LifecycleResumeEffect(Unit) {
|
||||||
|
bluetoothManager.checkBluetoothState()
|
||||||
|
bluetoothManager.checkBluetoothPermission()
|
||||||
|
permissionHandler.checkLocationState()
|
||||||
|
onPauseOrDispose { }
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(networkMonitor) {
|
||||||
|
networkMonitor.start()
|
||||||
|
onDispose {
|
||||||
|
networkMonitor.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val requirements = Requirements(
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
hasPermission = hasPermission,
|
||||||
|
hasPermanentlyDenied = hasPermanentlyDenied,
|
||||||
|
isLocationEnabled = isLocationEnabled,
|
||||||
|
hasBleFeature = hasBleFeature,
|
||||||
|
networkAvailable = networkStatus == NetworkStatus.Available
|
||||||
|
)
|
||||||
|
|
||||||
|
val state = requirements.toState()
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
if (state == RequirementState.Ready) {
|
||||||
|
onReady()
|
||||||
|
} else {
|
||||||
|
onPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MainScreen */
|
||||||
|
content()
|
||||||
|
/* Overlay content */
|
||||||
|
if (state != RequirementState.Ready) {
|
||||||
|
RequirementContent(
|
||||||
|
state = state,
|
||||||
|
onEnableBluetooth = { bluetoothManager.enableBluetooth() },
|
||||||
|
onEnableLocation = { permissionHandler.enableLocation() },
|
||||||
|
onRequestPermission = { showPermissionRequest = true },
|
||||||
|
onOpenSettings = { bluetoothManager.openSettings() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RequirementContent(
|
||||||
|
state: RequirementState,
|
||||||
|
onEnableBluetooth: () -> Unit,
|
||||||
|
onEnableLocation: () -> Unit,
|
||||||
|
onRequestPermission: () -> Unit,
|
||||||
|
onOpenSettings: () -> Unit
|
||||||
|
) {
|
||||||
|
val platform = getPlatform().name
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.padding(12.dp)
|
||||||
|
){
|
||||||
|
when (state){
|
||||||
|
RequirementState.BleUnsupported -> BluetoothNotAvailableView()
|
||||||
|
RequirementState.BluetoothDisabled -> BluetoothDisabledView(onEnable = onEnableBluetooth)
|
||||||
|
is RequirementState.LocationPermissionRequired -> LocationPermissionRequiredView(
|
||||||
|
hasPermission = false,
|
||||||
|
hasPermanentlyDenied = state.permanentlyDenied,
|
||||||
|
onRequestPermission = onRequestPermission,
|
||||||
|
onOpenSetting = onOpenSettings
|
||||||
|
)
|
||||||
|
is RequirementState.BluetoothPermissionRequired -> BluetoothPermissionRequiredView(
|
||||||
|
hasPermission = false,
|
||||||
|
hasPermanentlyDenied = state.permanentlyDenied,
|
||||||
|
onRequestPermission = onRequestPermission,
|
||||||
|
onOpenSetting = onOpenSettings
|
||||||
|
)
|
||||||
|
RequirementState.LocationDisabled -> LocationDisabledView(onEnable = onEnableLocation)
|
||||||
|
RequirementState.NetworkUnavailable -> NetworkNotAvailableView()
|
||||||
|
RequirementState.Ready -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-3
@@ -1,7 +1,6 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -10,7 +9,6 @@ import androidx.compose.material3.Button
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import torquevaultkmp.composeapp.generated.resources.Res
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
+1
-16
@@ -1,30 +1,15 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views
|
||||||
|
|
||||||
//import android.Manifest
|
|
||||||
//import android.content.Context
|
|
||||||
//import android.content.Intent
|
|
||||||
//import android.net.Uri
|
|
||||||
//import android.os.Build
|
|
||||||
//import android.provider.Settings
|
|
||||||
//import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
//import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.BluetoothDisabled
|
import androidx.compose.material.icons.rounded.BluetoothDisabled
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults.Icon
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
//import androidx.compose.ui.platform.LocalContext
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||||
import org.jetbrains.compose.resources.painterResource
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import torquevaultkmp.composeapp.generated.resources.Res
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
import torquevaultkmp.composeapp.generated.resources.action_grant_permission
|
import torquevaultkmp.composeapp.generated.resources.action_grant_permission
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
+1
-5
@@ -1,18 +1,14 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bluetooth.views
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.LocationOff
|
import androidx.compose.material.icons.rounded.LocationOff
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import torquevaultkmp.composeapp.generated.resources.Res
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package com.digitoolsolutions.app.torquevaultkmp.screens.requirement.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.WifiOff
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.network_not_available_info
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.network_not_available_title
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun NetworkNotAvailableView() {
|
||||||
|
WarningView(
|
||||||
|
imageVector = Icons.Rounded.WifiOff,
|
||||||
|
title = stringResource(Res.string.network_not_available_title),
|
||||||
|
hint = stringResource(Res.string.network_not_available_info),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
)
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.digitoolsolutions.app.torquevaultkmp.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
enum class NetworkStatus {
|
||||||
|
Available, Unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetworkMonitor {
|
||||||
|
val status: StateFlow<NetworkStatus>
|
||||||
|
fun start()
|
||||||
|
fun stop()
|
||||||
|
}
|
||||||
+2
-1
@@ -6,9 +6,10 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
interface PermissionHandler {
|
interface PermissionHandler {
|
||||||
@Composable
|
@Composable
|
||||||
fun HandlePermissionRequest(
|
fun HandlePermissionRequest(
|
||||||
onPermissionResult: (Boolean, Boolean) -> Unit,
|
onPermissionResult: (Boolean) -> Unit,
|
||||||
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
||||||
)
|
)
|
||||||
|
val hasBleFeature: Boolean
|
||||||
val isLocationEnabled: StateFlow<Boolean>
|
val isLocationEnabled: StateFlow<Boolean>
|
||||||
fun enableLocation()
|
fun enableLocation()
|
||||||
fun checkLocationState()
|
fun checkLocationState()
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ package com.digitoolsolutions.app.torquevaultkmp
|
|||||||
import platform.UIKit.UIDevice
|
import platform.UIKit.UIDevice
|
||||||
|
|
||||||
class IOSPlatform: Platform {
|
class IOSPlatform: Platform {
|
||||||
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
|
override val name: String = "IOS"
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getPlatform(): Platform = IOSPlatform()
|
actual fun getPlatform(): Platform = IOSPlatform()
|
||||||
+3
@@ -6,7 +6,9 @@ import com.digitoolsolutions.app.torquevaultkmp.AppStoragePlatform
|
|||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.db.AppDatabase
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.db.AppDatabase
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
import com.digitoolsolutions.app.torquevaultkmp.utils.BluetoothManager
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.IOSNetworkMonitor
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.IosBluetoothManager
|
import com.digitoolsolutions.app.torquevaultkmp.utils.IosBluetoothManager
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.utils.NetworkMonitor
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandler
|
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandler
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandlerPlatform
|
import com.digitoolsolutions.app.torquevaultkmp.utils.PermissionHandlerPlatform
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
@@ -21,4 +23,5 @@ actual val platformModule = module {
|
|||||||
name = "app.db"
|
name = "app.db"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
single<NetworkMonitor> { IOSNetworkMonitor() }
|
||||||
}
|
}
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package com.digitoolsolutions.app.torquevaultkmp.utils
|
||||||
|
|
||||||
|
import platform.Network.nw_path_monitor_create
|
||||||
|
import platform.Network.nw_path_monitor_set_queue
|
||||||
|
import platform.Network.nw_path_monitor_set_update_handler
|
||||||
|
import platform.Network.nw_path_monitor_start
|
||||||
|
import platform.Network.nw_path_monitor_cancel
|
||||||
|
import platform.Network.nw_path_get_status
|
||||||
|
import platform.Network.nw_path_status_satisfied
|
||||||
|
import platform.darwin.dispatch_get_main_queue
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class IOSNetworkMonitor: NetworkMonitor {
|
||||||
|
private val monitor = nw_path_monitor_create()
|
||||||
|
private val _status = MutableStateFlow(NetworkStatus.Unavailable)
|
||||||
|
override val status: StateFlow<NetworkStatus> = _status.asStateFlow()
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
nw_path_monitor_set_update_handler(monitor) { path ->
|
||||||
|
val isAvailable = nw_path_get_status(path) == nw_path_status_satisfied
|
||||||
|
_status.value = if (isAvailable) NetworkStatus.Available else NetworkStatus.Unavailable
|
||||||
|
}
|
||||||
|
nw_path_monitor_set_queue(monitor, dispatch_get_main_queue())
|
||||||
|
nw_path_monitor_start(monitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
nw_path_monitor_cancel(monitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -10,7 +10,7 @@ import org.koin.compose.koinInject
|
|||||||
class PermissionHandlerPlatform: PermissionHandler {
|
class PermissionHandlerPlatform: PermissionHandler {
|
||||||
@Composable
|
@Composable
|
||||||
override fun HandlePermissionRequest(
|
override fun HandlePermissionRequest(
|
||||||
onPermissionResult: (Boolean, Boolean) -> Unit,
|
onPermissionResult: (Boolean) -> Unit,
|
||||||
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
val bluetoothManager: BluetoothManager = koinInject()
|
val bluetoothManager: BluetoothManager = koinInject()
|
||||||
@@ -24,12 +24,12 @@ class PermissionHandlerPlatform: PermissionHandler {
|
|||||||
// will update asynchronously via the CBCentralManagerDelegate
|
// will update asynchronously via the CBCentralManagerDelegate
|
||||||
|
|
||||||
val granted = bluetoothManager.hasBluetoothPermission.value
|
val granted = bluetoothManager.hasBluetoothPermission.value
|
||||||
val denied = !granted
|
onPermissionResult(granted)
|
||||||
onPermissionResult(granted, denied)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val _isLocationEnabled = MutableStateFlow(true)
|
private val _isLocationEnabled = MutableStateFlow(true)
|
||||||
override val isLocationEnabled: StateFlow<Boolean> = _isLocationEnabled.asStateFlow()
|
override val isLocationEnabled: StateFlow<Boolean> = _isLocationEnabled.asStateFlow()
|
||||||
override fun enableLocation() { /* no-op */ }
|
override fun enableLocation() { /* no-op */ }
|
||||||
override fun checkLocationState() { /* no-op */ }
|
override fun checkLocationState() { /* no-op */ }
|
||||||
|
override val hasBleFeature: Boolean = true
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user