Add GLS Details screen

This commit is contained in:
Sylwester Zieliński
2022-01-24 15:11:40 +01:00
parent 7541cc762a
commit e3010c80e8
28 changed files with 480 additions and 218 deletions

View File

@@ -1,27 +1,17 @@
package no.nordicsemi.android.nrftoolbox
import no.nordicsemi.android.bps.repository.BPS_SERVICE_UUID
import no.nordicsemi.android.bps.view.BPSScreen
import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID
import no.nordicsemi.android.cgms.view.CGMScreen
import no.nordicsemi.android.csc.repository.CSC_SERVICE_UUID
import no.nordicsemi.android.csc.view.CSCScreen
import no.nordicsemi.android.gls.repository.GLS_SERVICE_UUID
import no.nordicsemi.android.gls.view.GLSScreen
import no.nordicsemi.android.hrs.service.HRS_SERVICE_UUID
import no.nordicsemi.android.gls.main.view.GLSScreen
import no.nordicsemi.android.hrs.view.HRSScreen
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
import no.nordicsemi.android.hts.view.HTSScreen
import no.nordicsemi.android.navigation.ComposeDestination
import no.nordicsemi.android.navigation.ComposeDestinations
import no.nordicsemi.android.prx.service.PRX_SERVICE_UUID
import no.nordicsemi.android.prx.view.PRXScreen
import no.nordicsemi.android.rscs.service.RSCS_SERVICE_UUID
import no.nordicsemi.android.rscs.view.RSCSScreen
import no.nordicsemi.android.uart.repository.UART_SERVICE_UUID
import no.nordicsemi.android.uart.view.UARTScreen
import no.nordicsemi.ui.scanner.navigation.view.FindDeviceScreen
import java.util.*
val HomeDestinations = ComposeDestinations(HomeDestination.values().map { it.destination })
val ProfileDestinations = ComposeDestinations(ProfileDestination.values().map { it.destination })
@@ -31,14 +21,14 @@ enum class HomeDestination(val destination: ComposeDestination) {
SCANNER(ComposeDestination("scanner-destination") { FindDeviceScreen() });
}
enum class ProfileDestination(val destination: ComposeDestination, val uuid: UUID) {
CSC(ComposeDestination("csc-destination") { CSCScreen() }, CSC_SERVICE_UUID),
HRS(ComposeDestination("hrs-destination") { HRSScreen() }, HRS_SERVICE_UUID),
HTS(ComposeDestination("hts-destination") { HTSScreen() }, HTS_SERVICE_UUID),
GLS(ComposeDestination("gls-destination") { GLSScreen() }, GLS_SERVICE_UUID),
BPS(ComposeDestination("bps-destination") { BPSScreen() }, BPS_SERVICE_UUID),
PRX(ComposeDestination("prx-destination") { PRXScreen() }, PRX_SERVICE_UUID),
RSCS(ComposeDestination("rscs-destination") { RSCSScreen() }, RSCS_SERVICE_UUID),
CGMS(ComposeDestination("cgms-destination") { CGMScreen() }, CGMS_SERVICE_UUID),
UART(ComposeDestination("uart-destination") { UARTScreen() }, UART_SERVICE_UUID);
enum class ProfileDestination(val destination: ComposeDestination) {
CSC(ComposeDestination("csc-destination") { CSCScreen() }),
HRS(ComposeDestination("hrs-destination") { HRSScreen() }),
HTS(ComposeDestination("hts-destination") { HTSScreen() }),
GLS(ComposeDestination("gls-destination") { GLSScreen() }),
BPS(ComposeDestination("bps-destination") { BPSScreen() }),
PRX(ComposeDestination("prx-destination") { PRXScreen() }),
RSCS(ComposeDestination("rscs-destination") { RSCSScreen() }),
CGMS(ComposeDestination("cgms-destination") { CGMScreen() }),
UART(ComposeDestination("uart-destination") { UARTScreen() });
}

View File

@@ -2,9 +2,7 @@ package no.nordicsemi.android.nrftoolbox
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import no.nordicsemi.android.navigation.ForwardDestination
import no.nordicsemi.android.navigation.NavigationManager
import no.nordicsemi.android.navigation.UUIDArgument
import javax.inject.Inject
@HiltViewModel
@@ -13,9 +11,6 @@ class HomeViewModel @Inject constructor(
) : ViewModel() {
fun openProfile(destination: ProfileDestination) {
navigationManager.navigateTo(
ForwardDestination(destination.destination.id),
UUIDArgument(destination.destination.id, destination.uuid)
)
navigationManager.navigateTo(destination.destination.id)
}
}

View File

@@ -7,6 +7,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint
import no.nordicsemi.android.gls.GLSDestinations
import no.nordicsemi.android.material.you.NordicActivity
import no.nordicsemi.android.material.you.NordicTheme
import no.nordicsemi.android.navigation.NavigationView
@@ -24,7 +25,7 @@ class MainActivity : NordicActivity() {
color = MaterialTheme.colorScheme.surface,
modifier = Modifier.fillMaxSize()
) {
NavigationView(HomeDestinations + ProfileDestinations + ScannerDestinations)
NavigationView(HomeDestinations + ProfileDestinations + ScannerDestinations + GLSDestinations)
}
}
}

