Init
This commit is contained in:
parent
ffba61a55e
commit
ce95316494
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetDropDown">
|
|
||||||
<runningDeviceTargetSelectedWithDropDown>
|
|
||||||
<Target>
|
|
||||||
<type value="RUNNING_DEVICE_TARGET" />
|
|
||||||
<deviceKey>
|
|
||||||
<Key>
|
|
||||||
<type value="SERIAL_NUMBER" />
|
|
||||||
<value value="55381e0a" />
|
|
||||||
</Key>
|
|
||||||
</deviceKey>
|
|
||||||
</Target>
|
|
||||||
</runningDeviceTargetSelectedWithDropDown>
|
|
||||||
<timeTargetWasSelectedWithDropDown value="2023-03-22T08:48:05.794601500Z" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -2,11 +2,18 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
|
android:usesPermissionFlags="neverForLocation"
|
||||||
|
tools:targetApi="s" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.location.gps" />
|
||||||
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package llc.arma.ble.app.ui
|
package llc.arma.ble.app.ui
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
|
@ -7,28 +8,65 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import llc.arma.ble.app.ui.screen.main.MainScreen
|
import llc.arma.ble.app.ui.screen.main.MainScreen
|
||||||
import llc.arma.ble.app.ui.theme.BleTheme
|
import llc.arma.ble.app.ui.theme.BleTheme
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
|
||||||
BleTheme {
|
BleTheme {
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize().navigationBarsPadding(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.navigationBarsPadding(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
MainScreen()
|
|
||||||
|
val multiplePermissionsState = if (Build.VERSION.SDK_INT >= 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
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(
|
LazyColumn(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,17 @@ class BleListViewModel @Inject constructor(
|
||||||
init {
|
init {
|
||||||
|
|
||||||
getBleAroundFlow().onEach {
|
getBleAroundFlow().onEach {
|
||||||
setState {
|
it.fold(
|
||||||
BleListContract.State(it)
|
onSuccess = {
|
||||||
}
|
setState {
|
||||||
|
BleListContract.State(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -123,7 +124,11 @@ fun ConnectionScreen(
|
||||||
private fun LoadingState(){
|
private fun LoadingState(){
|
||||||
Column {
|
Column {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
CircularProgressIndicator(
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
modifier = Modifier.align(Alignment.Center
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,31 @@ enum class SheetPage {
|
||||||
INTERVAL, POWER, TEMPERATURE_HISTORY
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ThermometerScreen(
|
fun ThermometerScreen(
|
||||||
|
|
@ -140,8 +165,6 @@ fun ThermometerScreen(
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +220,7 @@ fun ThermometerScreen(
|
||||||
Text(
|
Text(
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
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 {
|
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 {
|
it.writeRequest.historyInterval?.let {
|
||||||
|
|
|
||||||
|
|
@ -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 = "Интервал измерений"
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package llc.arma.ble.app.ui.screen.thermometer.view
|
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.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
|
@ -32,9 +36,15 @@ import kotlin.random.Random
|
||||||
import kotlin.random.nextInt
|
import kotlin.random.nextInt
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
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.AxisPosition
|
||||||
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
|
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
|
||||||
|
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
|
||||||
import com.patrykandpatrick.vico.core.entry.ChartEntry
|
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 llc.arma.ble.domain.model.Ble
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
@ -80,7 +90,7 @@ fun TemperatureHistory(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial))
|
viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial))
|
||||||
},
|
},
|
||||||
enabled = state is TemperatureHistoryContract.State.Display
|
enabled = state.loadingHistoryState is ProgressState.Finished
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.Refresh,
|
imageVector = Icons.Rounded.Refresh,
|
||||||
|
|
@ -92,12 +102,14 @@ fun TemperatureHistory(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
when (state) {
|
when (state.loadingHistoryState) {
|
||||||
is TemperatureHistoryContract.State.Display -> {
|
is ProgressState.Finished -> {
|
||||||
|
|
||||||
val producer = state.history.mapIndexed { index, measurePoint ->
|
Text(text = "${state.loadingHistoryState.data.size}")
|
||||||
|
|
||||||
|
val producer = state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||||
TemperatureEntry(measurePoint.date, index.toFloat(), measurePoint.value) }.let {
|
TemperatureEntry(measurePoint.date, index.toFloat(), measurePoint.value) }.let {
|
||||||
ChartEntryModelProducer(it)
|
ChartEntryModelProducer(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
||||||
|
|
@ -107,14 +119,20 @@ fun TemperatureHistory(
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lineChart = lineChart()
|
val lineChart = lineChart(
|
||||||
|
spacing = 110.dp
|
||||||
|
)
|
||||||
|
|
||||||
Box(modifier = Modifier.padding(8.dp)) {
|
Box(modifier = Modifier.padding(8.dp)) {
|
||||||
|
|
||||||
|
val scrollState = rememberChartScrollState()
|
||||||
|
|
||||||
|
LaunchedEffect(scrollState.maxValue){
|
||||||
|
scrollState.scrollBy(scrollState.maxValue)
|
||||||
|
}
|
||||||
|
|
||||||
Chart(
|
Chart(
|
||||||
modifier = Modifier
|
chartScrollState = scrollState,
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(1.5f),
|
|
||||||
chart = lineChart,
|
chart = lineChart,
|
||||||
chartModelProducer = producer,
|
chartModelProducer = producer,
|
||||||
startAxis = startAxis(),
|
startAxis = startAxis(),
|
||||||
|
|
@ -122,12 +140,14 @@ fun TemperatureHistory(
|
||||||
valueFormatter = axisValueFormatter,
|
valueFormatter = axisValueFormatter,
|
||||||
labelRotationDegrees = 0f,
|
labelRotationDegrees = 0f,
|
||||||
),
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1.5f),
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
is TemperatureHistoryContract.State.Loading -> {
|
is ProgressState.Indeterminate -> {
|
||||||
|
|
||||||
Box(modifier = Modifier.padding(8.dp)) {
|
Box(modifier = Modifier.padding(8.dp)) {
|
||||||
|
|
||||||
|
|
@ -138,15 +158,37 @@ fun TemperatureHistory(
|
||||||
){
|
){
|
||||||
|
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
modifier = Modifier.align(Alignment.Center)
|
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 {
|
class State(
|
||||||
|
val loadingHistoryState : ProgressState<List<Ble.Thermometer.MeasurePoint>>
|
||||||
object Loading : State()
|
) : ViewState
|
||||||
|
|
||||||
data class Display(
|
|
||||||
var history : List<Ble.Thermometer.MeasurePoint>
|
|
||||||
) : State()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Effect : ViewSideEffect {
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
|
|
@ -186,7 +222,9 @@ class TemperatureHistoryViewModel @Inject constructor(
|
||||||
private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial
|
private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial
|
||||||
) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() {
|
) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() {
|
||||||
|
|
||||||
override fun setInitialState() = TemperatureHistoryContract.State.Loading
|
override fun setInitialState() = TemperatureHistoryContract.State(
|
||||||
|
ProgressState.Indeterminate
|
||||||
|
)
|
||||||
|
|
||||||
override fun handleEvents(event: TemperatureHistoryContract.Event) {
|
override fun handleEvents(event: TemperatureHistoryContract.Event) {
|
||||||
when(event){
|
when(event){
|
||||||
|
|
@ -201,14 +239,21 @@ class TemperatureHistoryViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
||||||
setState {
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,23 @@ import android.bluetooth.le.ScanResult
|
||||||
import android.bluetooth.le.ScanSettings
|
import android.bluetooth.le.ScanSettings
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelUuid
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import llc.arma.ble.domain.Result
|
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.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
import llc.arma.ble.domain.usecase.GetBleBySerial
|
import llc.arma.ble.domain.usecase.GetBleBySerial
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class BleRepositoryImpl @Inject constructor(
|
class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
@ -69,67 +68,86 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
(this[idx].toUInt() and 0xFFu)
|
(this[idx].toUInt() and 0xFFu)
|
||||||
|
|
||||||
private val deviceCache = mutableMapOf<String, ScanResult>()
|
private val deviceCache = mutableMapOf<String, ScanResult>()
|
||||||
|
val resultList = mutableMapOf<String, BleInfo>()
|
||||||
|
|
||||||
override fun getBleAroundFlow(): Flow<List<BleInfo>> {
|
override fun getBleAroundFlow(): Flow<Result<List<BleInfo>, BleException>> {
|
||||||
|
|
||||||
val resultList = mutableMapOf<String, BleInfo>()
|
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(
|
callbackFlow {
|
||||||
callbackType: Int,
|
|
||||||
result: ScanResult
|
|
||||||
) {
|
|
||||||
|
|
||||||
super.onScanResult(callbackType, result)
|
val bleCallback = object : ScanCallback() {
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(
|
override fun onScanResult(
|
||||||
app,
|
callbackType: Int,
|
||||||
Manifest.permission.BLUETOOTH_CONNECT
|
result: ScanResult
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
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(
|
||||||
bleScanner.startScan(
|
listOf(),
|
||||||
listOf(),
|
ScanSettings.Builder()
|
||||||
ScanSettings.Builder()
|
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
||||||
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
|
||||||
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
|
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
|
||||||
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
|
.setReportDelay(0L)
|
||||||
.setReportDelay(0L)
|
.build(),
|
||||||
.build(),
|
bleCallback)
|
||||||
bleCallback)
|
|
||||||
|
|
||||||
val timer = Timer().apply {
|
val timer = Timer().apply {
|
||||||
schedule(object : TimerTask() {
|
schedule(object : TimerTask() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
send(resultList.values.toList())
|
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 -> {
|
BleInfo.Type.THERMOMETER -> {
|
||||||
|
|
||||||
|
val thermometer = readThermometerState(result).fold(
|
||||||
|
onFailure = { _ ->
|
||||||
|
return@launch it.resume(Result.failure(GetBleBySerial.GetBleException.BlePermissionDenied))
|
||||||
|
},
|
||||||
|
onSuccess = { return@fold it }
|
||||||
|
)
|
||||||
|
|
||||||
Ble.Thermometer(
|
Ble.Thermometer(
|
||||||
info = info,
|
info = info,
|
||||||
state = state,
|
state = state,
|
||||||
thermometerState = readThermometerState(result)
|
thermometerState = thermometer
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -204,33 +229,54 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
private suspend fun readThermometerState(
|
private suspend fun readThermometerState(
|
||||||
record: ScanResult
|
record: ScanResult
|
||||||
): Ble.Thermometer.ThermometerState {
|
): Result<Ble.Thermometer.ThermometerState, BleException> {
|
||||||
|
|
||||||
return Ble.Thermometer.ThermometerState(
|
val temperature = readTemperature(record).fold(
|
||||||
temperature = readTemperature(record),
|
onFailure = {
|
||||||
saveHistory = record.timerEnabled,
|
return Result.failure(it)
|
||||||
historyInterval = readHistoryInterval(record)
|
},
|
||||||
|
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(
|
private suspend fun readTemperature(
|
||||||
record: ScanResult
|
record: ScanResult
|
||||||
): Float {
|
): Result<Float, BleException> {
|
||||||
|
|
||||||
val dataResult = readCharacteristic(
|
val dataResult = readCharacteristic(
|
||||||
device = record.device,
|
device = record.device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
characteristicId = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
|
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(
|
private suspend fun readHistoryInterval(
|
||||||
record: ScanResult
|
record: ScanResult
|
||||||
): Long {
|
): Result<Long, BleException> {
|
||||||
|
|
||||||
writeCharacteristic(
|
writeCharacteristic(
|
||||||
device = record.device,
|
device = record.device,
|
||||||
|
|
@ -243,19 +289,26 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
device = record.device,
|
device = record.device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
|
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
|
||||||
|
).fold(
|
||||||
|
onFailure = {
|
||||||
|
return Result.failure(it)
|
||||||
|
},
|
||||||
|
onSuccess = { return@fold it }
|
||||||
)
|
)
|
||||||
|
|
||||||
return if(dataResult.size == 4){
|
return Result.success(
|
||||||
dataResult.getUIntAt(0).toLong()
|
if(dataResult.size == 4){
|
||||||
}else{
|
dataResult.getUIntAt(0).toLong()
|
||||||
0
|
}else{
|
||||||
}
|
0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTemperatureHistoryBySerial(
|
override suspend fun getTemperatureHistoryBySerial(
|
||||||
serial: String
|
serial: String
|
||||||
): List<Ble.Thermometer.MeasurePoint> {
|
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> = flow {
|
||||||
|
|
||||||
fun ByteArray.getUIntAt(idx: Int) =
|
fun ByteArray.getUIntAt(idx: Int) =
|
||||||
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
|
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
|
||||||
|
|
@ -265,6 +318,8 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
deviceCache[serial]?.device?.let { device ->
|
deviceCache[serial]?.device?.let { device ->
|
||||||
|
|
||||||
|
emit(Result.success(ProgressState.Indeterminate))
|
||||||
|
|
||||||
writeCharacteristic(
|
writeCharacteristic(
|
||||||
device = device,
|
device = device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
|
|
@ -276,6 +331,12 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
device = device,
|
device = device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
|
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
|
||||||
|
).fold(
|
||||||
|
onFailure = {
|
||||||
|
emit(Result.failure(it))
|
||||||
|
return@flow
|
||||||
|
},
|
||||||
|
onSuccess = { return@fold it }
|
||||||
)
|
)
|
||||||
|
|
||||||
writeCharacteristic(
|
writeCharacteristic(
|
||||||
|
|
@ -295,6 +356,12 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
device = device,
|
device = device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
|
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()){
|
if(firstPackageResponse[0] == 250.toByte()){
|
||||||
|
|
@ -311,11 +378,14 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
(it[0] + it[1] * 256).toFloat() / 100f
|
(it[0] + it[1] * 256).toFloat() / 100f
|
||||||
}.toMutableList()
|
}.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(
|
writeCharacteristic(
|
||||||
device = device,
|
device = device,
|
||||||
|
|
@ -328,9 +398,15 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
device = device,
|
device = device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
|
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)
|
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())
|
emit(
|
||||||
|
Result.success(
|
||||||
return temperaturePackage.withIndex().map {
|
ProgressState.Finished(
|
||||||
Ble.Thermometer.MeasurePoint(
|
temperaturePackage.withIndex().map {
|
||||||
date = lastMeasureSystemTime - (((temperaturePackage.size - 1) - it.index) * interval),
|
Ble.Thermometer.MeasurePoint(
|
||||||
value = it.value
|
date = lastMeasureSystemTime - (((temperaturePackage.size - 1) - it.index) * interval),
|
||||||
|
value = it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return emptyList()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeBle(ble: Ble) {
|
override suspend fun writeBle(ble: Ble) {
|
||||||
|
|
@ -381,6 +459,9 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
request.saveHistory?.let { writeSaveEnabled(result.device, it) }
|
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(
|
private suspend fun writeSaveEnabled(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
enabled: Boolean
|
enabled: Boolean
|
||||||
) {
|
): Result<Unit, BleException> {
|
||||||
|
|
||||||
writeCharacteristic(
|
writeCharacteristic(
|
||||||
device = device,
|
device = device,
|
||||||
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
|
||||||
characteristicId = UUID.fromString("0000b6f2-0000-1000-8000-00805f9b34fb"),
|
characteristicId = UUID.fromString("0000b6f2-0000-1000-8000-00805f9b34fb"),
|
||||||
writeData = mutableListOf<Byte>(3).apply {
|
writeData = mutableListOf<Byte>(4).apply {
|
||||||
add(if(enabled) 1 else 0)
|
add(if(enabled) 1 else 0)
|
||||||
}.toByteArray()
|
}.toByteArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return Result.success(Unit)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun readCharacteristic(
|
private suspend fun readCharacteristic(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
serviceId: UUID,
|
serviceId: UUID,
|
||||||
characteristicId: UUID
|
characteristicId: UUID
|
||||||
): ByteArray = suspendCoroutine {
|
): Result<ByteArray, BleException> = suspendCancellableCoroutine {
|
||||||
|
|
||||||
val callback = object : BluetoothGattCallback() {
|
val callback = object : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
|
@ -492,10 +575,10 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
gatt?.discoverServices()
|
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
|
service.uuid == serviceId
|
||||||
}?.characteristics?.firstOrNull { characteristic ->
|
}?.characteristics?.firstOrNull { characteristic ->
|
||||||
characteristic.uuid == characteristicId
|
characteristic.uuid == characteristicId
|
||||||
}?.let {
|
}?.let { char ->
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
|
||||||
app,
|
app,
|
||||||
Manifest.permission.BLUETOOTH_CONNECT
|
Manifest.permission.BLUETOOTH_CONNECT
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
|
gatt.readCharacteristic(char)
|
||||||
gatt.readCharacteristic(it)
|
} else {
|
||||||
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -537,14 +620,29 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
super.onCharacteristicRead(gatt, characteristic, value, status)
|
super.onCharacteristicRead(gatt, characteristic, value, status)
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
it.resume(value)
|
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,
|
serviceId: UUID,
|
||||||
characteristicId: UUID,
|
characteristicId: UUID,
|
||||||
writeData: ByteArray
|
writeData: ByteArray
|
||||||
) = suspendCoroutine {
|
) = suspendCancellableCoroutine {
|
||||||
|
|
||||||
val callback = object : BluetoothGattCallback() {
|
val callback = object : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
|
@ -573,8 +671,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
gatt?.discoverServices()
|
gatt?.discoverServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -591,7 +687,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
service.uuid == serviceId
|
service.uuid == serviceId
|
||||||
}?.characteristics?.firstOrNull { characteristic ->
|
}?.characteristics?.firstOrNull { characteristic ->
|
||||||
characteristic.uuid == characteristicId
|
characteristic.uuid == characteristicId
|
||||||
}?.let {
|
}?.let { char ->
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
|
||||||
app,
|
app,
|
||||||
|
|
@ -600,10 +696,10 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
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{
|
}else{
|
||||||
it.value = writeData
|
char.value = writeData
|
||||||
gatt.writeCharacteristic(it)
|
gatt.writeCharacteristic(char)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -620,7 +716,17 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package llc.arma.ble.domain.common
|
||||||
|
|
||||||
|
sealed class BleException {
|
||||||
|
|
||||||
|
object PermissionDenied : BleException()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package llc.arma.ble.domain.common
|
||||||
|
|
||||||
|
sealed class ProgressState<out T> {
|
||||||
|
|
||||||
|
object Indeterminate : ProgressState<Nothing>()
|
||||||
|
|
||||||
|
data class Progress(
|
||||||
|
val value: Float
|
||||||
|
) : ProgressState<Nothing>()
|
||||||
|
|
||||||
|
data class Finished<T>(
|
||||||
|
val data: T
|
||||||
|
) : ProgressState<T>()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,17 +2,19 @@ package llc.arma.ble.domain.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import llc.arma.ble.domain.Result
|
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.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.usecase.GetBleBySerial
|
import llc.arma.ble.domain.usecase.GetBleBySerial
|
||||||
|
|
||||||
interface BleRepository {
|
interface BleRepository {
|
||||||
|
|
||||||
fun getBleAroundFlow(): Flow<List<BleInfo>>
|
fun getBleAroundFlow(): Flow<Result<List<BleInfo>, BleException>>
|
||||||
|
|
||||||
suspend fun getBleBySerial(serial: String): Result<Ble, GetBleBySerial.GetBleException>
|
suspend fun getBleBySerial(serial: String): Result<Ble, GetBleBySerial.GetBleException>
|
||||||
|
|
||||||
suspend fun getTemperatureHistoryBySerial(serial: String): List<Ble.Thermometer.MeasurePoint>
|
suspend fun getTemperatureHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>>
|
||||||
|
|
||||||
suspend fun writeBle(ble: Ble)
|
suspend fun writeBle(ble: Ble)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package llc.arma.ble.domain.usecase
|
package llc.arma.ble.domain.usecase
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
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.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
|
|
@ -10,6 +12,6 @@ class GetBleAroundFlow @Inject constructor(
|
||||||
private val bleRepository: BleRepository
|
private val bleRepository: BleRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(): Flow<List<BleInfo>> = bleRepository.getBleAroundFlow()
|
operator fun invoke(): Flow<Result<List<BleInfo>, BleException>> = bleRepository.getBleAroundFlow()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package llc.arma.ble.domain.usecase
|
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.model.Ble
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -8,7 +12,7 @@ class GetTemperatureHistoryBySerial @Inject constructor(
|
||||||
private val bleRepository: BleRepository
|
private val bleRepository: BleRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend operator fun invoke(serial: String): List<Ble.Thermometer.MeasurePoint> {
|
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
|
||||||
|
|
||||||
return bleRepository.getTemperatureHistoryBySerial(serial)
|
return bleRepository.getTemperatureHistoryBySerial(serial)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ class WriteBle @Inject constructor(
|
||||||
bleRepository.writeBle(ble)
|
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)
|
bleRepository.writeBle(serial, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue