Fix UI screens

This commit is contained in:
Sylwester Zieliński
2021-12-29 12:45:29 +01:00
parent a7224cee23
commit 3777480997
20 changed files with 341 additions and 60 deletions

View File

@@ -14,25 +14,4 @@ data class BPSData(
val systolic: Float = 0f,
val diastolic: Float = 0f,
val meanArterialPressure: Float = 0f,
) {
fun displaySystolic(): String {
return "$systolic"
}
fun displayDiastolic(): String {
return "$diastolic"
}
fun displayMeanArterialPressure(): String {
return "$meanArterialPressure"
}
fun displayPulse(): String {
return "$pulseRate"
}
fun displayTimeData(): String {
return ""
}
}
)

View File

@@ -1,6 +1,5 @@
package no.nordicsemi.android.bps.view
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
@@ -21,8 +20,6 @@ internal fun BPSContentView(state: BPSData, onEvent: (BPSScreenViewEvent) -> Uni
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
Log.d("AAATESTAAA", "state: $state")
BPSSensorsReadingView(state = state)
Spacer(modifier = Modifier.height(16.dp))

View File

@@ -0,0 +1,26 @@
package no.nordicsemi.android.bps.view
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.bps.R
import no.nordicsemi.android.bps.data.BPSData
@Composable
fun BPSData.displaySystolic(): String {
return stringResource(id = R.string.bps_blood_pressure, systolic)
}
@Composable
fun BPSData.displayDiastolic(): String {
return stringResource(id = R.string.bps_blood_pressure, diastolic)
}
@Composable
fun BPSData.displayMeanArterialPressure(): String {
return stringResource(id = R.string.bps_blood_pressure, meanArterialPressure)
}
@Composable
fun BPSData.displayHeartRate(): String? {
return pulseRate?.toString()
}

View File

@@ -19,17 +19,18 @@ import no.nordicsemi.android.theme.view.SectionTitle
internal fun BPSSensorsReadingView(state: BPSData) {
ScreenSection {
Column {
SectionTitle(resId = R.drawable.ic_records, title = "Records")
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.bps_records))
Spacer(modifier = Modifier.height(16.dp))
KeyValueField(stringResource(id = R.string.bps_systolic), state.displaySystolic())
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(stringResource(id = R.string.bps_diastolic), state.displayDiastolic())
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(stringResource(id = R.string.bps_mean), state.displayMeanArterialPressure())
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(stringResource(id = R.string.bps_pulse), state.displayPulse())
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(stringResource(id = R.string.bps_time_data), state.displayTimeData())
state.displayHeartRate()?.let {
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(stringResource(id = R.string.bps_pulse), it)
}
}
}

View File

@@ -2,9 +2,13 @@
<resources>
<string name="bps_title">Blood pressure</string>
<string name="bps_records">Records</string>
<string name="bps_systolic">Systolic</string>
<string name="bps_diastolic">Diastolic</string>
<string name="bps_mean">Mean ap</string>
<string name="bps_pulse">Pulse</string>
<string name="bps_mean">Mean AP</string>
<string name="bps_pulse">Heart rate</string>
<string name="bps_time_data">Time and Date</string>
<string name="bps_blood_pressure">%.0f kPa</string>
</resources>

View File

@@ -2,4 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="no.nordicsemi.android.cgms">
<application>
<service android:name=".repository.CGMService" />
</application>
</manifest>

View File

@@ -3,6 +3,6 @@
package="no.nordicsemi.android.prx">
<application>
<service android:name=".service.Hilt_PRXService"/>
<service android:name=".service.PRXService"/>
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
package no.nordicsemi.android.prx.data
internal sealed class PRXCommand
internal object EnableAlarm : PRXCommand()
internal object DisableAlarm : PRXCommand()

View File