View File

@@ -37,7 +37,7 @@ internal class BPSViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, BPS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(BPS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -33,7 +33,7 @@ internal class CGMScreenViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, CGMS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CGMS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -34,7 +34,7 @@ internal class CSCViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, CSC_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CSC_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -6,7 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import no.nordicsemi.android.navigation.*
import no.nordicsemi.android.navigation.CancelDestinationResult
import no.nordicsemi.android.navigation.DestinationResult
import no.nordicsemi.android.navigation.NavigationManager
import no.nordicsemi.android.navigation.SuccessDestinationResult
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.ServiceManager
import no.nordicsemi.android.utils.exhaustive
@@ -52,7 +55,7 @@ internal class DFUViewModel @Inject constructor(
device = args.getDevice()
serviceManager.startService(DFUService::class.java, args.getDevice())
}
null -> navigationManager.navigateTo(ForwardDestination(ScannerDestinationId))
null -> navigationManager.navigateTo(ScannerDestinationId)
}.exhaustive
}

View File

@@ -0,0 +1,12 @@
package no.nordicsemi.android.gls
import no.nordicsemi.android.gls.details.view.GLSDetailsScreen
import no.nordicsemi.android.navigation.ComposeDestination
import no.nordicsemi.android.navigation.ComposeDestinations
import no.nordicsemi.android.navigation.DestinationId
internal val GlsDetailsDestinationId = DestinationId("gls-details-screen")
private val destination: ComposeDestination = ComposeDestination(GlsDetailsDestinationId) { GLSDetailsScreen() }
val GLSDestinations = ComposeDestinations(destination)

View File

