Fix proximity profile

This commit is contained in:
Sylwester Zieliński
2022-02-16 09:59:42 +01:00
parent 166c08a191
commit bb3ea6a783
76 changed files with 176 additions and 1019 deletions

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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>()

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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

View File

@@ -36,7 +36,9 @@ internal fun BPSSensorsReadingView(state: BPSData) {
Spacer(modifier = Modifier.height(16.dp))
BatteryLevelView(state.batteryLevel)
state.batteryLevel?.let {
BatteryLevelView(it)
}
}
@Preview

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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)
}
}
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) }

View File

@@ -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

View File

@@ -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>

View File

@@ -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() }
}
}

View File

@@ -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()
)

View File

@@ -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() {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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
)

View File

@@ -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

View File

@@ -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) }

View File

@@ -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.*

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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,
)

View File

@@ -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() {

View File

@@ -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

View File

@@ -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

View File

@@ -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) }

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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,
)

View File

@@ -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() {

View File

@@ -1,3 +0,0 @@
package no.nordicsemi.android.hts.data
internal object DisconnectCommand

View File

@@ -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

View File

@@ -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

View File

@@ -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) }

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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()
)
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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>

View File

@@ -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() }
}
}

View File

@@ -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,

View File

@@ -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() {

View File

@@ -1,3 +0,0 @@
package no.nordicsemi.android.rscs.data
internal object DisconnectCommand

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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 ------------------------------------------------------------------------------