Implement preconditions for app usage (BLE support, permissions, bluetooth and network condition)
This commit is contained in:
@@ -72,4 +72,6 @@
|
||||
<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
|
||||
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>
|
||||
|
||||
@@ -4,22 +4,26 @@ 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.screens.requirement.RequirementWrapper
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.scanner.ScannerViewModel
|
||||
import com.digitoolsolutions.app.torquevaultkmp.theme.TorqueVaultTheme
|
||||
import io.github.aakira.napier.Napier
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App(
|
||||
viewModel: AppViewModel = koinViewModel()
|
||||
viewModel: AppViewModel = koinViewModel(),
|
||||
scannerViewModel: ScannerViewModel = koinViewModel(),
|
||||
) {
|
||||
val savedTheme = viewModel.isDarkTheme.value
|
||||
val isDarkTheme = savedTheme ?: isSystemInDarkTheme()
|
||||
val isLoggedIn = viewModel.isLoggedIn()
|
||||
|
||||
TorqueVaultTheme(darkTheme = isDarkTheme) {
|
||||
BluetoothRequirementWrapper {
|
||||
RequirementWrapper(
|
||||
onReady = { scannerViewModel.startScan() },
|
||||
onPause = { scannerViewModel.stopScan() }
|
||||
) {
|
||||
MainScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
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 org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.btn_login
|
||||
@@ -52,8 +53,7 @@ fun MainScreen(
|
||||
isDarkTheme: Boolean,
|
||||
onThemeToggle: (Boolean) -> Unit,
|
||||
isLoggedIn: Boolean,
|
||||
viewModel: AppViewModel = koinViewModel(),
|
||||
scannerViewModel: ScannerViewModel = koinViewModel(),
|
||||
viewModel: AppViewModel = koinInject()
|
||||
) {
|
||||
val destinations = listOf(
|
||||
HomeDestination,
|
||||
@@ -88,10 +88,6 @@ fun MainScreen(
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
scannerViewModel.startScan()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomBar = {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ class AuthViewModel(
|
||||
private val _loginState = MutableStateFlow<LoginUiState>(LoginUiState.Idle)
|
||||
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()
|
||||
|
||||
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(
|
||||
Icons.Rounded.DeleteForever,
|
||||
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.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -10,7 +9,6 @@ import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
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.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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.BluetoothDisabled
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.SegmentedButtonDefaults.Icon
|
||||
import androidx.compose.material3.Text
|
||||
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.platform.LocalContext
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
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.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.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.LocationOff
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.WarningView
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
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 {
|
||||
@Composable
|
||||
fun HandlePermissionRequest(
|
||||
onPermissionResult: (Boolean, Boolean) -> Unit,
|
||||
onPermissionResult: (Boolean) -> Unit,
|
||||
content: @Composable (requestPermissions: () -> Unit) -> Unit
|
||||
)
|
||||
val hasBleFeature: Boolean
|
||||
val isLocationEnabled: StateFlow<Boolean>
|
||||
fun enableLocation()
|
||||
fun checkLocationState()
|
||||
|
||||
Reference in New Issue
Block a user