@@ -21,29 +21,18 @@
*/
package no.nordicsemi.android.gls.data
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementCallback.GlucoseStatus
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.*
import java.util.*
internal data class GLSRecord(
/** Record sequence number */
val sequenceNumber: Int = 0,
/** The base time of the measurement */
val time: Calendar? = null,
/** The glucose concentration. 0 if not present */
val glucoseConcentration: Float = 0f,
/** Concentration unit. One of the following: [ConcentrationUnit.UNIT_KGPL], [ConcentrationUnit.UNIT_MOLPL] */
val unit: ConcentrationUnit = ConcentrationUnit.UNIT_KGPL,
val type: RecordType?,
/** The sample location. 0 if unknown */
val sampleLocation: Int = 0,
/** Sensor status annunciation flags. 0 if not present */
val status: Int = 0,
val status: GlucoseStatus?,
val sampleLocation: SampleLocation? = null,
var context: MeasurementContext? = null
)
@@ -72,35 +61,17 @@ internal enum class RecordType(val id: Int) {
}
internal data class MeasurementContext(
/** Record sequence number */
val sequenceNumber: Int = 0,
val carbohydrateId: CarbohydrateId = CarbohydrateId.NOT_PRESENT,
/** Number of kilograms of carbohydrate */
val carbohydrateUnits: Float = 0f,
val meal: TypeOfMeal = TypeOfMeal.NOT_PRESENT,
val tester: TestType = TestType.NOT_PRESENT,
val health: HealthStatus = HealthStatus.NOT_PRESENT,
/** Exercise duration in seconds. 0 if not present */
val carbohydrate: Carbohydrate? = null,
val carbohydrateAmount: Float = 0f,
val meal: Meal? = null,
val tester: Tester? = null,
val health: Health? = null,
val exerciseDuration: Int = 0,
/** Exercise intensity in percent. 0 if not present */
val exerciseIntensity: Int = 0,
val medicationId: MedicationId = MedicationId.NOT_PRESENT,
/** Quantity of medication. See [.medicationUnit] for the unit. */
val medication: Medication?,
val medicationQuantity: Float = 0f,
/** One of the following: [MeasurementContext.UNIT_kg], [MeasurementContext.UNIT_l]. */
val medicationUnit: MedicationUnit = MedicationUnit.UNIT_KG,
/** HbA1c value. 0 if not present */
val HbA1c: Float = 0f
)
@@ -116,88 +87,6 @@ internal enum class ConcentrationUnit(val id: Int) {
}
}
internal enum class CarbohydrateId(val id: Int) {
NOT_PRESENT(0),
BREAKFAST(1),
LUNCH(2),
DINNER(3),
SNACK(4),
DRINK(5),
SUPPER(6),
BRUNCH(7);
companion object {
fun create(value: Byte): CarbohydrateId {
return values().firstOrNull { it.id == value.toInt() }
?: throw IllegalArgumentException("Cannot find element for provided value.")
}
}
}
internal enum class TypeOfMeal(val id: Int) {
NOT_PRESENT(0),
PREPRANDIAL(1),
POSTPRANDIAL(2),
FASTING(3),
CASUAL(4),
BEDTIME(5);
companion object {
fun create(value: Byte): TypeOfMeal {
return values().firstOrNull { it.id == value.toInt() }
?: throw IllegalArgumentException("Cannot find element for provided value.")
}
}
}
internal enum class TestType(val id: Int) {
NOT_PRESENT(0),
SELF(1),
HEALTH_CARE_PROFESSIONAL(2),
LAB_TEST(3),
VALUE_NOT_AVAILABLE(15);
companion object {
fun create(value: Byte): TestType {
return values().firstOrNull { it.id == value.toInt() }
?: throw IllegalArgumentException("Cannot find element for provided value.")
}
}
}
internal enum class HealthStatus(val id: Int) {
NOT_PRESENT(0),
MINOR_HEALTH_ISSUES(1),
MAJOR_HEALTH_ISSUES(2),
DURING_MENSES(3),
UNDER_STRESS(4),
NO_HEALTH_ISSUES(5),
VALUE_NOT_AVAILABLE(15);
companion object {
fun create(value: Byte): HealthStatus {
return values().firstOrNull { it.id == value.toInt() }
?: throw IllegalArgumentException("Cannot find element for provided value.")
}
}
}
internal enum class MedicationId(val id: Int) {
NOT_PRESENT(0),
RAPID_ACTING_INSULIN(1),
SHORT_ACTING_INSULIN(2),
INTERMEDIATE_ACTING_INSULIN(3),
LONG_ACTING_INSULIN(4),
PRE_MIXED_INSULIN(5);
companion object {
fun create(value: Byte): MedicationId {
return values().firstOrNull { it.id == value.toInt() }
?: throw IllegalArgumentException("Cannot find element for provided value.")
}
}
}
internal enum class MedicationUnit(val id: Int) {
UNIT_KG(0),
UNIT_L(1);
@@ -209,3 +98,17 @@ internal enum class MedicationUnit(val id: Int) {
}
}
}
internal enum class SampleLocation(val id: Int) {
FINGER(1),
AST(2),
EARLOBE(3),
CONTROL_SOLUTION(4),
NOT_AVAILABLE(15);
companion object {
fun createOrNull(value: Int?): SampleLocation? {
return values().firstOrNull { it.id == value }
}
}
}

View File

@@ -0,0 +1,26 @@
package no.nordicsemi.android.gls.details.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
internal fun Field(title: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium
)
}
}

View File

