Change HomeScreen to new navigation

This commit is contained in:
Sylwester Zieliński
2022-01-14 11:45:01 +01:00
parent 8108863206
commit 08f0e4c293
8 changed files with 308 additions and 276 deletions

View File

@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}

View File

@@ -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<FindDeviceFlowStatus>(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 { }
}

View File

@@ -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()
}
}
}

View File

@@ -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<NavDestination>(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)
}
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -8,7 +8,7 @@
<string name="hts_module">HTS</string>
<string name="hts_module_full">Health Thermometer</string>
<string name="bps_module">BPS</string>
<string name="bps_module_full">Blood presure</string>
<string name="bps_module_full">Blood pressure</string>
<string name="rscs_module">RSCS</string>
<string name="rscs_module_full">Running Speed and Cadence</string>
<string name="prx_module">PRX</string>