Migrate profiles to new libraries

This commit is contained in:
Sylwester Zielinski
2023-01-20 14:15:35 +01:00
parent 0b497c1b87
commit 00d70863de
102 changed files with 960 additions and 674 deletions

View File

@@ -60,6 +60,7 @@ dependencies {
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(libs.nordic.core) implementation(libs.nordic.core)
implementation(libs.nordic.theme) implementation(libs.nordic.theme)

View File

@@ -33,34 +33,43 @@ package no.nordicsemi.android.nrftoolbox
import no.nordicsemi.android.bps.view.BPSScreen import no.nordicsemi.android.bps.view.BPSScreen
import no.nordicsemi.android.cgms.view.CGMScreen import no.nordicsemi.android.cgms.view.CGMScreen
import no.nordicsemi.android.common.navigation.createSimpleDestination
import no.nordicsemi.android.common.navigation.defineDestination
import no.nordicsemi.android.csc.view.CSCScreen import no.nordicsemi.android.csc.view.CSCScreen
import no.nordicsemi.android.gls.main.view.GLSScreen import no.nordicsemi.android.gls.main.view.GLSScreen
import no.nordicsemi.android.hrs.view.HRSScreen import no.nordicsemi.android.hrs.view.HRSScreen
import no.nordicsemi.android.hts.view.HTSScreen import no.nordicsemi.android.hts.view.HTSScreen
import no.nordicsemi.android.navigation.ComposeDestination
import no.nordicsemi.android.navigation.ComposeDestinations
import no.nordicsemi.android.nrftoolbox.view.HomeScreen import no.nordicsemi.android.nrftoolbox.view.HomeScreen
import no.nordicsemi.android.prx.view.PRXScreen import no.nordicsemi.android.prx.view.PRXScreen
import no.nordicsemi.android.rscs.view.RSCSScreen import no.nordicsemi.android.rscs.view.RSCSScreen
import no.nordicsemi.android.toolbox.scanner.ScannerDestination
import no.nordicsemi.android.uart.view.UARTScreen import no.nordicsemi.android.uart.view.UARTScreen
import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen
val HomeDestinations = ComposeDestinations(HomeDestination.values().map { it.destination }) val HomeDestinationId = createSimpleDestination("home-destination")
val ProfileDestinations = ComposeDestinations(ProfileDestination.values().map { it.destination })
enum class HomeDestination(val destination: ComposeDestination) { val HomeDestinations = listOf(
HOME(ComposeDestination("home-destination") { HomeScreen() }), defineDestination(HomeDestinationId) { HomeScreen() },
SCANNER(ComposeDestination("scanner-destination") { FindDeviceScreen() }); ScannerDestination
} )
enum class ProfileDestination(val destination: ComposeDestination) { val CSCDestinationId = createSimpleDestination("csc-destination")
CSC(ComposeDestination("csc-destination") { CSCScreen() }), val HRSDestinationId = createSimpleDestination("hrs-destination")
HRS(ComposeDestination("hrs-destination") { HRSScreen() }), val HTSDestinationId = createSimpleDestination("hts-destination")
HTS(ComposeDestination("hts-destination") { HTSScreen() }), val GLSDestinationId = createSimpleDestination("gls-destination")
GLS(ComposeDestination("gls-destination") { GLSScreen() }), val BPSDestinationId = createSimpleDestination("bps-destination")
BPS(ComposeDestination("bps-destination") { BPSScreen() }), val PRXDestinationId = createSimpleDestination("prx-destination")
PRX(ComposeDestination("prx-destination") { PRXScreen() }), val RSCSDestinationId = createSimpleDestination("rscs-destination")
RSCS(ComposeDestination("rscs-destination") { RSCSScreen() }), val CGMSDestinationId = createSimpleDestination("cgms-destination")
CGMS(ComposeDestination("cgms-destination") { CGMScreen() }), val UARTDestinationId = createSimpleDestination("uart-destination")
UART(ComposeDestination("uart-destination") { UARTScreen() });
} val ProfileDestinations = listOf(
defineDestination(CSCDestinationId) { CSCScreen() },
defineDestination(HRSDestinationId) { HRSScreen() },
defineDestination(HTSDestinationId) { HTSScreen() },
defineDestination(GLSDestinationId) { GLSScreen() },
defineDestination(BPSDestinationId) { BPSScreen() },
defineDestination(PRXDestinationId) { PRXScreen() },
defineDestination(RSCSDestinationId) { RSCSScreen() },
defineDestination(CGMSDestinationId) { CGMScreen() },
defineDestination(UARTDestinationId) { UARTScreen() },
)

View File

@@ -38,12 +38,16 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import no.nordicsemi.analytics.view.AnalyticsPermissionRequestDialog import no.nordicsemi.android.common.analytics.view.AnalyticsPermissionRequestDialog
import no.nordicsemi.android.common.navigation.NavigationView
import no.nordicsemi.android.common.theme.NordicActivity
import no.nordicsemi.android.common.theme.NordicTheme
import no.nordicsemi.android.gls.GLSDestination
import no.nordicsemi.android.gls.GLSDestinations import no.nordicsemi.android.gls.GLSDestinations
import no.nordicsemi.android.theme.NordicActivity
import no.nordicsemi.android.theme.NordicTheme
import no.nordicsemi.android.navigation.NavigationView import no.nordicsemi.android.navigation.NavigationView
import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals
import no.nordicsemi.android.theme.NordicTheme
import no.nordicsemi.android.toolbox.scanner.ScannerDestination
import no.nordicsemi.ui.scanner.ScannerDestinations import no.nordicsemi.ui.scanner.ScannerDestinations
import javax.inject.Inject import javax.inject.Inject
@@ -62,7 +66,7 @@ class MainActivity : NordicActivity() {
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
NavigationView(HomeDestinations + ProfileDestinations + ScannerDestinations + GLSDestinations) NavigationView(HomeDestinations + ProfileDestinations + ScannerDestination + GLSDestination)
} }
AnalyticsPermissionRequestDialog() AnalyticsPermissionRequestDialog()

View File

