diff --git a/app/build.gradle b/app/build.gradle index 10444470..eeaf0129 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'kotlin-android' + id 'kotlin-parcelize' id 'kotlin-kapt' id 'dagger.hilt.android.plugin' } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt index 3c6fe2ca..2d20ca47 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt @@ -2,22 +2,13 @@ package no.nordicsemi.android.nrftoolbox import android.app.Activity import android.os.ParcelUuid -import androidx.activity.OnBackPressedCallback +import android.util.Log import androidx.activity.compose.BackHandler -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.LocalContext -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.hilt.navigation.compose.hiltViewModel -import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -29,257 +20,77 @@ import no.nordicsemi.android.hrs.view.HRSScreen import no.nordicsemi.android.hts.view.HTSScreen import no.nordicsemi.android.prx.view.PRXScreen import no.nordicsemi.android.rscs.view.RSCSScreen -import no.nordicsemi.android.theme.view.CloseIconAppBar import no.nordicsemi.android.uart.view.UARTScreen -import no.nordicsemi.ui.scanner.navigation.view.FindDeviceCloseResult import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen -import no.nordicsemi.ui.scanner.navigation.view.FindDeviceFlowStatus -import no.nordicsemi.ui.scanner.navigation.view.FindDeviceProcessingResult -import no.nordicsemi.ui.scanner.navigation.view.FindDeviceSuccessResult import no.nordicsemi.ui.scanner.ui.exhaustive @Composable internal fun HomeScreen() { val navController = rememberNavController() + val viewModel: HomeViewModel = hiltViewModel() val activity = LocalContext.current as Activity BackHandler { - if (navController.currentDestination?.navigatorName != NavDestination.HOME.id) { + if (navController.currentDestination?.navigatorName != NavigationId.HOME.id) { navController.popBackStack() } else { activity.finish() } } - val goHome = { navController.navigate(NavDestination.HOME.id) } + val destination = viewModel.destination.collectAsState() + + val navigateUp = { viewModel.navigateUp() } NavHost( navController = navController, - startDestination = NavDestination.HOME.id + startDestination = NavigationId.HOME.id ) { - composable(NavDestination.HOME.id) { - HomeView { navController.navigate(it.id) } - } - composable(NavDestination.CSC.id) { - handleScannerFlow(navController) { CSCScreen { goHome() }} - } - composable(NavDestination.HRS.id) { - handleScannerFlow(navController) { HRSScreen { goHome() }} - } - composable(NavDestination.HTS.id) { - handleScannerFlow(navController) { HTSScreen { goHome() }} - } - composable(NavDestination.GLS.id) { - handleScannerFlow(navController) { GLSScreen { goHome() }} - } - composable(NavDestination.BPS.id) { - handleScannerFlow(navController) { BPSScreen { goHome() }} - } - composable(NavDestination.PRX.id) { - handleScannerFlow(navController) { PRXScreen { goHome() }} - } - composable(NavDestination.RSCS.id) { - handleScannerFlow(navController) { RSCSScreen { goHome() }} - } - composable(NavDestination.CGMS.id) { - handleScannerFlow(navController) { CGMScreen { goHome() }} - } - composable(NavDestination.UART.id) { - handleScannerFlow(navController) { UARTScreen { goHome() }} - } - } -} - -@Composable -private fun handleScannerFlow(navController: NavHostController, screen: @Composable () -> Unit) { - val deviceHolder: HomeViewModel = hiltViewModel() - - val findDeviceResult = remember { - mutableStateOf(FindDeviceProcessingResult) - } - - when (val result = findDeviceResult.value) { - FindDeviceProcessingResult -> FindDeviceScreen(ParcelUuid(NavDestination.CSC.uuid), findDeviceResult) - FindDeviceCloseResult -> navController.navigateUp() - is FindDeviceSuccessResult -> { - deviceHolder.onDeviceSelected(result.device) - screen() - } - }.exhaustive -} - -@Composable -fun HomeView(callback: (NavDestination) -> Unit) { - Column { - val context = LocalContext.current - CloseIconAppBar(stringResource(id = R.string.app_name)) { - (context as? Activity)?.finish() - } - - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp) - ) { - Spacer(modifier = Modifier.height(16.dp)) - - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_csc, - R.string.csc_module, - R.string.csc_module_full - ) { callback(NavDestination.CSC) } - } - Spacer(modifier = Modifier.width(16.dp)) - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_hrs, R.string.hrs_module, - R.string.hrs_module_full - ) { callback(NavDestination.HRS) } - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_gls, R.string.gls_module, - R.string.gls_module_full - ) { callback(NavDestination.GLS) } - } - Spacer(modifier = Modifier.width(16.dp)) - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_hts, R.string.hts_module, - R.string.hts_module_full - ) { callback(NavDestination.HTS) } - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_bps, R.string.bps_module, - R.string.bps_module_full - ) { callback(NavDestination.BPS) } - } - Spacer(modifier = Modifier.width(16.dp)) - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_rscs, - R.string.rscs_module, - R.string.rscs_module_full - ) { callback(NavDestination.RSCS) } - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_prx, R.string.prx_module, - R.string.prx_module_full - ) { callback(NavDestination.PRX) } - } - Spacer(modifier = Modifier.width(16.dp)) - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_cgm, R.string.cgm_module, - R.string.cgm_module_full - ) { callback(NavDestination.CGMS) } - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_uart, R.string.uart_module, - R.string.uart_module_full - ) { callback(NavDestination.UART) } - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - FeatureButton( - R.drawable.ic_dfu, R.string.dfu_module, - R.string.dfu_module_full - ) { } - } - } - - Spacer(modifier = Modifier.height(16.dp)) + composable(NavigationId.SCANNER.id) { + val profile = viewModel.profile!! + FindDeviceScreen(ParcelUuid(profile.uuid)) { + viewModel.onScannerFlowResult(it) } } + composable(NavigationId.HOME.id) { + HomeView(viewModel) + } + composable(NavigationId.CSC.id) { + CSCScreen(navigateUp) + } + composable(NavigationId.HRS.id) { + HRSScreen(navigateUp) + } + composable(NavigationId.HTS.id) { + HTSScreen(navigateUp) + } + composable(NavigationId.GLS.id) { + GLSScreen(navigateUp) + } + composable(NavigationId.BPS.id) { + BPSScreen(navigateUp) + } + composable(NavigationId.PRX.id) { + PRXScreen(navigateUp) + } + composable(NavigationId.RSCS.id) { + RSCSScreen(navigateUp) + } + composable(NavigationId.CGMS.id) { + CGMScreen(navigateUp) + } + composable(NavigationId.UART.id) { + UARTScreen(navigateUp) + } + } + + val context = LocalContext.current as Activity + LaunchedEffect(destination.value) { + when (val destination = destination.value) { + FinishDestination -> context.finish() + HomeDestination -> navController.navigateUp() + is ProfileDestination -> navController.navigate(destination.id.id) + is ScannerDestination -> navController.navigate(destination.id.id) + }.exhaustive } } - -@Preview(showBackground = true) -@Composable -fun DefaultPreview() { - HomeView { } -} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeView.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeView.kt new file mode 100644 index 00000000..f8ef4975 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeView.kt @@ -0,0 +1,143 @@ +package no.nordicsemi.android.nrftoolbox + +import android.app.Activity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import no.nordicsemi.android.theme.view.CloseIconAppBar + +@Composable +fun HomeView(viewModel: HomeViewModel) { + Column { + val context = LocalContext.current + CloseIconAppBar(stringResource(id = R.string.app_name)) { + (context as? Activity)?.finish() + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + Spacer(modifier = Modifier.height(16.dp)) + + DoubleSection( + leftView = { + FeatureButton(R.drawable.ic_csc, R.string.csc_module, R.string.csc_module_full) { + viewModel.openProfile(Profile.CSC) + } + }, + rightView = { + FeatureButton(R.drawable.ic_hrs, R.string.hrs_module, R.string.hrs_module_full) { + viewModel.openProfile(Profile.HRS) + } + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + DoubleSection( + leftView = { + FeatureButton(R.drawable.ic_gls, R.string.gls_module, R.string.gls_module_full) { + viewModel.openProfile(Profile.GLS) + } + }, + rightView = { + FeatureButton(R.drawable.ic_hts, R.string.hts_module, R.string.hts_module_full) { + viewModel.openProfile(Profile.HTS) + } + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + DoubleSection( + leftView = { + FeatureButton(R.drawable.ic_bps, R.string.bps_module, R.string.bps_module_full) { + viewModel.openProfile(Profile.BPS) + } + }, + rightView = { + FeatureButton(R.drawable.ic_rscs, R.string.rscs_module, R.string.rscs_module_full) { + viewModel.openProfile(Profile.RSCS) + } + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + DoubleSection( + leftView = { + FeatureButton(R.drawable.ic_prx, R.string.prx_module, R.string.prx_module_full) { + viewModel.openProfile(Profile.PRX) + } + }, + rightView = { + FeatureButton(R.drawable.ic_cgm, R.string.cgm_module, R.string.cgm_module_full) { + viewModel.openProfile(Profile.CGMS) + } + } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + FeatureButton(R.drawable.ic_uart, R.string.uart_module, R.string.uart_module_full) { + viewModel.openProfile(Profile.UART) + } + + Spacer(modifier = Modifier.height(16.dp)) + + FeatureButton(R.drawable.ic_dfu, R.string.dfu_module, R.string.dfu_module_full) { + + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } + } +} + +@Composable +private fun DoubleSection( + leftView: @Composable () -> Unit, + rightView: @Composable () -> Unit, +) { + Row(horizontalArrangement = Arrangement.SpaceEvenly) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + leftView() + } + Spacer(modifier = Modifier.width(16.dp)) + Box( + modifier = Modifier + .weight(1f) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + rightView() + } + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt index 4eea113a..d9d2b923 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeViewModel.kt @@ -2,8 +2,13 @@ package no.nordicsemi.android.nrftoolbox import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder -import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice +import no.nordicsemi.ui.scanner.navigation.view.FindDeviceCloseResult +import no.nordicsemi.ui.scanner.navigation.view.FindDeviceFlowStatus +import no.nordicsemi.ui.scanner.navigation.view.FindDeviceSuccessResult +import no.nordicsemi.ui.scanner.ui.exhaustive import javax.inject.Inject @HiltViewModel @@ -11,7 +16,36 @@ class HomeViewModel @Inject constructor( private val deviceHolder: SelectedBluetoothDeviceHolder ) : ViewModel() { - fun onDeviceSelected(device: DiscoveredBluetoothDevice) { - deviceHolder.attachDevice(device) + private val _destination = MutableStateFlow(HomeDestination) + val destination = _destination.asStateFlow() + + var profile: Profile? = null //to pass argument between nav destinations + + fun onScannerFlowResult(status: FindDeviceFlowStatus) { + when (status) { + FindDeviceCloseResult -> navigateUp() + is FindDeviceSuccessResult -> onDeviceSelected(status) + }.exhaustive + } + + fun openProfile(profile: Profile) { + this.profile = profile + _destination.value = ScannerDestination(profile) + } + + fun navigateUp() { + val currentDestination = _destination.value + when (currentDestination) { + FinishDestination -> FinishDestination + HomeDestination -> FinishDestination + is ProfileDestination -> HomeDestination + is ScannerDestination -> HomeDestination + }.exhaustive + } + + private fun onDeviceSelected(result: FindDeviceSuccessResult) { + val profile = requireNotNull(profile) + deviceHolder.attachDevice(result.device) + _destination.value = ProfileDestination(profile.toNavigationId(), profile.isPairingRequired) } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt index 181ae880..6dc6bde8 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt @@ -1,28 +1,22 @@ package no.nordicsemi.android.nrftoolbox -import no.nordicsemi.android.bps.repository.BPS_SERVICE_UUID -import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID -import no.nordicsemi.android.csc.repository.CYCLING_SPEED_AND_CADENCE_SERVICE_UUID -import no.nordicsemi.android.gls.repository.GLS_SERVICE_UUID -import no.nordicsemi.android.hrs.service.HR_SERVICE_UUID -import no.nordicsemi.android.hts.repository.HT_SERVICE_UUID -import no.nordicsemi.android.prx.service.PRX_SERVICE_UUID -import no.nordicsemi.android.rscs.service.RSCS_SERVICE_UUID -import no.nordicsemi.android.uart.repository.UART_SERVICE_UUID -import java.util.* +sealed class NavDestination -const val ARGS_KEY = "args" +object FinishDestination : NavDestination() -enum class NavDestination(val id: String, val uuid: UUID?, val pairingRequired: Boolean) { - HOME("home-screen", null, false), - CSC("csc-screen", CYCLING_SPEED_AND_CADENCE_SERVICE_UUID, false), - HRS("hrs-screen", HR_SERVICE_UUID, false), - HTS("hts-screen", HT_SERVICE_UUID, false), - GLS("gls-screen", GLS_SERVICE_UUID, true), - BPS("bps-screen", BPS_SERVICE_UUID, false), - PRX("prx-screen", PRX_SERVICE_UUID, true), - RSCS("rscs-screen", RSCS_SERVICE_UUID, false), - CGMS("cgms-screen", CGMS_SERVICE_UUID, false), - UART("uart-screen", UART_SERVICE_UUID, false), - DFU("dfu-screen", null, false); //todo check characteristic +sealed class ForwardDestination : NavDestination() { + abstract val id: NavigationId } + +object HomeDestination : ForwardDestination() { + override val id: NavigationId = NavigationId.HOME +} + +data class ScannerDestination(val profile: Profile) : ForwardDestination() { + override val id: NavigationId = NavigationId.SCANNER +} + +data class ProfileDestination( + override val id: NavigationId, + val isPairingRequired: Boolean +) : ForwardDestination() diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationTarget.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationTarget.kt deleted file mode 100644 index bcde30aa..00000000 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationTarget.kt +++ /dev/null @@ -1,6 +0,0 @@ -package no.nordicsemi.android.nrftoolbox - -data class NavigationTarget(val destination: NavDestination, val args: String? = null) { - - val url: String = args?.let { destination.id.replace("{$ARGS_KEY}", it) } ?: destination.id -} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/Profile.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/Profile.kt new file mode 100644 index 00000000..5ac0d5f5 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/Profile.kt @@ -0,0 +1,55 @@ +package no.nordicsemi.android.nrftoolbox + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import no.nordicsemi.android.bps.repository.BPS_SERVICE_UUID +import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID +import no.nordicsemi.android.csc.repository.CYCLING_SPEED_AND_CADENCE_SERVICE_UUID +import no.nordicsemi.android.gls.repository.GLS_SERVICE_UUID +import no.nordicsemi.android.hrs.service.HR_SERVICE_UUID +import no.nordicsemi.android.hts.repository.HT_SERVICE_UUID +import no.nordicsemi.android.prx.service.PRX_SERVICE_UUID +import no.nordicsemi.android.rscs.service.RSCS_SERVICE_UUID +import no.nordicsemi.android.uart.repository.UART_SERVICE_UUID +import java.util.* + +enum class NavigationId(val id: String) { + HOME("home-screen"), + SCANNER("scanner-screen"), + CSC("csc-screen"), + HRS("hrs-screen"), + HTS("hts-screen"), + GLS("gls-screen"), + BPS("bps-screen"), + PRX("prx-screen"), + RSCS("rscs-screen"), + CGMS("cgms-screen"), + UART("uart-screen"); +} + +@Parcelize +enum class Profile(val uuid: UUID, val isPairingRequired: Boolean) : Parcelable { + CSC(CYCLING_SPEED_AND_CADENCE_SERVICE_UUID, false), + HRS(HR_SERVICE_UUID, false), + HTS(HT_SERVICE_UUID, false), + GLS(GLS_SERVICE_UUID, true), + BPS(BPS_SERVICE_UUID, false), + PRX(PRX_SERVICE_UUID, true), + RSCS(RSCS_SERVICE_UUID, false), + CGMS(CGMS_SERVICE_UUID, false), + UART(UART_SERVICE_UUID, false); +} + +fun Profile.toNavigationId(): NavigationId { + return when (this) { + Profile.CSC -> NavigationId.CSC + Profile.HRS -> NavigationId.HRS + Profile.HTS -> NavigationId.HTS + Profile.GLS -> NavigationId.GLS + Profile.BPS -> NavigationId.BPS + Profile.PRX -> NavigationId.PRX + Profile.RSCS -> NavigationId.RSCS + Profile.CGMS -> NavigationId.CGMS + Profile.UART -> NavigationId.UART + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65139ad1..6a3412ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,7 +8,7 @@ HTS Health Thermometer BPS - Blood presure + Blood pressure RSCS Running Speed and Cadence PRX