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_utils"))
implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(libs.nordic.core)
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.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.gls.main.view.GLSScreen
import no.nordicsemi.android.hrs.view.HRSScreen
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.prx.view.PRXScreen
import no.nordicsemi.android.rscs.view.RSCSScreen
import no.nordicsemi.android.toolbox.scanner.ScannerDestination
import no.nordicsemi.android.uart.view.UARTScreen
import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen
val HomeDestinations = ComposeDestinations(HomeDestination.values().map { it.destination })
val ProfileDestinations = ComposeDestinations(ProfileDestination.values().map { it.destination })
val HomeDestinationId = createSimpleDestination("home-destination")
enum class HomeDestination(val destination: ComposeDestination) {
HOME(ComposeDestination("home-destination") { HomeScreen() }),
SCANNER(ComposeDestination("scanner-destination") { FindDeviceScreen() });
}
val HomeDestinations = listOf(
defineDestination(HomeDestinationId) { HomeScreen() },
ScannerDestination
)
enum class ProfileDestination(val destination: ComposeDestination) {
CSC(ComposeDestination("csc-destination") { CSCScreen() }),
HRS(ComposeDestination("hrs-destination") { HRSScreen() }),
HTS(ComposeDestination("hts-destination") { HTSScreen() }),
GLS(ComposeDestination("gls-destination") { GLSScreen() }),
BPS(ComposeDestination("bps-destination") { BPSScreen() }),
PRX(ComposeDestination("prx-destination") { PRXScreen() }),
RSCS(ComposeDestination("rscs-destination") { RSCSScreen() }),
CGMS(ComposeDestination("cgms-destination") { CGMScreen() }),
UART(ComposeDestination("uart-destination") { UARTScreen() });
}
val CSCDestinationId = createSimpleDestination("csc-destination")
val HRSDestinationId = createSimpleDestination("hrs-destination")
val HTSDestinationId = createSimpleDestination("hts-destination")
val GLSDestinationId = createSimpleDestination("gls-destination")
val BPSDestinationId = createSimpleDestination("bps-destination")
val PRXDestinationId = createSimpleDestination("prx-destination")
val RSCSDestinationId = createSimpleDestination("rscs-destination")
val CGMSDestinationId = createSimpleDestination("cgms-destination")
val UARTDestinationId = createSimpleDestination("uart-destination")
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.ui.Modifier
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.theme.NordicActivity
import no.nordicsemi.android.theme.NordicTheme
import no.nordicsemi.android.navigation.NavigationView
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 javax.inject.Inject
@@ -62,7 +66,7 @@ class MainActivity : NordicActivity() {
color = MaterialTheme.colorScheme.surface,
modifier = Modifier.fillMaxSize()
) {
NavigationView(HomeDestinations + ProfileDestinations + ScannerDestinations + GLSDestinations)
NavigationView(HomeDestinations + ProfileDestinations + ScannerDestination + GLSDestination)
}
AnalyticsPermissionRequestDialog()

View File

@@ -48,9 +48,17 @@ import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.analytics.Link
import no.nordicsemi.android.analytics.Profile
import no.nordicsemi.android.analytics.ProfileOpenEvent
import no.nordicsemi.android.nrftoolbox.BPSDestinationId
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.RSCSDestinationId
import no.nordicsemi.android.nrftoolbox.UARTDestinationId
import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel
private const val DFU_PACKAGE_NAME = "no.nordicsemi.android.dfu"
@@ -87,14 +95,14 @@ fun HomeScreen() {
Spacer(modifier = Modifier.height(16.dp))
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))
}
Spacer(modifier = Modifier.height(16.dp))
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))
}
@@ -109,42 +117,42 @@ fun HomeScreen() {
Spacer(modifier = Modifier.height(16.dp))
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))
}
Spacer(modifier = Modifier.height(16.dp))
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))
}
Spacer(modifier = Modifier.height(16.dp))
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))
}
Spacer(modifier = Modifier.height(16.dp))
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))
}
Spacer(modifier = Modifier.height(16.dp))
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))
}
Spacer(modifier = Modifier.height(16.dp))
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))
}
@@ -159,7 +167,7 @@ fun HomeScreen() {
Spacer(modifier = Modifier.height(16.dp))
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))
}

