diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 9a31328..0000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 726a9f9..e3fed92 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,11 +2,18 @@ - - - - - + + + + + + + + = Build.VERSION_CODES.S) { + rememberMultiplePermissionsState( + listOf( + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.BLUETOOTH_CONNECT + ) + ) + } else { + rememberMultiplePermissionsState( + listOf() + ) + } + + if(multiplePermissionsState.allPermissionsGranted) { + + MainScreen() + + } else { + + LaunchedEffect(multiplePermissionsState){ + multiplePermissionsState.launchMultiplePermissionRequest() + } + + } + } + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt index 50c61d9..404422f 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.flow.launchIn @@ -49,6 +50,13 @@ fun BleListScreen( } ) + if(state.bleList.isEmpty()){ + LinearProgressIndicator( + strokeCap = StrokeCap.Round, + modifier = Modifier.fillMaxWidth().height(3.dp) + ) + } + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxSize() diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt index d082ddc..61b7b53 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListViewModel.kt @@ -18,9 +18,17 @@ class BleListViewModel @Inject constructor( init { getBleAroundFlow().onEach { - setState { - BleListContract.State(it) - } + it.fold( + onSuccess = { + setState { + BleListContract.State(it) + } + }, + onFailure = { + + } + ) + }.launchIn(viewModelScope) } diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt index b9cb9e7..3f294fd 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/connection/ConnectionScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.flow.launchIn @@ -123,7 +124,11 @@ fun ConnectionScreen( private fun LoadingState(){ Column { Box(modifier = Modifier.fillMaxSize()) { - CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + CircularProgressIndicator( + strokeCap = StrokeCap.Round, + modifier = Modifier.align(Alignment.Center + ) + ) } } } diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt index 2694001..b4e53fc 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/ThermometerScreen.kt @@ -26,6 +26,31 @@ enum class SheetPage { INTERVAL, POWER, TEMPERATURE_HISTORY } +private val Boolean.localizedName: String + get() { + return if(this){ + "Включено" + } else { + "Выключено" + } + } + +private val Ble.BleState.TX.localizedName: String + get() { + return when(this){ + Ble.BleState.TX.MINUS_40 -> -40 + Ble.BleState.TX.MINUS_20 -> -20 + Ble.BleState.TX.MINUS_16 -> -16 + Ble.BleState.TX.MINUS_12 -> -12 + Ble.BleState.TX.MINUS_8 -> -8 + Ble.BleState.TX.MINUS_4 -> -4 + Ble.BleState.TX.ZERO -> 0 + Ble.BleState.TX.PLUS_3 -> 3 + Ble.BleState.TX.PLUS_4 -> 4 + }.toString() + + } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun ThermometerScreen( @@ -140,8 +165,6 @@ fun ThermometerScreen( ) - - } } @@ -197,7 +220,7 @@ fun ThermometerScreen( Text( color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.bodyMedium, - text = "${it} db" + text = "${state.origin.state.tx.localizedName} db -> ${it.localizedName} db" ) } @@ -209,6 +232,39 @@ fun ThermometerScreen( it.writeRequest.saveHistory?.let { + Box( + modifier = Modifier.padding( + vertical = 8.dp, + horizontal = 8.dp + ) + ) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .padding(8.dp) + ) { + + Column( + modifier = Modifier.weight(1f) + ) { + + Text( + text = "Сохранять историю измерений" + ) + Text( + color = MaterialTheme.colorScheme.secondary, + style = MaterialTheme.typography.bodyMedium, + text = "${state.origin.thermometerState.saveHistory.localizedName} -> ${it.localizedName}" + ) + + } + + } + + } + } it.writeRequest.historyInterval?.let { diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/DisplayState.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/DisplayState.kt index f4a5a2d..96f3502 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/DisplayState.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/DisplayState.kt @@ -119,19 +119,6 @@ fun DisplayState( } - if (ble.thermometerState.temperature.loading) { - - CircularProgressIndicator() - - } else { - - Icon( - imageVector = Icons.Rounded.Refresh, - contentDescription = null - ) - - } - } } @@ -194,7 +181,7 @@ fun DisplayState( ) { Text( - text = "Интервал измерний" + text = "Интервал измерений" ) Text( color = MaterialTheme.colorScheme.secondary, diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/TemperatureHistory.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/TemperatureHistory.kt index 21cea30..815172c 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/TemperatureHistory.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/thermometer/view/TemperatureHistory.kt @@ -1,5 +1,9 @@ package llc.arma.ble.app.ui.screen.thermometer.view +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* @@ -32,9 +36,15 @@ import kotlin.random.Random import kotlin.random.nextInt import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Refresh +import androidx.compose.ui.graphics.StrokeCap +import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState import com.patrykandpatrick.vico.core.axis.AxisPosition import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter +import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp import com.patrykandpatrick.vico.core.entry.ChartEntry +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.model.Ble import java.text.SimpleDateFormat import java.util.* @@ -80,7 +90,7 @@ fun TemperatureHistory( onClick = { viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial)) }, - enabled = state is TemperatureHistoryContract.State.Display + enabled = state.loadingHistoryState is ProgressState.Finished ) { Icon( imageVector = Icons.Rounded.Refresh, @@ -92,12 +102,14 @@ fun TemperatureHistory( Spacer(modifier = Modifier.height(16.dp)) - when (state) { - is TemperatureHistoryContract.State.Display -> { - - val producer = state.history.mapIndexed { index, measurePoint -> + when (state.loadingHistoryState) { + is ProgressState.Finished -> { + + Text(text = "${state.loadingHistoryState.data.size}") + + val producer = state.loadingHistoryState.data.mapIndexed { index, measurePoint -> TemperatureEntry(measurePoint.date, index.toFloat(), measurePoint.value) }.let { - ChartEntryModelProducer(it) + ChartEntryModelProducer(it) } val axisValueFormatter = AxisValueFormatter { value, chartValues -> @@ -107,14 +119,20 @@ fun TemperatureHistory( .orEmpty() } - val lineChart = lineChart() + val lineChart = lineChart( + spacing = 110.dp + ) Box(modifier = Modifier.padding(8.dp)) { + val scrollState = rememberChartScrollState() + + LaunchedEffect(scrollState.maxValue){ + scrollState.scrollBy(scrollState.maxValue) + } + Chart( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1.5f), + chartScrollState = scrollState, chart = lineChart, chartModelProducer = producer, startAxis = startAxis(), @@ -122,13 +140,15 @@ fun TemperatureHistory( valueFormatter = axisValueFormatter, labelRotationDegrees = 0f, ), + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1.5f), ) } - } - is TemperatureHistoryContract.State.Loading -> { - + is ProgressState.Indeterminate -> { + Box(modifier = Modifier.padding(8.dp)) { Box( @@ -138,15 +158,37 @@ fun TemperatureHistory( ){ CircularProgressIndicator( + strokeCap = StrokeCap.Round, modifier = Modifier.align(Alignment.Center) ) } } + } + is ProgressState.Progress -> Box(modifier = Modifier.padding(8.dp)) { + + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(2f), + ){ + + val progressAnimDuration = 1500 + val progressAnimation by animateFloatAsState( + targetValue = state.loadingHistoryState.value, + animationSpec = tween(durationMillis = progressAnimDuration, easing = FastOutSlowInEasing) + ) + + CircularProgressIndicator( + strokeCap = StrokeCap.Round, + progress = progressAnimation, + modifier = Modifier.align(Alignment.Center) + ) + + } } - } } @@ -163,15 +205,9 @@ class TemperatureHistoryContract { } - sealed class State : ViewState { - - object Loading : State() - - data class Display( - var history : List - ) : State() - - } + class State( + val loadingHistoryState : ProgressState> + ) : ViewState sealed class Effect : ViewSideEffect { @@ -186,7 +222,9 @@ class TemperatureHistoryViewModel @Inject constructor( private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial ) : BaseViewModel() { - override fun setInitialState() = TemperatureHistoryContract.State.Loading + override fun setInitialState() = TemperatureHistoryContract.State( + ProgressState.Indeterminate + ) override fun handleEvents(event: TemperatureHistoryContract.Event) { when(event){ @@ -201,14 +239,21 @@ class TemperatureHistoryViewModel @Inject constructor( viewModelScope.launch { setState { - TemperatureHistoryContract.State.Loading + TemperatureHistoryContract.State(ProgressState.Indeterminate) } - val history = getTemperatureHistoryBySerial(event.serial) + getTemperatureHistoryBySerial(event.serial).onEach { + it.fold( + onSuccess = { + setState { + TemperatureHistoryContract.State(it) + } + }, + onFailure = { - setState { - TemperatureHistoryContract.State.Display(history) - } + } + ) + }.launchIn(this) } } diff --git a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt index 05d0528..a05c74d 100644 --- a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt +++ b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt @@ -8,24 +8,23 @@ import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanSettings import android.content.pm.PackageManager import android.os.Build -import android.os.ParcelUuid -import android.util.Log import androidx.core.app.ActivityCompat import kotlinx.coroutines.* import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flow import llc.arma.ble.domain.Result +import llc.arma.ble.domain.common.BleException +import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.repository.BleRepository import llc.arma.ble.domain.usecase.GetBleBySerial -import java.nio.ByteBuffer import java.util.* import javax.inject.Inject import javax.inject.Singleton import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine @Singleton class BleRepositoryImpl @Inject constructor( @@ -69,67 +68,86 @@ class BleRepositoryImpl @Inject constructor( (this[idx].toUInt() and 0xFFu) private val deviceCache = mutableMapOf() + val resultList = mutableMapOf() - override fun getBleAroundFlow(): Flow> { + override fun getBleAroundFlow(): Flow, BleException>> { - val resultList = mutableMapOf() + return if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_SCAN + ) != PackageManager.PERMISSION_GRANTED + ) { - return callbackFlow { + flow { emit(Result.failure(BleException.PermissionDenied)) } - val bleCallback = object : ScanCallback() { + } else { - override fun onScanResult( - callbackType: Int, - result: ScanResult - ) { + callbackFlow { - super.onScanResult(callbackType, result) + val bleCallback = object : ScanCallback() { - if (ActivityCompat.checkSelfPermission( - app, - Manifest.permission.BLUETOOTH_CONNECT - ) == PackageManager.PERMISSION_GRANTED + override fun onScanResult( + callbackType: Int, + result: ScanResult ) { - if(result.scanRecord?.deviceName?.contains("ArmA") == true) { + super.onScanResult(callbackType, result) - resultList[result.device.address] = result.info + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) == PackageManager.PERMISSION_GRANTED + ) { - deviceCache[result.device.address] = result + if (result.scanRecord?.deviceName?.contains("ArmA") == true) { + resultList[result.device.address] = result.info + + deviceCache[result.device.address] = result + + } + + } else { + CoroutineScope(Dispatchers.IO).launch { + send( + Result.failure(BleException.PermissionDenied) + ) + } } } } - } + val bleScanner = + app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner - val bleScanner = app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner - bleScanner.startScan( - listOf(), - ScanSettings.Builder() - .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) - .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) - .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) - .setReportDelay(0L) - .build(), - bleCallback) + bleScanner.startScan( + listOf(), + ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) + .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) + .setReportDelay(0L) + .build(), + bleCallback) - val timer = Timer().apply { - schedule(object : TimerTask() { - override fun run() { - CoroutineScope(Dispatchers.IO).launch { - send(resultList.values.toList()) + val timer = Timer().apply { + schedule(object : TimerTask() { + override fun run() { + CoroutineScope(Dispatchers.IO).launch { + send(Result.success(resultList.values.toList())) + } } - } - }, 100, 500) - } + }, 100, 500) + } + + awaitClose { + bleScanner.stopScan(bleCallback) + timer.cancel() + } - awaitClose { - bleScanner.stopScan(bleCallback) - timer.cancel() } } @@ -178,10 +196,17 @@ class BleRepositoryImpl @Inject constructor( BleInfo.Type.THERMOMETER -> { + val thermometer = readThermometerState(result).fold( + onFailure = { _ -> + return@launch it.resume(Result.failure(GetBleBySerial.GetBleException.BlePermissionDenied)) + }, + onSuccess = { return@fold it } + ) + Ble.Thermometer( info = info, state = state, - thermometerState = readThermometerState(result) + thermometerState = thermometer ) } @@ -204,33 +229,54 @@ class BleRepositoryImpl @Inject constructor( private suspend fun readThermometerState( record: ScanResult - ): Ble.Thermometer.ThermometerState { + ): Result { - return Ble.Thermometer.ThermometerState( - temperature = readTemperature(record), - saveHistory = record.timerEnabled, - historyInterval = readHistoryInterval(record) + val temperature = readTemperature(record).fold( + onFailure = { + return Result.failure(it) + }, + onSuccess = { return@fold it } + ) + + val history = readHistoryInterval(record).fold( + onFailure = { + return Result.failure(it) + }, + onSuccess = { return@fold it } + ) + + return Result.success( + Ble.Thermometer.ThermometerState( + temperature = temperature, + saveHistory = record.timerEnabled, + historyInterval = history + ) ) } private suspend fun readTemperature( record: ScanResult - ): Float { + ): Result { val dataResult = readCharacteristic( device = record.device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), characteristicId = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb") + ).fold( + onFailure = { + return Result.failure(it) + }, + onSuccess = { return@fold it } ) - return (dataResult[0] + dataResult[1] * 256).toFloat() / 100f + return Result.success((dataResult[0] + dataResult[1] * 256).toFloat() / 100f) } private suspend fun readHistoryInterval( record: ScanResult - ): Long { + ): Result { writeCharacteristic( device = record.device, @@ -243,19 +289,26 @@ class BleRepositoryImpl @Inject constructor( device = record.device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb") + ).fold( + onFailure = { + return Result.failure(it) + }, + onSuccess = { return@fold it } ) - return if(dataResult.size == 4){ - dataResult.getUIntAt(0).toLong() - }else{ - 0 - } + return Result.success( + if(dataResult.size == 4){ + dataResult.getUIntAt(0).toLong() + }else{ + 0 + } + ) } override suspend fun getTemperatureHistoryBySerial( serial: String - ): List { + ): Flow>, BleException>> = flow { fun ByteArray.getUIntAt(idx: Int) = ((this[idx + 3].toUInt() and 0xFFu) shl 24) or @@ -265,6 +318,8 @@ class BleRepositoryImpl @Inject constructor( deviceCache[serial]?.device?.let { device -> + emit(Result.success(ProgressState.Indeterminate)) + writeCharacteristic( device = device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), @@ -276,6 +331,12 @@ class BleRepositoryImpl @Inject constructor( device = device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"), + ).fold( + onFailure = { + emit(Result.failure(it)) + return@flow + }, + onSuccess = { return@fold it } ) writeCharacteristic( @@ -295,6 +356,12 @@ class BleRepositoryImpl @Inject constructor( device = device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"), + ).fold( + onFailure = { + emit(Result.failure(it)) + return@flow + }, + onSuccess = { return@fold it } ) if(firstPackageResponse[0] == 250.toByte()){ @@ -311,11 +378,14 @@ class BleRepositoryImpl @Inject constructor( (it[0] + it[1] * 256).toFloat() / 100f }.toMutableList() - Log.d("read", temperaturePackage.size.toString()) + var dataCount = firstPackageResponse[1].toUByte() + val totalDataSize = dataCount.toInt() + temperaturePackage.size - var dataCount = firstPackageResponse[1] + emit(Result.success(ProgressState.Progress(0f / totalDataSize.toFloat()))) + delay(100) + emit(Result.success(ProgressState.Progress(dataCount.toFloat() / totalDataSize.toFloat()))) - while(dataCount != 0.toByte()){ + while(dataCount != 0.toUByte()){ writeCharacteristic( device = device, @@ -328,9 +398,15 @@ class BleRepositoryImpl @Inject constructor( device = device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"), + ).fold( + onFailure = { + emit(Result.failure(it)) + return@flow + }, + onSuccess = { return@fold it } ) - dataCount = readResponse.get(1) + dataCount = readResponse[1].toUByte() temperatureDataArray = readResponse.toList().subList(2, readResponse.size) @@ -340,25 +416,27 @@ class BleRepositoryImpl @Inject constructor( } ) - Log.d("read",(temperatureDataArray.size / 2).toString()) + emit(Result.success(ProgressState.Progress(totalDataSize.toFloat() / temperaturePackage.size.toFloat()))) } - Log.d("metadata", interval.toString() + " " + lastMeasureSystemTime.toString()) - - return temperaturePackage.withIndex().map { - Ble.Thermometer.MeasurePoint( - date = lastMeasureSystemTime - (((temperaturePackage.size - 1) - it.index) * interval), - value = it.value + emit( + Result.success( + ProgressState.Finished( + temperaturePackage.withIndex().map { + Ble.Thermometer.MeasurePoint( + date = lastMeasureSystemTime - (((temperaturePackage.size - 1) - it.index) * interval), + value = it.value + ) + } + ) ) - } + ) } } - return emptyList() - } override suspend fun writeBle(ble: Ble) { @@ -381,6 +459,9 @@ class BleRepositoryImpl @Inject constructor( request.saveHistory?.let { writeSaveEnabled(result.device, it) } + deviceCache.remove(serial) + resultList.remove(serial) + } } @@ -457,24 +538,26 @@ class BleRepositoryImpl @Inject constructor( private suspend fun writeSaveEnabled( device: BluetoothDevice, enabled: Boolean - ) { + ): Result { writeCharacteristic( device = device, serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), characteristicId = UUID.fromString("0000b6f2-0000-1000-8000-00805f9b34fb"), - writeData = mutableListOf(3).apply { + writeData = mutableListOf(4).apply { add(if(enabled) 1 else 0) }.toByteArray() ) + return Result.success(Unit) + } private suspend fun readCharacteristic( device: BluetoothDevice, serviceId: UUID, characteristicId: UUID - ): ByteArray = suspendCoroutine { + ): Result = suspendCancellableCoroutine { val callback = object : BluetoothGattCallback() { @@ -492,10 +575,10 @@ class BleRepositoryImpl @Inject constructor( ) == PackageManager.PERMISSION_GRANTED ) { gatt?.discoverServices() + } else { + it.resume(Result.failure(BleException.PermissionDenied)) } - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - } } @@ -512,16 +595,16 @@ class BleRepositoryImpl @Inject constructor( service.uuid == serviceId }?.characteristics?.firstOrNull { characteristic -> characteristic.uuid == characteristicId - }?.let { + }?.let { char -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission( app, Manifest.permission.BLUETOOTH_CONNECT ) == PackageManager.PERMISSION_GRANTED ) { - - gatt.readCharacteristic(it) - + gatt.readCharacteristic(char) + } else { + it.resume(Result.failure(BleException.PermissionDenied)) } } @@ -537,14 +620,29 @@ class BleRepositoryImpl @Inject constructor( status: Int ) { super.onCharacteristicRead(gatt, characteristic, value, status) - - it.resume(value) - + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) != PackageManager.PERMISSION_GRANTED + ) { + it.resume(Result.failure(BleException.PermissionDenied)) + }else { + gatt.disconnect() + it.resume(Result.success(value)) + } } } - device.connectGatt(app, true, callback) + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) != PackageManager.PERMISSION_GRANTED + ) { + it.resume(Result.failure(BleException.PermissionDenied)) + } else { + device.connectGatt(app, true, callback) + } } @@ -553,7 +651,7 @@ class BleRepositoryImpl @Inject constructor( serviceId: UUID, characteristicId: UUID, writeData: ByteArray - ) = suspendCoroutine { + ) = suspendCancellableCoroutine { val callback = object : BluetoothGattCallback() { @@ -573,8 +671,6 @@ class BleRepositoryImpl @Inject constructor( gatt?.discoverServices() } - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - } } @@ -591,7 +687,7 @@ class BleRepositoryImpl @Inject constructor( service.uuid == serviceId }?.characteristics?.firstOrNull { characteristic -> characteristic.uuid == characteristicId - }?.let { + }?.let { char -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission( app, @@ -600,10 +696,10 @@ class BleRepositoryImpl @Inject constructor( ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - gatt.writeCharacteristic(it, writeData, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) + gatt.writeCharacteristic(char, writeData, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) }else{ - it.value = writeData - gatt.writeCharacteristic(it) + char.value = writeData + gatt.writeCharacteristic(char) } } @@ -620,7 +716,17 @@ class BleRepositoryImpl @Inject constructor( status: Int ) { super.onCharacteristicWrite(gatt, characteristic, status) - it.resume(Unit) + if (ActivityCompat.checkSelfPermission( + app, + Manifest.permission.BLUETOOTH_CONNECT + ) != PackageManager.PERMISSION_GRANTED + ) { + return + } else { + gatt.disconnect() + it.resume(Unit) + } + } } diff --git a/app/src/main/java/llc/arma/ble/domain/common/BleException.kt b/app/src/main/java/llc/arma/ble/domain/common/BleException.kt new file mode 100644 index 0000000..07bf6da --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/common/BleException.kt @@ -0,0 +1,7 @@ +package llc.arma.ble.domain.common + +sealed class BleException { + + object PermissionDenied : BleException() + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/common/ProgressState.kt b/app/src/main/java/llc/arma/ble/domain/common/ProgressState.kt new file mode 100644 index 0000000..d9bc9e3 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/common/ProgressState.kt @@ -0,0 +1,15 @@ +package llc.arma.ble.domain.common + +sealed class ProgressState { + + object Indeterminate : ProgressState() + + data class Progress( + val value: Float + ) : ProgressState() + + data class Finished( + val data: T + ) : ProgressState() + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt b/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt index 9fff913..eb24680 100644 --- a/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt +++ b/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt @@ -2,17 +2,19 @@ package llc.arma.ble.domain.repository import kotlinx.coroutines.flow.Flow import llc.arma.ble.domain.Result +import llc.arma.ble.domain.common.BleException +import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.usecase.GetBleBySerial interface BleRepository { - fun getBleAroundFlow(): Flow> + fun getBleAroundFlow(): Flow, BleException>> suspend fun getBleBySerial(serial: String): Result - suspend fun getTemperatureHistoryBySerial(serial: String): List + suspend fun getTemperatureHistoryBySerial(serial: String): Flow>, BleException>> suspend fun writeBle(ble: Ble) diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt b/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt index a25a75b..a594369 100644 --- a/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt +++ b/app/src/main/java/llc/arma/ble/domain/usecase/GetBleAroundFlow.kt @@ -1,6 +1,8 @@ package llc.arma.ble.domain.usecase import kotlinx.coroutines.flow.Flow +import llc.arma.ble.domain.Result +import llc.arma.ble.domain.common.BleException import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.repository.BleRepository @@ -10,6 +12,6 @@ class GetBleAroundFlow @Inject constructor( private val bleRepository: BleRepository ) { - operator fun invoke(): Flow> = bleRepository.getBleAroundFlow() + operator fun invoke(): Flow, BleException>> = bleRepository.getBleAroundFlow() } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/GetTemperatureHistoryBySerial.kt b/app/src/main/java/llc/arma/ble/domain/usecase/GetTemperatureHistoryBySerial.kt index af8899a..8979043 100644 --- a/app/src/main/java/llc/arma/ble/domain/usecase/GetTemperatureHistoryBySerial.kt +++ b/app/src/main/java/llc/arma/ble/domain/usecase/GetTemperatureHistoryBySerial.kt @@ -1,5 +1,9 @@ package llc.arma.ble.domain.usecase +import kotlinx.coroutines.flow.Flow +import llc.arma.ble.domain.Result +import llc.arma.ble.domain.common.BleException +import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.repository.BleRepository import javax.inject.Inject @@ -8,7 +12,7 @@ class GetTemperatureHistoryBySerial @Inject constructor( private val bleRepository: BleRepository ) { - suspend operator fun invoke(serial: String): List { + suspend operator fun invoke(serial: String): Flow>, BleException>> { return bleRepository.getTemperatureHistoryBySerial(serial) diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt b/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt index 7497509..8e876f3 100644 --- a/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt +++ b/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt @@ -13,7 +13,10 @@ class WriteBle @Inject constructor( bleRepository.writeBle(ble) } - suspend operator fun invoke(serial: String, request: Ble.Thermometer.WriteRequest){ + suspend operator fun invoke( + serial: String, + request: Ble.Thermometer.WriteRequest + ){ bleRepository.writeBle(serial, request) }