mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2026-01-09 09:44:22 +01:00
Fix proximity profile
This commit is contained in:
@@ -7,16 +7,16 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.cgms.repository.CGMRepository
|
||||
import no.nordicsemi.android.csc.repository.CSCRepository
|
||||
import no.nordicsemi.android.hrs.service.HRSRepository
|
||||
import no.nordicsemi.android.hts.repository.HTSRepository
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.nrftoolbox.ProfileDestination
|
||||
import no.nordicsemi.android.nrftoolbox.view.HomeViewState
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import no.nordicsemi.android.prx.repository.PRXRepository
|
||||
import no.nordicsemi.android.rscs.repository.RSCSRepository
|
||||
import no.nordicsemi.android.uart.repository.UARTRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.annotation.IntRange
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import no.nordicsemi.android.ble.BleManager
|
||||
import no.nordicsemi.android.ble.callback.DataReceivedCallback
|
||||
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelDataCallback
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import java.util.*
|
||||
|
||||
private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
abstract class BatteryManager(context: Context, protected val scope: CoroutineScope) : BleManager(context) {
|
||||
|
||||
private val TAG = "BLE-MANAGER"
|
||||
|
||||
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
private val batteryLevelDataCallback: DataReceivedCallback =
|
||||
object : BatteryLevelDataCallback() {
|
||||
override fun onBatteryLevelChanged(
|
||||
device: BluetoothDevice,
|
||||
@IntRange(from = 0, to = 100) batteryLevel: Int
|
||||
) {
|
||||
onBatteryLevelChanged(batteryLevel)
|
||||
}
|
||||
|
||||
override fun onInvalidDataReceived(device: BluetoothDevice, data: Data) {
|
||||
log(Log.WARN, "Invalid Battery Level data received: $data")
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun onBatteryLevelChanged(batteryLevel: Int)
|
||||
|
||||
fun readBatteryLevelCharacteristic() {
|
||||
if (isConnected) {
|
||||
readCharacteristic(batteryLevelCharacteristic)
|
||||
.with(batteryLevelDataCallback)
|
||||
.fail { device: BluetoothDevice?, status: Int ->
|
||||
log(Log.WARN, "Battery Level characteristic not found")
|
||||
}
|
||||
.enqueue()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableBatteryLevelCharacteristicNotifications() {
|
||||
if (isConnected) {
|
||||
// If the Battery Level characteristic is null, the request will be ignored
|
||||
setNotificationCallback(batteryLevelCharacteristic)
|
||||
.with(batteryLevelDataCallback)
|
||||
enableNotifications(batteryLevelCharacteristic)
|
||||
.done { device: BluetoothDevice? ->
|
||||
log(Log.INFO, "Battery Level notifications enabled")
|
||||
}
|
||||
.fail { device: BluetoothDevice?, status: Int ->
|
||||
log(Log.WARN, "Battery Level characteristic not found")
|
||||
}
|
||||
.enqueue()
|
||||
}
|
||||
}
|
||||
|
||||
override fun log(priority: Int, message: String) {
|
||||
super.log(priority, message)
|
||||
Log.println(priority, TAG, message)
|
||||
}
|
||||
|
||||
protected abstract inner class BatteryManagerGattCallback : BleManagerGattCallback() {
|
||||
override fun initialize() {
|
||||
readBatteryLevelCharacteristic()
|
||||
enableBatteryLevelCharacteristicNotifications()
|
||||
}
|
||||
|
||||
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
val service = gatt.getService(BATTERY_SERVICE_UUID)
|
||||
if (service != null) {
|
||||
batteryLevelCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return batteryLevelCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onDeviceDisconnected() {
|
||||
batteryLevelCharacteristic = null
|
||||
onBatteryLevelChanged(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ sealed class BleManagerResult <T> {
|
||||
class ConnectingResult<T> : BleManagerResult<T>()
|
||||
data class SuccessResult<T>(val data: T) : BleManagerResult<T>()
|
||||
|
||||
class LinkLossResult<T> : BleManagerResult<T>()
|
||||
class LinkLossResult<T>(val data: T) : BleManagerResult<T>()
|
||||
class DisconnectedResult<T> : BleManagerResult<T>()
|
||||
class UnknownErrorResult<T> : BleManagerResult<T>()
|
||||
class MissingServiceResult<T> : BleManagerResult<T>()
|
||||
|
||||
@@ -15,6 +15,10 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
|
||||
private var lastValue: T? = null
|
||||
|
||||
private fun getData(): T? {
|
||||
return (_status.value as? SuccessResult)?.data
|
||||
}
|
||||
|
||||
override fun onDeviceConnecting(device: BluetoothDevice) {
|
||||
Log.d(TAG, "onDeviceConnecting()")
|
||||
}
|
||||
@@ -41,7 +45,7 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
Log.d(TAG, "onDeviceDisconnected(), reason: $reason")
|
||||
_status.value = when (reason) {
|
||||
ConnectionObserver.REASON_NOT_SUPPORTED -> MissingServiceResult()
|
||||
ConnectionObserver.REASON_LINK_LOSS -> LinkLossResult()
|
||||
ConnectionObserver.REASON_LINK_LOSS -> LinkLossResult(getData()!!)
|
||||
ConnectionObserver.REASON_SUCCESS -> DisconnectedResult()
|
||||
else -> UnknownErrorResult()
|
||||
}
|
||||
|
||||
@@ -3,15 +3,9 @@ package no.nordicsemi.android.utils
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.ParcelableArgument
|
||||
import no.nordicsemi.android.navigation.SuccessDestinationResult
|
||||
@@ -33,20 +27,6 @@ fun SuccessDestinationResult.getDevice(): DiscoveredBluetoothDevice {
|
||||
return (argument as ParcelableArgument).value as DiscoveredBluetoothDevice
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> NavController.consumeResult(value: String): T? {
|
||||
|
||||
val secondScreenResult = currentBackStackEntry
|
||||
?.savedStateHandle
|
||||
?.getLiveData<T>(value)?.observeAsState()
|
||||
|
||||
return secondScreenResult?.value?.also {
|
||||
currentBackStackEntry
|
||||
?.savedStateHandle
|
||||
?.set(value, null)
|
||||
}
|
||||
}
|
||||
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, t ->
|
||||
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import no.nordicsemi.android.ble.common.profile.bp.BloodPressureTypes
|
||||
import java.util.*
|
||||
|
||||
data class BPSData(
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val cuffPressure: Float = 0f,
|
||||
val unit: Int = 0,
|
||||
val pulseRate: Float? = null,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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.bps.repository
|
||||
package no.nordicsemi.android.bps.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -112,12 +112,7 @@ internal class BPSManager(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return bpmCharacteristic != null && batteryLevelCharacteristic != null
|
||||
}
|
||||
|
||||
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
super.isOptionalServiceSupported(gatt) // ignore the result of this
|
||||
return icpCharacteristic != null
|
||||
return bpmCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.bps.data
|
||||
package no.nordicsemi.android.bps.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -9,7 +9,8 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.bps.repository.BPSManager
|
||||
import no.nordicsemi.android.bps.data.BPSData
|
||||
import no.nordicsemi.android.bps.data.BPSManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -36,7 +36,9 @@ internal fun BPSSensorsReadingView(state: BPSData) {
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.bps.data.BPSRepository
|
||||
import no.nordicsemi.android.bps.repository.BPS_SERVICE_UUID
|
||||
import no.nordicsemi.android.bps.data.BPS_SERVICE_UUID
|
||||
import no.nordicsemi.android.bps.repository.BPSRepository
|
||||
import no.nordicsemi.android.bps.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package no.nordicsemi.android.bps
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.bps.repository.BPSManager
|
||||
import no.nordicsemi.android.bps.view.DisconnectEvent
|
||||
import no.nordicsemi.android.bps.viewmodel.BPSViewModel
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class BPSViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = BPSRepository()
|
||||
val manager = mockk<BPSManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
every { manager.isConnected } returns true
|
||||
justRun { manager.setConnectionObserver(any()) }
|
||||
justRun { manager.disconnect().enqueue() }
|
||||
|
||||
val viewModel = BPSViewModel(manager, repository, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by manager
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if manager not connected event returns success`() {
|
||||
val repository = BPSRepository()
|
||||
val manager = mockk<BPSManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
every { manager.isConnected } returns false
|
||||
justRun { manager.setConnectionObserver(any()) }
|
||||
|
||||
val viewModel = BPSViewModel(manager, repository, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,6 @@ package no.nordicsemi.android.cgms.data
|
||||
|
||||
internal data class CGMData(
|
||||
val records: List<CGMRecord> = emptyList(),
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val requestStatus: RequestStatus = RequestStatus.IDLE
|
||||
) {
|
||||
|
||||
fun copyWithNewRecord(record: CGMRecord): CGMData {
|
||||
return copy(records = records + record)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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.cgms.repository
|
||||
package no.nordicsemi.android.cgms.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -44,9 +44,7 @@ import no.nordicsemi.android.ble.common.profile.cgm.CGMSpecificOpsControlPointCa
|
||||
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.ble.ktx.suspendForValidResponse
|
||||
import no.nordicsemi.android.cgms.data.CGMData
|
||||
import no.nordicsemi.android.cgms.data.CGMRecord
|
||||
import no.nordicsemi.android.cgms.data.RequestStatus
|
||||
import no.nordicsemi.android.cgms.repository.toList
|
||||
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||
import no.nordicsemi.android.utils.launchWithCatch
|
||||
import java.util.*
|
||||
@@ -105,20 +103,18 @@ internal class CGMManager(
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
|
||||
enableNotifications(cgmMeasurementCharacteristic).enqueue()
|
||||
enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue()
|
||||
enableIndications(recordAccessControlPointCharacteristic).enqueue()
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
|
||||
setNotificationCallback(cgmMeasurementCharacteristic).asValidResponseFlow<ContinuousGlucoseMeasurementResponse>()
|
||||
.onEach {
|
||||
if (sessionStartTime == 0L && !recordAccessRequestInProgress) {
|
||||
sessionStartTime = System.currentTimeMillis() - it.timeOffset * 60000L
|
||||
val timeOffset = it.items.minOf { it.timeOffset }
|
||||
sessionStartTime = System.currentTimeMillis() - timeOffset * 60000L
|
||||
}
|
||||
|
||||
val timestamp = sessionStartTime + it.timeOffset * 60000L
|
||||
val record = CGMRecord(it.timeOffset, it.glucoseConcentration, timestamp)
|
||||
records.put(record.sequenceNumber, record)
|
||||
it.items.map {
|
||||
val timestamp = sessionStartTime + it.timeOffset * 60000L
|
||||
val item = CGMRecord(it.timeOffset, it.glucoseConcentration, timestamp)
|
||||
records.put(item.sequenceNumber, item)
|
||||
}
|
||||
|
||||
data.value = data.value.copy(records = records.toList())
|
||||
}.launchIn(scope)
|
||||
@@ -162,15 +158,24 @@ internal class CGMManager(
|
||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||
}.launchIn(scope)
|
||||
|
||||
enableNotifications(cgmMeasurementCharacteristic).enqueue()
|
||||
enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue()
|
||||
enableIndications(recordAccessControlPointCharacteristic).enqueue()
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
|
||||
scope.launchWithCatch {
|
||||
val cgmResponse = readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse<CGMFeatureResponse>()
|
||||
this@CGMManager.secured = cgmResponse.features.e2eCrcSupported
|
||||
}
|
||||
|
||||
scope.launchWithCatch {
|
||||
val response = readCharacteristic(cgmStatusCharacteristic).suspendForValidResponse<CGMStatusResponse>()
|
||||
if (response.status?.sessionStopped == false) {
|
||||
sessionStartTime = System.currentTimeMillis() - response.timeOffset * 60000L
|
||||
}
|
||||
}
|
||||
|
||||
scope.launchWithCatch {
|
||||
if (sessionStartTime == 0L) {
|
||||
writeCharacteristic(
|
||||
cgmSpecificOpsControlPointCharacteristic,
|
||||
@@ -240,8 +245,7 @@ internal class CGMManager(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return batteryLevelCharacteristic != null
|
||||
&& cgmMeasurementCharacteristic != null
|
||||
return cgmMeasurementCharacteristic != null
|
||||
&& cgmSpecificOpsControlPointCharacteristic != null
|
||||
&& recordAccessControlPointCharacteristic != null
|
||||
&& cgmStatusCharacteristic != null
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.cgms.data
|
||||
package no.nordicsemi.android.cgms.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.cgms.repository.CGMManager
|
||||
import no.nordicsemi.android.cgms.repository.CGMService
|
||||
import no.nordicsemi.android.cgms.data.CGMData
|
||||
import no.nordicsemi.android.cgms.data.CGMManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -38,9 +38,11 @@ internal fun CGMContentView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(DisconnectEvent) }
|
||||
|
||||
@@ -5,9 +5,9 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.CGMS_SERVICE_UUID
|
||||
import no.nordicsemi.android.cgms.repository.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||
import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID
|
||||
import no.nordicsemi.android.cgms.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cgms_title">CGMS</string>
|
||||
<string name="cgms_no_records_info">There is no data available. Every record is created once a minute. Please wait.</string>
|
||||
<string name="cgms_no_records_info">There is no data available. Every record is created once a minute or longer. Please wait.</string>
|
||||
|
||||
<string name="cgms__working_mode__all">All</string>
|
||||
<string name="cgms__working_mode__last">Last</string>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.cgms
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.view.DisconnectEvent
|
||||
import no.nordicsemi.android.cgms.viewmodel.CGMScreenViewModel
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class CGMSViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = CGMRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = CGMScreenViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = CGMRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = CGMScreenViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ internal data class CSCData(
|
||||
val distance: Float = 0f,
|
||||
val totalDistance: Float = 0f,
|
||||
val gearRatio: Float = 0f,
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val wheelSize: WheelSize = WheelSize()
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.csc.repository
|
||||
package no.nordicsemi.android.csc.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -114,7 +114,7 @@ internal class CSCManager(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return cscMeasurementCharacteristic != null && batteryLevelCharacteristic != null
|
||||
return cscMeasurementCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.csc.data
|
||||
package no.nordicsemi.android.csc.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -7,8 +7,9 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.csc.repository.CSCManager
|
||||
import no.nordicsemi.android.csc.repository.CSCService
|
||||
import no.nordicsemi.android.csc.data.CSCData
|
||||
import no.nordicsemi.android.csc.data.CSCManager
|
||||
import no.nordicsemi.android.csc.data.WheelSize
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -40,7 +40,9 @@ internal fun SensorsReadingView(state: CSCData, speedUnit: SpeedUnit) {
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -5,8 +5,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.csc.repository.CSC_SERVICE_UUID
|
||||
import no.nordicsemi.android.csc.data.CSC_SERVICE_UUID
|
||||
import no.nordicsemi.android.csc.repository.CSCRepository
|
||||
import no.nordicsemi.android.csc.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.csc
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.csc.view.OnDisconnectButtonClick
|
||||
import no.nordicsemi.android.csc.viewmodel.CSCViewModel
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class CSCViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = CSCRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = CSCViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(OnDisconnectButtonClick)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = CSCRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = CSCViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(OnDisconnectButtonClick)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@ package no.nordicsemi.android.gls.data
|
||||
|
||||
internal data class GLSData(
|
||||
val records: List<GLSRecord> = emptyList(),
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val requestStatus: RequestStatus = RequestStatus.IDLE
|
||||
)
|
||||
|
||||
@@ -175,17 +175,10 @@ internal class GLSManager @Inject constructor(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null && batteryLevelCharacteristic != null
|
||||
return glucoseMeasurementCharacteristic != null && recordAccessControlPointCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {}
|
||||
|
||||
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
super.isOptionalServiceSupported(gatt)
|
||||
return glucoseMeasurementContextCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onDeviceDisconnected() {
|
||||
override fun onServicesInvalidated() {
|
||||
glucoseMeasurementCharacteristic = null
|
||||
glucoseMeasurementContextCharacteristic = null
|
||||
recordAccessControlPointCharacteristic = null
|
||||
|
||||
@@ -45,9 +45,11 @@ internal fun GLSContentView(state: GLSData, onEvent: (GLSScreenViewEvent) -> Uni
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(DisconnectEvent) }
|
||||
|
||||
@@ -5,7 +5,7 @@ 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.repository.GLSRepository
|
||||
import no.nordicsemi.android.gls.data.GLS_SERVICE_UUID
|
||||
import no.nordicsemi.android.gls.main.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.gls.data
|
||||
package no.nordicsemi.android.gls.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -11,6 +11,9 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.gls.data.GLSData
|
||||
import no.nordicsemi.android.gls.data.GLSManager
|
||||
import no.nordicsemi.android.gls.data.WorkingMode
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import javax.inject.Inject
|
||||
@@ -1,82 +0,0 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.gls.data.GLSRepository
|
||||
import no.nordicsemi.android.gls.main.view.DisconnectEvent
|
||||
import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel
|
||||
import no.nordicsemi.android.gls.repository.GLSManager
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class GLSViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = GLSRepository()
|
||||
val manager = mockk<GLSManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
every { manager.isConnected } returns true
|
||||
justRun { manager.setConnectionObserver(any()) }
|
||||
justRun { manager.disconnect().enqueue() }
|
||||
|
||||
val viewModel = GLSViewModel(manager, repository, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by manager
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if manager not connected event returns success`() {
|
||||
val repository = GLSRepository()
|
||||
val manager = mockk<GLSManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
every { manager.isConnected } returns false
|
||||
justRun { manager.setConnectionObserver(any()) }
|
||||
|
||||
val viewModel = GLSViewModel(manager, repository, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@ package no.nordicsemi.android.hrs.data
|
||||
|
||||
internal data class HRSData(
|
||||
val heartRates: List<Int> = emptyList(),
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val sensorLocation: Int = 0,
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.hrs.service
|
||||
package no.nordicsemi.android.hrs.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -34,7 +34,6 @@ import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationResponse
|
||||
import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementResponse
|
||||
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
||||
import no.nordicsemi.android.ble.ktx.suspendForValidResponse
|
||||
import no.nordicsemi.android.hrs.data.HRSData
|
||||
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||
import no.nordicsemi.android.utils.launchWithCatch
|
||||
import java.util.*
|
||||
@@ -99,19 +98,12 @@ internal class HRSManager(
|
||||
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
gatt.getService(HRS_SERVICE_UUID)?.run {
|
||||
heartRateCharacteristic = getCharacteristic(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID)
|
||||
bodySensorLocationCharacteristic = getCharacteristic(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID)
|
||||
}
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return heartRateCharacteristic != null && batteryLevelCharacteristic != null
|
||||
}
|
||||
|
||||
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
super.isOptionalServiceSupported(gatt)
|
||||
gatt.getService(HRS_SERVICE_UUID)?.run {
|
||||
bodySensorLocationCharacteristic = getCharacteristic(BODY_SENSOR_LOCATION_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return bodySensorLocationCharacteristic != null
|
||||
return heartRateCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.hrs.data
|
||||
package no.nordicsemi.android.hrs.service
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.hrs.service.HRSManager
|
||||
import no.nordicsemi.android.hrs.service.HRSService
|
||||
import no.nordicsemi.android.hrs.data.HRSData
|
||||
import no.nordicsemi.android.hrs.data.HRSManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -34,9 +34,11 @@ internal fun HRSContentView(state: HRSData, onEvent: (HRSScreenViewEvent) -> Uni
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(DisconnectEvent) }
|
||||
|
||||
@@ -5,8 +5,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.hrs.service.HRS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hrs.data.HRS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hrs.service.HRSRepository
|
||||
import no.nordicsemi.android.hrs.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.hrs
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.hrs.view.DisconnectEvent
|
||||
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class HRSViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = HRSRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = HRSViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = HRSRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = HRSViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,5 @@ package no.nordicsemi.android.hts.data
|
||||
|
||||
internal data class HTSData(
|
||||
val temperatureValue: Float = 0f,
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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.repository
|
||||
package no.nordicsemi.android.hts.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -89,7 +89,7 @@ internal class HTSManager internal constructor(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return htCharacteristic != null && batteryLevelCharacteristic != null
|
||||
return htCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
@@ -1,3 +0,0 @@
|
||||
package no.nordicsemi.android.hts.data
|
||||
|
||||
internal object DisconnectCommand
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.hts.data
|
||||
package no.nordicsemi.android.hts.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.hts.repository.HTSManager
|
||||
import no.nordicsemi.android.hts.repository.HTSService
|
||||
import no.nordicsemi.android.hts.data.HTSData
|
||||
import no.nordicsemi.android.hts.data.HTSManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -50,9 +50,11 @@ internal fun HTSContentView(state: HTSData, temperatureUnit: TemperatureUnit, on
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(DisconnectEvent) }
|
||||
|
||||
@@ -5,8 +5,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.hts.repository.HTS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hts.data.HTS_SERVICE_UUID
|
||||
import no.nordicsemi.android.hts.repository.HTSRepository
|
||||
import no.nordicsemi.android.hts.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.hts
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.hts.view.DisconnectEvent
|
||||
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class HTSViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = HTSRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = HTSViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = HTSRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = HTSViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package no.nordicsemi.android.prx.data
|
||||
|
||||
internal sealed class PRXCommand
|
||||
|
||||
internal object EnableAlarm : PRXCommand()
|
||||
|
||||
internal object DisableAlarm : PRXCommand()
|
||||
|
||||
internal object Disconnect : PRXCommand()
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.prx.data
|
||||
|
||||
internal data class PRXData(
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val localAlarmLevel: AlarmLevel = AlarmLevel.NONE,
|
||||
val isRemoteAlarm: Boolean = false,
|
||||
val linkLossAlarmLevel: AlarmLevel = AlarmLevel.HIGH
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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.repository
|
||||
package no.nordicsemi.android.prx.data
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
@@ -124,15 +124,10 @@ internal class PRXManager(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return linkLossCharacteristic != null
|
||||
}
|
||||
|
||||
override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
super.isOptionalServiceSupported(gatt)
|
||||
gatt.getService(PRX_SERVICE_UUID)?.run {
|
||||
alertLevelCharacteristic = getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return alertLevelCharacteristic != null && batteryLevelCharacteristic != null
|
||||
return linkLossCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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.repository
|
||||
package no.nordicsemi.android.prx.data
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattService
|
||||
@@ -20,7 +20,7 @@ internal class AlarmHandler @Inject constructor(
|
||||
private val TAG = "ALARM_MANAGER"
|
||||
|
||||
private var mediaPlayer = MediaPlayer()
|
||||
private var originalVolume = 0
|
||||
private var volume = 0
|
||||
|
||||
init {
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM)
|
||||
@@ -38,17 +38,16 @@ internal class AlarmHandler @Inject constructor(
|
||||
|
||||
fun playAlarm(alarmLevel: AlarmLevel) {
|
||||
val am = context.getSystemService(LifecycleService.AUDIO_SERVICE) as AudioManager
|
||||
originalVolume = am.getStreamVolume(AudioManager.STREAM_ALARM)
|
||||
|
||||
val soundLevel = when (alarmLevel) {
|
||||
volume = when (alarmLevel) {
|
||||
AlarmLevel.NONE -> 0
|
||||
AlarmLevel.MEDIUM -> originalVolume / 2
|
||||
AlarmLevel.HIGH -> originalVolume
|
||||
AlarmLevel.MEDIUM -> am.getStreamVolume(AudioManager.STREAM_NOTIFICATION)
|
||||
AlarmLevel.HIGH -> am.getStreamVolume(AudioManager.STREAM_ALARM)
|
||||
}
|
||||
|
||||
am.setStreamVolume(
|
||||
AudioManager.STREAM_ALARM,
|
||||
soundLevel,
|
||||
volume,
|
||||
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE
|
||||
)
|
||||
try {
|
||||
@@ -65,7 +64,7 @@ internal class AlarmHandler @Inject constructor(
|
||||
mediaPlayer.stop()
|
||||
// Restore original volume
|
||||
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
am.setStreamVolume(AudioManager.STREAM_ALARM, originalVolume, 0)
|
||||
am.setStreamVolume(AudioManager.STREAM_ALARM, volume, 0)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Prepare Alarm failed: ", e)
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package no.nordicsemi.android.prx.data
|
||||
package no.nordicsemi.android.prx.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.prx.repository.AlarmHandler
|
||||
import no.nordicsemi.android.prx.repository.PRXManager
|
||||
import no.nordicsemi.android.prx.repository.PRXService
|
||||
import no.nordicsemi.android.prx.repository.ProximityServerManager
|
||||
import no.nordicsemi.android.prx.data.AlarmLevel
|
||||
import no.nordicsemi.android.prx.data.PRXData
|
||||
import no.nordicsemi.android.prx.data.PRXManager
|
||||
import no.nordicsemi.android.prx.data.ProximityServerManager
|
||||
import no.nordicsemi.android.service.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -47,11 +45,7 @@ class PRXRepository @Inject internal constructor(
|
||||
handleLocalAlarm(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
manager.start(device)
|
||||
}
|
||||
|
||||
private fun PRXManager.start(device: BluetoothDevice) {
|
||||
connect(device)
|
||||
manager.connect(device)
|
||||
.useAutoConnect(true)
|
||||
.retry(3, 100)
|
||||
.enqueue()
|
||||
@@ -65,10 +59,9 @@ class PRXRepository @Inject internal constructor(
|
||||
alarmHandler.pauseAlarm()
|
||||
}
|
||||
}
|
||||
// (result as? LinkLossResult<PRXData>)?.let {
|
||||
// alarmHandler.playAlarm(it.data.localAlarmLevel)
|
||||
// repository.setLocalAlarmLevel(repository.data.value.linkLossAlarmLevel)
|
||||
// }
|
||||
(result as? LinkLossResult<PRXData>)?.let {
|
||||
alarmHandler.playAlarm(it.data.linkLossAlarmLevel)
|
||||
}
|
||||
}
|
||||
|
||||
fun enableAlarm() {
|
||||
@@ -80,6 +73,7 @@ class PRXRepository @Inject internal constructor(
|
||||
}
|
||||
|
||||
fun release() {
|
||||
alarmHandler.releaseAlarm()
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
}
|
||||
@@ -6,7 +6,6 @@ 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.PRXRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -34,9 +34,11 @@ internal fun ContentView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(DisconnectEvent) }
|
||||
@@ -89,12 +91,12 @@ private fun RecordsSection(state: PRXData) {
|
||||
Column {
|
||||
KeyValueField(
|
||||
stringResource(id = R.string.prx_is_remote_alarm),
|
||||
state.isRemoteAlarm.toString()
|
||||
state.isRemoteAlarm.toDisplayString()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
KeyValueField(
|
||||
stringResource(id = R.string.prx_local_alarm_level),
|
||||
state.localAlarmLevel.toDisplayString()
|
||||
state.localAlarmLevel.toDisplayString().uppercase()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@ import androidx.compose.ui.res.stringResource
|
||||
import no.nordicsemi.android.prx.R
|
||||
import no.nordicsemi.android.prx.data.AlarmLevel
|
||||
|
||||
@Composable
|
||||
internal fun Boolean.toDisplayString(): String {
|
||||
return when (this) {
|
||||
true -> stringResource(id = R.string.prx_alarm_on)
|
||||
false -> stringResource(id = R.string.prx_alarm_off)
|
||||
}.uppercase()
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun AlarmLevel.toDisplayString(): String {
|
||||
return when (this) {
|
||||
|
||||
@@ -6,8 +6,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.prx.repository.PRX_SERVICE_UUID
|
||||
import no.nordicsemi.android.prx.data.PRX_SERVICE_UUID
|
||||
import no.nordicsemi.android.prx.repository.PRXRepository
|
||||
import no.nordicsemi.android.prx.view.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.utils.getDevice
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
<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_silent_me">Silent me</string>
|
||||
<string name="prx_find_me">Find me</string>
|
||||
|
||||
<string name="prx_records">Data</string>
|
||||
<string name="prx_settings">Settings</string>
|
||||
|
||||
<string name="prx_alarm_on">on</string>
|
||||
<string name="prx_alarm_off">off</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>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.prx
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.prx.view.DisconnectEvent
|
||||
import no.nordicsemi.android.prx.viewmodel.PRXViewModel
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class PRXViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = PRXRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = PRXViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = PRXRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = PRXViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.rscs.data
|
||||
|
||||
internal data class RSCSData(
|
||||
val batteryLevel: Int = 0,
|
||||
val batteryLevel: Int? = null,
|
||||
val running: Boolean = false,
|
||||
val instantaneousSpeed: Float = 1.0f,
|
||||
val instantaneousCadence: Int = 0,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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.rscs.repository
|
||||
package no.nordicsemi.android.rscs.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -94,7 +94,7 @@ internal class RSCSManager internal constructor(
|
||||
gatt.getService(BATTERY_SERVICE_UUID)?.run {
|
||||
batteryLevelCharacteristic = getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
|
||||
}
|
||||
return rscMeasurementCharacteristic != null && batteryLevelCharacteristic != null
|
||||
return rscMeasurementCharacteristic != null
|
||||
}
|
||||
|
||||
override fun onServicesInvalidated() {
|
||||
@@ -1,3 +0,0 @@
|
||||
package no.nordicsemi.android.rscs.data
|
||||
|
||||
internal object DisconnectCommand
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.rscs.data
|
||||
package no.nordicsemi.android.rscs.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.rscs.repository.RSCSManager
|
||||
import no.nordicsemi.android.rscs.repository.RSCSService
|
||||
import no.nordicsemi.android.rscs.data.RSCSData
|
||||
import no.nordicsemi.android.rscs.data.RSCSManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -26,7 +26,11 @@ internal fun RSCSContentView(state: RSCSData, onEvent: (RSCScreenViewEvent) -> U
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BatteryLevelView(state.batteryLevel)
|
||||
state.batteryLevel?.let {
|
||||
BatteryLevelView(it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.rscs.repository.RSCS_SERVICE_UUID
|
||||
import no.nordicsemi.android.rscs.data.RSCS_SERVICE_UUID
|
||||
import no.nordicsemi.android.rscs.repository.RSCSRepository
|
||||
import no.nordicsemi.android.rscs.view.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.utils.getDevice
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.rscs
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.rscs.view.DisconnectEvent
|
||||
import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class RSCSViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = RSCSRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = RSCSViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = RSCSRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = RSCSViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,5 @@ import no.nordicsemi.android.utils.EMPTY
|
||||
|
||||
internal data class UARTData(
|
||||
val text: String = String.EMPTY,
|
||||
val batteryLevel: Int = 0
|
||||
val batteryLevel: Int? = null,
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.uart.repository
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
@@ -1,7 +0,0 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
internal sealed class UARTServiceCommand
|
||||
|
||||
internal data class SendTextCommand(val command: String) : UARTServiceCommand()
|
||||
|
||||
internal object DisconnectCommand : UARTServiceCommand()
|
||||
@@ -1,4 +1,4 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
package no.nordicsemi.android.uart.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
@@ -10,8 +10,9 @@ import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.uart.repository.UARTManager
|
||||
import no.nordicsemi.android.uart.repository.UARTService
|
||||
import no.nordicsemi.android.uart.data.UARTData
|
||||
import no.nordicsemi.android.uart.data.UARTMacro
|
||||
import no.nordicsemi.android.uart.data.UARTManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.uart.data.UARTMacro
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import no.nordicsemi.android.uart.repository.UART_SERVICE_UUID
|
||||
import no.nordicsemi.android.uart.data.UART_SERVICE_UUID
|
||||
import no.nordicsemi.android.uart.repository.UARTRepository
|
||||
import no.nordicsemi.android.uart.view.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.utils.getDevice
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.navigation.NavigationManager
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import no.nordicsemi.android.uart.view.OnDisconnectButtonClick
|
||||
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UARTViewModelTest {
|
||||
|
||||
val dispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect event returns success`() {
|
||||
val repository = UARTRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = UARTViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(OnDisconnectButtonClick)
|
||||
|
||||
//Invoke by remote service
|
||||
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check if navigation up called after disconnect if no service started event returns success`() {
|
||||
val repository = UARTRepository()
|
||||
val serviceManager = mockk<ServiceManager>()
|
||||
val navigationManager = mockk<NavigationManager>()
|
||||
|
||||
every { navigationManager.recentResult } returns MutableSharedFlow(
|
||||
extraBufferCapacity = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
justRun { navigationManager.navigateTo(any(), any()) }
|
||||
|
||||
val viewModel = UARTViewModel(repository, serviceManager, navigationManager)
|
||||
|
||||
viewModel.onEvent(OnDisconnectButtonClick)
|
||||
|
||||
verify { navigationManager.navigateUp() }
|
||||
}
|
||||
}
|
||||
@@ -27,38 +27,37 @@ dependencyResolutionManagement {
|
||||
alias('nordic-theme').to('no.nordicsemi.android.common', 'theme').versionRef('commonlibraries')
|
||||
|
||||
alias('localbroadcastmanager').to('androidx.localbroadcastmanager:localbroadcastmanager:1.1.0')
|
||||
alias('material').to('com.google.android.material:material:1.6.0-alpha01')
|
||||
alias('material').to('com.google.android.material:material:1.6.0-alpha02')
|
||||
|
||||
version('lifecycle', '2.4.0')
|
||||
version('lifecycle', '2.4.1')
|
||||
alias('lifecycle-activity').to('androidx.lifecycle', 'lifecycle-runtime-ktx').versionRef('lifecycle')
|
||||
alias('lifecycle-service').to('androidx.lifecycle', 'lifecycle-service').versionRef('lifecycle')
|
||||
alias('compose-lifecycle').to('androidx.lifecycle', 'lifecycle-viewmodel-compose').versionRef('lifecycle')
|
||||
|
||||
alias('androidx-core').to('androidx.core:core-ktx:1.7.0')
|
||||
alias('compose-activity').to('androidx.activity:activity-compose:1.4.0')
|
||||
alias('compose-lifecycle').to('androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0')
|
||||
|
||||
version('compose', '1.1.0')
|
||||
alias('compose-livedata').to('androidx.compose.runtime', 'runtime-livedata').versionRef('compose')
|
||||
alias('compose-ui').to('androidx.compose.ui', 'ui').versionRef('compose')
|
||||
alias('compose-material').to('androidx.compose.material3:material3:1.0.0-alpha04')
|
||||
alias('compose-material').to('androidx.compose.material3:material3:1.0.0-alpha05')
|
||||
alias('compose-tooling-preview').to('androidx.compose.ui', 'ui-tooling-preview').versionRef('compose')
|
||||
alias('compose-navigation').to('androidx.navigation:navigation-compose:2.4.0-alpha09')
|
||||
bundle('compose', ['compose-livedata', 'compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
||||
alias('compose-navigation').to('androidx.navigation:navigation-compose:2.4.1')
|
||||
bundle('compose', ['compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
||||
|
||||
alias('material-icons').to('androidx.compose.material', 'material-icons-core').versionRef('compose')
|
||||
alias('material-icons-extended').to('androidx.compose.material', 'material-icons-extended').versionRef('compose')
|
||||
bundle('icons', ['material-icons', 'material-icons-extended'])
|
||||
|
||||
alias('hilt-android').to('com.google.dagger:hilt-android:2.38.1')
|
||||
alias('hilt-android').to('com.google.dagger:hilt-android:2.40.4')
|
||||
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-compose').to('androidx.hilt:hilt-navigation-compose:1.0.0')
|
||||
alias('hilt-lifecycle').to('androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03')
|
||||
alias('hilt-lifecyclecompiler').to('androidx.hilt:hilt-compiler:1.0.0')
|
||||
bundle('hilt', ['hilt-android', 'hilt-compose', 'hilt-lifecycle'])
|
||||
bundle('hiltkapt', ['hilt-compiler', 'hilt-lifecyclecompiler'])
|
||||
|
||||
alias('kotlin-coroutines').to('org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2')
|
||||
alias('google-permissions').to('com.google.accompanist:accompanist-permissions:0.18.0')
|
||||
alias('kotlin-coroutines').to('org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0')
|
||||
alias('google-permissions').to('com.google.accompanist:accompanist-permissions:0.20.0')
|
||||
alias('chart').to('com.github.PhilJay:MPAndroidChart:v3.1.0')
|
||||
|
||||
//-- Test ------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user