@@ -0,0 +1,119 @@
package no.nordicsemi.android.gls.details.view
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
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.ble.common.profile.glucose.GlucoseMeasurementCallback
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback
import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.data.*
import no.nordicsemi.android.gls.main.view.toDisplayString
import java.util.*
@Composable
internal fun GLSDetailsContentView(record: GLSRecord) {
Field(stringResource(id = R.string.gls_details_sequence_number), record.sequenceNumber.toString())
record.time?.let {
Field(stringResource(id = R.string.gls_details_date_and_time), stringResource(R.string.gls_timestamp, it))
Spacer(modifier = Modifier.size(4.dp))
}
record.type?.let {
Field(stringResource(id = R.string.gls_details_type), it.toDisplayString())
Spacer(modifier = Modifier.size(4.dp))
}
record.sampleLocation?.let {
Field(stringResource(id = R.string.gls_details_location), it.toDisplayString())
Spacer(modifier = Modifier.size(4.dp))
}
Field(stringResource(id = R.string.gls_details_glucose_condensation_title), stringResource(id = R.string.gls_details_glucose_condensation_field, record.glucoseConcentration, record.unit.toDisplayString()))
record.status?.let {
Field(stringResource(id = R.string.gls_details_battery_low), it.deviceBatteryLow.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_sensor_malfunction), it.sensorMalfunction.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_insufficient_sample), it.sampleSizeInsufficient.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_strip_insertion_error), it.stripInsertionError.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_strip_type_incorrect), it.stripTypeIncorrect.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_sensor_result_too_high), it.sensorResultHigherThenDeviceCanProcess.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_sensor_result_too_low), it.sensorResultLowerThenDeviceCanProcess.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_temperature_too_high), it.sensorTemperatureTooHigh.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_temperature_too_low), it.sensorTemperatureTooLow.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_strip_pulled_too_soon), it.sensorReadInterrupted.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_general_device_fault), it.generalDeviceFault.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_details_time_fault), it.timeFault.toGLSStatus())
Spacer(modifier = Modifier.size(4.dp))
}
record.context?.let {
Field(stringResource(id = R.string.gls_context_title), stringResource(id = R.string.gls_available))
Spacer(modifier = Modifier.size(4.dp))
it.carbohydrate?.let {
Field(stringResource(id = R.string.gls_context_carbohydrate), it.toDisplayString())
Spacer(modifier = Modifier.size(4.dp))
}
it.meal?.let {
Field(stringResource(id = R.string.gls_context_meal), it.toDisplayString())
Spacer(modifier = Modifier.size(4.dp))
}
it.tester?.let {
Field(stringResource(id = R.string.gls_context_tester), it.toDisplayString())
Spacer(modifier = Modifier.size(4.dp))
}
it.health?.let {
Field(stringResource(id = R.string.gls_context_health), it.toDisplayString())
Spacer(modifier = Modifier.size(4.dp))
}
Field(stringResource(id = R.string.gls_context_exercise_title), stringResource(id = R.string.gls_context_exercise_field, it.exerciseDuration, it.exerciseIntensity))
Spacer(modifier = Modifier.size(4.dp))
val medicationField = String.format(stringResource(id = R.string.gls_context_medication_field), it.medicationQuantity, it.medicationUnit.toDisplayString(), it.medication?.toDisplayString())
Field(stringResource(id = R.string.gls_context_medication_title), medicationField)
Spacer(modifier = Modifier.size(4.dp))
Field(stringResource(id = R.string.gls_context_hba1c_title), stringResource(id = R.string.gls_context_hba1c_field, it.HbA1c))
Spacer(modifier = Modifier.size(4.dp))
} ?: Field(stringResource(id = R.string.gls_context_title), stringResource(id = R.string.gls_unavailable))
}
@Composable
private fun GLSDetailsContentView() {
val record = GLSRecord(
sequenceNumber = 1,
time = Calendar.getInstance(),
glucoseConcentration = 12f,
type = RecordType.ARTERIAL_PLASMA,
status = GlucoseMeasurementCallback.GlucoseStatus(0x0004),
unit = ConcentrationUnit.UNIT_KGPL,
sampleLocation = SampleLocation.FINGER,
context = MeasurementContext(
sequenceNumber = 3,
carbohydrate = GlucoseMeasurementContextCallback.Carbohydrate.BREAKFAST,
carbohydrateAmount = 23f,
meal = GlucoseMeasurementContextCallback.Meal.BEDTIME,
tester = GlucoseMeasurementContextCallback.Tester.HEALTH_CARE_PROFESSIONAL,
health = GlucoseMeasurementContextCallback.Health.MAJOR_HEALTH_ISSUES,
exerciseDuration = 3,
exerciseIntensity = 3,
medication = GlucoseMeasurementContextCallback.Medication.INTERMEDIATE_ACTING_INSULIN,
medicationQuantity = 4f,
medicationUnit = MedicationUnit.UNIT_KG,
HbA1c = 21f
)
)
GLSDetailsContentView(record)
}

View File