@@ -4,16 +4,7 @@ internal data class PRXData(
val batteryLevel: Int = 0,
val localAlarmLevel: AlarmLevel = AlarmLevel.NONE,
val isRemoteAlarm: Boolean = false
) {
fun displayLocalAlarm(): String {
return when (localAlarmLevel) {
AlarmLevel.NONE -> "none"
AlarmLevel.MEDIUM -> "medium"
AlarmLevel.HIGH -> "height"
}
}
}
)
internal enum class AlarmLevel(val value: Int) {
NONE(0x00),

View File

@@ -1,7 +1,10 @@
package no.nordicsemi.android.prx.data
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject
import javax.inject.Singleton
@@ -11,6 +14,9 @@ internal class PRXDataHolder @Inject constructor() {
private val _data = MutableStateFlow(PRXData())
val data: StateFlow<PRXData> = _data
private val _command = MutableSharedFlow<PRXCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val command = _command.asSharedFlow()
fun setBatteryLevel(batteryLevel: Int) {
_data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
}
@@ -24,6 +30,10 @@ internal class PRXDataHolder @Inject constructor() {
_data.tryEmit(_data.value.copy(isRemoteAlarm = isOn))
}
fun invokeCommand(command: PRXCommand) {
_command.tryEmit(command)
}
fun clear(){
_data.tryEmit(PRXData())
}

View File

@@ -0,0 +1,65 @@
package no.nordicsemi.android.prx.service
import android.content.Context
import android.media.AudioManager
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.util.Log
import androidx.lifecycle.LifecycleService
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.IOException
import javax.inject.Inject
class AlarmHandler @Inject constructor(
@ApplicationContext
private val context: Context
) {
private val TAG = "ALARM_MANAGER"
private var mediaPlayer = MediaPlayer()
private var originalVolume = 0
init {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM)
mediaPlayer.isLooping = true
mediaPlayer.setVolume(1.0f, 1.0f)
try {
mediaPlayer.setDataSource(
context,
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
)
} catch (e: IOException) {
Log.e(TAG, "Initialize Alarm failed: ", e)
}
}
fun playAlarm() {
val am = context.getSystemService(LifecycleService.AUDIO_SERVICE) as AudioManager
originalVolume = am.getStreamVolume(AudioManager.STREAM_ALARM)
am.setStreamVolume(
AudioManager.STREAM_ALARM,
am.getStreamMaxVolume(AudioManager.STREAM_ALARM),
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE
)
try {
mediaPlayer.prepare()
mediaPlayer.start()
} catch (e: IOException) {
Log.e(TAG, "Prepare Alarm failed: ", e)
}
}
fun pauseAlarm() {
if (mediaPlayer.isPlaying) {
mediaPlayer.stop()
// Restore original volume
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.setStreamVolume(AudioManager.STREAM_ALARM, originalVolume, 0)
}
}
fun releaseAlarm() {
mediaPlayer.release()
}
}

View File

@@ -1,8 +1,15 @@
package no.nordicsemi.android.prx.service
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.prx.data.AlarmLevel
import no.nordicsemi.android.prx.data.DisableAlarm
import no.nordicsemi.android.prx.data.EnableAlarm
import no.nordicsemi.android.prx.data.PRXDataHolder
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@AndroidEntryPoint
@@ -11,5 +18,41 @@ internal class PRXService : ForegroundBleService() {
@Inject
lateinit var dataHolder: PRXDataHolder
override val manager: PRXManager by lazy { PRXManager(this, dataHolder) }
@Inject
lateinit var alarmHandler: AlarmHandler
private var serverManager: ProximityServerManager = ProximityServerManager(this)
override val manager: PRXManager by lazy {
PRXManager(this, dataHolder).apply {
useServer(serverManager)
}
}
override fun onCreate() {
super.onCreate()
serverManager.open()
dataHolder.command.onEach {
when (it) {
DisableAlarm -> manager.writeImmediateAlert(false)
EnableAlarm -> manager.writeImmediateAlert(true)
}.exhaustive
}.launchIn(lifecycleScope)
dataHolder.data.onEach {
if (it.localAlarmLevel != AlarmLevel.NONE) {
alarmHandler.playAlarm()
} else {
alarmHandler.pauseAlarm()
}
}.launchIn(lifecycleScope)
}
override fun onDestroy() {
super.onDestroy()
alarmHandler.releaseAlarm()
serverManager.close()
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.prx.service
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import android.content.Context
import android.util.Log
import no.nordicsemi.android.ble.BleServerManager
import no.nordicsemi.android.ble.common.data.alert.AlertLevelData
import java.util.*
internal class ProximityServerManager(context: Context) : BleServerManager(context) {
override fun log(priority: Int, message: String) {
Log.println(priority, "BleManager", message)
}
override fun initializeServer(): List<BluetoothGattService> {
val services: MutableList<BluetoothGattService> = ArrayList()
services.add(
service(
PRX_SERVICE_UUID,
characteristic(
ALERT_LEVEL_CHARACTERISTIC_UUID,
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
BluetoothGattCharacteristic.PERMISSION_WRITE
)
)
)
services.add(
service(
LINK_LOSS_SERVICE_UUID,
characteristic(
ALERT_LEVEL_CHARACTERISTIC_UUID,
BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE or BluetoothGattCharacteristic.PERMISSION_READ,
AlertLevelData.highAlert()
)
)
)
return services
}
}

View File

@@ -3,9 +3,13 @@ package no.nordicsemi.android.prx.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.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -14,10 +18,74 @@ import no.nordicsemi.android.prx.data.PRXData
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.SectionTitle
@Composable
internal fun ContentView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
SettingsSection(state, onEvent)
Spacer(modifier = Modifier.height(16.dp))
RecordsSection(state)
Spacer(modifier = Modifier.height(16.dp))
BatteryLevelView(state.batteryLevel)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onEvent(DisconnectEvent) }
) {
Text(text = stringResource(id = R.string.disconnect))
}
}
}
@Composable
private fun SettingsSection(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
ScreenSection {
SectionTitle(icon = Icons.Default.Settings, title = stringResource(R.string.prx_settings))
Spacer(modifier = Modifier.height(16.dp))
if (state.isRemoteAlarm) {
TurnAlarmOffButton(onEvent)
} else {
TurnAlarmOnButton(onEvent)
}
}
}
@Composable
private fun TurnAlarmOnButton(onEvent: (PRXScreenViewEvent) -> Unit) {
Button(
onClick = { onEvent(TurnOnAlert) }
) {
Text(text = stringResource(id = R.string.prx_find_me))
}
}
@Composable
private fun TurnAlarmOffButton(onEvent: (PRXScreenViewEvent) -> Unit) {
Button(
onClick = { onEvent(TurnOffAlert) }
) {
Text(text = stringResource(id = R.string.prx_silent_me))
}
}
@Composable
private fun RecordsSection(state: PRXData) {
ScreenSection {
SectionTitle(resId = R.drawable.ic_records, title = stringResource(id = R.string.prx_records))
Spacer(modifier = Modifier.height(16.dp))
Column {
KeyValueField(
stringResource(id = R.string.prx_is_remote_alarm),
@@ -26,20 +94,8 @@ internal fun ContentView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit)
Spacer(modifier = Modifier.height(4.dp))
KeyValueField(
stringResource(id = R.string.prx_local_alarm_level),
state.displayLocalAlarm()
state.localAlarmLevel.toDisplayString()
)
}
}
Spacer(modifier = Modifier.height(16.dp))
BatteryLevelView(state.batteryLevel)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onEvent(DisconnectEvent) }
) {
Text(text = stringResource(id = R.string.disconnect))
}
}

