diff --git a/app/build.gradle b/app/build.gradle
index 5f9029ee..8654dc87 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -50,11 +50,11 @@ android {
dependencies {
//Hilt requires to implement every module in the main app module
//https://github.com/google/dagger/issues/2123
- implementation project(":feature_csc")
- implementation project(":feature_hrs")
- implementation project(":feature_hts")
- implementation project(":feature_gls")
- implementation project(':feature_scanner')
+ implementation project(':profile_csc')
+ implementation project(':profile_hrs')
+ implementation project(':profile_hts')
+ implementation project(':profile_gls')
+ implementation project(':profile_scanner')
implementation project(":lib_theme")
implementation project(":lib_utils")
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt
index 6aab9719..fd3eee3e 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/FeatureButton.kt
@@ -4,16 +4,16 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
@@ -25,10 +25,13 @@ import no.nordicsemi.android.theme.NordicColors
@Composable
fun FeatureButton(@DrawableRes iconId: Int, @StringRes nameId: Int, onClick: () -> Unit) {
- Button(
- modifier = Modifier.fillMaxWidth(),
- onClick = { onClick() },
- colors = ButtonDefaults.buttonColors(backgroundColor = NordicColors.NordicGray4.value()),
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onClick() }
+ .background(NordicColors.ItemHighlight.value())
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(iconId),
@@ -41,14 +44,10 @@ fun FeatureButton(@DrawableRes iconId: Int, @StringRes nameId: Int, onClick: ()
)
Row(
modifier = Modifier
- .padding(16.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
- Text(
- text = stringResource(id = nameId),
- modifier = Modifier.padding(16.dp),
- )
+ Text(text = stringResource(id = nameId))
}
}
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
index aca4b7b3..27fe1b12 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
@@ -1,10 +1,11 @@
package no.nordicsemi.android.nrftoolbox
+import android.app.Activity
import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Column
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -12,14 +13,17 @@ import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import no.nordicsemi.android.csc.view.CscScreen
+import no.nordicsemi.android.csc.view.CSCScreen
import no.nordicsemi.android.gls.view.GLSScreen
import no.nordicsemi.android.hrs.view.HRSScreen
import no.nordicsemi.android.hts.view.HTSScreen
@@ -28,6 +32,7 @@ import no.nordicsemi.android.scanner.view.BluetoothNotEnabledScreen
import no.nordicsemi.android.scanner.view.RequestPermissionScreen
import no.nordicsemi.android.scanner.view.ScanDeviceScreen
import no.nordicsemi.android.scanner.view.ScanDeviceScreenResult
+import no.nordicsemi.android.theme.view.CloseIconAppBar
import no.nordicsemi.android.utils.exhaustive
@Composable
@@ -42,12 +47,12 @@ internal fun HomeScreen() {
NavHost(navController = navController, startDestination = NavDestination.HOME.id) {
composable(NavDestination.HOME.id) { HomeView { viewModel.navigate(it) } }
- composable(NavDestination.CSC.id) { CscScreen { viewModel.navigateUp() } }
+ composable(NavDestination.CSC.id) { CSCScreen { viewModel.navigateUp() } }
composable(NavDestination.HRS.id) { HRSScreen { viewModel.navigateUp() } }
composable(NavDestination.HTS.id) { HTSScreen { viewModel.navigateUp() } }
composable(NavDestination.GLS.id) { GLSScreen { viewModel.navigateUp() } }
composable(NavDestination.REQUEST_PERMISSION.id) { RequestPermissionScreen(continueAction) }
- composable(NavDestination.BLUETOOTH_NOT_AVAILABLE.id) { BluetoothNotAvailableScreen() }
+ composable(NavDestination.BLUETOOTH_NOT_AVAILABLE.id) { BluetoothNotAvailableScreen{ viewModel.finish() } }
composable(NavDestination.BLUETOOTH_NOT_ENABLED.id) {
BluetoothNotEnabledScreen(continueAction)
}
@@ -69,11 +74,18 @@ internal fun HomeScreen() {
@Composable
fun HomeView(callback: (NavDestination) -> Unit) {
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) })
+ val context = LocalContext.current
+ CloseIconAppBar(stringResource(id = R.string.app_name)) {
+ (context as? Activity)?.finish()
+ }
FeatureButton(R.drawable.ic_csc, R.string.csc_module) { callback(NavDestination.CSC) }
+ Spacer(modifier = Modifier.height(1.dp))
FeatureButton(R.drawable.ic_hrs, R.string.hrs_module) { callback(NavDestination.HRS) }
+ Spacer(modifier = Modifier.height(1.dp))
FeatureButton(R.drawable.ic_gls, R.string.gls_module) { callback(NavDestination.GLS) }
+ Spacer(modifier = Modifier.height(1.dp))
+ FeatureButton(R.drawable.ic_hts, R.string.hts_module) { callback(NavDestination.HTS) }
}
}
@@ -102,7 +114,6 @@ private fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) {
}
}
-
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt
index de07cd82..026d2ae7 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt
@@ -1,15 +1,15 @@
package no.nordicsemi.android.nrftoolbox
import android.os.Bundle
-import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import dagger.hilt.android.AndroidEntryPoint
import no.nordicsemi.android.theme.TestTheme
@AndroidEntryPoint
-class MainActivity : ComponentActivity() {
+class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NrfToolboxApplication.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NrfToolboxApplication.kt
index c7090164..ce115ffb 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NrfToolboxApplication.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NrfToolboxApplication.kt
@@ -4,5 +4,4 @@ import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
-class NrfToolboxApplication : Application() {
-}
\ No newline at end of file
+class NrfToolboxApplication : Application()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f8f8f56c..4188a864 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,4 +2,5 @@
CSC
HRS
GLS
+ HTS
\ No newline at end of file
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCDataReadBroadcast.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCDataReadBroadcast.kt
deleted file mode 100644
index d4c1e1ff..00000000
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCDataReadBroadcast.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package no.nordicsemi.android.csc.service
-
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import no.nordicsemi.android.csc.data.CSCData
-import no.nordicsemi.android.service.BluetoothDataReadBroadcast
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-internal class CSCDataReadBroadcast @Inject constructor() : BluetoothDataReadBroadcast() {
-
- private val _wheelSize = MutableSharedFlow(
- replay = 1,
- extraBufferCapacity = 1,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
- val wheelSize: SharedFlow = _wheelSize
-
- fun setWheelSize(size: Int) {
- _wheelSize.tryEmit(size)
- }
-}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManagerCallbacks.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManagerCallbacks.kt
deleted file mode 100644
index b5596083..00000000
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManagerCallbacks.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (c) 2015, 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.csc.service
-
-import no.nordicsemi.android.ble.common.profile.csc.CyclingSpeedAndCadenceCallback
-import no.nordicsemi.android.service.BatteryManagerCallbacks
-
-internal interface CSCManagerCallbacks : BatteryManagerCallbacks, CyclingSpeedAndCadenceCallback
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCService.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCService.kt
deleted file mode 100644
index 2857cfcc..00000000
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCService.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package no.nordicsemi.android.csc.service
-
-import android.bluetooth.BluetoothDevice
-import androidx.lifecycle.lifecycleScope
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import no.nordicsemi.android.csc.data.CSCData
-import no.nordicsemi.android.service.ForegroundBleService
-import no.nordicsemi.android.service.LoggableBleManager
-import javax.inject.Inject
-
-@AndroidEntryPoint
-internal class CSCService : ForegroundBleService(), CSCManagerCallbacks {
-
- private var data = CSCData()
-
- @Inject
- lateinit var localBroadcast: CSCDataReadBroadcast
-
- override val manager: CSCManager by lazy {
- CSCManager(this).apply {
- setGattCallbacks(this@CSCService)
- }
- }
-
- override fun initializeManager(): LoggableBleManager {
- return manager
- }
-
- override fun onCreate() {
- super.onCreate()
-
- localBroadcast.wheelSize.onEach {
- manager.setWheelSize(it)
- }.launchIn(lifecycleScope)
- }
-
- override fun onDistanceChanged(
- device: BluetoothDevice,
- totalDistance: Float,
- distance: Float,
- speed: Float
- ) {
- localBroadcast.offer(data.copy(speed = speed, distance = distance, totalDistance = totalDistance))
- }
-
- override fun onCrankDataChanged(
- device: BluetoothDevice,
- crankCadence: Float,
- gearRatio: Float
- ) {
- localBroadcast.offer(data.copy(cadence = crankCadence.toInt(), gearRatio = gearRatio))
- }
-
- override fun onBatteryLevelChanged(device: BluetoothDevice, batteryLevel: Int) {
- localBroadcast.offer(data.copy(batteryLevel = batteryLevel))
- }
-}
\ No newline at end of file
diff --git a/feature_gls/src/main/AndroidManifest.xml b/feature_gls/src/main/AndroidManifest.xml
deleted file mode 100644
index 37b4a974..00000000
--- a/feature_gls/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/data/GLSData.kt b/feature_gls/src/main/java/no/nordicsemi/android/gls/data/GLSData.kt
deleted file mode 100644
index 7b429a39..00000000
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/data/GLSData.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package no.nordicsemi.android.gls.data
-
-internal data class GLSData(
- val record: List = emptyList(),
- val batteryLevel: Int = 0,
- val requestStatus: RequestStatus = RequestStatus.IDLE,
- val isDeviceBonded: Boolean = false
-)
-
-internal enum class RequestStatus {
- IDLE, PENDING, SUCCESS, ABORTED, FAILED, NOT_SUPPORTED
-}
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSScreenViewEvent.kt b/feature_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSScreenViewEvent.kt
deleted file mode 100644
index e1ef521a..00000000
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSScreenViewEvent.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package no.nordicsemi.android.gls.viewmodel
-
-sealed class GLSScreenViewEvent
-
-object DisconnectEvent : GLSScreenViewEvent()
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt b/feature_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt
deleted file mode 100644
index 11acfcb7..00000000
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package no.nordicsemi.android.gls.viewmodel
-
-import androidx.lifecycle.ViewModel
-import dagger.hilt.android.lifecycle.HiltViewModel
-import no.nordicsemi.android.gls.repository.GLSManager
-import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
-import javax.inject.Inject
-
-@HiltViewModel
-internal class GLSViewModel @Inject constructor(
- private val glsManager: GLSManager,
- private val deviceHolder: SelectedBluetoothDeviceHolder
-) : ViewModel() {
-
- val state = glsManager.data
-
- fun bondDevice() {
- if (deviceHolder.isDeviceBonded()) {
- deviceHolder.bondDevice()
- } else {
- //start work
- }
- }
-}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSDataBroadcast.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSDataBroadcast.kt
deleted file mode 100644
index ac426907..00000000
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSDataBroadcast.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package no.nordicsemi.android.hrs.service
-
-import no.nordicsemi.android.hrs.data.HRSData
-import no.nordicsemi.android.service.BluetoothDataReadBroadcast
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-internal class HRSDataBroadcast @Inject constructor() : BluetoothDataReadBroadcast()
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManagerCallbacks.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManagerCallbacks.kt
deleted file mode 100644
index dbb1cb6a..00000000
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManagerCallbacks.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2015, 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.hrs.service
-
-import no.nordicsemi.android.ble.common.profile.hr.BodySensorLocationCallback
-import no.nordicsemi.android.ble.common.profile.hr.HeartRateMeasurementCallback
-import no.nordicsemi.android.service.BatteryManagerCallbacks
-
-interface HRSManagerCallbacks
- : BatteryManagerCallbacks, BodySensorLocationCallback, HeartRateMeasurementCallback
\ No newline at end of file
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt
deleted file mode 100644
index cc07781d..00000000
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package no.nordicsemi.android.hrs.service
-
-import android.bluetooth.BluetoothDevice
-import dagger.hilt.android.AndroidEntryPoint
-import no.nordicsemi.android.ble.BleManagerCallbacks
-import no.nordicsemi.android.hrs.data.HRSData
-import no.nordicsemi.android.service.ForegroundBleService
-import no.nordicsemi.android.service.LoggableBleManager
-import javax.inject.Inject
-
-@AndroidEntryPoint
-internal class HRSService : ForegroundBleService(), HRSManagerCallbacks {
-
- private var data = HRSData()
- private val points = mutableListOf()
-
- @Inject
- lateinit var localBroadcast: HRSDataBroadcast
-
- override val manager: HRSManager by lazy {
- HRSManager(this).apply {
- setGattCallbacks(this@HRSService)
- }
- }
-
- override fun initializeManager(): LoggableBleManager {
- return manager
- }
-
- override fun onBatteryLevelChanged(device: BluetoothDevice, batteryLevel: Int) {
- sendNewData(data.copy(batteryLevel = batteryLevel))
- }
-
- override fun onBodySensorLocationReceived(device: BluetoothDevice, sensorLocation: Int) {
- sendNewData(data.copy(sensorLocation = sensorLocation))
- }
-
- override fun onHeartRateMeasurementReceived(
- device: BluetoothDevice,
- heartRate: Int,
- contactDetected: Boolean?,
- energyExpanded: Int?,
- rrIntervals: MutableList?
- ) {
- points.add(heartRate)
- sendNewData(data.copy(heartRates = points))
- }
-
- private fun sendNewData(newData: HRSData) {
- data = newData
- localBroadcast.offer(newData)
- }
-}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt
deleted file mode 100644
index 3437e7cd..00000000
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package no.nordicsemi.android.hrs.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.withContext
-import no.nordicsemi.android.hrs.data.HRSData
-import no.nordicsemi.android.hrs.service.HRSDataBroadcast
-import no.nordicsemi.android.hrs.view.DisconnectEvent
-import no.nordicsemi.android.hrs.view.HRSScreenViewEvent
-import javax.inject.Inject
-
-@HiltViewModel
-internal class HRSViewModel @Inject constructor(
- private val localBroadcast: HRSDataBroadcast
-) : ViewModel() {
-
- val state = MutableStateFlow(HRSViewState())
-
- init {
- localBroadcast.events.onEach {
- withContext(Dispatchers.Main) { consumeEvent(it) }
- }.launchIn(viewModelScope)
- }
-
- private fun consumeEvent(event: HRSData) {
- state.value = state.value.copy(
- points = event.heartRates,
- batteryLevel = event.batteryLevel,
- sensorLocation = event.sensorLocation
- )
- }
-
- fun onEvent(event: HRSScreenViewEvent) {
- (event as? DisconnectEvent)?.let {
- onDisconnectButtonClick()
- }
- }
-
- private fun onDisconnectButtonClick() {
- state.tryEmit(state.value.copy(isScreenActive = false))
- }
-}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewState.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewState.kt
deleted file mode 100644
index 75ef9d94..00000000
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewState.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package no.nordicsemi.android.hrs.viewmodel
-
-data class HRSViewState(
- val points: List = listOf(1, 2, 3),
- val batteryLevel: Int = 0,
- val sensorLocation: Int = 0,
- val isScreenActive: Boolean = true
-)
diff --git a/feature_hrs/src/main/res/drawable/fade_red.xml b/feature_hrs/src/main/res/drawable/fade_red.xml
deleted file mode 100644
index 54ac10ba..00000000
--- a/feature_hrs/src/main/res/drawable/fade_red.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt
deleted file mode 100644
index bd7a5c19..00000000
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package no.nordicsemi.android.hts.data
-
-internal data class HTSData(
- val heartRates: List = emptyList(),
- val temperature: Temperature = Temperature.CELSIUS,
- val batteryLevel: Int = 0,
- val sensorLocation: Int = 0,
- val isScreenActive: Boolean = true
-) {
-
- fun displayTemperature() {
- val value = when (temperature) {
- Temperature.CELSIUS -> TODO()
- Temperature.FAHRENHEIT -> TODO()
- Temperature.KELVIN -> TODO()
- }
- }
-}
-
-internal enum class Temperature {
- CELSIUS, FAHRENHEIT, KELVIN
-}
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDataBroadcast.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDataBroadcast.kt
deleted file mode 100644
index feaafcbd..00000000
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDataBroadcast.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package no.nordicsemi.android.hts.service
-
-import no.nordicsemi.android.hts.data.HTSData
-import no.nordicsemi.android.service.BluetoothDataReadBroadcast
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-internal class HTSDataBroadcast @Inject constructor() : BluetoothDataReadBroadcast()
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManagerCallbacks.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManagerCallbacks.kt
deleted file mode 100644
index 821cf28c..00000000
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManagerCallbacks.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2015, 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.hts.service
-
-import no.nordicsemi.android.ble.common.profile.ht.TemperatureMeasurementCallback
-import no.nordicsemi.android.service.BatteryManagerCallbacks
-
-/**
- * Interface [HTSManagerCallbacks] must be implemented by [HTActivity] in order
- * to receive callbacks from [HTSManager].
- */
-interface HTSManagerCallbacks : BatteryManagerCallbacks, TemperatureMeasurementCallback
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSService.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSService.kt
deleted file mode 100644
index 8fd638a4..00000000
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSService.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package no.nordicsemi.android.hts.service
-
-import android.bluetooth.BluetoothDevice
-import dagger.hilt.android.AndroidEntryPoint
-import no.nordicsemi.android.ble.BleManagerCallbacks
-import no.nordicsemi.android.hts.data.HTSData
-import no.nordicsemi.android.service.ForegroundBleService
-import no.nordicsemi.android.service.LoggableBleManager
-import java.util.*
-import javax.inject.Inject
-
-@AndroidEntryPoint
-internal class HTSService : ForegroundBleService(), HTSManagerCallbacks {
-
- private var data = HTSData()
- private val points = mutableListOf()
-
- @Inject
- lateinit var localBroadcast: HTSDataBroadcast
-
- override val manager: HTSManager by lazy {
- HTSManager(this).apply {
- setGattCallbacks(this@HTSService)
- }
- }
-
- override fun initializeManager(): LoggableBleManager {
- return manager
- }
-
- override fun onBatteryLevelChanged(device: BluetoothDevice, batteryLevel: Int) {
- sendNewData(data.copy(batteryLevel = batteryLevel))
- }
-
- override fun onTemperatureMeasurementReceived(
- device: BluetoothDevice,
- temperature: Float,
- unit: Int,
- calendar: Calendar?,
- type: Int?
- ) {
- TODO("Not yet implemented")
- }
-
- private fun sendNewData(newData: HTSData) {
- data = newData
- localBroadcast.offer(newData)
- }
-}
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt
deleted file mode 100644
index 8c826645..00000000
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package no.nordicsemi.android.hts.view
-
-sealed class HTSScreenViewEvent
-
-object DisconnectEvent : HTSScreenViewEvent()
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HRSViewModel.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HRSViewModel.kt
deleted file mode 100644
index 918e938d..00000000
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HRSViewModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package no.nordicsemi.android.hts.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.withContext
-import no.nordicsemi.android.hts.data.HTSData
-import no.nordicsemi.android.hts.service.HTSDataBroadcast
-import no.nordicsemi.android.hts.view.DisconnectEvent
-import no.nordicsemi.android.hts.view.HTSScreenViewEvent
-import javax.inject.Inject
-
-@HiltViewModel
-internal class HTSViewModel @Inject constructor(
- private val localBroadcast: HTSDataBroadcast
-) : ViewModel() {
-
- val state = MutableStateFlow(HTSData())
-
- init {
- localBroadcast.events.onEach {
- withContext(Dispatchers.Main) { consumeEvent(it) }
- }.launchIn(viewModelScope)
- }
-
- private fun consumeEvent(event: HTSData) {
- state.value = state.value.copy(
-
- batteryLevel = event.batteryLevel,
- sensorLocation = event.sensorLocation
- )
- }
-
- fun onEvent(event: HTSScreenViewEvent) {
- (event as? DisconnectEvent)?.let {
- onDisconnectButtonClick()
- }
- }
-
- private fun onDisconnectButtonClick() {
- state.tryEmit(state.value.copy(isScreenActive = false))
- }
-}
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt
index fb67f14e..e93cecd1 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt
@@ -6,6 +6,7 @@ import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.util.Log
import androidx.annotation.IntRange
+import no.nordicsemi.android.ble.BleManager
import no.nordicsemi.android.ble.callback.DataReceivedCallback
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelDataCallback
import no.nordicsemi.android.ble.data.Data
@@ -18,17 +19,10 @@ import java.util.*
* @param The profile callbacks type.
* @see BleManager
*/
-abstract class BatteryManager(context: Context) : LoggableBleManager(context) {
+abstract class BatteryManager(context: Context) : BleManager(context) {
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
- /**
- * Returns the last received Battery Level value.
- * The value is set to null when the device disconnects.
- * @return Battery Level value, in percent.
- */
- /** Last received Battery Level value. */
- var batteryLevel: Int? = null
- private set
+
private val batteryLevelDataCallback: DataReceivedCallback =
object : BatteryLevelDataCallback() {
override fun onBatteryLevelChanged(
@@ -36,8 +30,7 @@ abstract class BatteryManager(context: Context) :
@IntRange(from = 0, to = 100) batteryLevel: Int
) {
log(LogContract.Log.Level.APPLICATION, "Battery Level received: $batteryLevel%")
- this@BatteryManager.batteryLevel = batteryLevel
- mCallbacks?.onBatteryLevelChanged(device, batteryLevel)
+ onBatteryLevelChanged(batteryLevel)
}
override fun onInvalidDataReceived(device: BluetoothDevice, data: Data) {
@@ -45,15 +38,14 @@ abstract class BatteryManager(context: Context) :
}
}
+ protected abstract fun onBatteryLevelChanged(batteryLevel: Int)
+
fun readBatteryLevelCharacteristic() {
if (isConnected) {
readCharacteristic(batteryLevelCharacteristic)
.with(batteryLevelDataCallback)
.fail { device: BluetoothDevice?, status: Int ->
- log(
- Log.WARN,
- "Battery Level characteristic not found"
- )
+ log(Log.WARN, "Battery Level characteristic not found")
}
.enqueue()
}
@@ -66,32 +58,10 @@ abstract class BatteryManager(context: Context) :
.with(batteryLevelDataCallback)
enableNotifications(batteryLevelCharacteristic)
.done { device: BluetoothDevice? ->
- log(
- Log.INFO,
- "Battery Level notifications enabled"
- )
+ log(Log.INFO, "Battery Level notifications enabled")
}
.fail { device: BluetoothDevice?, status: Int ->
- log(
- Log.WARN,
- "Battery Level characteristic not found"
- )
- }
- .enqueue()
- }
- }
-
- /**
- * Disables Battery Level notifications on the Server.
- */
- fun disableBatteryLevelCharacteristicNotifications() {
- if (isConnected) {
- disableNotifications(batteryLevelCharacteristic)
- .done { device: BluetoothDevice? ->
- log(
- Log.INFO,
- "Battery Level notifications disabled"
- )
+ log(Log.WARN, "Battery Level characteristic not found")
}
.enqueue()
}
@@ -106,16 +76,14 @@ abstract class BatteryManager(context: Context) :
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(BATTERY_SERVICE_UUID)
if (service != null) {
- batteryLevelCharacteristic = service.getCharacteristic(
- BATTERY_LEVEL_CHARACTERISTIC_UUID
- )
+ batteryLevelCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
}
return batteryLevelCharacteristic != null
}
override fun onDeviceDisconnected() {
batteryLevelCharacteristic = null
- batteryLevel = null
+ onBatteryLevelChanged(0)
}
}
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManagerCallbacks.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManagerCallbacks.kt
deleted file mode 100644
index feb51fcd..00000000
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManagerCallbacks.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package no.nordicsemi.android.service
-
-import no.nordicsemi.android.ble.BleManagerCallbacks
-import no.nordicsemi.android.ble.common.profile.battery.BatteryLevelCallback
-
-interface BatteryManagerCallbacks : BleManagerCallbacks, BatteryLevelCallback
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt
index 6f5440d8..a7aa15d5 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt
@@ -21,33 +21,21 @@
*/
package no.nordicsemi.android.service
-import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothGatt
-import android.content.BroadcastReceiver
-import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
-import android.net.Uri
-import android.os.Binder
import android.os.Handler
-import android.os.IBinder
-import android.util.Log
import android.widget.Toast
-import androidx.annotation.StringRes
import androidx.lifecycle.LifecycleService
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint
-import no.nordicsemi.android.ble.BleManagerCallbacks
-import no.nordicsemi.android.ble.utils.ILogger
+import no.nordicsemi.android.ble.BleManager
import no.nordicsemi.android.log.ILogSession
import no.nordicsemi.android.log.Logger
import javax.inject.Inject
@AndroidEntryPoint
-abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
+abstract class BleProfileService : LifecycleService() {
- private var bleManager: LoggableBleManager? = null
+ protected abstract val manager: BleManager
@Inject
lateinit var bluetoothDeviceHolder: SelectedBluetoothDeviceHolder
@@ -56,9 +44,8 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
* Returns a handler that is created in onCreate().
* The handler may be used to postpone execution of some operations or to run them in UI thread.
*/
- protected var handler: Handler? = null
- private set
- protected var bound = false
+ private var handler: Handler? = null
+
private var activityIsChangingConfiguration = false
/**
@@ -66,256 +53,45 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
*
* @return bluetooth device
*/
- protected val bluetoothDevice: BluetoothDevice by lazy {
+ private val bluetoothDevice: BluetoothDevice by lazy {
bluetoothDeviceHolder.device ?: throw IllegalArgumentException(
- "No device address at EXTRA_DEVICE_ADDRESS key"
+ "No device associated with the application."
)
}
- /**
- * Returns the device name
- *
- * @return the device name
- */
- protected var deviceName: String? = null
- private set
-
/**
* Returns the log session that can be used to append log entries. The method returns `null` if the nRF Logger app was not installed. It is safe to use logger when
* [.onServiceStarted] has been called.
*
* @return the log session
*/
- protected var logSession: ILogSession? = null
+ private var logSession: ILogSession? = null
private set
- private val bluetoothStateBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
- val logger: ILogger = binder
- val stateString =
- "[Broadcast] Action received: " + BluetoothAdapter.ACTION_STATE_CHANGED + ", state changed to " + state2String(
- state
- )
- logger.log(Log.DEBUG, stateString)
- when (state) {
- BluetoothAdapter.STATE_ON -> onBluetoothEnabled()
- BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_OFF -> onBluetoothDisabled()
- }
- }
-
- private fun state2String(state: Int): String {
- return when (state) {
- BluetoothAdapter.STATE_TURNING_ON -> "TURNING ON"
- BluetoothAdapter.STATE_ON -> "ON"
- BluetoothAdapter.STATE_TURNING_OFF -> "TURNING OFF"
- BluetoothAdapter.STATE_OFF -> "OFF"
- else -> "UNKNOWN ($state)"
- }
- }
- }
-
- inner class LocalBinder : Binder(), ILogger {
- /**
- * Disconnects from the sensor.
- */
- fun disconnect() {
- val state = bleManager!!.connectionState
- if (state == BluetoothGatt.STATE_DISCONNECTED || state == BluetoothGatt.STATE_DISCONNECTING) {
- bleManager!!.close()
- onDeviceDisconnected(bluetoothDevice!!)
- return
- }
- bleManager!!.disconnect().enqueue()
- }
-
- /**
- * Sets whether the bound activity if changing configuration or not.
- * If `false`, we will turn off battery level notifications in onUnbind(..) method below.
- *
- * @param changing true if the bound activity is finishing
- */
- fun setActivityIsChangingConfiguration(changing: Boolean) {
- activityIsChangingConfiguration = changing
- }
-
- /**
- * Returns the device address
- *
- * @return device address
- */
- val deviceAddress: String
- get() = bluetoothDevice!!.address
-
- /**
- * Returns the device name
- *
- * @return the device name
- */
- fun getDeviceName(): String? {
- return deviceName
- }
-
- /**
- * Returns the Bluetooth device
- *
- * @return the Bluetooth device
- */
- fun getBluetoothDevice(): BluetoothDevice? {
- return bluetoothDevice
- }
-
- /**
- * Returns `true` if the device is connected to the sensor.
- *
- * @return `true` if device is connected to the sensor, `false` otherwise
- */
- val isConnected: Boolean
- get() = bleManager!!.isConnected
-
- /**
- * Returns the connection state of given device.
- *
- * @return the connection state, as in [BleManager.getConnectionState].
- */
- val connectionState: Int
- get() = bleManager!!.connectionState
-
- /**
- * Returns the log session that can be used to append log entries.
- * The log session is created when the service is being created.
- * The method returns `null` if the nRF Logger app was not installed.
- *
- * @return the log session
- */
- fun getLogSession(): ILogSession? {
- return logSession
- }
-
- override fun log(level: Int, message: String) {
- Logger.log(logSession, level, message)
- }
-
- override fun log(level: Int, @StringRes messageRes: Int, vararg params: Any) {
- Logger.log(logSession, level, messageRes, *params)
- }
- }// default implementation returns the basic binder. You can overwrite the LocalBinder with your own, wider implementation
-
- /**
- * Returns the binder implementation. This must return class implementing the additional manager interface that may be used in the bound activity.
- *
- * @return the service binder
- */
- protected val binder: LocalBinder
- protected get() =// default implementation returns the basic binder. You can overwrite the LocalBinder with your own, wider implementation
- LocalBinder()
-
- override fun onBind(intent: Intent): IBinder? {
- super.onBind(intent)
- bound = true
- return binder
- }
-
- override fun onRebind(intent: Intent) {
- bound = true
- if (!activityIsChangingConfiguration) onRebind()
- }
-
- /**
- * Called when the activity has rebound to the service after being recreated.
- * This method is not called when the activity was killed to be recreated when the phone orientation changed
- * if prior to being killed called [LocalBinder.setActivityIsChangingConfiguration] with parameter true.
- */
- protected open fun onRebind() {
- // empty default implementation
- }
-
- override fun onUnbind(intent: Intent): Boolean {
- bound = false
- if (!activityIsChangingConfiguration) onUnbind()
-
- // We want the onRebind method be called if anything else binds to it again
- return true
- }
-
- /**
- * Called when the activity has unbound from the service before being finished.
- * This method is not called when the activity is killed to be recreated when the phone orientation changed.
- */
- protected open fun onUnbind() {
- // empty default implementation
- }
override fun onCreate() {
super.onCreate()
handler = Handler()
-
- // Initialize the manager
- bleManager = initializeManager()
-
- // Register broadcast receivers
- registerReceiver(
- bluetoothStateBroadcastReceiver,
- IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
- )
-
- // Service has now been created
- onServiceCreated()
-
- // Call onBluetoothEnabled if Bluetooth enabled
- val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
- if (bluetoothAdapter.isEnabled) {
- onBluetoothEnabled()
- }
}
- /**
- * Called when the service has been created, before the [.onBluetoothEnabled] is called.
- */
- protected fun onServiceCreated() {
- // empty default implementation
- }
-
- /**
- * Initializes the Ble Manager responsible for connecting to a single device.
- *
- * @return a new BleManager object
- */
- protected abstract fun initializeManager(): LoggableBleManager
-
/**
* This method returns whether autoConnect option should be used.
*
* @return true to use autoConnect feature, false (default) otherwise.
*/
- protected fun shouldAutoConnect(): Boolean {
+ private fun shouldAutoConnect(): Boolean {
return false
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
- val logUri = intent?.getParcelableExtra(EXTRA_LOG_URI)
- logSession = Logger.openSession(applicationContext, logUri)
- deviceName = intent?.getStringExtra(EXTRA_DEVICE_NAME)
- Logger.i(logSession, "Service started")
- val adapter = BluetoothAdapter.getDefaultAdapter()
- bleManager!!.setLogger(logSession)
- onServiceStarted()
- bleManager!!.connect(bluetoothDevice)
+ manager.connect(bluetoothDevice)
.useAutoConnect(shouldAutoConnect())
.retry(3, 100)
.enqueue()
return START_REDELIVER_INTENT
}
- /**
- * Called when the service has been started. The device name and address are set.
- * The BLE Manager will try to connect to the device after this method finishes.
- */
- protected fun onServiceStarted() {
- // empty default implementation
- }
-
override fun onTaskRemoved(rootIntent: Intent) {
super.onTaskRemoved(rootIntent)
// This method is called when user removed the app from Recents.
@@ -326,58 +102,15 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
override fun onDestroy() {
super.onDestroy()
- // Unregister broadcast receivers
- unregisterReceiver(bluetoothStateBroadcastReceiver)
// shutdown the manager
- bleManager!!.close()
+ manager.close()
Logger.i(logSession, "Service destroyed")
- bleManager = null
bluetoothDeviceHolder.forgetDevice()
- deviceName = null
logSession = null
handler = null
}
- /**
- * Method called when Bluetooth Adapter has been disabled.
- */
- protected fun onBluetoothDisabled() {
- // empty default implementation
- }
-
- /**
- * This method is called when Bluetooth Adapter has been enabled and
- * after the service was created if Bluetooth Adapter was enabled at that moment.
- * This method could initialize all Bluetooth related features, for example open the GATT server.
- */
- protected fun onBluetoothEnabled() {
- // empty default implementation
- }
-
- override fun onDeviceConnecting(device: BluetoothDevice) {
- val broadcast = Intent(BROADCAST_CONNECTION_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTING)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onDeviceConnected(device: BluetoothDevice) {
- val broadcast = Intent(BROADCAST_CONNECTION_STATE)
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_CONNECTED)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_DEVICE_NAME, deviceName)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onDeviceDisconnecting(device: BluetoothDevice) {
- // Notify user about changing the state to DISCONNECTING
- val broadcast = Intent(BROADCAST_CONNECTION_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
/**
* This method should return false if the service needs to do some asynchronous work after if has disconnected from the device.
* In that case the [.stopService] method must be called when done.
@@ -388,102 +121,19 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
return true
}
- override fun onDeviceDisconnected(device: BluetoothDevice) {
- // Note 1: Do not use the device argument here unless you change calling onDeviceDisconnected from the binder above
-
- // Note 2: if BleManager#shouldAutoConnect() for this device returned true, this callback will be
- // invoked ONLY when user requested disconnection (using Disconnect button). If the device
- // disconnects due to a link loss, the onLinkLossOccurred(BluetoothDevice) method will be called instead.
- val broadcast = Intent(BROADCAST_CONNECTION_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTED)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- if (stopWhenDisconnected()) stopService()
- }
-
- protected fun stopService() {
+ private fun stopService() {
// user requested disconnection. We must stop the service
Logger.v(logSession, "Stopping service...")
stopSelf()
}
- override fun onLinkLossOccurred(device: BluetoothDevice) {
- val broadcast = Intent(BROADCAST_CONNECTION_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_LINK_LOSS)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onServicesDiscovered(device: BluetoothDevice, optionalServicesFound: Boolean) {
- val broadcast = Intent(BROADCAST_SERVICES_DISCOVERED)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true)
- broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onDeviceReady(device: BluetoothDevice) {
- val broadcast = Intent(BROADCAST_DEVICE_READY)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onDeviceNotSupported(device: BluetoothDevice) {
- val broadcast = Intent(BROADCAST_SERVICES_DISCOVERED)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false)
- broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
-
- // no need for disconnecting, it will be disconnected by the manager automatically
- }
-
- override fun onBatteryValueReceived(device: BluetoothDevice, value: Int) {
- val broadcast = Intent(BROADCAST_BATTERY_LEVEL)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_BATTERY_LEVEL, value)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onBondingRequired(device: BluetoothDevice) {
- showToast(R.string.csc_bonding)
- val broadcast = Intent(BROADCAST_BOND_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onBonded(device: BluetoothDevice) {
- showToast(R.string.csc_bonded)
- val broadcast = Intent(BROADCAST_BOND_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onBondingFailed(device: BluetoothDevice) {
- showToast(R.string.csc_bonding_failed)
- val broadcast = Intent(BROADCAST_BOND_STATE)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
- override fun onError(device: BluetoothDevice, message: String, errorCode: Int) {
- val broadcast = Intent(BROADCAST_ERROR)
- broadcast.putExtra(EXTRA_DEVICE, bluetoothDevice)
- broadcast.putExtra(EXTRA_ERROR_MESSAGE, message)
- broadcast.putExtra(EXTRA_ERROR_CODE, errorCode)
- LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
- }
-
/**
* Shows a message as a Toast notification. This method is thread safe, you can call it from any thread
*
* @param messageResId an resource id of the message to be shown
*/
protected fun showToast(messageResId: Int) {
- handler!!.post {
+ handler?.post {
Toast.makeText(this@BleProfileService, messageResId, Toast.LENGTH_SHORT).show()
}
}
@@ -494,7 +144,7 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
* @param message a message to be shown
*/
protected fun showToast(message: String?) {
- handler!!.post {
+ handler?.post {
Toast.makeText(this@BleProfileService, message, Toast.LENGTH_SHORT).show()
}
}
@@ -505,7 +155,7 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
* @return device address
*/
protected val deviceAddress: String
- protected get() = bluetoothDevice!!.address
+ get() = bluetoothDevice.address
/**
* Returns `true` if the device is connected to the sensor.
@@ -513,41 +163,5 @@ abstract class BleProfileService : LifecycleService(), BleManagerCallbacks {
* @return `true` if device is connected to the sensor, `false` otherwise
*/
protected val isConnected: Boolean
- protected get() = bleManager != null && bleManager!!.isConnected
-
- companion object {
- private const val TAG = "BleProfileService"
- const val BROADCAST_CONNECTION_STATE =
- "no.nordicsemi.android.nrftoolbox.BROADCAST_CONNECTION_STATE"
- const val BROADCAST_SERVICES_DISCOVERED =
- "no.nordicsemi.android.nrftoolbox.BROADCAST_SERVICES_DISCOVERED"
- const val BROADCAST_DEVICE_READY = "no.nordicsemi.android.nrftoolbox.DEVICE_READY"
- const val BROADCAST_BOND_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_BOND_STATE"
-
- @Deprecated("")
- val BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL"
- const val BROADCAST_ERROR = "no.nordicsemi.android.nrftoolbox.BROADCAST_ERROR"
-
- /**
- * The key for the device name that is returned in [.BROADCAST_CONNECTION_STATE] with state [.STATE_CONNECTED].
- */
- const val EXTRA_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE_NAME"
- const val EXTRA_DEVICE = "no.nordicsemi.android.nrftoolbox.EXTRA_DEVICE"
- const val EXTRA_LOG_URI = "no.nordicsemi.android.nrftoolbox.EXTRA_LOG_URI"
- const val EXTRA_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_CONNECTION_STATE"
- const val EXTRA_BOND_STATE = "no.nordicsemi.android.nrftoolbox.EXTRA_BOND_STATE"
- const val EXTRA_SERVICE_PRIMARY = "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_PRIMARY"
- const val EXTRA_SERVICE_SECONDARY =
- "no.nordicsemi.android.nrftoolbox.EXTRA_SERVICE_SECONDARY"
-
- @Deprecated("")
- val EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL"
- const val EXTRA_ERROR_MESSAGE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_MESSAGE"
- const val EXTRA_ERROR_CODE = "no.nordicsemi.android.nrftoolbox.EXTRA_ERROR_CODE"
- const val STATE_LINK_LOSS = -1
- const val STATE_DISCONNECTED = 0
- const val STATE_CONNECTED = 1
- const val STATE_CONNECTING = 2
- const val STATE_DISCONNECTING = 3
- }
-}
\ No newline at end of file
+ get() = manager.isConnected
+}
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BluetoothDataReadBroadcast.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BluetoothDataReadBroadcast.kt
deleted file mode 100644
index 3ea5baef..00000000
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BluetoothDataReadBroadcast.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package no.nordicsemi.android.service
-
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-
-abstract class BluetoothDataReadBroadcast {
-
- private val _event = MutableSharedFlow(
- replay = 1,
- extraBufferCapacity = 1,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
- val events: SharedFlow = _event
-
- fun offer(newEvent: T) {
- _event.tryEmit(newEvent)
- }
-}
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/ForegroundBleService.kt b/lib_service/src/main/java/no/nordicsemi/android/service/ForegroundBleService.kt
index 5422ace3..6f4de89b 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/ForegroundBleService.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/ForegroundBleService.kt
@@ -31,9 +31,7 @@ import androidx.core.app.NotificationCompat
private const val CHANNEL_ID = "FOREGROUND_BLE_SERVICE"
-abstract class ForegroundBleService> : BleProfileService() {
-
- protected abstract val manager: T
+abstract class ForegroundBleService : BleProfileService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val result = super.onStartCommand(intent, flags, startId)
@@ -48,22 +46,6 @@ abstract class ForegroundBleService the callbacks class.
- */
-abstract class LoggableBleManager(context: Context) : LegacyBleManager(context) {
- private var logSession: ILogSession? = null
-
- /**
- * Sets the log session to log into.
- *
- * @param session nRF Logger log session to log inti, or null, if nRF Logger is not installed.
- */
- fun setLogger(session: ILogSession?) {
- logSession = session
- }
-
- override fun log(priority: Int, message: String) {
- Logger.log(logSession, LogContract.Log.Level.fromPriority(priority), message)
- Log.println(priority, "BleManager", message)
- }
-}
\ No newline at end of file
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt b/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt
index 645ae68e..ddefcae2 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/SelectedBluetoothDeviceHolder.kt
@@ -16,7 +16,7 @@ class SelectedBluetoothDeviceHolder constructor(
return deviceManager.associations.firstOrNull()?.let { bluetoothAdapter?.getRemoteDevice(it) }
}
- fun isDeviceBonded(): Boolean {
+ fun isBondingRequired(): Boolean {
return device?.bondState == BluetoothDevice.BOND_NONE
}
fun bondDevice() {
diff --git a/lib_theme/src/main/AndroidManifest.xml b/lib_theme/src/main/AndroidManifest.xml
index a251fc74..423e376a 100644
--- a/lib_theme/src/main/AndroidManifest.xml
+++ b/lib_theme/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-
+
\ No newline at end of file
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/Color.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/Color.kt
index 82bbd901..d1cb0537 100644
--- a/lib_theme/src/main/java/no/nordicsemi/android/theme/Color.kt
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/Color.kt
@@ -28,16 +28,17 @@ object NordicColors {
val TableViewBackground = NeutralColor(Color(0xFFF2F2F6))
val TableViewSeparator = NeutralColor(Color(0xFFD2D2D6))
- val Primary = ThemedColor(Color(0xFF00A9CE), Color(0xFF212121))
- val PrimaryVariant = ThemedColor(Color(0xFF008CD2), Color.Black)
- val Secondary = ThemedColor(Color(0xFF00A9CE), Color(0xFF008CD2))
- val SecondaryVariant = ThemedColor(Color(0xFF008CD2), Color(0xFF008CD2))
+ val Primary = ThemedColor(Color(0xFF00A9CE), Color(0xFF00A9CE))
+ val PrimaryVariant = ThemedColor(Color(0xFF008CD2), Color(0xFF00A9CE))
+ val Secondary = ThemedColor(Color(0xFF00A9CE), Color(0xFF00A9CE))
+ val SecondaryVariant = ThemedColor(Color(0xFF008CD2), Color(0xFF00A9CE))
val OnPrimary = ThemedColor(Color.White, Color.White)
val OnSecondary = ThemedColor(Color.White, Color.White)
val OnBackground = ThemedColor(Color.Black, Color.White)
val OnSurface = ThemedColor(Color.Black, Color.White)
- val Background = ThemedColor(Color(0xFFDADADA), Color.Black)
- val Surface = ThemedColor(Color(0xFFDADADA), Color.Black)
+ val ItemHighlight = ThemedColor(Color.White, Color(0xFF1E1E1E))
+ val Background = ThemedColor(Color(0xFFF5F5F5), Color(0xFF121212))
+ val Surface = ThemedColor(Color(0xFFF5F5F5), Color(0xFF121212))
}
sealed class NordicColor {
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/Theme.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/Theme.kt
index f8fa424e..7de953b0 100644
--- a/lib_theme/src/main/java/no/nordicsemi/android/theme/Theme.kt
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/Theme.kt
@@ -7,7 +7,7 @@ import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
@Composable
-fun TestTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
+fun TestTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val darkColorPalette = darkColors(
primary = NordicColors.Primary.value(),
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/BatteryLevelView.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/BatteryLevelView.kt
index 0a1fbd52..7b2603ee 100644
--- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/BatteryLevelView.kt
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/BatteryLevelView.kt
@@ -1,28 +1,15 @@
package no.nordicsemi.android.theme.view
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Card
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import no.nordicsemi.android.theme.NordicColors
import no.nordicsemi.android.theme.R
@Composable
fun BatteryLevelView(batteryLevel: Int) {
- Card(
- backgroundColor = NordicColors.NordicGray4.value(),
- shape = RoundedCornerShape(10.dp),
- elevation = 0.dp
- ) {
- Box(modifier = Modifier.padding(16.dp)) {
- KeyValueField(
- stringResource(id = R.string.field_battery),
- "$batteryLevel%"
- )
- }
+ ScreenSection {
+ KeyValueField(
+ stringResource(id = R.string.field_battery),
+ "$batteryLevel%"
+ )
}
}
\ No newline at end of file
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/KeyValueField.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/KeyValueField.kt
index 08e6a58a..4083c1de 100644
--- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/KeyValueField.kt
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/KeyValueField.kt
@@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier
import no.nordicsemi.android.theme.NordicColors
@Composable
- fun KeyValueField(key: String, value: String) {
+fun KeyValueField(key: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SensorRecordCard.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SensorRecordCard.kt
index c5c02880..88c5bcc4 100644
--- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SensorRecordCard.kt
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SensorRecordCard.kt
@@ -1,18 +1,23 @@
package no.nordicsemi.android.theme.view
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.NordicColors
@Composable
-fun SensorRecordCard(content: @Composable () -> Unit) {
+fun ScreenSection(content: @Composable () -> Unit) {
Card(
- backgroundColor = NordicColors.NordicGray4.value(),
- shape = RoundedCornerShape(10.dp),
+ backgroundColor = NordicColors.ItemHighlight.value(),
+ shape = RoundedCornerShape(4.dp),
elevation = 0.dp
) {
- content()
+ Box(modifier = Modifier.padding(16.dp)) {
+ content()
+ }
}
}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SpeedUnitRadioGroup.kt
similarity index 59%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt
rename to lib_theme/src/main/java/no/nordicsemi/android/theme/view/SpeedUnitRadioGroup.kt
index 964fe107..c43d94e0 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SpeedUnitRadioGroup.kt
@@ -1,4 +1,4 @@
-package no.nordicsemi.android.csc.view
+package no.nordicsemi.android.theme.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
@@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
-internal fun SpeedUnitRadioGroup(
- currentItem: RadioGroupItem,
- items: List,
- onEvent: (RadioGroupItem) -> Unit
+fun SpeedUnitRadioGroup(
+ currentItem: T,
+ items: List>,
+ onEvent: (RadioGroupItem) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -28,14 +28,14 @@ internal fun SpeedUnitRadioGroup(
}
@Composable
-internal fun SpeedUnitRadioButton(
- selectedItem: RadioGroupItem,
- displayedItem: RadioGroupItem,
- onEvent: (RadioGroupItem) -> Unit
+internal fun SpeedUnitRadioButton(
+ selectedItem: T,
+ displayedItem: RadioGroupItem,
+ onEvent: (RadioGroupItem) -> Unit
) {
Row {
RadioButton(
- selected = (selectedItem == displayedItem),
+ selected = (selectedItem == displayedItem.unit),
onClick = { onEvent(displayedItem) }
)
Spacer(modifier = Modifier.width(4.dp))
@@ -43,12 +43,4 @@ internal fun SpeedUnitRadioButton(
}
}
-internal fun createSpeedUnitLabel(unit: SpeedUnit): String {
- return when (unit) {
- SpeedUnit.M_S -> "m/s"
- SpeedUnit.KM_H -> "km/h"
- SpeedUnit.MPH -> "mph"
- }
-}
-
-data class RadioGroupItem(val label: String)
+data class RadioGroupItem(val unit: T, val label: String)
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt
new file mode 100644
index 00000000..e9d32b85
--- /dev/null
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt
@@ -0,0 +1,42 @@
+package no.nordicsemi.android.theme.view
+
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import no.nordicsemi.android.theme.R
+
+@Composable
+fun CloseIconAppBar(text: String, onClick: () -> Unit) {
+ TopAppBar(
+ title = { Text(text) },
+ navigationIcon = {
+ IconButton(onClick = { onClick() }) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = stringResource(id = R.string.close_app),
+ )
+ }
+ }
+ )
+}
+
+@Composable
+fun BackIconAppBar(text: String, onClick: () -> Unit) {
+ TopAppBar(
+ title = { Text(text) },
+ navigationIcon = {
+ IconButton(onClick = { onClick() }) {
+ Icon(
+ Icons.Default.ArrowBack,
+ contentDescription = stringResource(id = R.string.back_screen),
+ )
+ }
+ }
+ )
+}
diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/viewmodel/CloseableViewModel.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/viewmodel/CloseableViewModel.kt
new file mode 100644
index 00000000..2b8af830
--- /dev/null
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/viewmodel/CloseableViewModel.kt
@@ -0,0 +1,13 @@
+package no.nordicsemi.android.theme.viewmodel
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+abstract class CloseableViewModel : ViewModel() {
+
+ var isActive = MutableStateFlow(true)
+
+ protected fun finish() {
+ isActive.tryEmit(false)
+ }
+}
diff --git a/lib_theme/src/main/res/values/colors.xml b/lib_theme/src/main/res/values/colors.xml
index cdc236f2..c3b04f40 100644
--- a/lib_theme/src/main/res/values/colors.xml
+++ b/lib_theme/src/main/res/values/colors.xml
@@ -6,6 +6,6 @@
#FF0077c8
#FF004c97
#FFFFFFFF
- #FFDADADA
+ #FFF5F5F5
#FF0090B0
diff --git a/lib_theme/src/main/res/values/strings.xml b/lib_theme/src/main/res/values/strings.xml
index 522d4b3d..93e85944 100644
--- a/lib_theme/src/main/res/values/strings.xml
+++ b/lib_theme/src/main/res/values/strings.xml
@@ -2,6 +2,9 @@
nRF Toolbox
- Disconnect
+ Close the application.
+ Close the current screen.
+
+ DISCONNECT
Battery
\ No newline at end of file
diff --git a/lib_utils/src/main/AndroidManifest.xml b/lib_utils/src/main/AndroidManifest.xml
index 82b96384..26b65fb5 100644
--- a/lib_utils/src/main/AndroidManifest.xml
+++ b/lib_utils/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-
+
\ No newline at end of file
diff --git a/feature_csc/build.gradle b/profile_csc/build.gradle
similarity index 100%
rename from feature_csc/build.gradle
rename to profile_csc/build.gradle
diff --git a/feature_csc/src/androidTest/java/no/nordicsemi/android/csc/ExampleInstrumentedTest.kt b/profile_csc/src/androidTest/java/no/nordicsemi/android/csc/ExampleInstrumentedTest.kt
similarity index 100%
rename from feature_csc/src/androidTest/java/no/nordicsemi/android/csc/ExampleInstrumentedTest.kt
rename to profile_csc/src/androidTest/java/no/nordicsemi/android/csc/ExampleInstrumentedTest.kt
diff --git a/feature_csc/src/main/AndroidManifest.xml b/profile_csc/src/main/AndroidManifest.xml
similarity index 100%
rename from feature_csc/src/main/AndroidManifest.xml
rename to profile_csc/src/main/AndroidManifest.xml
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
similarity index 74%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
index ccc3727e..3aeb1bd4 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
@@ -1,7 +1,9 @@
package no.nordicsemi.android.csc.data
+import androidx.compose.runtime.Composable
import no.nordicsemi.android.csc.view.CSCSettings
import no.nordicsemi.android.csc.view.SpeedUnit
+import no.nordicsemi.android.theme.view.RadioGroupItem
import java.util.*
internal data class CSCData(
@@ -9,15 +11,21 @@ internal data class CSCData(
val scanDevices: Boolean = false,
val selectedSpeedUnit: SpeedUnit = SpeedUnit.M_S,
val speed: Float = 0f,
- val cadence: Int = 0,
+ val cadence: Float = 0f,
val distance: Float = 0f,
val totalDistance: Float = 0f,
val gearRatio: Float = 0f,
val batteryLevel: Int = 0,
- val wheelSize: String = CSCSettings.DefaultWheelSize.NAME,
- val isScreenActive: Boolean = true
+ val wheelSize: Int = CSCSettings.DefaultWheelSize.VALUE,
+ val wheelSizeDisplay: String = CSCSettings.DefaultWheelSize.NAME
) {
+ @Composable
+ fun drawItself() {
+
+ }
+
+
private val speedWithUnit = when (selectedSpeedUnit) {
SpeedUnit.M_S -> speed
SpeedUnit.KM_H -> speed * 3.6f
@@ -33,7 +41,7 @@ internal data class CSCData(
}
fun displayCadence(): String {
- return String.format("%d RPM", cadence)
+ return String.format("%.0f RPM", cadence)
}
fun displayDistance(): String {
@@ -56,7 +64,11 @@ internal data class CSCData(
return String.format(Locale.US, "%.1f", gearRatio)
}
- fun items(): List<> {
-
+ fun items(): List> {
+ return listOf(
+ RadioGroupItem(SpeedUnit.M_S,"m/s"),
+ RadioGroupItem(SpeedUnit.KM_H, "km/h"),
+ RadioGroupItem(SpeedUnit.MPH, "mph")
+ )
}
}
diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCDataHolder.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCDataHolder.kt
new file mode 100644
index 00000000..810c113f
--- /dev/null
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCDataHolder.kt
@@ -0,0 +1,46 @@
+package no.nordicsemi.android.csc.data
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import no.nordicsemi.android.csc.view.SpeedUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class CSCDataHolder @Inject constructor() {
+
+ private val _data = MutableStateFlow(CSCData())
+ val data: StateFlow = _data
+
+ fun setWheelSize(wheelSize: Int, wheelSizeDisplay: String) {
+ _data.tryEmit(_data.value.copy(
+ wheelSize = wheelSize,
+ wheelSizeDisplay = wheelSizeDisplay,
+ showDialog = false
+ ))
+ }
+
+ fun setSpeedUnit(selectedSpeedUnit: SpeedUnit) {
+ _data.tryEmit(_data.value.copy(selectedSpeedUnit = selectedSpeedUnit))
+ }
+
+ fun setDisplayWheelSizeDialog() {
+ _data.tryEmit(_data.value.copy(showDialog = true))
+ }
+
+ fun setNewDistance(totalDistance: Float, distance: Float, speed: Float) {
+ _data.tryEmit(_data.value.copy(totalDistance = totalDistance, distance = distance, speed = speed))
+ }
+
+ fun setNewCrankCadence(crankCadence: Float, gearRatio: Float) {
+ _data.tryEmit(_data.value.copy(cadence = crankCadence, gearRatio = gearRatio))
+ }
+
+ fun setBatteryLevel(batteryLevel: Int) {
+ _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
+ }
+
+ fun clear() {
+ _data.tryEmit(CSCData())
+ }
+}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManager.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManager.kt
similarity index 92%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManager.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManager.kt
index 2e90c1cf..a9e17354 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManager.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/service/CSCManager.kt
@@ -29,6 +29,7 @@ import android.util.Log
import androidx.annotation.FloatRange
import no.nordicsemi.android.ble.common.callback.csc.CyclingSpeedAndCadenceMeasurementDataCallback
import no.nordicsemi.android.ble.data.Data
+import no.nordicsemi.android.csc.data.CSCDataHolder
import no.nordicsemi.android.csc.service.CSCMeasurementParser.parse
import no.nordicsemi.android.csc.view.CSCSettings
import no.nordicsemi.android.log.LogContract
@@ -41,11 +42,15 @@ private val CYCLING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001816-0
/** Cycling Speed and Cadence Measurement characteristic UUID. */
private val CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000-1000-8000-00805f9b34fb")
-internal class CSCManager(context: Context) : BatteryManager(context) {
+internal class CSCManager(context: Context, private val dataHolder: CSCDataHolder) : BatteryManager(context) {
private var cscMeasurementCharacteristic: BluetoothGattCharacteristic? = null
private var wheelSize = CSCSettings.DefaultWheelSize.VALUE
+ override fun onBatteryLevelChanged(batteryLevel: Int) {
+ dataHolder.setBatteryLevel(batteryLevel)
+ }
+
override fun getGattCallback(): BatteryManagerGattCallback {
return CSCManagerGattCallback()
}
@@ -82,7 +87,7 @@ internal class CSCManager(context: Context) : BatteryManager Unit) {
@@ -25,9 +25,10 @@ internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
}
Column(
- modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
+ Spacer(modifier = Modifier.height(16.dp))
+
SettingsSection(state, onEvent)
Spacer(modifier = Modifier.height(16.dp))
@@ -47,16 +48,17 @@ internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
@Composable
private fun SettingsSection(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
- SensorRecordCard {
+ ScreenSection {
Column(
- modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
WheelSizeView(state, onEvent)
Spacer(modifier = Modifier.height(16.dp))
- SpeedUnitRadioGroup(state.selectedSpeedUnit) { onEvent(it) }
+ SpeedUnitRadioGroup(state.selectedSpeedUnit, state.items()) {
+ onEvent(OnSelectedSpeedUnitSelected(it.unit))
+ }
}
}
}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt
similarity index 74%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt
index 558d50ac..b0255f1e 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt
@@ -2,8 +2,6 @@ package no.nordicsemi.android.csc.view
import android.content.Intent
import androidx.compose.foundation.layout.Column
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -11,19 +9,21 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.csc.R
-import no.nordicsemi.android.csc.service.CSCService
import no.nordicsemi.android.csc.data.CSCData
-import no.nordicsemi.android.csc.viewmodel.CscViewModel
+import no.nordicsemi.android.csc.service.CSCService
+import no.nordicsemi.android.csc.viewmodel.CSCViewModel
+import no.nordicsemi.android.theme.view.BackIconAppBar
import no.nordicsemi.android.utils.isServiceRunning
@Composable
-fun CscScreen(finishAction: () -> Unit) {
- val viewModel: CscViewModel = hiltViewModel()
+fun CSCScreen(finishAction: () -> Unit) {
+ val viewModel: CSCViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
+ val isScreenActive = viewModel.isActive.collectAsState().value
val context = LocalContext.current
- LaunchedEffect(state.isScreenActive) {
- if (!state.isScreenActive) {
+ LaunchedEffect(isScreenActive) {
+ if (!isScreenActive) {
finishAction()
}
if (context.isServiceRunning(CSCService::class.java.name)) {
@@ -45,7 +45,9 @@ fun CscScreen(finishAction: () -> Unit) {
@Composable
private fun CSCView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.csc_title)) })
+ BackIconAppBar(stringResource(id = R.string.csc_title)) {
+ onEvent(OnDisconnectButtonClick)
+ }
CSCContentView(state) { onEvent(it) }
}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCSettings.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCSettings.kt
similarity index 82%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCSettings.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCSettings.kt
index e38cdc12..08ace118 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCSettings.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCSettings.kt
@@ -1,6 +1,6 @@
package no.nordicsemi.android.csc.view
-object CSCSettings {
+internal object CSCSettings {
object DefaultWheelSize {
const val NAME = "60-622"
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt
similarity index 100%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCViewEvent.kt
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SelectWheelSizeDialog.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SelectWheelSizeDialog.kt
similarity index 100%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/SelectWheelSizeDialog.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/SelectWheelSizeDialog.kt
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
similarity index 89%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
index f37a793a..a61d02ee 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
@@ -3,7 +3,6 @@ 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.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -13,12 +12,12 @@ import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.data.CSCData
import no.nordicsemi.android.theme.view.BatteryLevelView
import no.nordicsemi.android.theme.view.KeyValueField
-import no.nordicsemi.android.theme.view.SensorRecordCard
+import no.nordicsemi.android.theme.view.ScreenSection
@Composable
internal fun SensorsReadingView(state: CSCData) {
- SensorRecordCard {
- Column(modifier = Modifier.padding(16.dp)) {
+ ScreenSection {
+ Column {
KeyValueField(stringResource(id = R.string.scs_field_speed), state.displaySpeed())
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(stringResource(id = R.string.scs_field_cadence), state.displayCadence())
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnit.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnit.kt
similarity index 100%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnit.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnit.kt
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/WheelSizeView.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/WheelSizeView.kt
similarity index 96%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/WheelSizeView.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/view/WheelSizeView.kt
index caf7ec7c..5314513b 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/WheelSizeView.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/WheelSizeView.kt
@@ -18,7 +18,7 @@ import no.nordicsemi.android.csc.data.CSCData
internal fun WheelSizeView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
- value = state.wheelSize,
+ value = state.wheelSizeDisplay,
onValueChange = { },
enabled = false,
label = { Text(text = stringResource(id = R.string.scs_field_wheel_size)) },
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CscViewModel.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt
similarity index 50%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CscViewModel.kt
rename to profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt
index e6efa2a5..bb9278e7 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CscViewModel.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt
@@ -1,35 +1,22 @@
package no.nordicsemi.android.csc.viewmodel
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.withContext
-import no.nordicsemi.android.csc.data.CSCData
-import no.nordicsemi.android.csc.service.CSCDataReadBroadcast
+import no.nordicsemi.android.csc.data.CSCDataHolder
import no.nordicsemi.android.csc.view.CSCViewEvent
import no.nordicsemi.android.csc.view.OnDisconnectButtonClick
import no.nordicsemi.android.csc.view.OnSelectedSpeedUnitSelected
import no.nordicsemi.android.csc.view.OnShowEditWheelSizeDialogButtonClick
import no.nordicsemi.android.csc.view.OnWheelSizeSelected
+import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@HiltViewModel
-internal class CscViewModel @Inject constructor(
- private val localBroadcast: CSCDataReadBroadcast
-) : ViewModel() {
+internal class CSCViewModel @Inject constructor(
+ private val dataHolder: CSCDataHolder
+) : CloseableViewModel() {
- val state = MutableStateFlow(CSCData())
-
- init {
- localBroadcast.events.onEach {
- withContext(Dispatchers.Main) { state.value = it }
- }.launchIn(viewModelScope)
- }
+ val state = dataHolder.data
fun onEvent(event: CSCViewEvent) {
when (event) {
@@ -41,22 +28,19 @@ internal class CscViewModel @Inject constructor(
}
private fun onSelectedSpeedUnit(event: OnSelectedSpeedUnitSelected) {
- state.tryEmit(state.value.copy(selectedSpeedUnit = event.selectedSpeedUnit))
+ dataHolder.setSpeedUnit(event.selectedSpeedUnit)
}
private fun onShowDialogEvent() {
- state.tryEmit(state.value.copy(showDialog = true))
+ dataHolder.setDisplayWheelSizeDialog()
}
private fun onWheelSizeChanged(event: OnWheelSizeSelected) {
- localBroadcast.setWheelSize(event.wheelSize)
- state.tryEmit(state.value.copy(
- showDialog = false,
- wheelSize = event.wheelSizeDisplayInfo
- ))
+ dataHolder.setWheelSize(event.wheelSize, event.wheelSizeDisplayInfo)
}
private fun onDisconnectButtonClick() {
- state.tryEmit(state.value.copy(isScreenActive = false))
+ finish()
+ dataHolder.clear()
}
}
diff --git a/feature_csc/src/main/res/values/strings.xml b/profile_csc/src/main/res/values/strings.xml
similarity index 100%
rename from feature_csc/src/main/res/values/strings.xml
rename to profile_csc/src/main/res/values/strings.xml
diff --git a/feature_csc/src/test/java/no/nordicsemi/android/csc/ExampleUnitTest.kt b/profile_csc/src/test/java/no/nordicsemi/android/csc/ExampleUnitTest.kt
similarity index 100%
rename from feature_csc/src/test/java/no/nordicsemi/android/csc/ExampleUnitTest.kt
rename to profile_csc/src/test/java/no/nordicsemi/android/csc/ExampleUnitTest.kt
diff --git a/feature_gls/build.gradle b/profile_gls/build.gradle
similarity index 100%
rename from feature_gls/build.gradle
rename to profile_gls/build.gradle
diff --git a/feature_gls/src/androidTest/java/no/nordicsemi/android/gls/ExampleInstrumentedTest.kt b/profile_gls/src/androidTest/java/no/nordicsemi/android/gls/ExampleInstrumentedTest.kt
similarity index 100%
rename from feature_gls/src/androidTest/java/no/nordicsemi/android/gls/ExampleInstrumentedTest.kt
rename to profile_gls/src/androidTest/java/no/nordicsemi/android/gls/ExampleInstrumentedTest.kt
diff --git a/profile_gls/src/main/AndroidManifest.xml b/profile_gls/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..eb9eb3d1
--- /dev/null
+++ b/profile_gls/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSData.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSData.kt
new file mode 100644
index 00000000..14dbbbc6
--- /dev/null
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSData.kt
@@ -0,0 +1,27 @@
+package no.nordicsemi.android.gls.data
+
+import no.nordicsemi.android.theme.view.RadioGroupItem
+
+internal data class GLSData(
+ val records: List = emptyList(),
+ val batteryLevel: Int = 0,
+ val requestStatus: RequestStatus = RequestStatus.IDLE,
+ val isDeviceBonded: Boolean = false,
+ val selectedMode: WorkingMode = WorkingMode.ALL
+) {
+ fun modeItems(): List> {
+ return listOf(
+ RadioGroupItem(WorkingMode.ALL, "All"),
+ RadioGroupItem(WorkingMode.FIRST, "First"),
+ RadioGroupItem(WorkingMode.LAST, "Last")
+ )
+ }
+}
+
+internal enum class WorkingMode {
+ ALL, LAST, FIRST
+}
+
+internal enum class RequestStatus {
+ IDLE, PENDING, SUCCESS, ABORTED, FAILED, NOT_SUPPORTED
+}
diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSDataHolder.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSDataHolder.kt
new file mode 100644
index 00000000..a0c489c0
--- /dev/null
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSDataHolder.kt
@@ -0,0 +1,49 @@
+package no.nordicsemi.android.gls.data
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class GLSDataHolder @Inject constructor() {
+
+ private val _data = MutableStateFlow(GLSData())
+ val data: StateFlow = _data
+
+ fun addNewRecord(record: GLSRecord) {
+ val newRecords = _data.value.records.toMutableList().apply {
+ add(record)
+ }
+ _data.tryEmit(_data.value.copy(records = newRecords))
+ }
+
+ fun addNewContext(context: MeasurementContext) {
+ _data.value.records.find { context.sequenceNumber == it.sequenceNumber }?.let {
+ it.context = context
+ }
+ _data.tryEmit(_data.value)
+ }
+
+ fun setRequestStatus(requestStatus: RequestStatus) {
+ _data.tryEmit(_data.value.copy(requestStatus = requestStatus))
+ }
+
+ fun records() = _data.value.records
+
+ fun clearRecords() {
+ _data.tryEmit(_data.value.copy(records = emptyList()))
+ }
+
+ fun setNewWorkingMode(workingMode: WorkingMode) {
+ _data.tryEmit(_data.value.copy(selectedMode = workingMode))
+ }
+
+ fun setNewBatteryLevel(batteryLevel: Int) {
+ _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
+ }
+
+ fun clear() {
+ _data.tryEmit(GLSData())
+ }
+}
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRecord.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRecord.kt
similarity index 97%
rename from feature_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRecord.kt
rename to profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRecord.kt
index 452d511e..c4ed474e 100644
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRecord.kt
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSRecord.kt
@@ -33,7 +33,7 @@ internal data class GLSRecord(
/** The glucose concentration. 0 if not present */
val glucoseConcentration: Float = 0f,
- /** Concentration unit. One of the following: [GLSRecord.UNIT_kgpl], [GLSRecord.UNIT_molpl] */
+ /** Concentration unit. One of the following: [ConcentrationUnit.UNIT_KGPL], [ConcentrationUnit.UNIT_MOLPL] */
val unit: ConcentrationUnit = ConcentrationUnit.UNIT_KGPL,
/** The type of the record. 0 if not present */
@@ -49,6 +49,8 @@ internal data class GLSRecord(
)
internal data class MeasurementContext(
+ /** Record sequence number */
+ val sequenceNumber: Int = 0,
val carbohydrateId: CarbohydrateId = CarbohydrateId.NOT_PRESENT,
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt
similarity index 86%
rename from feature_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt
rename to profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt
index 2eb43ad7..2adb1c1e 100644
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSManager.kt
@@ -28,7 +28,6 @@ import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext
-import kotlinx.coroutines.flow.MutableStateFlow
import no.nordicsemi.android.ble.common.callback.RecordAccessControlPointDataCallback
import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementContextDataCallback
import no.nordicsemi.android.ble.common.callback.glucose.GlucoseMeasurementDataCallback
@@ -44,7 +43,7 @@ import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContex
import no.nordicsemi.android.ble.data.Data
import no.nordicsemi.android.gls.data.CarbohydrateId
import no.nordicsemi.android.gls.data.ConcentrationUnit
-import no.nordicsemi.android.gls.data.GLSData
+import no.nordicsemi.android.gls.data.GLSDataHolder
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.data.HealthStatus
import no.nordicsemi.android.gls.data.MeasurementContext
@@ -55,7 +54,6 @@ import no.nordicsemi.android.gls.data.TestType
import no.nordicsemi.android.gls.data.TypeOfMeal
import no.nordicsemi.android.log.LogContract
import no.nordicsemi.android.service.BatteryManager
-import no.nordicsemi.android.service.BatteryManagerCallbacks
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@@ -78,16 +76,18 @@ private val RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805
@Singleton
internal class GLSManager @Inject constructor(
- @ApplicationContext context: Context
-) : BatteryManager(context) {
-
- val data = MutableStateFlow(GLSData())
- private val records = hashMapOf()
+ @ApplicationContext context: Context,
+ private val dataHolder: GLSDataHolder
+) : BatteryManager(context) {
private var glucoseMeasurementCharacteristic: BluetoothGattCharacteristic? = null
private var glucoseMeasurementContextCharacteristic: BluetoothGattCharacteristic? = null
private var recordAccessControlPointCharacteristic: BluetoothGattCharacteristic? = null
+ override fun onBatteryLevelChanged(batteryLevel: Int) {
+ dataHolder.setNewBatteryLevel(batteryLevel)
+ }
+
override fun getGattCallback(): BatteryManagerGattCallback {
return GlucoseManagerGattCallback()
}
@@ -121,10 +121,14 @@ internal class GLSManager @Inject constructor(
.with(object : GlucoseMeasurementDataCallback() {
override fun onGlucoseMeasurementReceived(
- device: BluetoothDevice, sequenceNumber: Int,
- time: Calendar, glucoseConcentration: Float?,
- unit: Int?, type: Int?,
- sampleLocation: Int?, status: GlucoseStatus?,
+ device: BluetoothDevice,
+ sequenceNumber: Int,
+ time: Calendar,
+ glucoseConcentration: Float?,
+ unit: Int?,
+ type: Int?,
+ sampleLocation: Int?,
+ status: GlucoseStatus?,
contextInformationFollows: Boolean
) {
val record = GLSRecord(
@@ -138,27 +142,29 @@ internal class GLSManager @Inject constructor(
status = status?.value ?: 0
)
- records[record.sequenceNumber] = record
- if (!contextInformationFollows) {
- data.tryEmit(data.value.copy(record = records.values.toList()))
- }
+ dataHolder.addNewRecord(record)
}
})
setNotificationCallback(glucoseMeasurementContextCharacteristic)
.with(object : GlucoseMeasurementContextDataCallback() {
override fun onGlucoseMeasurementContextReceived(
- device: BluetoothDevice, sequenceNumber: Int,
- carbohydrate: Carbohydrate?, carbohydrateAmount: Float?,
- meal: Meal?, tester: Tester?,
- health: Health?, exerciseDuration: Int?,
- exerciseIntensity: Int?, medication: Medication?,
- medicationAmount: Float?, medicationUnit: Int?,
+ device: BluetoothDevice,
+ sequenceNumber: Int,
+ carbohydrate: Carbohydrate?,
+ carbohydrateAmount: Float?,
+ meal: Meal?,
+ tester: Tester?,
+ health: Health?,
+ exerciseDuration: Int?,
+ exerciseIntensity: Int?,
+ medication: Medication?,
+ medicationAmount: Float?,
+ medicationUnit: Int?,
HbA1c: Float?
) {
- val record = records[sequenceNumber] ?: return
-
val context = MeasurementContext(
+ sequenceNumber = sequenceNumber,
carbohydrateId = carbohydrate?.value?.let { CarbohydrateId.create(it) }
?: CarbohydrateId.NOT_PRESENT,
carbohydrateUnits = carbohydrateAmount ?: 0f,
@@ -177,9 +183,8 @@ internal class GLSManager @Inject constructor(
?: MedicationUnit.UNIT_KG,
HbA1c = HbA1c ?: 0f
)
- record.context = context
- data.tryEmit(data.value)
+ dataHolder.addNewContext(context)
}
})
setIndicationCallback(recordAccessControlPointCharacteristic)
@@ -194,14 +199,14 @@ internal class GLSManager @Inject constructor(
RACP_OP_CODE_ABORT_OPERATION -> RequestStatus.ABORTED
else -> RequestStatus.SUCCESS
}
- data.tryEmit(data.value.copy(requestStatus = status))
+ dataHolder.setRequestStatus(status)
}
override fun onRecordAccessOperationCompletedWithNoRecordsFound(
device: BluetoothDevice,
@RACPOpCode requestCode: Int
) {
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.SUCCESS))
+ dataHolder.setRequestStatus(RequestStatus.SUCCESS)
}
override fun onNumberOfRecordsReceived(
@@ -211,8 +216,8 @@ internal class GLSManager @Inject constructor(
//TODO("Probably not needed")
// mCallbacks!!.onNumberOfRecordsRequested(device, numberOfRecords)
if (numberOfRecords > 0) {
- if (records.size > 0) {
- val sequenceNumber = records.keys.last() + 1
+ if (dataHolder.records().isNotEmpty()) {
+ val sequenceNumber = dataHolder.records().last().sequenceNumber + 1 //TODO check if correct
writeCharacteristic(
recordAccessControlPointCharacteristic,
RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(
@@ -228,7 +233,7 @@ internal class GLSManager @Inject constructor(
.enqueue()
}
} else {
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.SUCCESS))
+ dataHolder.setRequestStatus(RequestStatus.SUCCESS)
}
}
@@ -239,9 +244,9 @@ internal class GLSManager @Inject constructor(
) {
log(Log.WARN, "Record Access operation failed (error $errorCode)")
if (errorCode == RACP_ERROR_OP_CODE_NOT_SUPPORTED) {
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.NOT_SUPPORTED))
+ dataHolder.setRequestStatus(RequestStatus.NOT_SUPPORTED)
} else {
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.FAILED))
+ dataHolder.setRequestStatus(RequestStatus.FAILED)
}
}
})
@@ -271,9 +276,7 @@ internal class GLSManager @Inject constructor(
return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null
}
- override fun onServicesInvalidated() {
- TODO("Not yet implemented")
- }
+ override fun onServicesInvalidated() { }
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
super.isOptionalServiceSupported(gatt)
@@ -290,11 +293,11 @@ internal class GLSManager @Inject constructor(
/**
* Clears the records list locally.
*/
- fun clear() {
- records.clear()
+ private fun clear() {
+ dataHolder.clearRecords()
val target = bluetoothDevice
if (target != null) {
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.SUCCESS))
+ dataHolder.setRequestStatus(RequestStatus.SUCCESS)
}
}
@@ -303,11 +306,11 @@ internal class GLSManager @Inject constructor(
* be returned to Glucose Measurement characteristic as a notification followed by Record Access
* Control Point indication with status code Success or other in case of error.
*/
- fun lastRecord(): Unit {
+ fun requestLastRecord() {
if (recordAccessControlPointCharacteristic == null) return
val target = bluetoothDevice ?: return
clear()
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING))
+ dataHolder.setRequestStatus(RequestStatus.PENDING)
writeCharacteristic(
recordAccessControlPointCharacteristic,
RecordAccessControlPointData.reportLastStoredRecord()
@@ -326,11 +329,11 @@ internal class GLSManager @Inject constructor(
* returned to Glucose Measurement characteristic as a notification followed by Record Access
* Control Point indication with status code Success or other in case of error.
*/
- fun requestFirstRecord(): Unit {
+ fun requestFirstRecord() {
if (recordAccessControlPointCharacteristic == null) return
val target = bluetoothDevice ?: return
clear()
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING))
+ dataHolder.setRequestStatus(RequestStatus.PENDING)
writeCharacteristic(
recordAccessControlPointCharacteristic,
RecordAccessControlPointData.reportFirstStoredRecord()
@@ -350,11 +353,11 @@ internal class GLSManager @Inject constructor(
* will be returned to Glucose Measurement characteristic as a notification followed by
* Record Access Control Point indication with status code Success or other in case of error.
*/
- fun requestAllRecords(): Unit {
+ fun requestAllRecords() {
if (recordAccessControlPointCharacteristic == null) return
val target = bluetoothDevice ?: return
clear()
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING))
+ dataHolder.setRequestStatus(RequestStatus.PENDING)
writeCharacteristic(
recordAccessControlPointCharacteristic,
RecordAccessControlPointData.reportNumberOfAllStoredRecords()
@@ -382,13 +385,13 @@ internal class GLSManager @Inject constructor(
fun refreshRecords() {
if (recordAccessControlPointCharacteristic == null) return
val target = bluetoothDevice ?: return
- if (records.size == 0) {
+ if (dataHolder.records().isEmpty()) {
requestAllRecords()
} else {
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING))
+ dataHolder.setRequestStatus(RequestStatus.PENDING)
// obtain the last sequence number
- val sequenceNumber = records.keys.last() + 1
+ val sequenceNumber = dataHolder.records().last().sequenceNumber + 1 //TODO check if correct
writeCharacteristic(
recordAccessControlPointCharacteristic,
RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(sequenceNumber)
@@ -432,7 +435,7 @@ internal class GLSManager @Inject constructor(
if (recordAccessControlPointCharacteristic == null) return
val target = bluetoothDevice ?: return
clear()
- data.tryEmit(data.value.copy(requestStatus = RequestStatus.PENDING))
+ dataHolder.setRequestStatus(RequestStatus.PENDING)
writeCharacteristic(
recordAccessControlPointCharacteristic,
RecordAccessControlPointData.deleteAllStoredRecords()
@@ -445,7 +448,7 @@ internal class GLSManager @Inject constructor(
}
.enqueue()
- val elements = listOf(1, 2, 3)
+ val elements = listOf(1, 2, 3)
val result = elements.all { it > 3 }
}
}
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRecordAccessControlPointParser.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRecordAccessControlPointParser.kt
similarity index 99%
rename from feature_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRecordAccessControlPointParser.kt
rename to profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRecordAccessControlPointParser.kt
index 41ac8c0c..ca6f393f 100644
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRecordAccessControlPointParser.kt
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRecordAccessControlPointParser.kt
@@ -23,7 +23,7 @@ package no.nordicsemi.android.gls.repository
import no.nordicsemi.android.ble.data.Data
-object GLSRecordAccessControlPointParser {
+object GLSRecordAccessControlPointParser {
private const val OP_CODE_REPORT_STORED_RECORDS = 1
private const val OP_CODE_DELETE_STORED_RECORDS = 2
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSContentView.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/view/GLSContentView.kt
similarity index 59%
rename from feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSContentView.kt
rename to profile_gls/src/main/java/no/nordicsemi/android/gls/view/GLSContentView.kt
index 16646538..41e9863f 100644
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSContentView.kt
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/view/GLSContentView.kt
@@ -3,8 +3,8 @@ package no.nordicsemi.android.gls.view
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
@@ -18,18 +18,27 @@ import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.data.GLSData
import no.nordicsemi.android.gls.viewmodel.DisconnectEvent
import no.nordicsemi.android.gls.viewmodel.GLSScreenViewEvent
+import no.nordicsemi.android.gls.viewmodel.OnWorkingModeSelected
import no.nordicsemi.android.theme.view.BatteryLevelView
+import no.nordicsemi.android.theme.view.ScreenSection
+import no.nordicsemi.android.theme.view.SpeedUnitRadioGroup
@Composable
internal fun GLSContentView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit) {
Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp),
+ modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(16.dp))
+ SettingsView(state, onEvent)
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ RecordsView(state)
+
+ Spacer(modifier = Modifier.height(16.dp))
+
BatteryLevelView(state.batteryLevel)
Spacer(modifier = Modifier.height(16.dp))
@@ -43,3 +52,22 @@ internal fun GLSContentView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Uni
}
}
+@Composable
+private fun SettingsView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit) {
+ ScreenSection {
+ SpeedUnitRadioGroup(state.selectedMode, state.modeItems()) {
+ onEvent(OnWorkingModeSelected(it.unit))
+ }
+ }
+}
+
+@Composable
+private fun RecordsView(state: GLSData) {
+ ScreenSection {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ state.records.forEach {
+ Text(text = String.format("Glocose concentration: ", it.glucoseConcentration))
+ }
+ }
+ }
+}
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
similarity index 64%
rename from feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
rename to profile_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
index 17f4a35b..c047ddb3 100644
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
@@ -1,8 +1,6 @@
package no.nordicsemi.android.gls.view
import androidx.compose.foundation.layout.Column
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -10,23 +8,38 @@ import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.data.GLSData
+import no.nordicsemi.android.gls.viewmodel.DisconnectEvent
import no.nordicsemi.android.gls.viewmodel.GLSScreenViewEvent
import no.nordicsemi.android.gls.viewmodel.GLSViewModel
+import no.nordicsemi.android.theme.view.BackIconAppBar
@Composable
fun GLSScreen(finishAction: () -> Unit) {
val viewModel: GLSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
+ val isScreenActive = viewModel.isActive.collectAsState().value
LaunchedEffect(state.isDeviceBonded) {
-// viewModel.bondDevice()
+ viewModel.bondDevice()
+ }
+
+ LaunchedEffect(isScreenActive) {
+ if (!isScreenActive) {
+ finishAction()
+ }
+ }
+
+ GLSView(state) {
+ viewModel.onEvent(it)
}
}
@Composable
private fun GLSView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Unit) {
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.gls_title)) })
+ BackIconAppBar(stringResource(id = R.string.gls_title)) {
+ onEvent(DisconnectEvent)
+ }
GLSContentView(state, onEvent)
}
diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSScreenViewEvent.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSScreenViewEvent.kt
new file mode 100644
index 00000000..68449ace
--- /dev/null
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSScreenViewEvent.kt
@@ -0,0 +1,9 @@
+package no.nordicsemi.android.gls.viewmodel
+
+import no.nordicsemi.android.gls.data.WorkingMode
+
+internal sealed class GLSScreenViewEvent
+
+internal data class OnWorkingModeSelected(val workingMode: WorkingMode) : GLSScreenViewEvent()
+
+internal object DisconnectEvent : GLSScreenViewEvent()
diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt
new file mode 100644
index 00000000..e075f08c
--- /dev/null
+++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/viewmodel/GLSViewModel.kt
@@ -0,0 +1,68 @@
+package no.nordicsemi.android.gls.viewmodel
+
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import no.nordicsemi.android.gls.data.GLSDataHolder
+import no.nordicsemi.android.gls.data.WorkingMode
+import no.nordicsemi.android.gls.repository.GLSManager
+import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
+import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
+import no.nordicsemi.android.utils.exhaustive
+import javax.inject.Inject
+
+@HiltViewModel
+internal class GLSViewModel @Inject constructor(
+ private val glsManager: GLSManager,
+ private val deviceHolder: SelectedBluetoothDeviceHolder,
+ private val dataHolder: GLSDataHolder
+) : CloseableViewModel() {
+
+ val state = dataHolder.data
+ private var lastSelectedMode = state.value.selectedMode
+
+ init {
+ dataHolder.data.onEach {
+ if (lastSelectedMode == it.selectedMode) {
+ return@onEach
+ }
+ lastSelectedMode = it.selectedMode
+ when (it.selectedMode) {
+ WorkingMode.ALL -> glsManager.requestAllRecords()
+ WorkingMode.LAST -> glsManager.requestLastRecord()
+ WorkingMode.FIRST -> glsManager.requestFirstRecord()
+ }.exhaustive
+ }.launchIn(GlobalScope)
+ }
+
+ fun onEvent(event: GLSScreenViewEvent) {
+ when (event) {
+ DisconnectEvent -> disconnect()
+ is OnWorkingModeSelected -> dataHolder.setNewWorkingMode(event.workingMode)
+ }.exhaustive
+ }
+
+ fun bondDevice() {
+ if (deviceHolder.isBondingRequired()) {
+ deviceHolder.bondDevice()
+ } else {
+ connectDevice()
+ }
+ }
+
+ private fun connectDevice() {
+ deviceHolder.device?.let {
+ glsManager.connect(it)
+ .useAutoConnect(false)
+ .retry(3, 100)
+ .enqueue()
+ }
+ }
+
+ private fun disconnect() {
+ finish()
+ deviceHolder.forgetDevice()
+ dataHolder.clear()
+ }
+}
diff --git a/feature_gls/src/main/res/values/strings.xml b/profile_gls/src/main/res/values/strings.xml
similarity index 100%
rename from feature_gls/src/main/res/values/strings.xml
rename to profile_gls/src/main/res/values/strings.xml
diff --git a/feature_gls/src/test/java/no/nordicsemi/android/gls/ExampleUnitTest.kt b/profile_gls/src/test/java/no/nordicsemi/android/gls/ExampleUnitTest.kt
similarity index 100%
rename from feature_gls/src/test/java/no/nordicsemi/android/gls/ExampleUnitTest.kt
rename to profile_gls/src/test/java/no/nordicsemi/android/gls/ExampleUnitTest.kt
diff --git a/feature_hrs/build.gradle b/profile_hrs/build.gradle
similarity index 100%
rename from feature_hrs/build.gradle
rename to profile_hrs/build.gradle
diff --git a/feature_hrs/src/androidTest/java/no/nordicsemi/android/hrs/ExampleInstrumentedTest.kt b/profile_hrs/src/androidTest/java/no/nordicsemi/android/hrs/ExampleInstrumentedTest.kt
similarity index 100%
rename from feature_hrs/src/androidTest/java/no/nordicsemi/android/hrs/ExampleInstrumentedTest.kt
rename to profile_hrs/src/androidTest/java/no/nordicsemi/android/hrs/ExampleInstrumentedTest.kt
diff --git a/feature_hrs/src/main/AndroidManifest.xml b/profile_hrs/src/main/AndroidManifest.xml
similarity index 100%
rename from feature_hrs/src/main/AndroidManifest.xml
rename to profile_hrs/src/main/AndroidManifest.xml
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSData.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSData.kt
similarity index 81%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSData.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSData.kt
index 0335f907..5ca9c7b2 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSData.kt
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSData.kt
@@ -3,5 +3,5 @@ package no.nordicsemi.android.hrs.data
internal data class HRSData(
val heartRates: List = emptyList(),
val batteryLevel: Int = 0,
- val sensorLocation: Int = 0
+ val sensorLocation: Int = 0,
)
diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSDataHolder.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSDataHolder.kt
new file mode 100644
index 00000000..24dcb982
--- /dev/null
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSDataHolder.kt
@@ -0,0 +1,32 @@
+package no.nordicsemi.android.hrs.data
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class HRSDataHolder @Inject constructor() {
+
+ private val _data = MutableStateFlow(HRSData())
+ val data: StateFlow = _data
+
+ fun addNewHeartRate(heartRate: Int) {
+ val result = _data.value.heartRates.toMutableList().apply {
+ add(heartRate)
+ }
+ _data.tryEmit(_data.value.copy(heartRates = result))
+ }
+
+ fun setSensorLocation(sensorLocation: Int) {
+ _data.tryEmit(_data.value.copy(sensorLocation = sensorLocation))
+ }
+
+ fun setBatteryLevel(batteryLevel: Int) {
+ _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
+ }
+
+ fun clear() {
+ _data.tryEmit(HRSData())
+ }
+}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
similarity index 95%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
index 10a68c76..5b65517c 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
@@ -25,8 +25,7 @@ import no.nordicsemi.android.ble.data.Data
internal object BodySensorLocationParser {
fun parse(data: Data): String {
- val value = data.getIntValue(Data.FORMAT_UINT8, 0)!!
- return when (value) {
+ return when (data.getIntValue(Data.FORMAT_UINT8, 0)!!) {
6 -> "Foot"
5 -> "Ear Lobe"
4 -> "Hand"
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt
similarity index 63%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt
index 76e74287..590ac0d5 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSManager.kt
@@ -31,21 +31,63 @@ import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationDataCallba
import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementDataCallback
import no.nordicsemi.android.ble.common.profile.hr.BodySensorLocation
import no.nordicsemi.android.ble.data.Data
+import no.nordicsemi.android.hrs.data.HRSDataHolder
import no.nordicsemi.android.log.LogContract
import no.nordicsemi.android.service.BatteryManager
import java.util.*
+private val HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb")
+private val BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb")
+private val HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb")
+
/**
* HRSManager class performs BluetoothGatt operations for connection, service discovery,
* enabling notification and reading characteristics.
* All operations required to connect to device with BLE Heart Rate Service and reading
* heart rate values are performed here.
*/
-class HRSManager(context: Context) : BatteryManager(context) {
+internal class HRSManager(context: Context, private val dataHolder: HRSDataHolder) : BatteryManager(context) {
private var heartRateCharacteristic: BluetoothGattCharacteristic? = null
private var bodySensorLocationCharacteristic: BluetoothGattCharacteristic? = null
+ private val bodySensorLocationDataCallback = object : BodySensorLocationDataCallback() {
+
+ override fun onDataReceived(device: BluetoothDevice, data: Data) {
+ log(LogContract.Log.Level.APPLICATION, "\"" + BodySensorLocationParser.parse(data) + "\" received")
+ super.onDataReceived(device, data)
+ }
+
+ override fun onBodySensorLocationReceived(
+ device: BluetoothDevice,
+ @BodySensorLocation sensorLocation: Int
+ ) {
+ dataHolder.setSensorLocation(sensorLocation)
+ }
+ }
+
+ private val heartRateMeasurementDataCallback = object : HeartRateMeasurementDataCallback() {
+
+ override fun onDataReceived(device: BluetoothDevice, data: Data) {
+ log(LogContract.Log.Level.APPLICATION, "\"" + HeartRateMeasurementParser.parse(data) + "\" received")
+ super.onDataReceived(device, data)
+ }
+
+ override fun onHeartRateMeasurementReceived(
+ device: BluetoothDevice,
+ @IntRange(from = 0) heartRate: Int,
+ contactDetected: Boolean?,
+ @IntRange(from = 0) energyExpanded: Int?,
+ rrIntervals: List?
+ ) {
+ dataHolder.addNewHeartRate(heartRate)
+ }
+ }
+
+ override fun onBatteryLevelChanged(batteryLevel: Int) {
+ dataHolder.setBatteryLevel(batteryLevel)
+ }
+
override fun getGattCallback(): BatteryManagerGattCallback {
return HeartRateManagerCallback()
}
@@ -58,56 +100,14 @@ class HRSManager(context: Context) : BatteryManager(context
override fun initialize() {
super.initialize()
readCharacteristic(bodySensorLocationCharacteristic)
- .with(object : BodySensorLocationDataCallback() {
-
- override fun onDataReceived(device: BluetoothDevice, data: Data) {
- log(
- LogContract.Log.Level.APPLICATION,
- "\"" + BodySensorLocationParser.parse(data) + "\" received"
- )
- super.onDataReceived(device, data)
- }
-
- override fun onBodySensorLocationReceived(
- device: BluetoothDevice,
- @BodySensorLocation sensorLocation: Int
- ) {
- mCallbacks?.onBodySensorLocationReceived(device, sensorLocation)
- }
-
- })
+ .with(bodySensorLocationDataCallback)
.fail { device: BluetoothDevice?, status: Int ->
log(Log.WARN, "Body Sensor Location characteristic not found")
}
.enqueue()
setNotificationCallback(heartRateCharacteristic)
- .with(object : HeartRateMeasurementDataCallback() {
-
- override fun onDataReceived(device: BluetoothDevice, data: Data) {
- log(
- LogContract.Log.Level.APPLICATION,
- "\"" + HeartRateMeasurementParser.parse(data) + "\" received"
- )
- super.onDataReceived(device, data)
- }
-
- override fun onHeartRateMeasurementReceived(
- device: BluetoothDevice,
- @IntRange(from = 0) heartRate: Int,
- contactDetected: Boolean?,
- @IntRange(from = 0) energyExpanded: Int?,
- rrIntervals: List?
- ) {
- mCallbacks?.onHeartRateMeasurementReceived(
- device,
- heartRate,
- contactDetected,
- energyExpanded,
- rrIntervals
- )
- }
- })
+ .with(heartRateMeasurementDataCallback)
enableNotifications(heartRateCharacteristic).enqueue()
}
@@ -140,23 +140,4 @@ class HRSManager(context: Context) : BatteryManager(context
override fun onServicesInvalidated() {}
}
-
- companion object {
-
- val HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb")
- private val BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb")
- private val HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb")
- private var managerInstance: HRSManager? = null
-
- /**
- * Singleton implementation of HRSManager class.
- */
- @Synchronized
- fun getInstance(context: Context): HRSManager? {
- if (managerInstance == null) {
- managerInstance = HRSManager(context)
- }
- return managerInstance
- }
- }
-}
\ No newline at end of file
+}
diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt
new file mode 100644
index 00000000..11189213
--- /dev/null
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt
@@ -0,0 +1,15 @@
+package no.nordicsemi.android.hrs.service
+
+import dagger.hilt.android.AndroidEntryPoint
+import no.nordicsemi.android.hrs.data.HRSDataHolder
+import no.nordicsemi.android.service.ForegroundBleService
+import javax.inject.Inject
+
+@AndroidEntryPoint
+internal class HRSService : ForegroundBleService() {
+
+ @Inject
+ lateinit var dataHolder: HRSDataHolder
+
+ override val manager: HRSManager by lazy { HRSManager(this, dataHolder) }
+}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt
similarity index 100%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
similarity index 75%
rename from feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
index 74e9befc..72cf830f 100644
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
@@ -1,9 +1,8 @@
-package no.nordicsemi.android.hts.view
+package no.nordicsemi.android.hrs.view
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
@@ -16,22 +15,21 @@ 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.hts.R
-import no.nordicsemi.android.hts.data.HTSData
+import no.nordicsemi.android.hrs.R
+import no.nordicsemi.android.hrs.data.HRSData
import no.nordicsemi.android.theme.view.BatteryLevelView
-import no.nordicsemi.android.theme.view.SensorRecordCard
+import no.nordicsemi.android.theme.view.ScreenSection
@Composable
-internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
+internal fun HRSContentView(state: HRSData, onEvent: (HRSScreenViewEvent) -> Unit) {
Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
- SensorRecordCard {
- Box(modifier = Modifier.padding(16.dp)) {
+ Spacer(modifier = Modifier.height(16.dp))
+ ScreenSection {
+ Box(modifier = Modifier.padding(16.dp)) {
+ LineChartView(state)
}
}
@@ -53,5 +51,5 @@ internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Uni
@Preview
@Composable
private fun Preview() {
- HTSContentView(state = HTSData()) { }
+ HRSContentView(state = HRSData()) { }
}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
similarity index 77%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
index 6cfea08e..241f21f3 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
@@ -2,8 +2,6 @@ package no.nordicsemi.android.hrs.view
import android.content.Intent
import androidx.compose.foundation.layout.Column
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -11,19 +9,21 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.hrs.R
+import no.nordicsemi.android.hrs.data.HRSData
import no.nordicsemi.android.hrs.service.HRSService
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
-import no.nordicsemi.android.hrs.viewmodel.HRSViewState
+import no.nordicsemi.android.theme.view.BackIconAppBar
import no.nordicsemi.android.utils.isServiceRunning
@Composable
fun HRSScreen(finishAction: () -> Unit) {
val viewModel: HRSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
+ val isActive = viewModel.isActive.collectAsState().value
val context = LocalContext.current
- LaunchedEffect(state.isScreenActive) {
- if (!state.isScreenActive) {
+ LaunchedEffect(isActive) {
+ if (!isActive) {
finishAction()
}
if (context.isServiceRunning(HRSService::class.java.name)) {
@@ -43,9 +43,11 @@ fun HRSScreen(finishAction: () -> Unit) {
}
@Composable
-private fun HRSView(state: HRSViewState, onEvent: (HRSScreenViewEvent) -> Unit) {
+private fun HRSView(state: HRSData, onEvent: (HRSScreenViewEvent) -> Unit) {
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.hrs_title)) })
+ BackIconAppBar(stringResource(id = R.string.hrs_title)) {
+ onEvent(DisconnectEvent)
+ }
HRSContentView(state) { onEvent(it) }
}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt
similarity index 100%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreenViewEvent.kt
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt
similarity index 50%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
rename to profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt
index 1ca046aa..4fa231eb 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/LineChartView.kt
@@ -3,101 +3,72 @@ package no.nordicsemi.android.hrs.view
import android.content.Context
import android.graphics.Color
import android.graphics.DashPathEffect
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Card
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.content.ContextCompat
import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
-import com.github.mikephil.charting.formatter.IFillFormatter
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
-import com.github.mikephil.charting.utils.Utils
-import no.nordicsemi.android.hrs.R
-import no.nordicsemi.android.hrs.viewmodel.HRSViewState
-import no.nordicsemi.android.theme.NordicColors
-import no.nordicsemi.android.theme.view.BatteryLevelView
+import no.nordicsemi.android.hrs.data.HRSData
import java.util.*
-@Composable
-internal fun HRSContentView(state: HRSViewState, onEvent: (HRSScreenViewEvent) -> Unit) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Card(
- backgroundColor = NordicColors.NordicGray4.value(),
- shape = RoundedCornerShape(10.dp),
- elevation = 0.dp
- ) {
- Box(modifier = Modifier.padding(16.dp)) {
- LineChartView(state)
- }
- }
-
- Spacer(modifier = Modifier.height(16.dp))
-
- BatteryLevelView(state.batteryLevel)
-
- Spacer(modifier = Modifier.height(16.dp))
-
- Button(
- colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary),
- onClick = { onEvent(DisconnectEvent) }
- ) {
- Text(text = stringResource(id = R.string.disconnect))
- }
- }
-}
+private const val X_AXIS_ELEMENTS_COUNT = 40f
@Composable
-internal fun LineChartView(state: HRSViewState) {
+internal fun LineChartView(state: HRSData) {
+ val items = state.heartRates.takeLast(X_AXIS_ELEMENTS_COUNT.toInt()).reversed()
+ val isSystemInDarkTheme = isSystemInDarkTheme()
AndroidView(
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
- factory = { createLineChartView(it, state) },
- update = { updateData(state.points, it) }
+ factory = { createLineChartView(isSystemInDarkTheme, it, items) },
+ update = { updateData(items, it) }
)
}
-internal fun createLineChartView(context: Context, state: HRSViewState): LineChart {
+internal fun createLineChartView(isDarkTheme: Boolean, context: Context, points: List): LineChart {
return LineChart(context).apply {
- setBackgroundColor(Color.WHITE)
-
description.isEnabled = false
- setTouchEnabled(true)
+ legend.isEnabled = false
+
+ setTouchEnabled(false)
-// setOnChartValueSelectedListener(this)
setDrawGridBackground(false)
isDragEnabled = true
- setScaleEnabled(true)
- setPinchZoom(true)
+ setScaleEnabled(false)
+ setPinchZoom(false)
+
+ if (isDarkTheme) {
+ setBackgroundColor(Color.TRANSPARENT)
+ xAxis.gridColor = Color.WHITE
+ xAxis.textColor = Color.WHITE
+ axisLeft.gridColor = Color.WHITE
+ axisLeft.textColor = Color.WHITE
+ } else {
+ setBackgroundColor(Color.WHITE)
+ xAxis.gridColor = Color.BLACK
+ xAxis.textColor = Color.BLACK
+ axisLeft.gridColor = Color.BLACK
+ axisLeft.textColor = Color.BLACK
+ }
xAxis.apply {
xAxis.enableGridDashedLine(10f, 10f, 0f)
+
+ axisMinimum = -X_AXIS_ELEMENTS_COUNT
+ axisMaximum = 0f
+ setAvoidFirstLastClipping(true)
+ position = XAxis.XAxisPosition.BOTTOM
}
axisLeft.apply {
enableGridDashedLine(10f, 10f, 0f)
@@ -107,11 +78,9 @@ internal fun createLineChartView(context: Context, state: HRSViewState): LineCha
}
axisRight.isEnabled = false
- //---
-
- val entries = state.points.mapIndexed { i, v ->
- Entry(i.toFloat(), v.toFloat())
- }
+ val entries = points.mapIndexed { i, v ->
+ Entry(-i.toFloat(), v.toFloat())
+ }.reversed()
// create a dataset and give it a type
if (data != null && data.dataSetCount > 0) {
@@ -124,6 +93,7 @@ internal fun createLineChartView(context: Context, state: HRSViewState): LineCha
val set1 = LineDataSet(entries, "DataSet 1")
set1.setDrawIcons(false)
+ set1.setDrawValues(false)
// draw dashed line
@@ -133,8 +103,13 @@ internal fun createLineChartView(context: Context, state: HRSViewState): LineCha
// black lines and points
// black lines and points
- set1.color = Color.BLACK
- set1.setCircleColor(Color.BLACK)
+ if (isDarkTheme) {
+ set1.color = Color.WHITE
+ set1.setCircleColor(Color.WHITE)
+ } else {
+ set1.color = Color.BLACK
+ set1.setCircleColor(Color.BLACK)
+ }
// line thickness and point size
@@ -164,31 +139,9 @@ internal fun createLineChartView(context: Context, state: HRSViewState): LineCha
// draw selection line as dashed
set1.enableDashedHighlightLine(10f, 5f, 0f)
- // set the filled area
-
- // set the filled area
- set1.setDrawFilled(true)
- set1.fillFormatter = IFillFormatter { _, _ ->
- axisLeft.axisMinimum
- }
-
- // set color of filled area
-
- // set color of filled area
- if (Utils.getSDKInt() >= 18) {
- // drawables only supported on api level 18 and above
- val drawable = ContextCompat.getDrawable(context, R.drawable.fade_red)
- set1.fillDrawable = drawable
- } else {
- set1.fillColor = Color.BLACK
- }
-
val dataSets = ArrayList()
dataSets.add(set1) // add the data sets
-
- // create a data object with the data sets
-
// create a data object with the data sets
val data = LineData(dataSets)
@@ -202,8 +155,8 @@ internal fun createLineChartView(context: Context, state: HRSViewState): LineCha
private fun updateData(points: List, chart: LineChart) {
val entries = points.mapIndexed { i, v ->
- Entry(i.toFloat(), v.toFloat())
- }
+ Entry(-i.toFloat(), v.toFloat())
+ }.reversed()
with(chart) {
if (data != null && data.dataSetCount > 0) {
@@ -216,9 +169,3 @@ private fun updateData(points: List, chart: LineChart) {
}
}
}
-
-@Preview
-@Composable
-private fun Preview() {
- HRSContentView(state = HRSViewState()) { }
-}
diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt
new file mode 100644
index 00000000..7251d4eb
--- /dev/null
+++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt
@@ -0,0 +1,27 @@
+package no.nordicsemi.android.hrs.viewmodel
+
+import dagger.hilt.android.lifecycle.HiltViewModel
+import no.nordicsemi.android.hrs.data.HRSDataHolder
+import no.nordicsemi.android.hrs.view.DisconnectEvent
+import no.nordicsemi.android.hrs.view.HRSScreenViewEvent
+import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+internal class HRSViewModel @Inject constructor(
+ private val dataHolder: HRSDataHolder
+) : CloseableViewModel() {
+
+ val state = dataHolder.data
+
+ fun onEvent(event: HRSScreenViewEvent) {
+ (event as? DisconnectEvent)?.let {
+ onDisconnectButtonClick()
+ }
+ }
+
+ private fun onDisconnectButtonClick() {
+ finish()
+ dataHolder.clear()
+ }
+}
diff --git a/feature_hrs/src/main/res/values/strings.xml b/profile_hrs/src/main/res/values/strings.xml
similarity index 100%
rename from feature_hrs/src/main/res/values/strings.xml
rename to profile_hrs/src/main/res/values/strings.xml
diff --git a/feature_hrs/src/test/java/no/nordicsemi/android/hrs/ExampleUnitTest.kt b/profile_hrs/src/test/java/no/nordicsemi/android/hrs/ExampleUnitTest.kt
similarity index 100%
rename from feature_hrs/src/test/java/no/nordicsemi/android/hrs/ExampleUnitTest.kt
rename to profile_hrs/src/test/java/no/nordicsemi/android/hrs/ExampleUnitTest.kt
diff --git a/feature_hts/build.gradle b/profile_hts/build.gradle
similarity index 100%
rename from feature_hts/build.gradle
rename to profile_hts/build.gradle
diff --git a/feature_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt b/profile_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt
similarity index 100%
rename from feature_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt
rename to profile_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt
diff --git a/feature_hts/src/main/AndroidManifest.xml b/profile_hts/src/main/AndroidManifest.xml
similarity index 100%
rename from feature_hts/src/main/AndroidManifest.xml
rename to profile_hts/src/main/AndroidManifest.xml
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt
new file mode 100644
index 00000000..0b0473f8
--- /dev/null
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt
@@ -0,0 +1,30 @@
+package no.nordicsemi.android.hts.data
+
+import no.nordicsemi.android.theme.view.RadioGroupItem
+
+internal data class HTSData(
+ val temperatureValue: Float = 0f,
+ val temperatureUnit: TemperatureUnit = TemperatureUnit.CELSIUS,
+ val batteryLevel: Int = 0,
+) {
+
+ fun displayTemperature(): String {
+ return when (temperatureUnit) {
+ TemperatureUnit.CELSIUS -> String.format("%.1f °C", temperatureValue)
+ TemperatureUnit.FAHRENHEIT -> String.format("%.1f °F", temperatureValue * 1.8f + 32f)
+ TemperatureUnit.KELVIN -> String.format("%.1f °K", temperatureValue + 273.15f)
+ }
+ }
+
+ fun temperatureSettingsItems(): List> {
+ return listOf(
+ RadioGroupItem(TemperatureUnit.CELSIUS,"°C"),
+ RadioGroupItem(TemperatureUnit.FAHRENHEIT, "°F"),
+ RadioGroupItem(TemperatureUnit.KELVIN, "°K")
+ )
+ }
+}
+
+internal enum class TemperatureUnit {
+ CELSIUS, FAHRENHEIT, KELVIN
+}
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSDataHolder.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSDataHolder.kt
new file mode 100644
index 00000000..630a46a7
--- /dev/null
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSDataHolder.kt
@@ -0,0 +1,29 @@
+package no.nordicsemi.android.hts.data
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class HTSDataHolder @Inject constructor() {
+
+ private val _data = MutableStateFlow(HTSData())
+ val data: StateFlow = _data
+
+ fun setNewTemperature(temperature: Float) {
+ _data.tryEmit(_data.value.copy(temperatureValue = temperature))
+ }
+
+ fun setBatteryLevel(batteryLevel: Int) {
+ _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
+ }
+
+ fun setTemperatureUnit(unit: TemperatureUnit) {
+ _data.tryEmit(_data.value.copy(temperatureUnit = unit))
+ }
+
+ fun clear() {
+ _data.tryEmit(HTSData())
+ }
+}
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
similarity index 100%
rename from feature_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
rename to profile_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
similarity index 75%
rename from feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
rename to profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
index ba78d138..e54b14bb 100644
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
@@ -29,6 +29,7 @@ import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementDataCa
import no.nordicsemi.android.ble.common.profile.ht.TemperatureType
import no.nordicsemi.android.ble.common.profile.ht.TemperatureUnit
import no.nordicsemi.android.ble.data.Data
+import no.nordicsemi.android.hts.data.HTSDataHolder
import no.nordicsemi.android.log.LogContract
import no.nordicsemi.android.service.BatteryManager
import java.util.*
@@ -41,10 +42,34 @@ private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-
* enabling indication and reading characteristics. All operations required to connect to device
* with BLE HT Service and reading health thermometer values are performed here.
*/
-class HTSManager internal constructor(context: Context) : BatteryManager(context) {
+class HTSManager internal constructor(context: Context, private val dataHolder: HTSDataHolder) : BatteryManager(context) {
private var htCharacteristic: BluetoothGattCharacteristic? = null
+ private val temperatureMeasurementDataCallback = object : TemperatureMeasurementDataCallback() {
+ override fun onDataReceived(device: BluetoothDevice, data: Data) {
+ log(
+ LogContract.Log.Level.APPLICATION,
+ "\"" + TemperatureMeasurementParser.parse(data) + "\" received"
+ )
+ super.onDataReceived(device, data)
+ }
+
+ override fun onTemperatureMeasurementReceived(
+ device: BluetoothDevice,
+ temperature: Float,
+ @TemperatureUnit unit: Int,
+ calendar: Calendar?,
+ @TemperatureType type: Int?
+ ) {
+ dataHolder.setNewTemperature(temperature)
+ }
+ }
+
+ override fun onBatteryLevelChanged(batteryLevel: Int) {
+ dataHolder.setBatteryLevel(batteryLevel)
+ }
+
override fun getGattCallback(): BatteryManagerGattCallback {
return HTManagerGattCallback()
}
@@ -57,31 +82,7 @@ class HTSManager internal constructor(context: Context) : BatteryManager "Armpit"
2 -> "Body (general)"
3 -> "Ear (usually ear lobe)"
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt
new file mode 100644
index 00000000..4337f403
--- /dev/null
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt
@@ -0,0 +1,70 @@
+package no.nordicsemi.android.hts.view
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import no.nordicsemi.android.hts.R
+import no.nordicsemi.android.hts.data.HTSData
+import no.nordicsemi.android.theme.view.BatteryLevelView
+import no.nordicsemi.android.theme.view.KeyValueField
+import no.nordicsemi.android.theme.view.ScreenSection
+import no.nordicsemi.android.theme.view.SpeedUnitRadioGroup
+
+@Composable
+internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(16.dp))
+
+ ScreenSection {
+ Box(modifier = Modifier.padding(16.dp)) {
+ SpeedUnitRadioGroup(state.temperatureUnit, state.temperatureSettingsItems()) {
+ onEvent(OnTemperatureUnitSelected(it.unit))
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ ScreenSection {
+ KeyValueField(
+ stringResource(id = R.string.hts_temperature),
+ state.displayTemperature()
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ BatteryLevelView(state.batteryLevel)
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Button(
+ colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary),
+ onClick = { onEvent(DisconnectEvent) }
+ ) {
+ Text(text = stringResource(id = R.string.disconnect))
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ HTSContentView(state = HTSData()) { }
+}
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
similarity index 78%
rename from feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
rename to profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
index 8f0074cc..f33f1074 100644
--- a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
@@ -2,8 +2,6 @@ package no.nordicsemi.android.hts.view
import android.content.Intent
import androidx.compose.foundation.layout.Column
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -14,16 +12,18 @@ import no.nordicsemi.android.hts.R
import no.nordicsemi.android.hts.data.HTSData
import no.nordicsemi.android.hts.service.HTSService
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
+import no.nordicsemi.android.theme.view.BackIconAppBar
import no.nordicsemi.android.utils.isServiceRunning
@Composable
fun HTSScreen(finishAction: () -> Unit) {
val viewModel: HTSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
+ val isActive = viewModel.isActive.collectAsState().value
val context = LocalContext.current
- LaunchedEffect(state.isScreenActive) {
- if (!state.isScreenActive) {
+ LaunchedEffect(isActive) {
+ if (!isActive) {
finishAction()
}
if (context.isServiceRunning(HTSService::class.java.name)) {
@@ -39,13 +39,15 @@ fun HTSScreen(finishAction: () -> Unit) {
}
}
- HRSView(state) { viewModel.onEvent(it) }
+ HTSView(state) { viewModel.onEvent(it) }
}
@Composable
-private fun HRSView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
+private fun HTSView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.hts_title)) })
+ BackIconAppBar(stringResource(id = R.string.hts_title)) {
+ onEvent(DisconnectEvent)
+ }
HTSContentView(state) { onEvent(it) }
}
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt
new file mode 100644
index 00000000..886df9b6
--- /dev/null
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt
@@ -0,0 +1,9 @@
+package no.nordicsemi.android.hts.view
+
+import no.nordicsemi.android.hts.data.TemperatureUnit
+
+internal sealed class HTSScreenViewEvent
+
+internal data class OnTemperatureUnitSelected(val value: TemperatureUnit) : HTSScreenViewEvent()
+
+internal object DisconnectEvent : HTSScreenViewEvent()
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt
new file mode 100644
index 00000000..5faa9dae
--- /dev/null
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt
@@ -0,0 +1,34 @@
+package no.nordicsemi.android.hts.viewmodel
+
+import dagger.hilt.android.lifecycle.HiltViewModel
+import no.nordicsemi.android.hts.data.HTSDataHolder
+import no.nordicsemi.android.hts.view.DisconnectEvent
+import no.nordicsemi.android.hts.view.HTSScreenViewEvent
+import no.nordicsemi.android.hts.view.OnTemperatureUnitSelected
+import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
+import no.nordicsemi.android.utils.exhaustive
+import javax.inject.Inject
+
+@HiltViewModel
+internal class HTSViewModel @Inject constructor(
+ private val dataHolder: HTSDataHolder
+) : CloseableViewModel() {
+
+ val state = dataHolder.data
+
+ fun onEvent(event: HTSScreenViewEvent) {
+ when (event) {
+ DisconnectEvent -> onDisconnectButtonClick()
+ is OnTemperatureUnitSelected -> onTemperatureUnitSelected(event)
+ }.exhaustive
+ }
+
+ private fun onDisconnectButtonClick() {
+ finish()
+ dataHolder.clear()
+ }
+
+ private fun onTemperatureUnitSelected(event: OnTemperatureUnitSelected) {
+ dataHolder.setTemperatureUnit(event.value)
+ }
+}
diff --git a/feature_hts/src/main/res/values/strings.xml b/profile_hts/src/main/res/values/strings.xml
similarity index 81%
rename from feature_hts/src/main/res/values/strings.xml
rename to profile_hts/src/main/res/values/strings.xml
index cf6cda54..5c8ad9a6 100644
--- a/feature_hts/src/main/res/values/strings.xml
+++ b/profile_hts/src/main/res/values/strings.xml
@@ -5,4 +5,6 @@
%.1f °C
%.1f °F
%.1f °K
+
+ Temperature
diff --git a/feature_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt b/profile_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt
similarity index 100%
rename from feature_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt
rename to profile_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt
diff --git a/feature_scanner/build.gradle b/profile_scanner/build.gradle
similarity index 100%
rename from feature_scanner/build.gradle
rename to profile_scanner/build.gradle
diff --git a/feature_scanner/src/androidTest/java/no/nordicsemi/android/scanner/ExampleInstrumentedTest.kt b/profile_scanner/src/androidTest/java/no/nordicsemi/android/scanner/ExampleInstrumentedTest.kt
similarity index 100%
rename from feature_scanner/src/androidTest/java/no/nordicsemi/android/scanner/ExampleInstrumentedTest.kt
rename to profile_scanner/src/androidTest/java/no/nordicsemi/android/scanner/ExampleInstrumentedTest.kt
diff --git a/feature_scanner/src/main/AndroidManifest.xml b/profile_scanner/src/main/AndroidManifest.xml
similarity index 100%
rename from feature_scanner/src/main/AndroidManifest.xml
rename to profile_scanner/src/main/AndroidManifest.xml
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/HiltModule.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/HiltModule.kt
similarity index 87%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/HiltModule.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/HiltModule.kt
index 02216cab..8bb0dd5f 100644
--- a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/HiltModule.kt
+++ b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/HiltModule.kt
@@ -25,8 +25,8 @@ internal object HiltModule {
fun createSelectedBluetoothDeviceHolder(
@ApplicationContext context: Context,
bluetoothAdapter: BluetoothAdapter?
- ): no.nordicsemi.android.service.SelectedBluetoothDeviceHolder {
- return no.nordicsemi.android.service.SelectedBluetoothDeviceHolder(
+ ): SelectedBluetoothDeviceHolder {
+ return SelectedBluetoothDeviceHolder(
context,
bluetoothAdapter
)
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/tools/NordicBleScanner.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/tools/NordicBleScanner.kt
similarity index 100%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/tools/NordicBleScanner.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/tools/NordicBleScanner.kt
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/tools/PermissionHelper.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/tools/PermissionHelper.kt
similarity index 100%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/tools/PermissionHelper.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/tools/PermissionHelper.kt
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/tools/ScannerStatus.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/tools/ScannerStatus.kt
similarity index 100%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/tools/ScannerStatus.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/tools/ScannerStatus.kt
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/BluetoothNotAvailableScreen.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/BluetoothNotAvailableScreen.kt
similarity index 87%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/BluetoothNotAvailableScreen.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/BluetoothNotAvailableScreen.kt
index 1756e495..b76dae26 100644
--- a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/BluetoothNotAvailableScreen.kt
+++ b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/BluetoothNotAvailableScreen.kt
@@ -15,7 +15,6 @@ import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -23,11 +22,15 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.scanner.R
+import no.nordicsemi.android.theme.view.BackIconAppBar
+import no.nordicsemi.android.theme.view.CloseIconAppBar
@Composable
-fun BluetoothNotAvailableScreen() {
+fun BluetoothNotAvailableScreen(finish: () -> Unit) {
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.scanner__request_permission)) })
+ CloseIconAppBar(stringResource(id = R.string.scanner__request_permission)) {
+ finish()
+ }
Column(
modifier = Modifier
.fillMaxWidth()
@@ -49,7 +52,9 @@ fun BluetoothNotEnabledScreen(finish: () -> Unit) {
})
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.scanner__request_permission)) })
+ BackIconAppBar(stringResource(id = R.string.scanner__request_permission)) {
+ finish()
+ }
Column(
modifier = Modifier
.fillMaxWidth()
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/NotConnectedView.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/NotConnectedView.kt
similarity index 100%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/NotConnectedView.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/NotConnectedView.kt
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/RequestPermissionScreen.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/RequestPermissionScreen.kt
similarity index 96%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/RequestPermissionScreen.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/RequestPermissionScreen.kt
index 13552db1..3f31af98 100644
--- a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/RequestPermissionScreen.kt
+++ b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/RequestPermissionScreen.kt
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
@@ -31,6 +30,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionsRequired
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import no.nordicsemi.android.scanner.R
+import no.nordicsemi.android.theme.view.BackIconAppBar
@OptIn(ExperimentalPermissionsApi::class)
@Composable
@@ -40,7 +40,9 @@ fun RequestPermissionScreen(finish: () -> Unit) {
))
Column {
- TopAppBar(title = { Text(text = stringResource(id = R.string.scanner__request_permission)) })
+ BackIconAppBar(stringResource(id = R.string.scanner__request_permission)) {
+ finish()
+ }
PermissionsRequired(
multiplePermissionsState = permissionsState,
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/ScanDeviceScreen.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/ScanDeviceScreen.kt
similarity index 100%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/view/ScanDeviceScreen.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/view/ScanDeviceScreen.kt
diff --git a/feature_scanner/src/main/java/no/nordicsemi/android/scanner/viewmodel/BluetoothPermissionState.kt b/profile_scanner/src/main/java/no/nordicsemi/android/scanner/viewmodel/BluetoothPermissionState.kt
similarity index 100%
rename from feature_scanner/src/main/java/no/nordicsemi/android/scanner/viewmodel/BluetoothPermissionState.kt
rename to profile_scanner/src/main/java/no/nordicsemi/android/scanner/viewmodel/BluetoothPermissionState.kt
diff --git a/feature_scanner/src/main/res/values/strings.xml b/profile_scanner/src/main/res/values/strings.xml
similarity index 100%
rename from feature_scanner/src/main/res/values/strings.xml
rename to profile_scanner/src/main/res/values/strings.xml
diff --git a/feature_scanner/src/test/java/no/nordicsemi/android/scanner/ExampleUnitTest.kt b/profile_scanner/src/test/java/no/nordicsemi/android/scanner/ExampleUnitTest.kt
similarity index 100%
rename from feature_scanner/src/test/java/no/nordicsemi/android/scanner/ExampleUnitTest.kt
rename to profile_scanner/src/test/java/no/nordicsemi/android/scanner/ExampleUnitTest.kt
diff --git a/settings.gradle b/settings.gradle
index b4b29012..2222c137 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -61,11 +61,11 @@ rootProject.name = "Android-nRF-Toolbox"
include ':app'
-include ':feature_csc'
-include ':feature_gls'
-include ':feature_hrs'
-include ':feature_hts'
-include ':feature_scanner'
+include ':profile_csc'
+include ':profile_gls'
+include ':profile_hrs'
+include ':profile_hts'
+include ':profile_scanner'
include ':lib_service'
include ':lib_theme'
@@ -74,3 +74,7 @@ include ':lib_utils'
if (file('../Android-BLE-Library').exists()) {
includeBuild('../Android-BLE-Library')
}
+
+if (file('../Android-Scanner-Compat-Library').exists()) {
+ includeBuild('../Android-Scanner-Compat-Library')
+}