@@ -0,0 +1,111 @@
package no.nordicsemi.android.gls.details.view
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.data.MedicationUnit
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Carbohydrate
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Meal
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Tester
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Health
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Medication
import no.nordicsemi.android.gls.data.ConcentrationUnit
import no.nordicsemi.android.gls.data.SampleLocation
@Composable
internal fun SampleLocation.toDisplayString(): String {
return when (this) {
SampleLocation.FINGER -> stringResource(id = R.string.gls_sample_location_finger)
SampleLocation.AST -> stringResource(id = R.string.gls_sample_location_ast)
SampleLocation.EARLOBE -> stringResource(id = R.string.gls_sample_location_earlobe)
SampleLocation.CONTROL_SOLUTION -> stringResource(id = R.string.gls_sample_location_control_solution)
SampleLocation.NOT_AVAILABLE -> stringResource(id = R.string.gls_sample_location_value_not_available)
}
}
@Composable
internal fun ConcentrationUnit.toDisplayString(): String {
return when (this) {
ConcentrationUnit.UNIT_KGPL -> stringResource(id = R.string.gls_sample_location_kg_l)
ConcentrationUnit.UNIT_MOLPL -> stringResource(id = R.string.gls_sample_location_mol_l)
}
}
@Composable
internal fun MedicationUnit.toDisplayString(): String {
return when (this) {
MedicationUnit.UNIT_KG -> stringResource(id = R.string.gls_sample_location_kg)
MedicationUnit.UNIT_L -> stringResource(id = R.string.gls_sample_location_l)
}
}
@Composable
internal fun Medication.toDisplayString(): String {
return when (this) {
Medication.RESERVED -> stringResource(id = R.string.gls_reserved)
Medication.RAPID_ACTING_INSULIN -> stringResource(id = R.string.gls_sample_location_rapid_acting_insulin)
Medication.SHORT_ACTING_INSULIN -> stringResource(id = R.string.gls_sample_location_short_acting_insulin)
Medication.INTERMEDIATE_ACTING_INSULIN -> stringResource(id = R.string.gls_sample_location_intermediate_acting_insulin)
Medication.LONG_ACTING_INSULIN -> stringResource(id = R.string.gls_sample_location_long_acting_insulin)
Medication.PRE_MIXED_INSULIN -> stringResource(id = R.string.gls_sample_location_pre_mixed_insulin)
}
}
@Composable
internal fun Health.toDisplayString(): String {
return when (this) {
Health.RESERVED -> stringResource(id = R.string.gls_reserved)
Health.MINOR_HEALTH_ISSUES -> stringResource(id = R.string.gls_health_minor_issues)
Health.MAJOR_HEALTH_ISSUES -> stringResource(id = R.string.gls_health_major_issues)
Health.DURING_MENSES -> stringResource(id = R.string.gls_health_during_menses)
Health.UNDER_STRESS -> stringResource(id = R.string.gls_health_under_stress)
Health.NO_HEALTH_ISSUES -> stringResource(id = R.string.gls_health_no_issues)
Health.NOT_AVAILABLE -> stringResource(id = R.string.gls_health_not_available)
}
}
@Composable
internal fun Tester.toDisplayString(): String {
return when (this) {
Tester.RESERVED -> stringResource(id = R.string.gls_reserved)
Tester.SELF -> stringResource(id = R.string.gls_tester_self)
Tester.HEALTH_CARE_PROFESSIONAL -> stringResource(id = R.string.gls_tester_health_care_professional)
Tester.LAB_TEST -> stringResource(id = R.string.gls_tester_lab_test)
Tester.NOT_AVAILABLE -> stringResource(id = R.string.gls_tester_not_available)
}
}
@Composable
internal fun Carbohydrate.toDisplayString(): String {
return when (this) {
Carbohydrate.RESERVED -> stringResource(id = R.string.gls_reserved)
Carbohydrate.BREAKFAST -> stringResource(id = R.string.gls_carbohydrate_breakfast)
Carbohydrate.LUNCH -> stringResource(id = R.string.gls_carbohydrate_lunch)
Carbohydrate.DINNER -> stringResource(id = R.string.gls_carbohydrate_dinner)
Carbohydrate.SNACK -> stringResource(id = R.string.gls_carbohydrate_snack)
Carbohydrate.DRINK -> stringResource(id = R.string.gls_carbohydrate_drink)
Carbohydrate.SUPPER -> stringResource(id = R.string.gls_carbohydrate_supper)
Carbohydrate.BRUNCH -> stringResource(id = R.string.gls_carbohydrate_brunch)
}
}
@Composable
internal fun Meal.toDisplayString(): String {
return when (this) {
Meal.RESERVED -> stringResource(id = R.string.gls_reserved)
Meal.PREPRANDIAL -> stringResource(id = R.string.gls_meal_preprandial)
Meal.POSTPRANDIAL -> stringResource(id = R.string.gls_meal_posprandial)
Meal.FASTING -> stringResource(id = R.string.gls_meal_fasting)
Meal.CASUAL -> stringResource(id = R.string.gls_meal_casual)
Meal.BEDTIME -> stringResource(id = R.string.gls_meal_bedtime)
}
}
@Composable
internal fun Boolean.toGLSStatus(): String {
return if (this) {
stringResource(id = R.string.gls_yes)
} else {
stringResource(id = R.string.gls_no)
}
}

View File

@@ -0,0 +1,23 @@
package no.nordicsemi.android.gls.details.view
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.details.viewmodel.GLSDetailsViewModel
import no.nordicsemi.android.theme.view.BackIconAppBar
@Composable
internal fun GLSDetailsScreen() {
val viewModel: GLSDetailsViewModel = hiltViewModel()
val record = viewModel.record
Column {
BackIconAppBar(stringResource(id = R.string.gls_title)) {
viewModel.navigateBack()
}
GLSDetailsContentView(record)
}
}

