diff --git a/app/build.gradle b/app/build.gradle
index 1f21139f..5f9029ee 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -52,6 +52,7 @@ dependencies {
//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(":lib_theme")
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 79456427..aca4b7b3 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
@@ -22,6 +22,7 @@ import androidx.navigation.compose.rememberNavController
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
import no.nordicsemi.android.scanner.view.BluetoothNotAvailableScreen
import no.nordicsemi.android.scanner.view.BluetoothNotEnabledScreen
import no.nordicsemi.android.scanner.view.RequestPermissionScreen
@@ -43,6 +44,7 @@ internal fun HomeScreen() {
composable(NavDestination.HOME.id) { HomeView { viewModel.navigate(it) } }
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() }
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt
index 5820a21f..c6513cfc 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt
@@ -4,6 +4,7 @@ enum class NavDestination(val id: String) {
HOME("home-screen"),
CSC("csc-screen"),
HRS("hrs-screen"),
+ HTS("hts-screen"),
GLS("gls-screen"),
REQUEST_PERMISSION("request-permission"),
BLUETOOTH_NOT_AVAILABLE("bluetooth-not-available"),
diff --git a/app/src/main/res/drawable/ic_hts.xml b/app/src/main/res/drawable/ic_hts.xml
new file mode 100644
index 00000000..b575adf5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hts.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
index 974acd0f..ccc3727e 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
+++ b/feature_csc/src/main/java/no/nordicsemi/android/csc/data/CSCData.kt
@@ -55,4 +55,8 @@ internal data class CSCData(
fun displayGearRatio(): String {
return String.format(Locale.US, "%.1f", gearRatio)
}
+
+ fun items(): List<> {
+
+ }
}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/ConnectedView.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCConnectedView.kt
similarity index 82%
rename from feature_csc/src/main/java/no/nordicsemi/android/csc/view/ConnectedView.kt
rename to feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCConnectedView.kt
index ff755728..48a3dccc 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/ConnectedView.kt
+++ b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CSCConnectedView.kt
@@ -4,10 +4,8 @@ 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.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
@@ -18,10 +16,10 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.data.CSCData
-import no.nordicsemi.android.theme.NordicColors
+import no.nordicsemi.android.theme.view.SensorRecordCard
@Composable
-internal fun ContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
+internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
if (state.showDialog) {
SelectWheelSizeDialog { onEvent(it) }
}
@@ -49,11 +47,7 @@ internal fun ContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
@Composable
private fun SettingsSection(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
- Card(
- backgroundColor = NordicColors.NordicGray4.value(),
- shape = RoundedCornerShape(10.dp),
- elevation = 0.dp
- ) {
+ SensorRecordCard {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
@@ -70,5 +64,5 @@ private fun SettingsSection(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
@Preview
@Composable
private fun ConnectedPreview() {
- ContentView(CSCData()) { }
+ CSCContentView(CSCData()) { }
}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt
index e1431aa3..558d50ac 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt
+++ b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/CscScreen.kt
@@ -47,6 +47,6 @@ private fun CSCView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
Column {
TopAppBar(title = { Text(text = stringResource(id = R.string.csc_title)) })
- ContentView(state) { onEvent(it) }
+ CSCContentView(state) { onEvent(it) }
}
}
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
index 3abd742f..f37a793a 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
+++ b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SensorsReadingView.kt
@@ -4,8 +4,6 @@ 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.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Card
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -13,17 +11,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.csc.R
import no.nordicsemi.android.csc.data.CSCData
-import no.nordicsemi.android.theme.NordicColors
import no.nordicsemi.android.theme.view.BatteryLevelView
import no.nordicsemi.android.theme.view.KeyValueField
+import no.nordicsemi.android.theme.view.SensorRecordCard
@Composable
internal fun SensorsReadingView(state: CSCData) {
- Card(
- backgroundColor = NordicColors.NordicGray4.value(),
- shape = RoundedCornerShape(10.dp),
- elevation = 0.dp
- ) {
+ SensorRecordCard {
Column(modifier = Modifier.padding(16.dp)) {
KeyValueField(stringResource(id = R.string.scs_field_speed), state.displaySpeed())
Spacer(modifier = Modifier.height(4.dp))
diff --git a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt
index 36b51e83..964fe107 100644
--- a/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt
+++ b/feature_csc/src/main/java/no/nordicsemi/android/csc/view/SpeedUnitRadioGroup.kt
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
@@ -14,32 +13,33 @@ import androidx.compose.ui.unit.dp
@Composable
internal fun SpeedUnitRadioGroup(
- currentUnit: SpeedUnit,
- onEvent: (OnSelectedSpeedUnitSelected) -> Unit
+ currentItem: RadioGroupItem,
+ items: List,
+ onEvent: (RadioGroupItem) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
- SpeedUnitRadioButton(currentUnit, SpeedUnit.KM_H, onEvent)
- SpeedUnitRadioButton(currentUnit, SpeedUnit.MPH, onEvent)
- SpeedUnitRadioButton(currentUnit, SpeedUnit.M_S, onEvent)
+ items.forEach {
+ SpeedUnitRadioButton(currentItem, it, onEvent)
+ }
}
}
@Composable
internal fun SpeedUnitRadioButton(
- selectedUnit: SpeedUnit,
- displayedUnit: SpeedUnit,
- onEvent: (OnSelectedSpeedUnitSelected) -> Unit
+ selectedItem: RadioGroupItem,
+ displayedItem: RadioGroupItem,
+ onEvent: (RadioGroupItem) -> Unit
) {
Row {
RadioButton(
- selected = (selectedUnit == displayedUnit),
- onClick = { onEvent(OnSelectedSpeedUnitSelected(displayedUnit)) }
+ selected = (selectedItem == displayedItem),
+ onClick = { onEvent(displayedItem) }
)
Spacer(modifier = Modifier.width(4.dp))
- Text(text = createSpeedUnitLabel(displayedUnit))
+ Text(text = displayedItem.label)
}
}
@@ -50,3 +50,5 @@ internal fun createSpeedUnitLabel(unit: SpeedUnit): String {
SpeedUnit.MPH -> "mph"
}
}
+
+data class RadioGroupItem(val label: String)
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
index d5abc761..7b429a39 100644
--- 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
@@ -3,7 +3,8 @@ package no.nordicsemi.android.gls.data
internal data class GLSData(
val record: List = emptyList(),
val batteryLevel: Int = 0,
- val requestStatus: RequestStatus = RequestStatus.IDLE
+ val requestStatus: RequestStatus = RequestStatus.IDLE,
+ val isDeviceBonded: Boolean = false
)
internal enum class RequestStatus {
diff --git a/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt b/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
index 96071a8a..17f4a35b 100644
--- a/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
+++ b/feature_gls/src/main/java/no/nordicsemi/android/gls/view/GLSScreen.kt
@@ -4,6 +4,7 @@ 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
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
@@ -16,6 +17,10 @@ import no.nordicsemi.android.gls.viewmodel.GLSViewModel
fun GLSScreen(finishAction: () -> Unit) {
val viewModel: GLSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
+
+ LaunchedEffect(state.isDeviceBonded) {
+// viewModel.bondDevice()
+ }
}
@Composable
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
index 8a566bc0..11acfcb7 100644
--- 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
@@ -15,6 +15,10 @@ internal class GLSViewModel @Inject constructor(
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/BodySensorLocationParser.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
index 6b1d706a..10a68c76 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
+++ b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/BodySensorLocationParser.kt
@@ -23,7 +23,7 @@ package no.nordicsemi.android.hrs.service
import no.nordicsemi.android.ble.data.Data
-object BodySensorLocationParser {
+internal object BodySensorLocationParser {
fun parse(data: Data): String {
val value = data.getIntValue(Data.FORMAT_UINT8, 0)!!
return when (value) {
@@ -37,4 +37,4 @@ object BodySensorLocationParser {
else -> "Other"
}
}
-}
\ No newline at end of file
+}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt
index c69ad1c5..46e0729a 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt
+++ b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/service/HeartRateMeasurementParser.kt
@@ -24,7 +24,7 @@ package no.nordicsemi.android.hrs.service
import no.nordicsemi.android.ble.data.Data
import java.util.*
-object HeartRateMeasurementParser {
+internal object HeartRateMeasurementParser {
private const val HEART_RATE_VALUE_FORMAT: Byte = 0x01 // 1 bit
private const val SENSOR_CONTACT_STATUS: Byte = 0x06 // 2 bits
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/ContentView.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
similarity index 97%
rename from feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/ContentView.kt
rename to feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
index a4122d9a..1ca046aa 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/ContentView.kt
+++ b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSContentView.kt
@@ -38,7 +38,7 @@ import no.nordicsemi.android.theme.view.BatteryLevelView
import java.util.*
@Composable
-internal fun ContentView(state: HRSViewState, onEvent: (HRSScreenViewEvent) -> Unit) {
+internal fun HRSContentView(state: HRSViewState, onEvent: (HRSScreenViewEvent) -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
@@ -220,5 +220,5 @@ private fun updateData(points: List, chart: LineChart) {
@Preview
@Composable
private fun Preview() {
- ContentView(state = HRSViewState()) { }
+ HRSContentView(state = HRSViewState()) { }
}
diff --git a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
index a6dc4d2a..6cfea08e 100644
--- a/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
+++ b/feature_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt
@@ -47,6 +47,6 @@ private fun HRSView(state: HRSViewState, onEvent: (HRSScreenViewEvent) -> Unit)
Column {
TopAppBar(title = { Text(text = stringResource(id = R.string.hrs_title)) })
- ContentView(state) { onEvent(it) }
+ HRSContentView(state) { onEvent(it) }
}
}
diff --git a/feature_hts/build.gradle b/feature_hts/build.gradle
new file mode 100644
index 00000000..d397c91b
--- /dev/null
+++ b/feature_hts/build.gradle
@@ -0,0 +1,26 @@
+apply from: rootProject.file("library.gradle")
+apply plugin: 'kotlin-parcelize'
+
+dependencies {
+ implementation project(":lib_service")
+ implementation project(":lib_theme")
+ implementation project(":lib_utils")
+
+ implementation libs.nordic.ble.common
+
+ implementation libs.nordic.log
+
+ implementation libs.bundles.compose
+ implementation libs.androidx.core
+ implementation libs.material
+ implementation libs.lifecycle.activity
+ implementation libs.lifecycle.service
+ implementation libs.compose.lifecycle
+ implementation libs.compose.activity
+
+ testImplementation libs.test.junit
+ androidTestImplementation libs.android.test.junit
+ androidTestImplementation libs.android.test.espresso
+ androidTestImplementation libs.android.test.compose.ui
+ debugImplementation libs.android.test.compose.tooling
+}
diff --git a/feature_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt b/feature_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..6b416079
--- /dev/null
+++ b/feature_hts/src/androidTest/java/no/nordicsemi/android/hts/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package no.nordicsemi.android.hts
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("no.nordicsemi.android.hts.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/feature_hts/src/main/AndroidManifest.xml b/feature_hts/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..f102f082
--- /dev/null
+++ b/feature_hts/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 00000000..bd7a5c19
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/data/HTSData.kt
@@ -0,0 +1,22 @@
+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/DateTimeParser.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
new file mode 100644
index 00000000..9b1d266f
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.callback.DateTimeDataCallback
+import no.nordicsemi.android.ble.data.Data
+import java.util.*
+
+object DateTimeParser {
+ /**
+ * Parses the date and time info.
+ *
+ * @param data
+ * @return time in human readable format
+ */
+ fun parse(data: Data): String {
+ return parse(data, 0)
+ }
+
+ /**
+ * Parses the date and time info. This data has 7 bytes
+ *
+ * @param data
+ * @param offset
+ * offset to start reading the time
+ * @return time in human readable format
+ */
+ /* package */
+ @JvmStatic
+ fun parse(data: Data, offset: Int): String {
+ val calendar = DateTimeDataCallback.readDateTime(data, offset)
+ return String.format(Locale.US, "%1\$te %1\$tb %1\$tY, %1\$tH:%1\$tM:%1\$tS", calendar)
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 00000000..feaafcbd
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDataBroadcast.kt
@@ -0,0 +1,9 @@
+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/HTSManager.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
new file mode 100644
index 00000000..ba78d138
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
@@ -0,0 +1,103 @@
+/*
+ * 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 android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothGatt
+import android.bluetooth.BluetoothGattCharacteristic
+import android.content.Context
+import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementDataCallback
+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.log.LogContract
+import no.nordicsemi.android.service.BatteryManager
+import java.util.*
+
+private val HT_SERVICE_UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb")
+private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb")
+
+/**
+ * [HTSManager] class performs [BluetoothGatt] operations for connection, service discovery,
+ * 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) {
+
+ private var htCharacteristic: BluetoothGattCharacteristic? = null
+
+ override fun getGattCallback(): BatteryManagerGattCallback {
+ return HTManagerGattCallback()
+ }
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery,
+ * receiving indication, etc..
+ */
+ private inner class HTManagerGattCallback : BatteryManagerGattCallback() {
+ override fun initialize() {
+ super.initialize()
+ setIndicationCallback(htCharacteristic)
+ .with(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?
+ ) {
+ mCallbacks!!.onTemperatureMeasurementReceived(
+ device,
+ temperature,
+ unit,
+ calendar,
+ type
+ )
+ }
+ })
+ enableIndications(htCharacteristic).enqueue()
+ }
+
+ override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
+ val service = gatt.getService(HT_SERVICE_UUID)
+ if (service != null) {
+ htCharacteristic = service.getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID)
+ }
+ return htCharacteristic != null
+ }
+
+ override fun onDeviceDisconnected() {
+ super.onDeviceDisconnected()
+ htCharacteristic = null
+ }
+
+ override fun onServicesInvalidated() {}
+ }
+}
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
new file mode 100644
index 00000000..821cf28c
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManagerCallbacks.kt
@@ -0,0 +1,31 @@
+/*
+ * 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
new file mode 100644
index 00000000..8fd638a4
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/HTSService.kt
@@ -0,0 +1,49 @@
+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/service/TemperatureMeasurementParser.java b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureMeasurementParser.java
new file mode 100644
index 00000000..2d0b5b66
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureMeasurementParser.java
@@ -0,0 +1,85 @@
+/*
+ * 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 java.util.Locale;
+
+import no.nordicsemi.android.ble.data.Data;
+
+@SuppressWarnings("ConstantConditions")
+public class TemperatureMeasurementParser {
+ private static final byte TEMPERATURE_UNIT_FLAG = 0x01; // 1 bit
+ private static final byte TIMESTAMP_FLAG = 0x02; // 1 bits
+ private static final byte TEMPERATURE_TYPE_FLAG = 0x04; // 1 bit
+
+ public static String parse(final Data data) {
+ int offset = 0;
+ final int flags = data.getIntValue(Data.FORMAT_UINT8, offset++);
+
+ /*
+ * false Temperature is in Celsius degrees
+ * true Temperature is in Fahrenheit degrees
+ */
+ final boolean fahrenheit = (flags & TEMPERATURE_UNIT_FLAG) > 0;
+
+ /*
+ * false No Timestamp in the packet
+ * true There is a timestamp information
+ */
+ final boolean timestampIncluded = (flags & TIMESTAMP_FLAG) > 0;
+
+ /*
+ * false Temperature type is not included
+ * true Temperature type included in the packet
+ */
+ final boolean temperatureTypeIncluded = (flags & TEMPERATURE_TYPE_FLAG) > 0;
+
+ final float tempValue = data.getFloatValue(Data.FORMAT_FLOAT, offset);
+ offset += 4;
+
+ String dateTime = null;
+ if (timestampIncluded) {
+ dateTime = DateTimeParser.parse(data, offset);
+ offset += 7;
+ }
+
+ String type = null;
+ if (temperatureTypeIncluded) {
+ type = TemperatureTypeParser.parse(data, offset);
+ // offset++;
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append(String.format(Locale.US, "%.02f", tempValue));
+
+ if (fahrenheit)
+ builder.append("°F");
+ else
+ builder.append("°C");
+
+ if (timestampIncluded)
+ builder.append("\nTime: ").append(dateTime);
+ if (temperatureTypeIncluded)
+ builder.append("\nType: ").append(type);
+ return builder.toString();
+ }
+}
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureTypeParser.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureTypeParser.kt
new file mode 100644
index 00000000..be841f02
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureTypeParser.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.data.Data
+
+object TemperatureTypeParser {
+ fun parse(data: Data): String {
+ return parse(data, 0)
+ }
+
+ /* package */
+ @JvmStatic
+ fun parse(data: Data, offset: Int): String {
+ val type = data.value!![offset].toInt()
+ return when (type) {
+ 1 -> "Armpit"
+ 2 -> "Body (general)"
+ 3 -> "Ear (usually ear lobe)"
+ 4 -> "Finger"
+ 5 -> "Gastro-intestinal Tract"
+ 6 -> "Mouth"
+ 7 -> "Rectum"
+ 8 -> "Toe"
+ 9 -> "Tympanum (ear drum)"
+ else -> "Unknown"
+ }
+ }
+}
diff --git a/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt b/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt
new file mode 100644
index 00000000..74e9befc
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSContentView.kt
@@ -0,0 +1,57 @@
+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.SensorRecordCard
+
+@Composable
+internal fun HTSContentView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ SensorRecordCard {
+ Box(modifier = Modifier.padding(16.dp)) {
+
+ }
+ }
+
+ 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/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
new file mode 100644
index 00000000..8f0074cc
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt
@@ -0,0 +1,52 @@
+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
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.hilt.navigation.compose.hiltViewModel
+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.utils.isServiceRunning
+
+@Composable
+fun HTSScreen(finishAction: () -> Unit) {
+ val viewModel: HTSViewModel = hiltViewModel()
+ val state = viewModel.state.collectAsState().value
+
+ val context = LocalContext.current
+ LaunchedEffect(state.isScreenActive) {
+ if (!state.isScreenActive) {
+ finishAction()
+ }
+ if (context.isServiceRunning(HTSService::class.java.name)) {
+ val intent = Intent(context, HTSService::class.java)
+ context.stopService(intent)
+ }
+ }
+
+ LaunchedEffect("start-service") {
+ if (!context.isServiceRunning(HTSService::class.java.name)) {
+ val intent = Intent(context, HTSService::class.java)
+ context.startService(intent)
+ }
+ }
+
+ HRSView(state) { viewModel.onEvent(it) }
+}
+
+@Composable
+private fun HRSView(state: HTSData, onEvent: (HTSScreenViewEvent) -> Unit) {
+ Column {
+ TopAppBar(title = { Text(text = stringResource(id = R.string.hts_title)) })
+
+ HTSContentView(state) { onEvent(it) }
+ }
+}
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
new file mode 100644
index 00000000..8c826645
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreenViewEvent.kt
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 00000000..918e938d
--- /dev/null
+++ b/feature_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HRSViewModel.kt
@@ -0,0 +1,47 @@
+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/feature_hts/src/main/res/values/strings.xml b/feature_hts/src/main/res/values/strings.xml
new file mode 100644
index 00000000..cf6cda54
--- /dev/null
+++ b/feature_hts/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+ HTS
+
+ %.1f °C
+ %.1f °F
+ %.1f °K
+
diff --git a/feature_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt b/feature_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt
new file mode 100644
index 00000000..67ea2c81
--- /dev/null
+++ b/feature_hts/src/test/java/no/nordicsemi/android/hts/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package no.nordicsemi.android.hts
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ 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 c1cc9747..645ae68e 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,13 +16,11 @@ class SelectedBluetoothDeviceHolder constructor(
return deviceManager.associations.firstOrNull()?.let { bluetoothAdapter?.getRemoteDevice(it) }
}
- //TODO: Check if starts automatically
+ fun isDeviceBonded(): Boolean {
+ return device?.bondState == BluetoothDevice.BOND_NONE
+ }
fun bondDevice() {
- device?.let {
- if (it.bondState == BluetoothDevice.BOND_NONE) {
- it.createBond()
- }
- }
+ device?.createBond()
}
fun forgetDevice() {
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
new file mode 100644
index 00000000..c5c02880
--- /dev/null
+++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/SensorRecordCard.kt
@@ -0,0 +1,18 @@
+package no.nordicsemi.android.theme.view
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+import no.nordicsemi.android.theme.NordicColors
+
+@Composable
+fun SensorRecordCard(content: @Composable () -> Unit) {
+ Card(
+ backgroundColor = NordicColors.NordicGray4.value(),
+ shape = RoundedCornerShape(10.dp),
+ elevation = 0.dp
+ ) {
+ content()
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index ec304a49..b4b29012 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -64,6 +64,7 @@ include ':app'
include ':feature_csc'
include ':feature_gls'
include ':feature_hrs'
+include ':feature_hts'
include ':feature_scanner'
include ':lib_service'