@@ -48,9 +48,17 @@ import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.analytics.Link import no.nordicsemi.android.analytics.Link
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileOpenEvent import no.nordicsemi.android.analytics.ProfileOpenEvent
import no.nordicsemi.android.nrftoolbox.BPSDestinationId
import no.nordicsemi.android.nrftoolbox.BuildConfig import no.nordicsemi.android.nrftoolbox.BuildConfig
import no.nordicsemi.android.nrftoolbox.ProfileDestination import no.nordicsemi.android.nrftoolbox.CGMSDestinationId
import no.nordicsemi.android.nrftoolbox.CSCDestinationId
import no.nordicsemi.android.nrftoolbox.GLSDestinationId
import no.nordicsemi.android.nrftoolbox.HRSDestinationId
import no.nordicsemi.android.nrftoolbox.HTSDestinationId
import no.nordicsemi.android.nrftoolbox.PRXDestinationId
import no.nordicsemi.android.nrftoolbox.R import no.nordicsemi.android.nrftoolbox.R
import no.nordicsemi.android.nrftoolbox.RSCSDestinationId
import no.nordicsemi.android.nrftoolbox.UARTDestinationId
import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel
private const val DFU_PACKAGE_NAME = "no.nordicsemi.android.dfu" private const val DFU_PACKAGE_NAME = "no.nordicsemi.android.dfu"
@@ -87,14 +95,14 @@ fun HomeScreen() {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_gls, R.string.gls_module, R.string.gls_module_full) { FeatureButton(R.drawable.ic_gls, R.string.gls_module, R.string.gls_module_full) {
viewModel.openProfile(ProfileDestination.GLS) viewModel.openProfile(GLSDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.GLS)) viewModel.logEvent(ProfileOpenEvent(Profile.GLS))
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_bps, R.string.bps_module, R.string.bps_module_full) { FeatureButton(R.drawable.ic_bps, R.string.bps_module, R.string.bps_module_full) {
viewModel.openProfile(ProfileDestination.BPS) viewModel.openProfile(BPSDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.BPS)) viewModel.logEvent(ProfileOpenEvent(Profile.BPS))
} }
@@ -109,42 +117,42 @@ fun HomeScreen() {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_csc, R.string.csc_module, R.string.csc_module_full, state.isCSCModuleRunning) { FeatureButton(R.drawable.ic_csc, R.string.csc_module, R.string.csc_module_full, state.isCSCModuleRunning) {
viewModel.openProfile(ProfileDestination.CSC) viewModel.openProfile(CSCDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.CSC)) viewModel.logEvent(ProfileOpenEvent(Profile.CSC))
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_hrs, R.string.hrs_module, R.string.hrs_module_full, state.isHRSModuleRunning) { FeatureButton(R.drawable.ic_hrs, R.string.hrs_module, R.string.hrs_module_full, state.isHRSModuleRunning) {
viewModel.openProfile(ProfileDestination.HRS) viewModel.openProfile(HRSDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.HRS)) viewModel.logEvent(ProfileOpenEvent(Profile.HRS))
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_hts, R.string.hts_module, R.string.hts_module_full, state.isHTSModuleRunning) { FeatureButton(R.drawable.ic_hts, R.string.hts_module, R.string.hts_module_full, state.isHTSModuleRunning) {
viewModel.openProfile(ProfileDestination.HTS) viewModel.openProfile(HTSDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.HTS)) viewModel.logEvent(ProfileOpenEvent(Profile.HTS))
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_rscs, R.string.rscs_module, R.string.rscs_module_full, state.isRSCSModuleRunning) { FeatureButton(R.drawable.ic_rscs, R.string.rscs_module, R.string.rscs_module_full, state.isRSCSModuleRunning) {
viewModel.openProfile(ProfileDestination.RSCS) viewModel.openProfile(RSCSDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.RSCS)) viewModel.logEvent(ProfileOpenEvent(Profile.RSCS))
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_cgm, R.string.cgm_module, R.string.cgm_module_full, state.isCGMModuleRunning) { FeatureButton(R.drawable.ic_cgm, R.string.cgm_module, R.string.cgm_module_full, state.isCGMModuleRunning) {
viewModel.openProfile(ProfileDestination.CGMS) viewModel.openProfile(CGMSDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.CGMS)) viewModel.logEvent(ProfileOpenEvent(Profile.CGMS))
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_prx, R.string.prx_module, R.string.prx_module_full, state.isPRXModuleRunning) { FeatureButton(R.drawable.ic_prx, R.string.prx_module, R.string.prx_module_full, state.isPRXModuleRunning) {
viewModel.openProfile(ProfileDestination.PRX) viewModel.openProfile(PRXDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.PRX)) viewModel.logEvent(ProfileOpenEvent(Profile.PRX))
} }
@@ -159,7 +167,7 @@ fun HomeScreen() {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureButton(R.drawable.ic_uart, R.string.uart_module, R.string.uart_module_full, state.isUARTModuleRunning) { FeatureButton(R.drawable.ic_uart, R.string.uart_module, R.string.uart_module_full, state.isUARTModuleRunning) {
viewModel.openProfile(ProfileDestination.UART) viewModel.openProfile(UARTDestinationId)
viewModel.logEvent(ProfileOpenEvent(Profile.UART)) viewModel.logEvent(ProfileOpenEvent(Profile.UART))
} }

View File

@@ -41,12 +41,12 @@ import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.ProfileOpenEvent import no.nordicsemi.android.analytics.ProfileOpenEvent
import no.nordicsemi.android.cgms.repository.CGMRepository import no.nordicsemi.android.cgms.repository.CGMRepository
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.common.navigation.DestinationId
import no.nordicsemi.android.csc.repository.CSCRepository import no.nordicsemi.android.csc.repository.CSCRepository
import no.nordicsemi.android.hrs.service.HRSRepository import no.nordicsemi.android.hrs.service.HRSRepository
import no.nordicsemi.android.hts.repository.HTSRepository import no.nordicsemi.android.hts.repository.HTSRepository
import no.nordicsemi.android.logger.LoggerAppRunner import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.navigation.NavigationManager
import no.nordicsemi.android.nrftoolbox.ProfileDestination
import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals
import no.nordicsemi.android.nrftoolbox.view.HomeViewState import no.nordicsemi.android.nrftoolbox.view.HomeViewState
import no.nordicsemi.android.prx.repository.PRXRepository import no.nordicsemi.android.prx.repository.PRXRepository
@@ -56,7 +56,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class HomeViewModel @Inject constructor( class HomeViewModel @Inject constructor(
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val activitySignals: ActivitySignals, private val activitySignals: ActivitySignals,
cgmRepository: CGMRepository, cgmRepository: CGMRepository,
cscRepository: CSCRepository, cscRepository: CSCRepository,
@@ -65,7 +65,6 @@ class HomeViewModel @Inject constructor(
prxRepository: PRXRepository, prxRepository: PRXRepository,
rscsRepository: RSCSRepository, rscsRepository: RSCSRepository,
uartRepository: UARTRepository, uartRepository: UARTRepository,
private val loggerAppRunner: LoggerAppRunner,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -106,11 +105,12 @@ class HomeViewModel @Inject constructor(
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
} }
fun openProfile(destination: ProfileDestination) { fun openProfile(destination: DestinationId<Unit, Unit>) {
navigationManager.navigateTo(destination.destination.id) navigationManager.navigateTo(destination)
} }
fun openLogger() { fun openLogger() {
NordicLogger.Companion.launch()
loggerAppRunner.runLogger() loggerAppRunner.runLogger()
} }

View File

@@ -35,6 +35,7 @@ plugins {
alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.kapt) apply true alias(libs.plugins.kotlin.kapt) apply true
alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.kotlin.parcelize) apply false
alias(libs.plugins.hilt) apply false alias(libs.plugins.hilt) apply false
alias(libs.plugins.secrets) apply false alias(libs.plugins.secrets) apply false
alias(libs.plugins.protobuf) apply false alias(libs.plugins.protobuf) apply false
@@ -44,4 +45,6 @@ plugins {
alias(libs.plugins.nordic.library.compose) apply false alias(libs.plugins.nordic.library.compose) apply false
alias(libs.plugins.nordic.hilt) apply false alias(libs.plugins.nordic.hilt) apply false
alias(libs.plugins.nordic.feature) apply false alias(libs.plugins.nordic.feature) apply false
id("com.android.library") version "7.4.0" apply false
id("org.jetbrains.kotlin.android") version "1.7.21" apply false
} }

View File

@@ -31,7 +31,7 @@
#Mon Feb 14 14:46:55 CET 2022 #Mon Feb 14 14:46:55 CET 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-rc-2-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
} }
android { android {

View File

@@ -32,7 +32,7 @@
package no.nordicsemi.android.analytics package no.nordicsemi.android.analytics
import android.annotation.SuppressLint import android.annotation.SuppressLint
import no.nordicsemi.analytics.NordicAnalytics import no.nordicsemi.android.common.analytics.NordicAnalytics
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2022, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
plugins {
alias(libs.plugins.nordic.feature)
}
android {
namespace = "no.nordicsemi.android.toolbox.scanner"
}
dependencies {
implementation(libs.nordic.uiscanner)
implementation(libs.nordic.navigation)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.activity.compose)
}

View File

@@ -0,0 +1,24 @@
package no.nordicsemi.android.toolbox.scanner
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("no.nordicsemi.android.toolbox.scanner.test", appContext.packageName)
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -0,0 +1,29 @@
package no.nordicsemi.android.toolbox.scanner
import android.os.ParcelUuid
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.navigation.createDestination
import no.nordicsemi.android.common.navigation.defineDestination
import no.nordicsemi.android.common.navigation.viewmodel.SimpleNavigationViewModel
import no.nordicsemi.android.common.ui.scanner.DeviceSelected
import no.nordicsemi.android.common.ui.scanner.ScannerScreen
import no.nordicsemi.android.common.ui.scanner.ScanningCancelled
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
val ScannerDestinationId = createDestination<ParcelUuid, DiscoveredBluetoothDevice>("uiscanner-destination")
val ScannerDestination = defineDestination(ScannerDestinationId) {
val navigationViewModel = hiltViewModel<SimpleNavigationViewModel>()
val arg = navigationViewModel.parameterOf(ScannerDestinationId)
ScannerScreen(
uuid = arg,
onResult = {
when (it) {
is DeviceSelected -> navigationViewModel.navigateUpWithResult(ScannerDestinationId, it.device)
ScanningCancelled -> navigationViewModel.navigateUp()
}
}
)
}

View File

@@ -0,0 +1,17 @@
package no.nordicsemi.android.toolbox.scanner
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -46,4 +46,5 @@ dependencies {
implementation(libs.androidx.lifecycle.service) implementation(libs.androidx.lifecycle.service)
implementation(libs.androidx.localbroadcastmanager) implementation(libs.androidx.localbroadcastmanager)
implementation(libs.androidx.core)
} }

View File