View File

@@ -0,0 +1,15 @@
package no.nordicsemi.android.prx.view
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import no.nordicsemi.android.prx.R
import no.nordicsemi.android.prx.data.AlarmLevel
@Composable
internal fun AlarmLevel.toDisplayString(): String {
return when (this) {
AlarmLevel.NONE -> stringResource(id = R.string.prx_alarm_level_none)
AlarmLevel.MEDIUM -> stringResource(id = R.string.prx_alarm_level_medium)
AlarmLevel.HIGH -> stringResource(id = R.string.prx_alarm_level_height)
}
}

View File

@@ -2,4 +2,8 @@ package no.nordicsemi.android.prx.view
internal sealed class PRXScreenViewEvent
internal object TurnOnAlert : PRXScreenViewEvent()
internal object TurnOffAlert : PRXScreenViewEvent()
internal object DisconnectEvent : PRXScreenViewEvent()

View File

@@ -1,9 +1,13 @@
package no.nordicsemi.android.prx.viewmodel
import dagger.hilt.android.lifecycle.HiltViewModel
import no.nordicsemi.android.prx.data.DisableAlarm
import no.nordicsemi.android.prx.data.EnableAlarm
import no.nordicsemi.android.prx.data.PRXDataHolder
import no.nordicsemi.android.prx.view.DisconnectEvent
import no.nordicsemi.android.prx.view.PRXScreenViewEvent
import no.nordicsemi.android.prx.view.TurnOffAlert
import no.nordicsemi.android.prx.view.TurnOnAlert
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@@ -18,6 +22,8 @@ internal class PRXViewModel @Inject constructor(
fun onEvent(event: PRXScreenViewEvent) {
when (event) {
DisconnectEvent -> onDisconnectButtonClick()
TurnOffAlert -> dataHolder.invokeCommand(DisableAlarm)
TurnOnAlert -> dataHolder.invokeCommand(EnableAlarm)
}.exhaustive
}

View File

@@ -2,6 +2,16 @@
<resources>
<string name="prx_title">Proximity</string>
<string name="prx_silent_me">SILENT ME</string>
<string name="prx_find_me">FIND ME</string>
<string name="prx_records">Records</string>
<string name="prx_settings">Settings</string>
<string name="prx_alarm_level_none">none</string>
<string name="prx_alarm_level_medium">medium</string>
<string name="prx_alarm_level_height">height</string>
<string name="prx_is_remote_alarm">Remote alarm</string>
<string name="prx_local_alarm_level">Local alarm level</string>
</resources>

View File

@@ -33,10 +33,10 @@ import no.nordicsemi.android.service.BatteryManager
import java.util.*
/** Running Speed and Cadence Measurement service UUID */
val RSCS_SERVICE_UUID: UUID = UUID.fromString("00001814-0000-1000-8000-00805f9b34fb")
val RSCS_SERVICE_UUID: UUID = UUID.fromString("00001814-0000-1000-8000-00805F9B34FB")
/** Running Speed and Cadence Measurement characteristic UUID */
private val RSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A53-0000-1000-8000-00805f9b34fb")
private val RSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A53-0000-1000-8000-00805F9B34FB")
internal class RSCSManager internal constructor(
context: Context,

View File

@@ -3,6 +3,7 @@ package no.nordicsemi.android.rscs.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.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -18,7 +19,8 @@ import no.nordicsemi.android.theme.view.BatteryLevelView
@Composable
internal fun RSCSContentView(state: RSCSData, onEvent: (RSCScreenViewEvent) -> Unit) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(16.dp))