View File

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

View File

@@ -35,6 +35,7 @@ plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.kapt) apply true
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.kotlin.parcelize) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.secrets) 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.hilt) 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
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

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

View File

@@ -32,7 +32,7 @@
package no.nordicsemi.android.analytics
import android.annotation.SuppressLint
import no.nordicsemi.analytics.NordicAnalytics
import no.nordicsemi.android.common.analytics.NordicAnalytics
import javax.inject.Inject
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 {
alias(libs.plugins.nordic.library)
alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization)
}
@@ -46,4 +46,5 @@ dependencies {
implementation(libs.androidx.lifecycle.service)
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.Intent
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
const val DEVICE_DATA = "device-data"

View File

@@ -30,7 +30,7 @@
*/
plugins {
alias(libs.plugins.nordic.library)
alias(libs.plugins.nordic.feature)
}
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
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.ui.R
@Composable
fun BatteryLevelView(batteryLevel: Int) {
ScreenSection {
OutlinedCard {
KeyValueField(
stringResource(id = R.string.field_battery),
"$batteryLevel%"

View File

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

View File

@@ -41,4 +41,5 @@ android {
dependencies {
implementation(libs.nordic.navigation)
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.Job
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
get() = ""
@@ -54,10 +48,6 @@ fun Context.isServiceRunning(serviceClassName: String): Boolean {
return services.find { it.service.className == serviceClassName } != null
}
fun SuccessDestinationResult.getDevice(): DiscoveredBluetoothDevice {
return (argument as ParcelableArgument).value as DiscoveredBluetoothDevice
}
private val exceptionHandler = CoroutineExceptionHandler { _, t ->
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
}

View File

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

View File

@@ -44,11 +44,11 @@ import kotlinx.coroutines.launch
import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.bps.data.BPSData
import no.nordicsemi.android.bps.data.BPSManager
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
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.service.BleManagerResult
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@ViewModelScoped
@@ -62,7 +62,7 @@ internal class BPSRepository @Inject constructor(
private var logger: NordicLogger? = null
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
}
val manager = BPSManager(context, scope, createdLogger)
@@ -93,7 +93,6 @@ internal class BPSRepository @Inject constructor(
}
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 no.nordicsemi.android.bps.R
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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun BPSScreen() {
@@ -62,18 +69,18 @@ fun BPSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
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.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -41,14 +42,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.bps.R
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.KeyValueField
import no.nordicsemi.android.ui.view.SectionTitle
@Composable
internal fun BPSSensorsReadingView(state: BPSData) {
ScreenSection {
OutlinedCard {
Column {
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.bps_records))
Spacer(modifier = Modifier.height(16.dp))

View File

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

View File

@@ -30,8 +30,8 @@
*/
plugins {
alias(libs.plugins.nordic.library)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.parcelize)
}
android {
@@ -41,6 +41,7 @@ android {
dependencies {
implementation(project(":lib_analytics"))
implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui"))
implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.uiscanner)
implementation(libs.nordic.navigation)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx)
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.suspend
import no.nordicsemi.android.ble.ktx.suspendForValidResponse
import no.nordicsemi.android.cgms.repository.toList
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.common.logger.NordicLogger
import no.nordicsemi.android.service.ConnectionObserverAdapter
import no.nordicsemi.android.utils.launchWithCatch
import java.util.*

View File

@@ -29,11 +29,10 @@
* 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 androidx.core.util.keyIterator
import no.nordicsemi.android.cgms.data.CGMRecord
internal fun SparseArray<CGMRecord>.toList(): List<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.cgms.data.CGMData
import no.nordicsemi.android.cgms.data.CGMManager
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
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.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -71,7 +71,7 @@ class CGMRepository @Inject constructor(
}
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
}
val manager = CGMManager(context, scope, createdLogger)
@@ -110,7 +110,7 @@ class CGMRepository @Inject constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
fun release() {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
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.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@AndroidEntryPoint

View File

@@ -38,6 +38,7 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.CGMServiceCommand
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.SectionTitle
@@ -87,7 +87,7 @@ internal fun CGMContentView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
@Composable
private fun SettingsView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
ScreenSection {
OutlinedCard {
SectionTitle(icon = Icons.Default.Settings, title = "Request items")
Spacer(modifier = Modifier.height(16.dp))
@@ -115,7 +115,7 @@ private fun SettingsView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
@Composable
private fun RecordsView(state: CGMData) {
ScreenSection {
OutlinedCard {
if (state.records.isEmpty()) {
RecordsViewWithoutData()
} else {

View File

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

View File

@@ -31,10 +31,15 @@
package no.nordicsemi.android.cgms.viewmodel
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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 no.nordicsemi.android.analytics.AppAnalytics
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.CGMServiceCommand
import no.nordicsemi.android.cgms.repository.CGMRepository
import no.nordicsemi.android.cgms.view.*
import no.nordicsemi.android.navigation.*
import no.nordicsemi.android.cgms.view.CGMViewEvent
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.utils.exhaustive
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import javax.inject.Inject
@HiltViewModel
internal class CGMViewModel @Inject constructor(
private val repository: CGMRepository,
private val navigationManager: NavigationManager,
private val navigationManager: Navigator,
private val analytics: AppAnalytics
) : ViewModel() {
@@ -82,24 +94,22 @@ internal class CGMViewModel @Inject constructor(
is OnWorkingModeSelected -> onCommandReceived(event.workingMode)
NavigateUp -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger()
}.exhaustive
}
}
private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CGMS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(CGMS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {
handleArgs(it)
}
}.launchIn(viewModelScope)
navigationManager.resultFrom(ScannerDestinationId)
.onEach { handleResult(it) }
.launchIn(viewModelScope)
}
private fun handleArgs(args: DestinationResult) {
when (args) {
is CancelDestinationResult -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice())
}.exhaustive
private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (result) {
is NavigationResult.Cancelled -> navigationManager.navigateUp()
is NavigationResult.Success -> repository.launch(result.value)
}
}
private fun onCommandReceived(workingMode: CGMServiceCommand) {
@@ -108,7 +118,7 @@ internal class CGMViewModel @Inject constructor(
CGMServiceCommand.REQUEST_LAST_RECORD -> repository.requestLastRecord()
CGMServiceCommand.REQUEST_FIRST_RECORD -> repository.requestFirstRecord()
CGMServiceCommand.DISCONNECT -> disconnect()
}.exhaustive
}
}
private fun disconnect() {

View File

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

View File

@@ -37,16 +37,16 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
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.CSCManager
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.IdleResult
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -72,7 +72,7 @@ class CSCRepository @Inject constructor(
}
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
}
val manager = CSCManager(context, scope, createdLogger)
@@ -103,7 +103,7 @@ class CSCRepository @Inject constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
fun release() {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
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.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@AndroidEntryPoint

View File

@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.tooling.preview.Preview
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.data.CSCData
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.dialog.FlowCanceled
import no.nordicsemi.android.ui.view.dialog.ItemSelectedResult
import no.nordicsemi.android.utils.exhaustive
@Composable
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])))
showDialog.value = false
}
}.exhaustive
}
}
}
@@ -105,7 +104,7 @@ private fun SettingsSection(
onEvent: (CSCViewEvent) -> Unit,
onWheelButtonClick: () -> Unit,
) {
ScreenSection {
OutlinedCard {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {

View File

@@ -31,9 +31,9 @@
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.theme.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity
import java.util.*
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.res.stringResource
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.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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun CSCScreen() {
@@ -62,18 +69,18 @@ fun CSCScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state.cscManagerState) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.cscManagerState.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is SuccessResult -> CSCContentView(state.cscManagerState.result.data, state.speedUnit) { viewModel.onEvent(it) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
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.stringResource
import androidx.compose.ui.tooling.preview.Preview
import no.nordicsemi.android.common.theme.NordicTheme
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.StringListDialogConfig
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.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -41,14 +42,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.csc.R
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.KeyValueField
import no.nordicsemi.android.ui.view.SectionTitle
@Composable
internal fun SensorsReadingView(state: CSCData, speedUnit: SpeedUnit) {
ScreenSection {
OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = "Records")
Spacer(modifier = Modifier.height(16.dp))

View File

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

View File

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

View File

@@ -31,13 +31,11 @@
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.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 GLSDestinations = ComposeDestinations(destination)
val GLSDestination = defineDestination(GlsDetailsDestinationId) { GLSDetailsScreen() }

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.ktx.asValidResponseFlow
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.utils.launchWithCatch
import java.util.*

View File

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

View File

@@ -84,7 +84,7 @@ internal fun BooleanField(title: String, value: Boolean) {
Text(
text = stringResource(id = R.string.gls_no),
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.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.data.GLSRecord
import no.nordicsemi.android.gls.main.view.toDisplayString
import no.nordicsemi.android.theme.ScreenSection
@Composable
internal fun GLSDetailsContentView(record: GLSRecord) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Column(modifier = Modifier.padding(16.dp)) {
ScreenSection() {
OutlinedCard {
Field(
stringResource(id = R.string.gls_details_sequence_number),
record.sequenceNumber.toString()

View File

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

View File

@@ -40,6 +40,7 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.WorkingMode
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.SectionTitle
@@ -94,7 +94,7 @@ internal fun GLSContentView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Uni
@Composable
private fun SettingsView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit) {
ScreenSection {
OutlinedCard {
SectionTitle(icon = Icons.Default.Settings, title = "Request items")
Spacer(modifier = Modifier.height(16.dp))
@@ -118,7 +118,7 @@ private fun SettingsView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit)
@Composable
private fun RecordsView(state: GLSData) {
ScreenSection {
OutlinedCard {
if (state.records.isEmpty()) {
RecordsViewWithoutData()
} else {

View File

@@ -39,16 +39,23 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun GLSScreen() {
@@ -62,18 +69,18 @@ fun GLSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> GLSContentView(state.result.data) { viewModel.onEvent(it) }
}
}.exhaustive
}
}
}
}

View File

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

View File

@@ -42,15 +42,14 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
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.GLSManager
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.ui.view.StringConst
import no.nordicsemi.android.utils.exhaustive
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@ViewModelScoped
@@ -65,7 +64,7 @@ internal class GLSRepository @Inject constructor(
private var logger: NordicLogger? = null
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
}
val managerInstance = manager ?: GLSManager(context, scope, createdLogger)
@@ -100,7 +99,7 @@ internal class GLSRepository @Inject constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
fun requestMode(workingMode: WorkingMode) {
@@ -108,6 +107,6 @@ internal class GLSRepository @Inject constructor(
WorkingMode.ALL -> manager?.requestAllRecords()
WorkingMode.LAST -> manager?.requestLastRecord()
WorkingMode.FIRST -> manager?.requestFirstRecord()
}.exhaustive
}
}
}

View File

@@ -30,7 +30,7 @@
*/
plugins {
alias(libs.plugins.nordic.library)
alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization)
}
@@ -41,6 +41,7 @@ android {
dependencies {
implementation(project(":lib_analytics"))
implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui"))
implementation(project(":lib_utils"))
@@ -53,6 +54,7 @@ dependencies {
implementation(libs.nordic.uiscanner)
implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx)
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.ktx.asValidResponseFlow
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.utils.launchWithCatch
import java.util.*

View File

@@ -34,18 +34,22 @@ package no.nordicsemi.android.hrs.service
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
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 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.HRSManager
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -71,7 +75,7 @@ class HRSRepository @Inject constructor(
}
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
}
val manager = HRSManager(context, scope, createdLogger)
@@ -87,7 +91,7 @@ class HRSRepository @Inject constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
private suspend fun HRSManager.start(device: DiscoveredBluetoothDevice) {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
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.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@AndroidEntryPoint

View File

@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -48,7 +49,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.hrs.R
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.SectionTitle
@@ -59,7 +59,7 @@ internal fun HRSContentView(state: HRSData, zoomIn: Boolean, onEvent: (HRSScreen
modifier = Modifier.padding(16.dp)
) {
ScreenSection {
OutlinedCard {
SectionTitle(
resId = R.drawable.ic_chart_line,
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.res.stringResource
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.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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun HRSScreen() {
@@ -62,18 +69,18 @@ fun HRSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> HRSContentView(state.result.data, state.zoomIn) { viewModel.onEvent(it) }
}
}.exhaustive
}
}
}
}

View File

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

View File

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

View File

@@ -34,18 +34,22 @@ package no.nordicsemi.android.hts.repository
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
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 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.HTSManager
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -71,7 +75,7 @@ class HTSRepository @Inject constructor(
}
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
}
val manager = HTSManager(context, scope, createdLogger)
@@ -87,7 +91,7 @@ class HTSRepository @Inject constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
private suspend fun HTSManager.start(device: DiscoveredBluetoothDevice) {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
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.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@AndroidEntryPoint

View File

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

View File

@@ -31,8 +31,8 @@
package no.nordicsemi.android.hts.view
import no.nordicsemi.android.theme.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity
import no.nordicsemi.android.common.theme.view.RadioButtonItem
import no.nordicsemi.android.common.theme.view.RadioGroupViewEntity
private const val DISPLAY_FAHRENHEIT = "°F"
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.res.stringResource
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.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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun HTSScreen() {
@@ -62,18 +69,18 @@ fun HTSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state.htsManagerState) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.htsManagerState.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
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
import android.os.ParcelUuid
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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 no.nordicsemi.android.analytics.AppAnalytics
import no.nordicsemi.android.analytics.Profile
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.repository.HTSRepository
import no.nordicsemi.android.hts.view.*
import no.nordicsemi.android.navigation.*
import no.nordicsemi.android.hts.view.DisconnectEvent
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.utils.exhaustive
import no.nordicsemi.android.utils.getDevice
import no.nordicsemi.ui.scanner.ScannerDestinationId
import no.nordicsemi.android.toolbox.scanner.ScannerDestinationId
import javax.inject.Inject
@HiltViewModel
internal class HTSViewModel @Inject constructor(
private val repository: HTSRepository,
private val navigationManager: NavigationManager,
private val navigationManager: Navigator,
private val analytics: AppAnalytics
) : ViewModel() {
@@ -76,20 +87,18 @@ internal class HTSViewModel @Inject constructor(
}
private fun requestBluetoothDevice() {
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HTS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(HTS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {
handleArgs(it)
}
}.launchIn(viewModelScope)
navigationManager.resultFrom(ScannerDestinationId)
.onEach { handleResult(it) }
.launchIn(viewModelScope)
}
private fun handleArgs(args: DestinationResult) {
when (args) {
is CancelDestinationResult -> navigationManager.navigateUp()
is SuccessDestinationResult -> repository.launch(args.getDevice())
}.exhaustive
private fun handleResult(result: NavigationResult<DiscoveredBluetoothDevice>) {
when (result) {
is NavigationResult.Cancelled -> navigationManager.navigateUp()
is NavigationResult.Success -> repository.launch(result.value)
}
}
fun onEvent(event: HTSScreenViewEvent) {
@@ -98,7 +107,7 @@ internal class HTSViewModel @Inject constructor(
is OnTemperatureUnitSelected -> onTemperatureUnitSelected(event)
NavigateUp -> navigationManager.navigateUp()
OpenLoggerEvent -> repository.openLogger()
}.exhaustive
}
}
private fun disconnect() {

View File

@@ -30,7 +30,7 @@
*/
plugins {
alias(libs.plugins.nordic.library)
alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization)
}
@@ -41,6 +41,7 @@ android {
dependencies {
implementation(project(":lib_analytics"))
implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui"))
implementation(project(":lib_utils"))
@@ -52,6 +53,7 @@ dependencies {
implementation(libs.nordic.navigation)
implementation(libs.nordic.uilogger)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.core.ktx)
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.ktx.asValidResponseFlow
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.utils.launchWithCatch
import java.util.*

View File

@@ -35,15 +35,15 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
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.prx.data.AlarmLevel
import no.nordicsemi.android.prx.data.PRXData
import no.nordicsemi.android.prx.data.PRXManager
import no.nordicsemi.android.prx.data.ProximityServerManager
import no.nordicsemi.android.service.*
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -73,7 +73,7 @@ class PRXRepository @Inject internal constructor(
}
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
}
val manager = PRXManager(context, scope, createdLogger)
@@ -114,7 +114,7 @@ class PRXRepository @Inject internal constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
fun release() {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
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.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@AndroidEntryPoint

View File

@@ -38,13 +38,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.prx.R
import no.nordicsemi.android.prx.data.PRXData
import no.nordicsemi.android.ui.view.BatteryLevelView
@@ -81,7 +81,7 @@ internal fun ContentView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit)
@Composable
private fun SettingsSection(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
ScreenSection {
OutlinedCard {
SectionTitle(icon = Icons.Default.Settings, title = stringResource(R.string.prx_settings))
Spacer(modifier = Modifier.height(16.dp))
@@ -114,7 +114,7 @@ private fun TurnAlarmOffButton(onEvent: (PRXScreenViewEvent) -> Unit) {
@Composable
private fun RecordsSection(state: PRXData) {
ScreenSection {
OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.prx_records))
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.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -46,7 +47,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.prx.R
@Composable
@@ -57,7 +57,7 @@ fun DeviceOutOfRangeView(navigateUp: () -> Unit) {
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ScreenSection {
OutlinedCard {
Icon(
imageVector = Icons.Default.HighlightOff,
contentDescription = null,

View File

@@ -40,16 +40,23 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun PRXScreen() {
@@ -63,18 +70,18 @@ fun PRXScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
is SuccessResult -> ContentView(state.result.data) { viewModel.onEvent(it) }
}
}.exhaustive
}
}
}
}

View File

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

View File

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

View File

@@ -34,18 +34,22 @@ package no.nordicsemi.android.rscs.repository
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
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 no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
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.rscs.data.RSCSData
import no.nordicsemi.android.rscs.data.RSCSManager
import no.nordicsemi.android.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.ui.view.StringConst
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -71,7 +75,7 @@ class RSCSRepository @Inject constructor(
}
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
}
val manager = RSCSManager(context, scope, createdLogger)
@@ -87,7 +91,7 @@ class RSCSRepository @Inject constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
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.res.stringResource
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.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.LoggerIconAppBar
import no.nordicsemi.android.utils.exhaustive
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
import no.nordicsemi.android.ui.view.NavigateUpButton
@Composable
fun RSCSScreen() {
@@ -62,18 +69,18 @@ fun RSCSScreen() {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
when (state) {
NoDeviceState -> NoDeviceView()
NoDeviceState -> DeviceConnectingView()
is WorkingState -> when (state.result) {
is IdleResult,
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is ConnectedResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER) { NavigateUpButton(navigateUp) }
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS) { NavigateUpButton(navigateUp) }
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE) { NavigateUpButton(navigateUp) }
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN) { NavigateUpButton(navigateUp) }
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.height
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.ScreenSection
import no.nordicsemi.android.rscs.R
import no.nordicsemi.android.rscs.data.RSCSData
import no.nordicsemi.android.ui.view.KeyValueField
@@ -46,7 +46,7 @@ import no.nordicsemi.android.ui.view.SectionTitle
@Composable
internal fun SensorsReadingView(state: RSCSData) {
ScreenSection {
OutlinedCard {
SectionTitle(resId = R.drawable.ic_records, title = "Records")
Spacer(modifier = Modifier.height(16.dp))

View File

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

View File

@@ -30,7 +30,7 @@
*/
plugins {
alias(libs.plugins.nordic.library)
alias(libs.plugins.nordic.feature)
alias(libs.plugins.kotlin.serialization)
// id("com.google.protobuf")
alias(libs.plugins.kotlin.kapt)
@@ -49,6 +49,7 @@ android {
dependencies {
implementation(project(":lib_analytics"))
implementation(project(":lib_service"))
implementation(project(":lib_scanner"))
implementation(project(":lib_ui"))
implementation(project(":lib_utils"))
@@ -56,7 +57,7 @@ dependencies {
implementation(libs.room.ktx)
// kapt(libs.room.compiler)
// kapt("androidx.room:room-compiler:2.5.0")
kapt(libs.room.compiler)
// kapt("")
implementation(libs.nordic.ble.common)
@@ -67,6 +68,10 @@ dependencies {
implementation(libs.nordic.navigation)
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.core.ktx)
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.asValidResponseFlow
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.utils.EMPTY
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,
* 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.Root;
import org.simpleframework.xml.core.PersistenceException;
import org.simpleframework.xml.core.Validate;
import org.simpleframework.xml.Attribute
import org.simpleframework.xml.ElementArray
import org.simpleframework.xml.Root
import org.simpleframework.xml.core.PersistenceException
import org.simpleframework.xml.core.Validate
@Root
public class XmlConfiguration {
public static final int COMMANDS_COUNT = 9;
class XmlConfiguration {
/**
* 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
private XmlMacro[] commands = new XmlMacro[COMMANDS_COUNT];
/**
* 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.");
}
}
companion object {
const val COMMANDS_COUNT = 9
}
}

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 dagger.hilt.android.qualifiers.ApplicationContext
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 no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.logger.NordicLogger
import no.nordicsemi.android.logger.NordicLoggerFactory
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.service.BleManagerResult
import no.nordicsemi.android.service.IdleResult
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.uart.data.*
import no.nordicsemi.android.utils.EMPTY
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
import javax.inject.Singleton
@@ -74,7 +83,7 @@ class UARTRepository @Inject internal constructor(
}
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
}
val manager = UARTManager(context, scope, createdLogger)
@@ -103,7 +112,7 @@ class UARTRepository @Inject internal constructor(
}
fun openLogger() {
logger?.openLogger()
NordicLogger.launch(context, logger)
}
suspend fun saveConfigurationName(name: String) {

View File

@@ -36,9 +36,9 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
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.NotificationService
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
import javax.inject.Inject
@AndroidEntryPoint

View File

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

View File

@@ -70,6 +70,7 @@ internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDism
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NameInput(
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.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.RadioButtonGroup
import no.nordicsemi.android.theme.RadioButtonItem
import no.nordicsemi.android.theme.RadioGroupViewEntity
import no.nordicsemi.android.common.theme.view.RadioButtonGroup
import no.nordicsemi.android.common.theme.view.RadioButtonItem
import no.nordicsemi.android.common.theme.view.RadioGroupViewEntity
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.MacroEol
import no.nordicsemi.android.uart.data.MacroIcon
@@ -130,6 +130,7 @@ internal fun UARTAddMacroDialog(macro: UARTMacro?, onEvent: (UARTViewEvent) -> U
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CommandInput(command: MutableState<String>) {
Column {

View File

@@ -50,7 +50,6 @@ import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.ui.view.dialog.*
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTConfiguration
import no.nordicsemi.android.utils.exhaustive
@Composable
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]))
showDialog.value = false
}
}.exhaustive
}
}
}
}

View File

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

View File

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