View File

@@ -0,0 +1,22 @@
package no.nordicsemi.android.gls.details.viewmodel
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.navigation.AnyArgument
import no.nordicsemi.android.navigation.NavigationManager
import no.nordicsemi.ui.scanner.ScannerDestinationId
import javax.inject.Inject
@HiltViewModel
internal class GLSDetailsViewModel @Inject constructor(
private val navigationManager: NavigationManager,
) : ViewModel() {
val record =
(navigationManager.getImmediateArgument(ScannerDestinationId) as AnyArgument).value as GLSRecord
fun navigateBack() {
navigationManager.navigateUp()
}
}

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.gls.view
package no.nordicsemi.android.gls.main.view
import no.nordicsemi.android.gls.data.GLSData

View File

@@ -1,14 +1,7 @@
package no.nordicsemi.android.gls.view
package no.nordicsemi.android.gls.main.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -22,14 +15,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.data.GLSData
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.data.RequestStatus
import no.nordicsemi.android.gls.data.WorkingMode
import no.nordicsemi.android.gls.viewmodel.DisconnectEvent
import no.nordicsemi.android.gls.viewmodel.GLSScreenViewEvent
import no.nordicsemi.android.gls.viewmodel.OnWorkingModeSelected
import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel
import no.nordicsemi.android.material.you.CircularProgressIndicator
import no.nordicsemi.android.theme.view.BatteryLevelView
import no.nordicsemi.android.theme.view.ScreenSection
@@ -123,11 +115,14 @@ private fun RecordsViewWithData(state: GLSData) {
@Composable
private fun RecordItem(record: GLSRecord) {
val viewModel: GLSViewModel = hiltViewModel()
Row(verticalAlignment = Alignment.CenterVertically) {
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.clickable { viewModel.onEvent(OnGLSRecordClick(record)) }
) {
record.time?.let {
Text(

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.gls.view
package no.nordicsemi.android.gls.main.view
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource

View File

@@ -1,14 +1,12 @@
package no.nordicsemi.android.gls.view
package no.nordicsemi.android.gls.main.view
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.gls.R
import no.nordicsemi.android.gls.viewmodel.DisconnectEvent
import no.nordicsemi.android.gls.viewmodel.GLSViewModel
import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel
import no.nordicsemi.android.theme.view.BackIconAppBar
import no.nordicsemi.android.theme.view.DeviceConnectingView
import no.nordicsemi.android.utils.exhaustive
@@ -18,8 +16,6 @@ fun GLSScreen() {
val viewModel: GLSViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
Log.d("AAATESTAAA", "$viewModel") //TODO fix screen rotation
Column {
BackIconAppBar(stringResource(id = R.string.gls_title)) {
viewModel.onEvent(DisconnectEvent)

View File

@@ -1,9 +1,12 @@
package no.nordicsemi.android.gls.viewmodel
package no.nordicsemi.android.gls.main.view
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.data.WorkingMode
internal sealed class GLSScreenViewEvent
internal data class OnWorkingModeSelected(val workingMode: WorkingMode) : GLSScreenViewEvent()
internal data class OnGLSRecordClick(val record: GLSRecord) : GLSScreenViewEvent()
internal object DisconnectEvent : GLSScreenViewEvent()

View File

@@ -1,16 +1,16 @@
package no.nordicsemi.android.gls.viewmodel
package no.nordicsemi.android.gls.main.viewmodel
import android.bluetooth.BluetoothDevice
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import no.nordicsemi.android.gls.GlsDetailsDestinationId
import no.nordicsemi.android.gls.data.GLSRepository
import no.nordicsemi.android.gls.data.WorkingMode
import no.nordicsemi.android.gls.main.view.*
import no.nordicsemi.android.gls.repository.GLSManager
import no.nordicsemi.android.gls.repository.GLS_SERVICE_UUID
import no.nordicsemi.android.gls.view.DisplayDataState
import no.nordicsemi.android.gls.view.LoadingState
import no.nordicsemi.android.navigation.*
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.ConnectionObserverAdapter
@@ -36,7 +36,7 @@ internal class GLSViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, GLS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(GLS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {
@@ -79,6 +79,7 @@ internal class GLSViewModel @Inject constructor(
when (event) {
DisconnectEvent -> disconnect()
is OnWorkingModeSelected -> requestData(event.workingMode)
is OnGLSRecordClick -> navigationManager.navigateTo(GlsDetailsDestinationId, AnyArgument(event.record))
}.exhaustive
}

View File

@@ -35,23 +35,8 @@ import no.nordicsemi.android.ble.common.data.RecordAccessControlPointData
import no.nordicsemi.android.ble.common.profile.RecordAccessControlPointCallback.RACPErrorCode
import no.nordicsemi.android.ble.common.profile.RecordAccessControlPointCallback.RACPOpCode
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementCallback.GlucoseStatus
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Carbohydrate
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Health
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Meal
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Medication
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.Tester
import no.nordicsemi.android.gls.data.CarbohydrateId
import no.nordicsemi.android.gls.data.ConcentrationUnit
import no.nordicsemi.android.gls.data.GLSRecord
import no.nordicsemi.android.gls.data.GLSRepository
import no.nordicsemi.android.gls.data.HealthStatus
import no.nordicsemi.android.gls.data.MeasurementContext
import no.nordicsemi.android.gls.data.MedicationId
import no.nordicsemi.android.gls.data.MedicationUnit
import no.nordicsemi.android.gls.data.RecordType
import no.nordicsemi.android.gls.data.RequestStatus
import no.nordicsemi.android.gls.data.TestType
import no.nordicsemi.android.gls.data.TypeOfMeal
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseMeasurementContextCallback.*
import no.nordicsemi.android.gls.data.*
import no.nordicsemi.android.service.BatteryManager
import java.util.*
import javax.inject.Inject
@@ -137,8 +122,8 @@ internal class GLSManager @Inject constructor(
unit = unit?.let { ConcentrationUnit.create(it) }
?: ConcentrationUnit.UNIT_KGPL,
type = RecordType.createOrNull(type),
sampleLocation = sampleLocation ?: 0,
status = status?.value ?: 0
sampleLocation = SampleLocation.createOrNull(sampleLocation),
status = status
)
repository.addNewRecord(record)
@@ -164,19 +149,14 @@ internal class GLSManager @Inject constructor(
) {
val context = MeasurementContext(
sequenceNumber = sequenceNumber,
carbohydrateId = carbohydrate?.value?.let { CarbohydrateId.create(it) }
?: CarbohydrateId.NOT_PRESENT,
carbohydrateUnits = carbohydrateAmount ?: 0f,
meal = meal?.value?.let { TypeOfMeal.create(it) }
?: TypeOfMeal.NOT_PRESENT,
tester = tester?.value?.let { TestType.create(it) }
?: TestType.NOT_PRESENT,
health = health?.value?.let { HealthStatus.create(it) }
?: HealthStatus.NOT_PRESENT,
carbohydrate = carbohydrate,
carbohydrateAmount = carbohydrateAmount ?: 0f,
meal = meal,
tester = tester,
health = health,
exerciseDuration = exerciseDuration ?: 0,
exerciseIntensity = exerciseIntensity ?: 0,
medicationId = medication?.value?.let { MedicationId.create(it) }
?: MedicationId.NOT_PRESENT,
medication = medication,
medicationQuantity = medicationAmount ?: 0f,
medicationUnit = medicationUnit?.let { MedicationUnit.create(it) }
?: MedicationUnit.UNIT_KG,

View File

@@ -23,4 +23,87 @@
<string name="gls__working_mode__all">All</string>
<string name="gls__working_mode__last">Last</string>
<string name="gls__working_mode__first">First</string>
<string name="gls_yes">YES</string>
<string name="gls_no">NO</string>
<string name="gls_sample_location_finger">Finger</string>
<string name="gls_sample_location_ast">Alternate Site Test (AST)</string>
<string name="gls_sample_location_earlobe">Earlobe</string>
<string name="gls_sample_location_control_solution">Control solution</string>
<string name="gls_sample_location_value_not_available">Value not available</string>
<string name="gls_sample_location_kg_l">kg/l</string>
<string name="gls_sample_location_mol_l">mol/l</string>
<string name="gls_sample_location_kg">kg</string>
<string name="gls_sample_location_l">l</string>
<string name="gls_reserved">Reserved</string>
<string name="gls_sample_location_rapid_acting_insulin">Rapid acting insulin</string>
<string name="gls_sample_location_short_acting_insulin">Short acting insulin</string>
<string name="gls_sample_location_intermediate_acting_insulin">intermediate acting insulin</string>
<string name="gls_sample_location_long_acting_insulin">Long acting insulin</string>
<string name="gls_sample_location_pre_mixed_insulin">Pre mixed insulin</string>
<string name="gls_health_minor_issues">Minor health issues</string>
<string name="gls_health_major_issues">Major health issues"</string>
<string name="gls_health_during_menses">During menses</string>
<string name="gls_health_under_stress">Under stress</string>
<string name="gls_health_no_issues">No health issues</string>
<string name="gls_health_not_available">Not available</string>
<string name="gls_tester_self">Self</string>
<string name="gls_tester_health_care_professional">Health care professional</string>
<string name="gls_tester_lab_test">Lab test</string>
<string name="gls_tester_not_available">Not available</string>
<string name="gls_carbohydrate_breakfast">Breakfast</string>
<string name="gls_carbohydrate_lunch">Lunch</string>
<string name="gls_carbohydrate_dinner">Dinner</string>
<string name="gls_carbohydrate_snack">Snack</string>
<string name="gls_carbohydrate_drink">Drink</string>
<string name="gls_carbohydrate_supper">Supper</string>
<string name="gls_carbohydrate_brunch">Brunch</string>
<string name="gls_meal_preprandial">Preprandial</string>
<string name="gls_meal_posprandial">Postprandial</string>
<string name="gls_meal_fasting">Fasting</string>
<string name="gls_meal_casual">Casual</string>
<string name="gls_meal_bedtime">Bedtime</string>
<string name="gls_available">Available</string>
<string name="gls_unavailable">Unavailable</string>
<string name="gls_details_sequence_number">Sequence number: </string>
<string name="gls_details_date_and_time">Date &amp; Time: </string>
<string name="gls_details_type">Type: </string>
<string name="gls_details_location">Location: </string>
<string name="gls_details_glucose_condensation_title">Glucose condensation: </string>
<string name="gls_details_glucose_condensation_field">%.2f %s</string>
<string name="gls_details_battery_low">Battery low:</string>
<string name="gls_details_sensor_malfunction">Sensor malfunction:</string>
<string name="gls_details_insufficient_sample">Insufficient sample:</string>
<string name="gls_details_strip_insertion_error">Strip insertion error:</string>
<string name="gls_details_strip_type_incorrect">Strip type incorrect:</string>
<string name="gls_details_sensor_result_too_high">Sensor result too high:</string>
<string name="gls_details_sensor_result_too_low">Sensor result too low:</string>
<string name="gls_details_temperature_too_high">Temperature too high:</string>
<string name="gls_details_temperature_too_low">Temperature too low:</string>
<string name="gls_details_strip_pulled_too_soon">Strip pulled too soon:</string>
<string name="gls_details_general_device_fault">General device fault:</string>
<string name="gls_details_time_fault">Time fault:</string>
<string name="gls_context_title">Measurement context:</string>
<string name="gls_context_carbohydrate">Carbohydrate:</string>
<string name="gls_context_meal">Meal:</string>
<string name="gls_context_tester">Tester:</string>
<string name="gls_context_health">Health:</string>
<string name="gls_context_exercise_title">Exercise:</string>
<string name="gls_context_exercise_field">%d min %d%%</string>
<string name="gls_context_medication_title">Medication:</string>
<string name="gls_context_medication_field">%d %s \n %s</string>
<string name="gls_context_hba1c_title">HbA1c:</string>
<string name="gls_context_hba1c_field">%.2f%%</string>
</resources>

View File

@@ -32,7 +32,7 @@ internal class HRSViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, HRS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HRS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -32,7 +32,7 @@ internal class HTSViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, HTS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(HTS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -35,7 +35,7 @@ internal class PRXViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, PRX_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(PRX_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -35,7 +35,7 @@ internal class RSCSViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, RSCS_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(RSCS_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -34,7 +34,7 @@ internal class UARTViewModel @Inject constructor(
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
init {
navigationManager.navigateTo(ForwardDestination(ScannerDestinationId), UUIDArgument(ScannerDestinationId, UART_SERVICE_UUID))
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(UART_SERVICE_UUID))
navigationManager.recentResult.onEach {
if (it.destinationId == ScannerDestinationId) {

View File

@@ -42,9 +42,8 @@ dependencyResolutionManagement {
alias('material-icons-extended').to('androidx.compose.material', 'material-icons-extended').versionRef('compose')
bundle('icons', ['material-icons', 'material-icons-extended'])
version('hilt', '2.38.1')
alias('hilt-android').to('com.google.dagger', 'hilt-android').versionRef('hilt')
alias('hilt-compiler').to('com.google.dagger', 'hilt-compiler').versionRef('hilt')
alias('hilt-android').to('com.google.dagger:hilt-android:2.38.1')
alias('hilt-compiler').to('com.google.dagger:hilt-compiler:2.40.4')
alias('hilt-compose').to('androidx.hilt:hilt-navigation-compose:1.0.0-rc01')
alias('hilt-lifecycle').to('androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03')
alias('hilt-lifecyclecompiler').to('androidx.hilt:hilt-compiler:1.0.0')