@@ -35,7 +35,7 @@ import android.bluetooth.BluetoothDevice
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
const val DEVICE_DATA = "device-data" const val DEVICE_DATA = "device-data"

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
} }
android { android {

View File

@@ -0,0 +1,14 @@
package no.nordicsemi.android.ui.view
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.ui.R
@Composable
fun NavigateUpButton(navigateUp: () -> Unit) {
Button(onClick = { navigateUp() }) {
Text(text = stringResource(id = R.string.go_up))
}
}

View File

@@ -31,14 +31,14 @@
package no.nordicsemi.android.ui.view package no.nordicsemi.android.ui.view
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.R import no.nordicsemi.android.ui.R
@Composable @Composable
fun BatteryLevelView(batteryLevel: Int) { fun BatteryLevelView(batteryLevel: Int) {
ScreenSection { OutlinedCard {
KeyValueField( KeyValueField(
stringResource(id = R.string.field_battery), stringResource(id = R.string.field_battery),
"$batteryLevel%" "$batteryLevel%"

View File

@@ -45,6 +45,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.ui.R import no.nordicsemi.android.ui.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CloseIconAppBar(text: String, onClick: () -> Unit) { fun CloseIconAppBar(text: String, onClick: () -> Unit) {
SmallTopAppBar( SmallTopAppBar(
@@ -67,6 +68,7 @@ fun CloseIconAppBar(text: String, onClick: () -> Unit) {
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LoggerBackIconAppBar(text: String, onClick: () -> Unit) { fun LoggerBackIconAppBar(text: String, onClick: () -> Unit) {
SmallTopAppBar( SmallTopAppBar(
@@ -100,6 +102,7 @@ fun LoggerBackIconAppBar(text: String, onClick: () -> Unit) {
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BackIconAppBar(text: String, onClick: () -> Unit) { fun BackIconAppBar(text: String, onClick: () -> Unit) {
SmallTopAppBar( SmallTopAppBar(
@@ -123,6 +126,7 @@ fun BackIconAppBar(text: String, onClick: () -> Unit) {
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LoggerIconAppBar(text: String, onClick: () -> Unit, onDisconnectClick: () -> Unit, onLoggerClick: () -> Unit) { fun LoggerIconAppBar(text: String, onClick: () -> Unit, onDisconnectClick: () -> Unit, onLoggerClick: () -> Unit) {
SmallTopAppBar( SmallTopAppBar(

View File

@@ -41,4 +41,5 @@ android {
dependencies { dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uiscanner) implementation(libs.nordic.uiscanner)
implementation(libs.kotlinx.coroutines.core)
} }

View File

@@ -38,12 +38,6 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.navigation.ParcelableArgument
import no.nordicsemi.android.navigation.SuccessDestinationResult
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
val <T> T.exhaustive
get() = this
val String.Companion.EMPTY val String.Companion.EMPTY
get() = "" get() = ""
@@ -54,10 +48,6 @@ fun Context.isServiceRunning(serviceClassName: String): Boolean {
return services.find { it.service.className == serviceClassName } != null return services.find { it.service.className == serviceClassName } != null
} }
fun SuccessDestinationResult.getDevice(): DiscoveredBluetoothDevice {
return (argument as ParcelableArgument).value as DiscoveredBluetoothDevice
}
private val exceptionHandler = CoroutineExceptionHandler { _, t -> private val exceptionHandler = CoroutineExceptionHandler { _, t ->
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t) Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
} }

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -53,6 +54,7 @@ dependencies {
implementation(libs.nordic.uiscanner) implementation(libs.nordic.uiscanner)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)

View File

@@ -45,7 +45,7 @@ import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.common.callback.bps.BloodPressureMeasurementResponse import no.nordicsemi.android.ble.common.callback.bps.BloodPressureMeasurementResponse
import no.nordicsemi.android.ble.common.callback.bps.IntermediateCuffPressureResponse import no.nordicsemi.android.ble.common.callback.bps.IntermediateCuffPressureResponse
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import java.util.* import java.util.*

View File

@@ -44,11 +44,11 @@ import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.bps.data.BPSData import no.nordicsemi.android.bps.data.BPSData
import no.nordicsemi.android.bps.data.BPSManager import no.nordicsemi.android.bps.data.BPSManager
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@ViewModelScoped @ViewModelScoped
@@ -62,7 +62,7 @@ internal class BPSRepository @Inject constructor(
private var logger: NordicLogger? = null private var logger: NordicLogger? = null
fun downloadData(scope: CoroutineScope, device: DiscoveredBluetoothDevice): Flow<BleManagerResult<BPSData>> = callbackFlow { fun downloadData(scope: CoroutineScope, device: DiscoveredBluetoothDevice): Flow<BleManagerResult<BPSData>> = callbackFlow {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "BPS", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "BPS", device.address).also {
logger = it logger = it
} }
val manager = BPSManager(context, scope, createdLogger) val manager = BPSManager(context, scope, createdLogger)
@@ -93,7 +93,6 @@ internal class BPSRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
} }

View File

@@ -41,14 +41,21 @@ import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.bps.R import no.nordicsemi.android.bps.R
import no.nordicsemi.android.bps.viewmodel.BPSViewModel import no.nordicsemi.android.bps.viewmodel.BPSViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun BPSScreen() { fun BPSScreen() {
@@ -62,18 +69,18 @@ fun BPSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) { when (state) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) { is WorkingState -> when (state.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> BPSContentView(state.result.data) { viewModel.onEvent(it) } is SuccessResult -> BPSContentView(state.result.data) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -34,6 +34,7 @@ package no.nordicsemi.android.bps.view
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -41,14 +42,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.bps.R import no.nordicsemi.android.bps.R
import no.nordicsemi.android.bps.data.BPSData import no.nordicsemi.android.bps.data.BPSData
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.KeyValueField import no.nordicsemi.android.ui.view.KeyValueField
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@Composable @Composable
internal fun BPSSensorsReadingView(state: BPSData) { internal fun BPSSensorsReadingView(state: BPSData) {
ScreenSection { OutlinedCard {
Column { Column {
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.bps_records)) SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.bps_records))
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))

View File

@@ -31,6 +31,7 @@
package no.nordicsemi.android.bps.viewmodel package no.nordicsemi.android.bps.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -43,19 +44,23 @@ import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.bps.data.BPS_SERVICE_UUID import no.nordicsemi.android.bps.data.BPS_SERVICE_UUID
import no.nordicsemi.android.bps.repository.BPSRepository import no.nordicsemi.android.bps.repository.BPSRepository
import no.nordicsemi.android.bps.view.* import no.nordicsemi.android.bps.view.BPSViewEvent
import no.nordicsemi.android.navigation.* import no.nordicsemi.android.bps.view.BPSViewState
import no.nordicsemi.android.bps.view.DisconnectEvent
import no.nordicsemi.android.bps.view.NoDeviceState
import no.nordicsemi.android.bps.view.OpenLoggerEvent
import no.nordicsemi.android.bps.view.WorkingState
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class BPSViewModel @Inject constructor( internal class BPSViewModel @Inject constructor(
private val repository: BPSRepository, private val repository: BPSRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -63,27 +68,25 @@ internal class BPSViewModel @Inject constructor(
val state = _state.asStateFlow() val state = _state.asStateFlow()
init { init {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(BPS_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(BPS_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleArgs(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleArgs(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> connectDevice(args.getDevice()) is NavigationResult.Success -> connectDevice(result.value)
}.exhaustive }
} }
fun onEvent(event: BPSViewEvent) { fun onEvent(event: BPSViewEvent) {
when (event) { when (event) {
DisconnectEvent -> navigationManager.navigateUp() DisconnectEvent -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
}.exhaustive }
} }
private fun connectDevice(device: DiscoveredBluetoothDevice) { private fun connectDevice(device: DiscoveredBluetoothDevice) {

View File

@@ -30,8 +30,8 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.parcelize)
} }
android { android {
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.uiscanner) implementation(libs.nordic.uiscanner)
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -53,8 +53,7 @@ import no.nordicsemi.android.ble.common.profile.cgm.CGMSpecificOpsControlPointCa
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.ble.ktx.suspendForValidResponse import no.nordicsemi.android.ble.ktx.suspendForValidResponse
import no.nordicsemi.android.cgms.repository.toList import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import no.nordicsemi.android.utils.launchWithCatch import no.nordicsemi.android.utils.launchWithCatch
import java.util.* import java.util.*

View File

@@ -29,11 +29,10 @@
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package no.nordicsemi.android.cgms.repository package no.nordicsemi.android.cgms.data
import android.util.SparseArray import android.util.SparseArray
import androidx.core.util.keyIterator import androidx.core.util.keyIterator
import no.nordicsemi.android.cgms.data.CGMRecord
internal fun SparseArray<CGMRecord>.toList(): List<CGMRecord> { internal fun SparseArray<CGMRecord>.toList(): List<CGMRecord> {
val list = mutableListOf<CGMRecord>() val list = mutableListOf<CGMRecord>()

View File

@@ -39,13 +39,13 @@ import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.cgms.data.CGMData import no.nordicsemi.android.cgms.data.CGMData
import no.nordicsemi.android.cgms.data.CGMManager import no.nordicsemi.android.cgms.data.CGMManager
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -71,7 +71,7 @@ class CGMRepository @Inject constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "CGMS", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "CGMS", device.address).also {
logger = it logger = it
} }
val manager = CGMManager(context, scope, createdLogger) val manager = CGMManager(context, scope, createdLogger)
@@ -110,7 +110,7 @@ class CGMRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
fun release() { fun release() {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.service.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint

View File

@@ -38,6 +38,7 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -49,7 +50,6 @@ import no.nordicsemi.android.cgms.data.CGMData
import no.nordicsemi.android.cgms.data.CGMRecord import no.nordicsemi.android.cgms.data.CGMRecord
import no.nordicsemi.android.cgms.data.CGMServiceCommand import no.nordicsemi.android.cgms.data.CGMServiceCommand
import no.nordicsemi.android.cgms.data.RequestStatus import no.nordicsemi.android.cgms.data.RequestStatus
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@@ -87,7 +87,7 @@ internal fun CGMContentView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
@Composable @Composable
private fun SettingsView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) { private fun SettingsView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
ScreenSection { OutlinedCard {
SectionTitle(icon = Icons.Default.Settings, title = "Request items") SectionTitle(icon = Icons.Default.Settings, title = "Request items")
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -115,7 +115,7 @@ private fun SettingsView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
@Composable @Composable
private fun RecordsView(state: CGMData) { private fun RecordsView(state: CGMData) {
ScreenSection { OutlinedCard {
if (state.records.isEmpty()) { if (state.records.isEmpty()) {
RecordsViewWithoutData() RecordsViewWithoutData()
} else { } else {

View File

@@ -41,14 +41,21 @@ import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.cgms.R import no.nordicsemi.android.cgms.R
import no.nordicsemi.android.cgms.viewmodel.CGMViewModel import no.nordicsemi.android.cgms.viewmodel.CGMViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun CGMScreen() { fun CGMScreen() {
@@ -62,18 +69,18 @@ fun CGMScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) { when (state) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) { is WorkingState -> when (state.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> CGMContentView(state.result.data) { viewModel.onEvent(it) } is SuccessResult -> CGMContentView(state.result.data) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -31,10 +31,15 @@
package no.nordicsemi.android.cgms.viewmodel package no.nordicsemi.android.cgms.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
@@ -42,18 +47,25 @@ import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.cgms.data.CGMS_SERVICE_UUID import no.nordicsemi.android.cgms.data.CGMS_SERVICE_UUID
import no.nordicsemi.android.cgms.data.CGMServiceCommand import no.nordicsemi.android.cgms.data.CGMServiceCommand
import no.nordicsemi.android.cgms.repository.CGMRepository import no.nordicsemi.android.cgms.repository.CGMRepository
import no.nordicsemi.android.cgms.view.* import no.nordicsemi.android.cgms.view.CGMViewEvent
import no.nordicsemi.android.navigation.* import no.nordicsemi.android.cgms.view.CGMViewState
import no.nordicsemi.android.cgms.view.DisconnectEvent
import no.nordicsemi.android.cgms.view.NavigateUp
import no.nordicsemi.android.cgms.view.NoDeviceState
import no.nordicsemi.android.cgms.view.OnWorkingModeSelected
import no.nordicsemi.android.cgms.view.OpenLoggerEvent
import no.nordicsemi.android.cgms.view.WorkingState
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class CGMViewModel @Inject constructor( internal class CGMViewModel @Inject constructor(
private val repository: CGMRepository, private val repository: CGMRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -82,24 +94,22 @@ internal class CGMViewModel @Inject constructor(
is OnWorkingModeSelected -> onCommandReceived(event.workingMode) is OnWorkingModeSelected -> onCommandReceived(event.workingMode)
NavigateUp -> navigationManager.navigateUp() NavigateUp -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
}.exhaustive }
} }
private fun requestBluetoothDevice() { private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CGMS_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(CGMS_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice()) is NavigationResult.Success -> repository.launch(result.value)
}.exhaustive }
} }
private fun onCommandReceived(workingMode: CGMServiceCommand) { private fun onCommandReceived(workingMode: CGMServiceCommand) {
@@ -108,7 +118,7 @@ internal class CGMViewModel @Inject constructor(
CGMServiceCommand.REQUEST_LAST_RECORD -> repository.requestLastRecord() CGMServiceCommand.REQUEST_LAST_RECORD -> repository.requestLastRecord()
CGMServiceCommand.REQUEST_FIRST_RECORD -> repository.requestFirstRecord() CGMServiceCommand.REQUEST_FIRST_RECORD -> repository.requestFirstRecord()
CGMServiceCommand.DISCONNECT -> disconnect() CGMServiceCommand.DISCONNECT -> disconnect()
}.exhaustive }
} }
private fun disconnect() { private fun disconnect() {

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uiscanner) implementation(libs.nordic.uiscanner)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -42,7 +42,7 @@ import no.nordicsemi.android.ble.BleManager
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.common.callback.csc.CyclingSpeedAndCadenceMeasurementResponse import no.nordicsemi.android.ble.common.callback.csc.CyclingSpeedAndCadenceMeasurementResponse
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import java.util.* import java.util.*

View File

@@ -37,16 +37,16 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.csc.data.CSCData import no.nordicsemi.android.csc.data.CSCData
import no.nordicsemi.android.csc.data.CSCManager import no.nordicsemi.android.csc.data.CSCManager
import no.nordicsemi.android.csc.data.WheelSize import no.nordicsemi.android.csc.data.WheelSize
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -72,7 +72,7 @@ class CSCRepository @Inject constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "CSC", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "CSC", device.address).also {
logger = it logger = it
} }
val manager = CSCManager(context, scope, createdLogger) val manager = CSCManager(context, scope, createdLogger)
@@ -103,7 +103,7 @@ class CSCRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
fun release() { fun release() {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.service.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint

View File

@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -48,15 +49,13 @@ import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.common.theme.view.RadioButtonGroup
import no.nordicsemi.android.csc.R import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.data.CSCData import no.nordicsemi.android.csc.data.CSCData
import no.nordicsemi.android.csc.data.WheelSize import no.nordicsemi.android.csc.data.WheelSize
import no.nordicsemi.android.theme.RadioButtonGroup
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
import no.nordicsemi.android.ui.view.dialog.FlowCanceled import no.nordicsemi.android.ui.view.dialog.FlowCanceled
import no.nordicsemi.android.ui.view.dialog.ItemSelectedResult import no.nordicsemi.android.ui.view.dialog.ItemSelectedResult
import no.nordicsemi.android.utils.exhaustive
@Composable @Composable
internal fun CSCContentView(state: CSCData, speedUnit: SpeedUnit, onEvent: (CSCViewEvent) -> Unit) { internal fun CSCContentView(state: CSCData, speedUnit: SpeedUnit, onEvent: (CSCViewEvent) -> Unit) {
@@ -74,7 +73,7 @@ internal fun CSCContentView(state: CSCData, speedUnit: SpeedUnit, onEvent: (CSCV
wheelEntries[it.index]))) wheelEntries[it.index])))
showDialog.value = false showDialog.value = false
} }
}.exhaustive }
} }
} }
@@ -105,7 +104,7 @@ private fun SettingsSection(
onEvent: (CSCViewEvent) -> Unit, onEvent: (CSCViewEvent) -> Unit,
onWheelButtonClick: () -> Unit, onWheelButtonClick: () -> Unit,
) { ) {
ScreenSection { OutlinedCard {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {

View File

@@ -31,9 +31,9 @@
package no.nordicsemi.android.csc.view package no.nordicsemi.android.csc.view
import no.nordicsemi.android.common.theme.view.RadioButtonItem
import no.nordicsemi.android.common.theme.view.RadioGroupViewEntity
import no.nordicsemi.android.csc.data.CSCData import no.nordicsemi.android.csc.data.CSCData
import no.nordicsemi.android.theme.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity
import java.util.* import java.util.*
private const val DISPLAY_M_S = "m/s" private const val DISPLAY_M_S = "m/s"

View File

@@ -39,16 +39,23 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.csc.R import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.viewmodel.CSCViewModel import no.nordicsemi.android.csc.viewmodel.CSCViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun CSCScreen() { fun CSCScreen() {
@@ -62,18 +69,18 @@ fun CSCScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state.cscManagerState) { when (state.cscManagerState) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.cscManagerState.result) { is WorkingState -> when (state.cscManagerState.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> CSCContentView(state.cscManagerState.result.data, state.speedUnit) { viewModel.onEvent(it) } is SuccessResult -> CSCContentView(state.cscManagerState.result.data, state.speedUnit) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -35,8 +35,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import no.nordicsemi.android.common.theme.NordicTheme
import no.nordicsemi.android.csc.R import no.nordicsemi.android.csc.R
import no.nordicsemi.android.theme.NordicTheme
import no.nordicsemi.android.ui.view.dialog.StringListDialog import no.nordicsemi.android.ui.view.dialog.StringListDialog
import no.nordicsemi.android.ui.view.dialog.StringListDialogConfig import no.nordicsemi.android.ui.view.dialog.StringListDialogConfig
import no.nordicsemi.android.ui.view.dialog.StringListDialogResult import no.nordicsemi.android.ui.view.dialog.StringListDialogResult

View File

@@ -34,6 +34,7 @@ package no.nordicsemi.android.csc.view
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -41,14 +42,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.csc.R import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.data.CSCData import no.nordicsemi.android.csc.data.CSCData
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.KeyValueField import no.nordicsemi.android.ui.view.KeyValueField
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@Composable @Composable
internal fun SensorsReadingView(state: CSCData, speedUnit: SpeedUnit) { internal fun SensorsReadingView(state: CSCData, speedUnit: SpeedUnit) {
ScreenSection { OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = "Records") SectionTitle(resId = R.drawable.ic_records, title = "Records")
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))

View File

@@ -31,6 +31,7 @@
package no.nordicsemi.android.csc.viewmodel package no.nordicsemi.android.csc.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -39,20 +40,20 @@ import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.csc.data.CSC_SERVICE_UUID import no.nordicsemi.android.csc.data.CSC_SERVICE_UUID
import no.nordicsemi.android.csc.repository.CSCRepository import no.nordicsemi.android.csc.repository.CSCRepository
import no.nordicsemi.android.csc.view.* import no.nordicsemi.android.csc.view.*
import no.nordicsemi.android.navigation.*
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class CSCViewModel @Inject constructor( internal class CSCViewModel @Inject constructor(
private val repository: CSCRepository, private val repository: CSCRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -76,20 +77,18 @@ internal class CSCViewModel @Inject constructor(
} }
private fun requestBluetoothDevice() { private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CSC_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(CSC_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice()) is NavigationResult.Success -> repository.launch(result.value)
}.exhaustive }
} }
fun onEvent(event: CSCViewEvent) { fun onEvent(event: CSCViewEvent) {
@@ -99,7 +98,7 @@ internal class CSCViewModel @Inject constructor(
OnDisconnectButtonClick -> disconnect() OnDisconnectButtonClick -> disconnect()
NavigateUp -> navigationManager.navigateUp() NavigateUp -> navigationManager.navigateUp()
OpenLogger -> repository.openLogger() OpenLogger -> repository.openLogger()
}.exhaustive }
} }
private fun setSpeedUnit(speedUnit: SpeedUnit) { private fun setSpeedUnit(speedUnit: SpeedUnit) {

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -53,6 +54,7 @@ dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger) implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -31,13 +31,11 @@
package no.nordicsemi.android.gls package no.nordicsemi.android.gls
import no.nordicsemi.android.common.navigation.createDestination
import no.nordicsemi.android.common.navigation.defineDestination
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.details.view.GLSDetailsScreen import no.nordicsemi.android.gls.details.view.GLSDetailsScreen
import no.nordicsemi.android.navigation.ComposeDestination
import no.nordicsemi.android.navigation.ComposeDestinations
import no.nordicsemi.android.navigation.DestinationId
internal val GlsDetailsDestinationId = DestinationId("gls-details-screen") internal val GlsDetailsDestinationId = createDestination<GLSRecord, Unit>("gls-details-screen")
private val destination: ComposeDestination = ComposeDestination(GlsDetailsDestinationId) { GLSDetailsScreen() } val GLSDestination = defineDestination(GlsDetailsDestinationId) { GLSDetailsScreen() }
val GLSDestinations = ComposeDestinations(destination)

View File

@@ -47,7 +47,7 @@ import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementRespo
import no.nordicsemi.android.ble.common.data.RecordAccessControlPointData import no.nordicsemi.android.ble.common.data.RecordAccessControlPointData
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import no.nordicsemi.android.utils.launchWithCatch import no.nordicsemi.android.utils.launchWithCatch
import java.util.* import java.util.*

View File

@@ -39,8 +39,8 @@ internal data class GLSRecord(
val time: Calendar? = null, val time: Calendar? = null,
val glucoseConcentration: Float = 0f, val glucoseConcentration: Float = 0f,
val unit: ConcentrationUnit = ConcentrationUnit.UNIT_KGPL, val unit: ConcentrationUnit = ConcentrationUnit.UNIT_KGPL,
val type: RecordType?, val type: RecordType? = null,
val status: GlucoseStatus?, val status: GlucoseStatus? = null,
val sampleLocation: SampleLocation? = null, val sampleLocation: SampleLocation? = null,
var context: MeasurementContext? = null var context: MeasurementContext? = null
) )

View File

@@ -84,7 +84,7 @@ internal fun BooleanField(title: String, value: Boolean) {
Text( Text(
text = stringResource(id = R.string.gls_no), text = stringResource(id = R.string.gls_no),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = colorResource(id = no.nordicsemi.android.theme.R.color.nordicGrass) color = colorResource(id = R.color.nordicGrass)
) )
} }
} }

View File

@@ -36,6 +36,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -45,13 +46,12 @@ import androidx.compose.ui.unit.dp
import no.nordicsemi.android.gls.R import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.data.GLSRecord import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.main.view.toDisplayString import no.nordicsemi.android.gls.main.view.toDisplayString
import no.nordicsemi.android.theme.ScreenSection
@Composable @Composable
internal fun GLSDetailsContentView(record: GLSRecord) { internal fun GLSDetailsContentView(record: GLSRecord) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
ScreenSection() { OutlinedCard {
Field( Field(
stringResource(id = R.string.gls_details_sequence_number), stringResource(id = R.string.gls_details_sequence_number),
record.sequenceNumber.toString() record.sequenceNumber.toString()

View File

@@ -31,21 +31,23 @@
package no.nordicsemi.android.gls.details.viewmodel package no.nordicsemi.android.gls.details.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.navigation.viewmodel.SimpleNavigationViewModel
import no.nordicsemi.android.gls.GlsDetailsDestinationId import no.nordicsemi.android.gls.GlsDetailsDestinationId
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.navigation.AnyArgument
import no.nordicsemi.android.navigation.NavigationManager
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class GLSDetailsViewModel @Inject constructor( internal class GLSDetailsViewModel @Inject constructor(
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
) : ViewModel() { private val savedStateHandle: SavedStateHandle
) : SimpleNavigationViewModel(navigationManager, savedStateHandle) {
val record = private val _record = MutableStateFlow(parameterOf(GlsDetailsDestinationId))
(navigationManager.getImmediateArgument(GlsDetailsDestinationId) as AnyArgument).value as GLSRecord val record = _record.asStateFlow()
fun navigateBack() { fun navigateBack() {
navigationManager.navigateUp() navigationManager.navigateUp()

View File

@@ -40,6 +40,7 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -54,7 +55,6 @@ import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.data.RequestStatus import no.nordicsemi.android.gls.data.RequestStatus
import no.nordicsemi.android.gls.data.WorkingMode import no.nordicsemi.android.gls.data.WorkingMode
import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@@ -94,7 +94,7 @@ internal fun GLSContentView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Uni
@Composable @Composable
private fun SettingsView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit) { private fun SettingsView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit) {
ScreenSection { OutlinedCard {
SectionTitle(icon = Icons.Default.Settings, title = "Request items") SectionTitle(icon = Icons.Default.Settings, title = "Request items")
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -118,7 +118,7 @@ private fun SettingsView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit)
@Composable @Composable
private fun RecordsView(state: GLSData) { private fun RecordsView(state: GLSData) {
ScreenSection { OutlinedCard {
if (state.records.isEmpty()) { if (state.records.isEmpty()) {
RecordsViewWithoutData() RecordsViewWithoutData()
} else { } else {

View File

@@ -39,16 +39,23 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.gls.R import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun GLSScreen() { fun GLSScreen() {
@@ -62,18 +69,18 @@ fun GLSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) { when (state) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) { is WorkingState -> when (state.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) } is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -31,6 +31,7 @@
package no.nordicsemi.android.gls.main.viewmodel package no.nordicsemi.android.gls.main.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -41,22 +42,21 @@ import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.gls.GlsDetailsDestinationId import no.nordicsemi.android.gls.GlsDetailsDestinationId
import no.nordicsemi.android.gls.data.GLS_SERVICE_UUID import no.nordicsemi.android.gls.data.GLS_SERVICE_UUID
import no.nordicsemi.android.gls.main.view.* import no.nordicsemi.android.gls.main.view.*
import no.nordicsemi.android.gls.repository.GLSRepository import no.nordicsemi.android.gls.repository.GLSRepository
import no.nordicsemi.android.navigation.*
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class GLSViewModel @Inject constructor( internal class GLSViewModel @Inject constructor(
private val repository: GLSRepository, private val repository: GLSRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -64,20 +64,18 @@ internal class GLSViewModel @Inject constructor(
val state = _state.asStateFlow() val state = _state.asStateFlow()
init { init {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(GLS_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(GLS_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> connectDevice(args.getDevice()) is NavigationResult.Success -> connectDevice(result.value)
}.exhaustive }
} }
fun onEvent(event: GLSScreenViewEvent) { fun onEvent(event: GLSScreenViewEvent) {
@@ -85,9 +83,9 @@ internal class GLSViewModel @Inject constructor(
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
DisconnectEvent -> navigationManager.navigateUp() DisconnectEvent -> navigationManager.navigateUp()
is OnWorkingModeSelected -> repository.requestMode(event.workingMode) is OnWorkingModeSelected -> repository.requestMode(event.workingMode)
is OnGLSRecordClick -> navigationManager.navigateTo(GlsDetailsDestinationId, AnyArgument(event.record)) is OnGLSRecordClick -> navigationManager.navigateTo(GlsDetailsDestinationId, event.record)
DisconnectEvent -> navigationManager.navigateUp() DisconnectEvent -> navigationManager.navigateUp()
}.exhaustive }
} }
private fun connectDevice(device: DiscoveredBluetoothDevice) { private fun connectDevice(device: DiscoveredBluetoothDevice) {

View File

@@ -42,15 +42,14 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.gls.data.GLSData import no.nordicsemi.android.gls.data.GLSData
import no.nordicsemi.android.gls.data.GLSManager import no.nordicsemi.android.gls.data.GLSManager
import no.nordicsemi.android.gls.data.WorkingMode import no.nordicsemi.android.gls.data.WorkingMode
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.android.utils.exhaustive
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@ViewModelScoped @ViewModelScoped
@@ -65,7 +64,7 @@ internal class GLSRepository @Inject constructor(
private var logger: NordicLogger? = null private var logger: NordicLogger? = null
fun downloadData(scope: CoroutineScope, device: DiscoveredBluetoothDevice): Flow<BleManagerResult<GLSData>> = callbackFlow { fun downloadData(scope: CoroutineScope, device: DiscoveredBluetoothDevice): Flow<BleManagerResult<GLSData>> = callbackFlow {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "GLS", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "GLS", device.address).also {
logger = it logger = it
} }
val managerInstance = manager ?: GLSManager(context, scope, createdLogger) val managerInstance = manager ?: GLSManager(context, scope, createdLogger)
@@ -100,7 +99,7 @@ internal class GLSRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
fun requestMode(workingMode: WorkingMode) { fun requestMode(workingMode: WorkingMode) {
@@ -108,6 +107,6 @@ internal class GLSRepository @Inject constructor(
WorkingMode.ALL -> manager?.requestAllRecords() WorkingMode.ALL -> manager?.requestAllRecords()
WorkingMode.LAST -> manager?.requestLastRecord() WorkingMode.LAST -> manager?.requestLastRecord()
WorkingMode.FIRST -> manager?.requestFirstRecord() WorkingMode.FIRST -> manager?.requestFirstRecord()
}.exhaustive }
} }
} }

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -53,6 +54,7 @@ dependencies {
implementation(libs.nordic.uiscanner) implementation(libs.nordic.uiscanner)
implementation(libs.nordic.uilogger) implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -44,7 +44,7 @@ import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationResponse
import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementResponse import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementResponse
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspendForValidResponse import no.nordicsemi.android.ble.ktx.suspendForValidResponse
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import no.nordicsemi.android.utils.launchWithCatch import no.nordicsemi.android.utils.launchWithCatch
import java.util.* import java.util.*

View File

@@ -34,18 +34,22 @@ package no.nordicsemi.android.hrs.service
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.hrs.data.HRSData import no.nordicsemi.android.hrs.data.HRSData
import no.nordicsemi.android.hrs.data.HRSManager import no.nordicsemi.android.hrs.data.HRSManager
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -71,7 +75,7 @@ class HRSRepository @Inject constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "HRS", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "HRS", device.address).also {
logger = it logger = it
} }
val manager = HRSManager(context, scope, createdLogger) val manager = HRSManager(context, scope, createdLogger)
@@ -87,7 +91,7 @@ class HRSRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
private suspend fun HRSManager.start(device: DiscoveredBluetoothDevice) { private suspend fun HRSManager.start(device: DiscoveredBluetoothDevice) {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.service.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint

View File

@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -48,7 +49,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.hrs.R import no.nordicsemi.android.hrs.R
import no.nordicsemi.android.hrs.data.HRSData import no.nordicsemi.android.hrs.data.HRSData
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@@ -59,7 +59,7 @@ internal fun HRSContentView(state: HRSData, zoomIn: Boolean, onEvent: (HRSScreen
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
ScreenSection { OutlinedCard {
SectionTitle( SectionTitle(
resId = R.drawable.ic_chart_line, resId = R.drawable.ic_chart_line,
title = stringResource(id = R.string.hrs_section_data), title = stringResource(id = R.string.hrs_section_data),

View File

@@ -39,16 +39,23 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.hrs.R import no.nordicsemi.android.hrs.R
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun HRSScreen() { fun HRSScreen() {
@@ -62,18 +69,18 @@ fun HRSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) { when (state) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) { is WorkingState -> when (state.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> HRSContentView(state.result.data, state.zoomIn) { viewModel.onEvent(it) } is SuccessResult -> HRSContentView(state.result.data, state.zoomIn) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -31,28 +31,40 @@
package no.nordicsemi.android.hrs.viewmodel package no.nordicsemi.android.hrs.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.hrs.data.HRS_SERVICE_UUID import no.nordicsemi.android.hrs.data.HRS_SERVICE_UUID
import no.nordicsemi.android.hrs.service.HRSRepository import no.nordicsemi.android.hrs.service.HRSRepository
import no.nordicsemi.android.hrs.view.* import no.nordicsemi.android.hrs.view.DisconnectEvent
import no.nordicsemi.android.navigation.* import no.nordicsemi.android.hrs.view.HRSScreenViewEvent
import no.nordicsemi.android.hrs.view.HRSViewState
import no.nordicsemi.android.hrs.view.NavigateUpEvent
import no.nordicsemi.android.hrs.view.NoDeviceState
import no.nordicsemi.android.hrs.view.OpenLoggerEvent
import no.nordicsemi.android.hrs.view.SwitchZoomEvent
import no.nordicsemi.android.hrs.view.WorkingState
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class HRSViewModel @Inject constructor( internal class HRSViewModel @Inject constructor(
private val repository: HRSRepository, private val repository: HRSRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -77,20 +89,18 @@ internal class HRSViewModel @Inject constructor(
} }
private fun requestBluetoothDevice() { private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HRS_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(HRS_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice()) is NavigationResult.Success -> repository.launch(result.value)
}.exhaustive }
} }
fun onEvent(event: HRSScreenViewEvent) { fun onEvent(event: HRSScreenViewEvent) {
@@ -99,7 +109,7 @@ internal class HRSViewModel @Inject constructor(
NavigateUpEvent -> navigationManager.navigateUp() NavigateUpEvent -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
SwitchZoomEvent -> onZoomButtonClicked() SwitchZoomEvent -> onZoomButtonClicked()
}.exhaustive }
} }
private fun onZoomButtonClicked() { private fun onZoomButtonClicked() {

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger) implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -42,7 +42,7 @@ import no.nordicsemi.android.ble.BleManager
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementResponse import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementResponse
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import java.util.* import java.util.*

View File

@@ -34,18 +34,22 @@ package no.nordicsemi.android.hts.repository
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.hts.data.HTSData import no.nordicsemi.android.hts.data.HTSData
import no.nordicsemi.android.hts.data.HTSManager import no.nordicsemi.android.hts.data.HTSManager
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -71,7 +75,7 @@ class HTSRepository @Inject constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "HTS", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "HTS", device.address).also {
logger = it logger = it
} }
val manager = HTSManager(context, scope, createdLogger) val manager = HTSManager(context, scope, createdLogger)
@@ -87,7 +91,7 @@ class HTSRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
private suspend fun HTSManager.start(device: DiscoveredBluetoothDevice) { private suspend fun HTSManager.start(device: DiscoveredBluetoothDevice) {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.service.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint

View File

@@ -33,6 +33,7 @@ package no.nordicsemi.android.hts.view
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -40,10 +41,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.common.theme.view.RadioButtonGroup
import no.nordicsemi.android.hts.R import no.nordicsemi.android.hts.R
import no.nordicsemi.android.hts.data.HTSData import no.nordicsemi.android.hts.data.HTSData
import no.nordicsemi.android.theme.RadioButtonGroup
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
import no.nordicsemi.android.ui.view.KeyValueField import no.nordicsemi.android.ui.view.KeyValueField
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
@@ -56,7 +56,7 @@ internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, on
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
ScreenSection { OutlinedCard {
SectionTitle(resId = R.drawable.ic_thermometer, title = "Settings") SectionTitle(resId = R.drawable.ic_thermometer, title = "Settings")
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -68,7 +68,7 @@ internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, on
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ScreenSection { OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.hts_records_section)) SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.hts_records_section))
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))

View File

@@ -31,8 +31,8 @@
package no.nordicsemi.android.hts.view package no.nordicsemi.android.hts.view
import no.nordicsemi.android.theme.RadioButtonItem import no.nordicsemi.android.common.theme.view.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity import no.nordicsemi.android.common.theme.view.RadioGroupViewEntity
private const val DISPLAY_FAHRENHEIT = "°F" private const val DISPLAY_FAHRENHEIT = "°F"
private const val DISPLAY_CELSIUS = "°C" private const val DISPLAY_CELSIUS = "°C"

View File

@@ -39,16 +39,23 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.hts.R import no.nordicsemi.android.hts.R
import no.nordicsemi.android.hts.viewmodel.HTSViewModel import no.nordicsemi.android.hts.viewmodel.HTSViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun HTSScreen() { fun HTSScreen() {
@@ -62,18 +69,18 @@ fun HTSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state.htsManagerState) { when (state.htsManagerState) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.htsManagerState.result) { is WorkingState -> when (state.htsManagerState.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> HTSContentView(state.htsManagerState.result.data, state.temperatureUnit) { viewModel.onEvent(it) } is SuccessResult -> HTSContentView(state.htsManagerState.result.data, state.temperatureUnit) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -31,28 +31,39 @@
package no.nordicsemi.android.hts.viewmodel package no.nordicsemi.android.hts.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.hts.data.HTS_SERVICE_UUID import no.nordicsemi.android.hts.data.HTS_SERVICE_UUID
import no.nordicsemi.android.hts.repository.HTSRepository import no.nordicsemi.android.hts.repository.HTSRepository
import no.nordicsemi.android.hts.view.* import no.nordicsemi.android.hts.view.DisconnectEvent
import no.nordicsemi.android.navigation.* import no.nordicsemi.android.hts.view.HTSScreenViewEvent
import no.nordicsemi.android.hts.view.HTSViewState
import no.nordicsemi.android.hts.view.NavigateUp
import no.nordicsemi.android.hts.view.OnTemperatureUnitSelected
import no.nordicsemi.android.hts.view.OpenLoggerEvent
import no.nordicsemi.android.hts.view.WorkingState
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class HTSViewModel @Inject constructor( internal class HTSViewModel @Inject constructor(
private val repository: HTSRepository, private val repository: HTSRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -76,20 +87,18 @@ internal class HTSViewModel @Inject constructor(
} }
private fun requestBluetoothDevice() { private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HTS_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(HTS_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice()) is NavigationResult.Success -> repository.launch(result.value)
}.exhaustive }
} }
fun onEvent(event: HTSScreenViewEvent) { fun onEvent(event: HTSScreenViewEvent) {
@@ -98,7 +107,7 @@ internal class HTSViewModel @Inject constructor(
is OnTemperatureUnitSelected -> onTemperatureUnitSelected(event) is OnTemperatureUnitSelected -> onTemperatureUnitSelected(event)
NavigateUp -> navigationManager.navigateUp() NavigateUp -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
}.exhaustive }
} }
private fun disconnect() { private fun disconnect() {

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger) implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -46,7 +46,7 @@ import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.common.data.alert.AlertLevelData import no.nordicsemi.android.ble.common.data.alert.AlertLevelData
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import no.nordicsemi.android.utils.launchWithCatch import no.nordicsemi.android.utils.launchWithCatch
import java.util.* import java.util.*

View File

@@ -35,15 +35,15 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.prx.data.AlarmLevel import no.nordicsemi.android.prx.data.AlarmLevel
import no.nordicsemi.android.prx.data.PRXData import no.nordicsemi.android.prx.data.PRXData
import no.nordicsemi.android.prx.data.PRXManager import no.nordicsemi.android.prx.data.PRXManager
import no.nordicsemi.android.prx.data.ProximityServerManager import no.nordicsemi.android.prx.data.ProximityServerManager
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.*
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -73,7 +73,7 @@ class PRXRepository @Inject internal constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "PRX", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "PRX", device.address).also {
logger = it logger = it
} }
val manager = PRXManager(context, scope, createdLogger) val manager = PRXManager(context, scope, createdLogger)
@@ -114,7 +114,7 @@ class PRXRepository @Inject internal constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
fun release() { fun release() {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.service.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint

View File

@@ -38,13 +38,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.prx.R import no.nordicsemi.android.prx.R
import no.nordicsemi.android.prx.data.PRXData import no.nordicsemi.android.prx.data.PRXData
import no.nordicsemi.android.ui.view.BatteryLevelView import no.nordicsemi.android.ui.view.BatteryLevelView
@@ -81,7 +81,7 @@ internal fun ContentView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit)
@Composable @Composable
private fun SettingsSection(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) { private fun SettingsSection(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
ScreenSection { OutlinedCard {
SectionTitle(icon = Icons.Default.Settings, title = stringResource(R.string.prx_settings)) SectionTitle(icon = Icons.Default.Settings, title = stringResource(R.string.prx_settings))
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -114,7 +114,7 @@ private fun TurnAlarmOffButton(onEvent: (PRXScreenViewEvent) -> Unit) {
@Composable @Composable
private fun RecordsSection(state: PRXData) { private fun RecordsSection(state: PRXData) {
ScreenSection { OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.prx_records)) SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.prx_records))
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))

View File

@@ -39,6 +39,7 @@ import androidx.compose.material.icons.filled.HighlightOff
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -46,7 +47,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.prx.R import no.nordicsemi.android.prx.R
@Composable @Composable
@@ -57,7 +57,7 @@ fun DeviceOutOfRangeView(navigateUp: () -> Unit) {
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
ScreenSection { OutlinedCard {
Icon( Icon(
imageVector = Icons.Default.HighlightOff, imageVector = Icons.Default.HighlightOff,
contentDescription = null, contentDescription = null,

View File

@@ -40,16 +40,23 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.prx.R import no.nordicsemi.android.prx.R
import no.nordicsemi.android.prx.viewmodel.PRXViewModel import no.nordicsemi.android.prx.viewmodel.PRXViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun PRXScreen() { fun PRXScreen() {
@@ -63,18 +70,18 @@ fun PRXScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) { when (state) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) { is WorkingState -> when (state.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) } is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> ContentView(state.result.data) { viewModel.onEvent(it) } is SuccessResult -> ContentView(state.result.data) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -31,6 +31,7 @@
package no.nordicsemi.android.prx.viewmodel package no.nordicsemi.android.prx.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -39,20 +40,20 @@ import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.navigation.* import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.prx.data.PRX_SERVICE_UUID import no.nordicsemi.android.prx.data.PRX_SERVICE_UUID
import no.nordicsemi.android.prx.repository.PRXRepository import no.nordicsemi.android.prx.repository.PRXRepository
import no.nordicsemi.android.prx.view.* import no.nordicsemi.android.prx.view.*
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class PRXViewModel @Inject constructor( internal class PRXViewModel @Inject constructor(
private val repository: PRXRepository, private val repository: PRXRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -76,20 +77,18 @@ internal class PRXViewModel @Inject constructor(
} }
private fun requestBluetoothDevice() { private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(PRX_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(PRX_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice()) is NavigationResult.Success -> repository.launch(result.value)
}.exhaustive }
} }
fun onEvent(event: PRXScreenViewEvent) { fun onEvent(event: PRXScreenViewEvent) {
@@ -99,7 +98,7 @@ internal class PRXViewModel @Inject constructor(
TurnOnAlert -> repository.enableAlarm() TurnOnAlert -> repository.enableAlarm()
NavigateUpEvent -> navigationManager.navigateUp() NavigateUpEvent -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
}.exhaustive }
} }
private fun disconnect() { private fun disconnect() {

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -41,6 +41,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger) implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -42,7 +42,7 @@ import no.nordicsemi.android.ble.BleManager
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.common.callback.rsc.RunningSpeedAndCadenceMeasurementResponse import no.nordicsemi.android.ble.common.callback.rsc.RunningSpeedAndCadenceMeasurementResponse
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import java.util.* import java.util.*

View File

@@ -34,18 +34,22 @@ package no.nordicsemi.android.rscs.repository
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.rscs.data.RSCSData import no.nordicsemi.android.rscs.data.RSCSData
import no.nordicsemi.android.rscs.data.RSCSManager import no.nordicsemi.android.rscs.data.RSCSManager
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -71,7 +75,7 @@ class RSCSRepository @Inject constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "RSCS", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "RSCS", device.address).also {
logger = it logger = it
} }
val manager = RSCSManager(context, scope, createdLogger) val manager = RSCSManager(context, scope, createdLogger)
@@ -87,7 +91,7 @@ class RSCSRepository @Inject constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
private suspend fun RSCSManager.start(device: DiscoveredBluetoothDevice) { private suspend fun RSCSManager.start(device: DiscoveredBluetoothDevice) {

View File

@@ -39,16 +39,23 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.rscs.R import no.nordicsemi.android.rscs.R
import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel
import no.nordicsemi.android.service.* import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun RSCSScreen() { fun RSCSScreen() {
@@ -62,18 +69,18 @@ fun RSCSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) { when (state) {
NoDeviceState -> NoDeviceView() NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) { is WorkingState -> when (state.result) {
is IdleResult, is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> RSCSContentView(state.result.data) { viewModel.onEvent(it) } is SuccessResult -> RSCSContentView(state.result.data) { viewModel.onEvent(it) }
} }
}.exhaustive }
} }
} }
} }

View File

@@ -33,12 +33,12 @@ package no.nordicsemi.android.rscs.view
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.rscs.R import no.nordicsemi.android.rscs.R
import no.nordicsemi.android.rscs.data.RSCSData import no.nordicsemi.android.rscs.data.RSCSData
import no.nordicsemi.android.ui.view.KeyValueField import no.nordicsemi.android.ui.view.KeyValueField
@@ -46,7 +46,7 @@ import no.nordicsemi.android.ui.view.SectionTitle
@Composable @Composable
internal fun SensorsReadingView(state: RSCSData) { internal fun SensorsReadingView(state: RSCSData) {
ScreenSection { OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = "Records") SectionTitle(resId = R.drawable.ic_records, title = "Records")
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))

View File

@@ -31,28 +31,39 @@
package no.nordicsemi.android.rscs.viewmodel package no.nordicsemi.android.rscs.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.analytics.AppAnalytics import no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileConnectedEvent import no.nordicsemi.android.analytics.ProfileConnectedEvent
import no.nordicsemi.android.navigation.* import no.nordicsemi.android.common.navigation.NavigationResult
import no.nordicsemi.android.common.navigation.Navigator
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.rscs.data.RSCS_SERVICE_UUID import no.nordicsemi.android.rscs.data.RSCS_SERVICE_UUID
import no.nordicsemi.android.rscs.repository.RSCSRepository import no.nordicsemi.android.rscs.repository.RSCSRepository
import no.nordicsemi.android.rscs.view.* import no.nordicsemi.android.rscs.view.DisconnectEvent
import no.nordicsemi.android.rscs.view.NavigateUpEvent
import no.nordicsemi.android.rscs.view.NoDeviceState
import no.nordicsemi.android.rscs.view.OpenLoggerEvent
import no.nordicsemi.android.rscs.view.RSCSViewState
import no.nordicsemi.android.rscs.view.RSCScreenViewEvent
import no.nordicsemi.android.rscs.view.WorkingState
import no.nordicsemi.android.service.ConnectedResult import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class RSCSViewModel @Inject constructor( internal class RSCSViewModel @Inject constructor(
private val repository: RSCSRepository, private val repository: RSCSRepository,
private val navigationManager: NavigationManager, private val navigationManager: Navigator,
private val analytics: AppAnalytics private val analytics: AppAnalytics
) : ViewModel() { ) : ViewModel() {
@@ -76,20 +87,18 @@ internal class RSCSViewModel @Inject constructor(
} }
private fun requestBluetoothDevice() { private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(RSCS_SERVICE_UUID)) navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(RSCS_SERVICE_UUID))
navigationManager.recentResult.onEach { navigationManager.resultFrom(ScannerDestinationId)
if (it.destinationId == ScannerDestinationId) { .onEach { handleResult(it) }
handleArgs(it) .launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
} }
private fun handleArgs(args: DestinationResult) { private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (args) { when (result) {
is CancelDestinationResult -> navigationManager.navigateUp() is NavigationResult.Cancelled -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice()) is NavigationResult.Success -> repository.launch(result.value)
}.exhaustive }
} }
fun onEvent(event: RSCScreenViewEvent) { fun onEvent(event: RSCScreenViewEvent) {
@@ -97,7 +106,7 @@ internal class RSCSViewModel @Inject constructor(
DisconnectEvent -> disconnect() DisconnectEvent -> disconnect()
NavigateUpEvent -> navigationManager.navigateUp() NavigateUpEvent -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger() OpenLoggerEvent -> repository.openLogger()
}.exhaustive }
} }
private fun disconnect() { private fun disconnect() {

View File

@@ -30,7 +30,7 @@
*/ */
plugins { plugins {
alias(libs.plugins.nordic.library) alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
// id("com.google.protobuf") // id("com.google.protobuf")
alias(libs.plugins.kotlin.kapt) alias(libs.plugins.kotlin.kapt)
@@ -49,6 +49,7 @@ android {
dependencies { dependencies {
implementation(project(":lib_analytics")) implementation(project(":lib_analytics"))
implementation(project(":lib_service")) implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui")) implementation(project(":lib_ui"))
implementation(project(":lib_utils")) implementation(project(":lib_utils"))
@@ -56,7 +57,7 @@ dependencies {
implementation(libs.room.ktx) implementation(libs.room.ktx)
// kapt(libs.room.compiler) // kapt(libs.room.compiler)
// kapt("androidx.room:room-compiler:2.5.0") kapt(libs.room.compiler)
// kapt("") // kapt("")
implementation(libs.nordic.ble.common) implementation(libs.nordic.ble.common)
@@ -67,6 +68,10 @@ dependencies {
implementation(libs.nordic.navigation) implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger) implementation(libs.nordic.uilogger)
implementation(libs.androidx.dataStore.core)
implementation(libs.androidx.dataStore.preferences)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)

View File

@@ -45,7 +45,7 @@ import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.ktx.asFlow import no.nordicsemi.android.ble.ktx.asFlow
import no.nordicsemi.android.ble.ktx.asValidResponseFlow import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter import no.nordicsemi.android.service.ConnectionObserverAdapter
import no.nordicsemi.android.utils.EMPTY import no.nordicsemi.android.utils.EMPTY
import no.nordicsemi.android.utils.launchWithCatch import no.nordicsemi.android.utils.launchWithCatch

View File

@@ -28,57 +28,41 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package no.nordicsemi.android.uart.db
package no.nordicsemi.android.uart.db; import org.simpleframework.xml.Attribute
import org.simpleframework.xml.ElementArray
import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Root
import org.simpleframework.xml.ElementArray; import org.simpleframework.xml.core.PersistenceException
import org.simpleframework.xml.Root; import org.simpleframework.xml.core.Validate
import org.simpleframework.xml.core.PersistenceException;
import org.simpleframework.xml.core.Validate;
@Root @Root
public class XmlConfiguration { class XmlConfiguration {
public static final int COMMANDS_COUNT = 9; /**
* Returns the field name
*
* @return optional name
*/
/**
* Sets the name to specified value
* @param name the new name
*/
@field:Attribute(required = false, empty = "Unnamed")
var name: String? = null
@Attribute(required = false, empty = "Unnamed") /**
private String name; * Returns the array of commands. There is always 9 of them.
* @return the commands array
*/
@field:ElementArray
var commands: Array<XmlMacro?> = arrayOfNulls(COMMANDS_COUNT)
@Validate
@Throws(PersistenceException::class)
private fun validate() {
if (commands.size != COMMANDS_COUNT) throw PersistenceException("There must be always $COMMANDS_COUNT commands in a configuration.")
}
@ElementArray companion object {
private XmlMacro[] commands = new XmlMacro[COMMANDS_COUNT]; const val COMMANDS_COUNT = 9
}
/**
* Returns the field name
*
* @return optional name
*/
public String getName() {
return name;
}
/**
* Sets the name to specified value
* @param name the new name
*/
public void setName(final String name) {
this.name = name;
}
/**
* Returns the array of commands. There is always 9 of them.
* @return the commands array
*/
public XmlMacro[] getCommands() {
return commands;
}
public void setCommands(XmlMacro[] commands) {
this.commands = commands;
}
@Validate
private void validate() throws PersistenceException{
if (commands == null || commands.length != COMMANDS_COUNT)
throw new PersistenceException("There must be always " + COMMANDS_COUNT + " commands in a configuration.");
}
} }

View File

@@ -1,127 +0,0 @@
/*
* Copyright (c) 2022, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.uart.db;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.Text;
//import no.nordicsemi.android.uart.data.MacroEol;
//import no.nordicsemi.android.uart.data.MacroIcon;
import no.nordicsemi.android.uart.data.*;
@Root
public class XmlMacro {
@Text(required = false)
private String command;
@Attribute(required = false)
private boolean active = false;
@Attribute(required = false)
private MacroEol eol = MacroEol.LF;
@Attribute(required = false)
private MacroIcon icon = MacroIcon.LEFT;
/**
* Sets the command.
* @param command the command that will be sent to UART device
*/
public void setCommand(final String command) {
this.command = command;
}
/**
* Sets whether the command is active.
* @param active true to make it active
*/
public void setActive(final boolean active) {
this.active = active;
}
/**
* Sets the new line type.
* @param eol end of line terminator
*/
public void setEol(final int eol) {
this.eol = MacroEol.values()[eol];
}
/**
* Sets the icon index.
* @param index index of the icon.
*/
public void setIconIndex(final int index) {
this.icon = MacroIcon.values()[index];
}
/**
* Returns the command that will be sent to UART device.
* @return the command
*/
public String getCommand() {
return command;
}
/**
* Returns whether the icon is active.
* @return true if it's active
*/
public boolean isActive() {
return active;
}
/**
* Returns the new line type.
* @return end of line terminator
*/
public MacroEol getEol() {
return eol;
}
/**
* Returns the icon index.
* @return the icon index
*/
public int getIconIndex() {
return icon.getIndex();
}
/**
* Returns the EOL index.
* @return the EOL index
*/
public int getEolIndex() {
return eol.getIndex();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2022, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.uart.db
import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.uart.data.MacroIcon
import org.simpleframework.xml.Attribute
import org.simpleframework.xml.Root
import org.simpleframework.xml.Text
//import no.nordicsemi.android.uart.data.MacroEol;
//import no.nordicsemi.android.uart.data.MacroIcon;
@Root
class XmlMacro {
/**
* Returns the command that will be sent to UART device.
* @return the command
*/
/**
* Sets the command.
* @param command the command that will be sent to UART device
*/
@field:Text(required = false)
var command: String? = null
/**
* Returns whether the icon is active.
* @return true if it's active
*/
/**
* Sets whether the command is active.
* @param active true to make it active
*/
@field:Attribute(required = false)
var isActive = false
/**
* Returns the new line type.
* @return end of line terminator
*/
@field:Attribute(required = false)
var eol = MacroEol.LF
private set
@field:Attribute(required = false)
private var icon = MacroIcon.LEFT
/**
* Sets the new line type.
* @param eol end of line terminator
*/
fun setEol(eol: Int) {
this.eol = MacroEol.values()[eol]
}
/**
* Returns the icon index.
* @return the icon index
*/
/**
* Sets the icon index.
* @param index index of the icon.
*/
var iconIndex: Int
get() = icon.index
set(index) {
icon = MacroIcon.values()[index]
}
/**
* Returns the EOL index.
* @return the EOL index
*/
val eolIndex: Int
get() = eol.index
}

View File

@@ -34,18 +34,27 @@ package no.nordicsemi.android.uart.repository
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory import no.nordicsemi.android.common.logger.NordicLoggerFactory
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.uart.data.ConfigurationDataSource
import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.uart.data.UARTData
import no.nordicsemi.android.uart.data.UARTMacro
import no.nordicsemi.android.uart.data.UARTManager
import no.nordicsemi.android.uart.data.parseWithNewLineChar
import no.nordicsemi.android.ui.view.StringConst import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.android.uart.data.*
import no.nordicsemi.android.utils.EMPTY import no.nordicsemi.android.utils.EMPTY
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -74,7 +83,7 @@ class UARTRepository @Inject internal constructor(
} }
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
val createdLogger = loggerFactory.create(stringConst.APP_NAME, "UART", device.address()).also { val createdLogger = loggerFactory.create(stringConst.APP_NAME, "UART", device.address).also {
logger = it logger = it
} }
val manager = UARTManager(context, scope, createdLogger) val manager = UARTManager(context, scope, createdLogger)
@@ -103,7 +112,7 @@ class UARTRepository @Inject internal constructor(
} }
fun openLogger() { fun openLogger() {
logger?.openLogger() NordicLogger.launch(context, logger)
} }
suspend fun saveConfigurationName(name: String) { suspend fun saveConfigurationName(name: String) {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.common.ui.scanner.model.DiscoveredBluetoothDevice
import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.DEVICE_DATA
import no.nordicsemi.android.service.NotificationService import no.nordicsemi.android.service.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint

View File

@@ -45,15 +45,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import no.nordicsemi.android.theme.RadioButtonGroup import no.nordicsemi.android.common.theme.view.RadioButtonGroup
import no.nordicsemi.android.theme.RadioButtonItem import no.nordicsemi.android.common.theme.view.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity import no.nordicsemi.android.common.theme.view.RadioGroupViewEntity
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.MacroEol import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.utils.EMPTY import no.nordicsemi.android.utils.EMPTY
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
internal fun InputSection(onEvent: (UARTViewEvent) -> Unit) { internal fun InputSection(onEvent: (UARTViewEvent) -> Unit) {
val text = rememberSaveable { mutableStateOf(String.EMPTY) } val text = rememberSaveable { mutableStateOf(String.EMPTY) }
@@ -105,7 +105,7 @@ internal fun EditInputSection(onEvent: (UARTViewEvent) -> Unit) {
} }
val viewEntity = RadioGroupViewEntity(items) val viewEntity = RadioGroupViewEntity(items)
ScreenSection { OutlinedCard {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {

View File

@@ -46,7 +46,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.view.SectionTitle import no.nordicsemi.android.ui.view.SectionTitle
import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.R
@@ -74,7 +73,7 @@ internal fun MacroSection(viewState: UARTViewState, onEvent: (UARTViewEvent) ->
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(16.dp) .padding(16.dp)
) { ) {
ScreenSection { OutlinedCard {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {

View File

@@ -70,6 +70,7 @@ internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDism
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun NameInput( private fun NameInput(
name: MutableState<String>, name: MutableState<String>,

View File

@@ -52,9 +52,9 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.RadioButtonGroup import no.nordicsemi.android.common.theme.view.RadioButtonGroup
import no.nordicsemi.android.theme.RadioButtonItem import no.nordicsemi.android.common.theme.view.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity import no.nordicsemi.android.common.theme.view.RadioGroupViewEntity
import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.MacroEol import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.uart.data.MacroIcon import no.nordicsemi.android.uart.data.MacroIcon
@@ -130,6 +130,7 @@ internal fun UARTAddMacroDialog(macro: UARTMacro?, onEvent: (UARTViewEvent) -> U
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun CommandInput(command: MutableState<String>) { private fun CommandInput(command: MutableState<String>) {
Column { Column {

View File

@@ -50,7 +50,6 @@ import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.ui.view.dialog.* import no.nordicsemi.android.ui.view.dialog.*
import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTConfiguration import no.nordicsemi.android.uart.data.UARTConfiguration
import no.nordicsemi.android.utils.exhaustive
@Composable @Composable
internal fun UARTConfigurationPicker(state: UARTViewState, onEvent: (UARTViewEvent) -> Unit) { internal fun UARTConfigurationPicker(state: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
@@ -68,7 +67,7 @@ internal fun UARTConfigurationPicker(state: UARTViewState, onEvent: (UARTViewEve
onEvent(OnConfigurationSelected(state.configurations[it.index])) onEvent(OnConfigurationSelected(state.configurations[it.index]))
showDialog.value = false showDialog.value = false
} }
}.exhaustive }
} }
} }
} }

View File

@@ -32,11 +32,11 @@
package no.nordicsemi.android.uart.view package no.nordicsemi.android.uart.view
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.Card
import no.nordicsemi.android.uart.data.UARTData import no.nordicsemi.android.uart.data.UARTData
@Composable @Composable

View File

@@ -39,20 +39,27 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.theme.PagerView import no.nordicsemi.android.common.theme.view.PagerView
import no.nordicsemi.android.theme.PagerViewEntity import no.nordicsemi.android.common.theme.view.PagerViewEntity
import no.nordicsemi.android.theme.PagerViewItem import no.nordicsemi.android.common.theme.view.PagerViewItem
import no.nordicsemi.android.service.* import no.nordicsemi.android.common.ui.scanner.view.DeviceConnectingView
import no.nordicsemi.android.ui.view.BackIconAppBar import no.nordicsemi.android.common.ui.scanner.view.DeviceDisconnectedView
import no.nordicsemi.android.ui.view.LoggerIconAppBar import no.nordicsemi.android.common.ui.scanner.view.Reason
import no.nordicsemi.android.service.ConnectedResult
import no.nordicsemi.android.service.ConnectingResult
import no.nordicsemi.android.service.DeviceHolder
import no.nordicsemi.android.service.DisconnectedResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.LinkLossResult
import no.nordicsemi.android.service.MissingServiceResult
import no.nordicsemi.android.service.SuccessResult
import no.nordicsemi.android.service.UnknownErrorResult
import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTData import no.nordicsemi.android.uart.data.UARTData
import no.nordicsemi.android.uart.viewmodel.UARTViewModel import no.nordicsemi.android.uart.viewmodel.UARTViewModel
import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.android.ui.view.BackIconAppBar
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.android.ui.view.LoggerIconAppBar
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView import no.nordicsemi.android.ui.view.NavigateUpButton
import no.nordicsemi.ui.scanner.ui.NoDeviceView
import no.nordicsemi.ui.scanner.ui.Reason
@Composable @Composable
fun UARTScreen() { fun UARTScreen() {
@@ -64,19 +71,21 @@ fun UARTScreen() {
AppBar(state = state, navigateUp = navigateUp) { viewModel.onEvent(it) } AppBar(state = state, navigateUp = navigateUp) { viewModel.onEvent(it) }
when (state.uartManagerState) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
NoDeviceState -> NoDeviceView() when (state.uartManagerState) {
is WorkingState -> when (state.uartManagerState.result) { NoDeviceState -> DeviceConnectingView()
is IdleResult, is WorkingState -> when (state.uartManagerState.result) {
is ConnectingResult -> Scroll { DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } } is IdleResult,
is ConnectedResult -> Scroll { DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } } is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> Scroll { DeviceDisconnectedView(Reason.USER, navigateUp) } is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is LinkLossResult -> Scroll { DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> Scroll { DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) } is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> Scroll { DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) } is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is SuccessResult -> SuccessScreen(state.uartManagerState.result.data, state, viewModel) is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> SuccessScreen(state.uartManagerState.result.data, state, viewModel)
}
} }
}.exhaustive }
} }
} }

Some files were not shown because too many files have changed in this diff Show More