Add HRS service

This commit is contained in:
Sylwester Zieliński
2021-09-27 13:50:48 +02:00
parent 3ef57bf5fd
commit 7a171a1402
112 changed files with 1837 additions and 635 deletions

View File

@@ -0,0 +1,54 @@
package no.nordicsemi.android.nrftoolbox
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.NordicColors
@Composable
fun FeatureButton(@DrawableRes iconId: Int, @StringRes nameId: Int, onClick: () -> Unit) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onClick() },
colors = ButtonDefaults.buttonColors(backgroundColor = NordicColors.NordicGray4.value()),
) {
Image(
painter = painterResource(iconId),
contentDescription = stringResource(id = nameId),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(64.dp)
.clip(CircleShape)
.background(Color.White)
)
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = nameId),
modifier = Modifier.padding(16.dp),
)
}
}
}

View File

@@ -1,86 +1,105 @@
package no.nordicsemi.android.nrftoolbox
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import no.nordicsemi.android.csc.CSCRoute
import no.nordicsemi.android.csc.view.CscScreen
import no.nordicsemi.android.hrs.view.HRSScreen
import no.nordicsemi.android.scanner.view.BluetoothNotAvailableScreen
import no.nordicsemi.android.scanner.view.BluetoothNotEnabledScreen
import no.nordicsemi.android.scanner.view.RequestPermissionScreen
import no.nordicsemi.android.scanner.view.ScanDeviceScreen
import no.nordicsemi.android.scanner.view.ScanDeviceScreenResult
import no.nordicsemi.android.utils.exhaustive
@Composable
fun HomeScreen() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeView(navController) }
composable("csc-route") { CSCRoute() }
val viewModel = hiltViewModel<NavigationViewModel>()
val continueAction: () -> Unit = { viewModel.finish() }
val state = viewModel.state.collectAsState().value
BackHandler { viewModel.navigateUp() }
NavHost(navController = navController, startDestination = NavDestination.HOME.id) {
composable(NavDestination.HOME.id) { HomeView { viewModel.navigate(it) } }
composable(NavDestination.CSC.id) { CscScreen { viewModel.navigateUp() } }
composable(NavDestination.HRS.id) { HRSScreen { viewModel.navigateUp() } }
composable(NavDestination.REQUEST_PERMISSION.id) { RequestPermissionScreen(continueAction) }
composable(NavDestination.BLUETOOTH_NOT_AVAILABLE.id) { BluetoothNotAvailableScreen() }
composable(NavDestination.BLUETOOTH_NOT_ENABLED.id) {
BluetoothNotEnabledScreen(continueAction)
}
composable(NavDestination.DEVICE_NOT_CONNECTED.id) {
ScanDeviceScreen {
when (it) {
ScanDeviceScreenResult.SUCCESS -> viewModel.finish()
ScanDeviceScreenResult.CANCEL -> viewModel.navigateUp()
}.exhaustive
}
}
}
LaunchedEffect(state) {
navController.navigate(state.id)
}
}
@Composable
fun HomeView(navHostController: NavController) {
fun HomeView(callback: (NavDestination) -> Unit) {
Column {
TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) })
FeatureButton(R.drawable.ic_csc, R.string.csc_module) { navHostController.navigate("csc-route") }
FeatureButton(R.drawable.ic_csc, R.string.csc_module) { callback(NavDestination.CSC) }
FeatureButton(R.drawable.ic_hrs, R.string.hrs_module) { callback(NavDestination.HRS) }
}
}
@Composable
fun FeatureButton(@DrawableRes iconId: Int, @StringRes nameId: Int, onClick: () -> Unit) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onClick() },
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)
) {
Image(
painter = painterResource(iconId),
contentDescription = stringResource(id = nameId),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(64.dp)
.clip(CircleShape)
.background(Color.White)
)
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = nameId),
modifier = Modifier.padding(16.dp),
)
private fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) {
val currentOnBack = rememberUpdatedState(onBack)
val backCallback = remember {
object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
currentOnBack.value()
}
}
}
SideEffect {
backCallback.isEnabled = enabled
}
val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
"No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner"
}.onBackPressedDispatcher
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner, backDispatcher) {
backDispatcher.addCallback(lifecycleOwner, backCallback)
onDispose {
backCallback.remove()
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
HomeView(rememberNavController())
HomeView { }
}

View File

@@ -0,0 +1,11 @@
package no.nordicsemi.android.nrftoolbox
enum class NavDestination(val id: String) {
HOME("home-screen"),
CSC("csc-screen"),
HRS("hrs-screen"),
REQUEST_PERMISSION("request-permission"),
BLUETOOTH_NOT_AVAILABLE("bluetooth-not-available"),
BLUETOOTH_NOT_ENABLED("bluetooth-not-enabled"),
DEVICE_NOT_CONNECTED("device-not-connected"),
}

View File

@@ -0,0 +1,58 @@
package no.nordicsemi.android.nrftoolbox
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import no.nordicsemi.android.scanner.tools.NordicBleScanner
import no.nordicsemi.android.scanner.tools.PermissionHelper
import no.nordicsemi.android.scanner.tools.ScannerStatus
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
import no.nordicsemi.android.scanner.viewmodel.BluetoothPermissionState
import javax.inject.Inject
@HiltViewModel
class NavigationViewModel @Inject constructor(
private val bleScanner: NordicBleScanner,
private val permissionHelper: PermissionHelper,
private val selectedDevice: no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
): ViewModel() {
val state= MutableStateFlow(NavDestination.HOME)
private var targetDestination = NavDestination.HOME
fun navigate(destination: NavDestination) {
targetDestination = destination
navigateToNextScreen()
}
fun navigateUp() {
targetDestination = NavDestination.HOME
state.value = NavDestination.HOME
}
fun finish() {
if (state.value != targetDestination) {
navigateToNextScreen()
}
}
private fun getBluetoothState(): BluetoothPermissionState {
return if (!permissionHelper.isRequiredPermissionGranted()) {
BluetoothPermissionState.PERMISSION_REQUIRED
} else when (bleScanner.getBluetoothStatus()) {
ScannerStatus.NOT_AVAILABLE -> BluetoothPermissionState.BLUETOOTH_NOT_AVAILABLE
ScannerStatus.DISABLED -> BluetoothPermissionState.BLUETOOTH_NOT_ENABLED
ScannerStatus.ENABLED -> selectedDevice.device?.let { BluetoothPermissionState.READY } ?: BluetoothPermissionState.DEVICE_NOT_CONNECTED
}
}
private fun navigateToNextScreen() {
state.value = when (getBluetoothState()) {
BluetoothPermissionState.PERMISSION_REQUIRED -> NavDestination.REQUEST_PERMISSION
BluetoothPermissionState.BLUETOOTH_NOT_AVAILABLE -> NavDestination.BLUETOOTH_NOT_AVAILABLE
BluetoothPermissionState.BLUETOOTH_NOT_ENABLED -> NavDestination.BLUETOOTH_NOT_ENABLED
BluetoothPermissionState.DEVICE_NOT_CONNECTED -> NavDestination.DEVICE_NOT_CONNECTED
BluetoothPermissionState.READY -> targetDestination
}
}
}