История изменений акселерометра
This commit is contained in:
parent
df30906056
commit
c59c929f35
|
|
@ -14,7 +14,7 @@ android {
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 5
|
versionCode 5
|
||||||
versionName "1.2.2"
|
versionName "1.2.3"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ class BleMapper @Inject constructor(
|
||||||
state = BleView.BleState(
|
state = BleView.BleState(
|
||||||
tx = txMapper.map(input.state.tx)
|
tx = txMapper.map(input.state.tx)
|
||||||
),
|
),
|
||||||
|
accelerometerState = BleView.Accelerometer.AccelerometerState(
|
||||||
|
saveHistory = input.accelerometerState.saveHistory,
|
||||||
|
historyInterval = input.accelerometerState.historyInterval,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ class BleViewMapper @Inject constructor(
|
||||||
state = Ble.BleState(
|
state = Ble.BleState(
|
||||||
tx = txMapper.map(input.state.tx)
|
tx = txMapper.map(input.state.tx)
|
||||||
),
|
),
|
||||||
|
accelerometerState = Ble.Accelerometer.AccelerometerState(
|
||||||
|
saveHistory = input.accelerometerState.saveHistory,
|
||||||
|
historyInterval = input.accelerometerState.historyInterval,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,21 @@ sealed class BleView(
|
||||||
|
|
||||||
class Accelerometer(
|
class Accelerometer(
|
||||||
info: BleInfo,
|
info: BleInfo,
|
||||||
val state: BleState
|
val state: BleState,
|
||||||
) : BleView(info)
|
val accelerometerState: AccelerometerState
|
||||||
|
) : BleView(info) {
|
||||||
|
|
||||||
|
class AccelerometerState(
|
||||||
|
saveHistory: Boolean,
|
||||||
|
historyInterval: Long
|
||||||
|
) {
|
||||||
|
|
||||||
|
var saveHistory by mutableStateOf(saveHistory)
|
||||||
|
var historyInterval by mutableStateOf(historyInterval)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Beacon(
|
class Beacon(
|
||||||
info: BleInfo,
|
info: BleInfo,
|
||||||
|
|
|
||||||
|
|
@ -260,16 +260,40 @@ private fun BleItem(
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
ItemIcon {
|
Box {
|
||||||
Icon(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
ItemIcon {
|
||||||
imageVector = when(ble.type){
|
Icon(
|
||||||
BleInfo.Type.BEACON -> Icons.Rounded.Nfc
|
modifier = Modifier.align(Alignment.Center),
|
||||||
BleInfo.Type.THERMOMETER -> Icons.Rounded.Thermostat
|
imageVector = when (ble.type) {
|
||||||
BleInfo.Type.ACCELEROMETER -> Icons.Rounded.Speed
|
BleInfo.Type.BEACON -> Icons.Rounded.Nfc
|
||||||
},
|
BleInfo.Type.THERMOMETER -> Icons.Rounded.Thermostat
|
||||||
contentDescription = null
|
BleInfo.Type.ACCELEROMETER -> Icons.Rounded.Speed
|
||||||
)
|
},
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ble.recordEnabled){
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = color,
|
||||||
|
modifier = Modifier.align(Alignment.TopEnd)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(12.dp).padding(2.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
|
@ -281,21 +305,6 @@ private fun BleItem(
|
||||||
text = ble.serial
|
text = ble.serial
|
||||||
)
|
)
|
||||||
|
|
||||||
/*Text(
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20)))
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
text = String.format("%.3f", (ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0)))
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
text = ble.tx.toString() + " tx"
|
|
||||||
)*/
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.alpha(0.7f)
|
modifier = Modifier.alpha(0.7f)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import llc.arma.ble.app.ui.common.ViewEvent
|
||||||
import llc.arma.ble.app.ui.common.ViewSideEffect
|
import llc.arma.ble.app.ui.common.ViewSideEffect
|
||||||
import llc.arma.ble.app.ui.common.ViewState
|
import llc.arma.ble.app.ui.common.ViewState
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
|
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
|
||||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
import llc.arma.ble.domain.common.BleException
|
import llc.arma.ble.domain.common.BleException
|
||||||
|
|
@ -26,6 +27,11 @@ class ConnectionContract {
|
||||||
val event: ThermometerContract.Effect.Navigation
|
val event: ThermometerContract.Effect.Navigation
|
||||||
) : Event()
|
) : Event()
|
||||||
|
|
||||||
|
data class OnAccelNavigationEvent(
|
||||||
|
val event: AccelerometerContract.Effect.Navigation
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
import llc.arma.ble.app.ui.screen.BleInfoView
|
import llc.arma.ble.app.ui.screen.BleInfoView
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerScreen
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerScreen
|
||||||
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconScreen
|
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconScreen
|
||||||
import llc.arma.ble.app.ui.screen.password.ChangePasswordContract
|
import llc.arma.ble.app.ui.screen.password.ChangePasswordContract
|
||||||
|
|
@ -117,7 +118,11 @@ fun ConnectionScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
is Ble.Accelerometer -> {
|
is Ble.Accelerometer -> {
|
||||||
AccelerometerScreen(ble = state.ble)
|
AccelerometerScreen(ble = state.ble) {
|
||||||
|
viewModel.setEvent(
|
||||||
|
ConnectionContract.Event.OnAccelNavigationEvent(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import llc.arma.ble.app.ui.common.BaseViewModel
|
||||||
import llc.arma.ble.app.ui.mapper.BleMapper
|
import llc.arma.ble.app.ui.mapper.BleMapper
|
||||||
import llc.arma.ble.app.ui.mapper.BleViewMapper
|
import llc.arma.ble.app.ui.mapper.BleViewMapper
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
|
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
|
||||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
@ -35,6 +36,7 @@ class ConnectionViewModel @Inject constructor(
|
||||||
is ConnectionContract.Event.OnNavigateUp -> reduce(viewState.value, event)
|
is ConnectionContract.Event.OnNavigateUp -> reduce(viewState.value, event)
|
||||||
is ConnectionContract.Event.OnThermometerNavigationEvent -> reduce(viewState.value, event)
|
is ConnectionContract.Event.OnThermometerNavigationEvent -> reduce(viewState.value, event)
|
||||||
is ConnectionContract.Event.RefreshBle -> reduce(viewState.value, event)
|
is ConnectionContract.Event.RefreshBle -> reduce(viewState.value, event)
|
||||||
|
is ConnectionContract.Event.OnAccelNavigationEvent -> reduce(viewState.value, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,6 +76,19 @@ class ConnectionViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: ConnectionContract.State,
|
||||||
|
event: ConnectionContract.Event.OnAccelNavigationEvent
|
||||||
|
) {
|
||||||
|
when(event.event){
|
||||||
|
AccelerometerContract.Effect.Navigation.NavigateToChangePassword -> {
|
||||||
|
setEffect {
|
||||||
|
ConnectionContract.Effect.Navigation.NavigateToChangePassword(savedStateHandle.get<String>("serial")!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: ConnectionContract.State,
|
state: ConnectionContract.State,
|
||||||
event: ConnectionContract.Event.OnNavigateUp
|
event: ConnectionContract.Event.OnNavigateUp
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import llc.arma.ble.app.ui.common.ViewEvent
|
||||||
import llc.arma.ble.app.ui.common.ViewSideEffect
|
import llc.arma.ble.app.ui.common.ViewSideEffect
|
||||||
import llc.arma.ble.app.ui.common.ViewState
|
import llc.arma.ble.app.ui.common.ViewState
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
import llc.arma.ble.domain.usecase.AccelScale
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
import llc.arma.ble.domain.usecase.AccelViewMode
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
|
@ -15,12 +16,15 @@ class AccelerometerContract {
|
||||||
|
|
||||||
sealed class Event : ViewEvent {
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
object OnShowAccelerometerMeasure : Event()
|
object OnShowAccelerometerAccel : Event()
|
||||||
object OnHideAccelerometerMeasure : Event()
|
object OnHideAccelerometerAccel : Event()
|
||||||
|
|
||||||
object OnShowAccelerometerSpectre : Event()
|
object OnShowAccelerometerSpectre : Event()
|
||||||
object OnHideAccelerometerSpectre : Event()
|
object OnHideAccelerometerSpectre : Event()
|
||||||
|
|
||||||
|
object OnShowAccelerometerHistory : Event()
|
||||||
|
object OnHideAccelerometerHistory : Event()
|
||||||
|
|
||||||
data class OnAccelViewModeEdit(
|
data class OnAccelViewModeEdit(
|
||||||
val next: Next
|
val next: Next
|
||||||
) : Event(){
|
) : Event(){
|
||||||
|
|
@ -44,6 +48,9 @@ class AccelerometerContract {
|
||||||
|
|
||||||
object OnHideWriteBlePreview : Event()
|
object OnHideWriteBlePreview : Event()
|
||||||
|
|
||||||
|
object OnChangePassword : Event()
|
||||||
|
object OnSaveIntervalEdit : Event()
|
||||||
|
|
||||||
data class OnBleChanged(
|
data class OnBleChanged(
|
||||||
val ble: Ble.Accelerometer,
|
val ble: Ble.Accelerometer,
|
||||||
): Event()
|
): Event()
|
||||||
|
|
@ -68,6 +75,14 @@ class AccelerometerContract {
|
||||||
val mode: FftViewMode
|
val mode: FftViewMode
|
||||||
) : Event()
|
) : Event()
|
||||||
|
|
||||||
|
data class OnSaveHistoryChanged(
|
||||||
|
val save: Boolean
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnSaveIntervalChanged(
|
||||||
|
val interval: Long
|
||||||
|
) : Event()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
@ -107,7 +122,8 @@ class AccelerometerContract {
|
||||||
|
|
||||||
sealed class Effect : ViewSideEffect {
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
object ShowAccelerometerMeasure : Effect()
|
object ShowAccelerometerAccel : Effect()
|
||||||
|
object ShowAccelerometerSpectre : Effect()
|
||||||
object ShowAccelerometerHistory : Effect()
|
object ShowAccelerometerHistory : Effect()
|
||||||
|
|
||||||
object ShowPowerPicker : Effect()
|
object ShowPowerPicker : Effect()
|
||||||
|
|
@ -125,6 +141,15 @@ class AccelerometerContract {
|
||||||
object ShowFftAxisEdit : Effect()
|
object ShowFftAxisEdit : Effect()
|
||||||
object ShowFftModeEdit : Effect()
|
object ShowFftModeEdit : Effect()
|
||||||
|
|
||||||
|
object HideIntervalPicker : Effect()
|
||||||
|
object ShowIntervalPicker : Effect()
|
||||||
|
|
||||||
|
sealed class Navigation : Effect() {
|
||||||
|
|
||||||
|
object NavigateToChangePassword : Navigation()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.ModalBottomSheetValue
|
import androidx.compose.material.ModalBottomSheetValue
|
||||||
|
|
@ -23,22 +22,25 @@ import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFftModeEdit
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFrequencyEdit
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFrequencyEdit
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelSpectreEdit
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelSpectreEdit
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelViewEdit
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelViewEdit
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerSpectre
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerAccel
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerHistory
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerHistory
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerMeasure
|
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.DisplayState
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.DisplayState
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.IntervalEdit
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.LoadingState
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.LoadingState
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.PowerEdit
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.PowerEdit
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.Write
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.Write
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
enum class SheetPage {
|
enum class SheetPage {
|
||||||
MEASURE, POWER, WRITE, HISTORY, ACCEL_MODE_EDIT, SPECTRE_MODE_EDIT, SPECTRE_EDIT, FREQUENCY_EDIT, AXIS_EDIT, FFT_MODE_EDIT
|
HISTORY, ACCEL, POWER, WRITE, SPECTRE, ACCEL_MODE_EDIT, SPECTRE_MODE_EDIT, SPECTRE_EDIT, FREQUENCY_EDIT, AXIS_EDIT, FFT_MODE_EDIT, INTERVAL_EDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AccelerometerScreen(
|
fun AccelerometerScreen(
|
||||||
ble: Ble.Accelerometer,
|
ble: Ble.Accelerometer,
|
||||||
|
onEvent: (AccelerometerContract.Effect.Navigation) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val viewModel = hiltViewModel<AccelerometerViewModel>()
|
val viewModel = hiltViewModel<AccelerometerViewModel>()
|
||||||
|
|
@ -57,9 +59,7 @@ fun AccelerometerScreen(
|
||||||
LaunchedEffect(
|
LaunchedEffect(
|
||||||
key1 = bottomDialog.sheetState?.currentValue,
|
key1 = bottomDialog.sheetState?.currentValue,
|
||||||
block = {
|
block = {
|
||||||
Log.d("sheet", bottomDialog.sheetState?.currentValue.toString())
|
|
||||||
if(bottomDialog.sheetState?.currentValue == ModalBottomSheetValue.Hidden) {
|
if(bottomDialog.sheetState?.currentValue == ModalBottomSheetValue.Hidden) {
|
||||||
Log.d("sheet", "dispose")
|
|
||||||
bottomDialog.setContent({})
|
bottomDialog.setContent({})
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
}
|
}
|
||||||
|
|
@ -70,12 +70,12 @@ fun AccelerometerScreen(
|
||||||
|
|
||||||
LaunchedEffect(sheetPage) {
|
LaunchedEffect(sheetPage) {
|
||||||
when (sheetPage) {
|
when (sheetPage) {
|
||||||
SheetPage.MEASURE -> launch {
|
SheetPage.HISTORY -> launch {
|
||||||
val currentState = viewModel.viewState.value
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
if (currentState is AccelerometerContract.State.Display) {
|
if (currentState is AccelerometerContract.State.Display) {
|
||||||
bottomDialog.show {
|
bottomDialog.show {
|
||||||
AccelerometerMeasure(
|
AccelerometerHistory(
|
||||||
ble = currentState.accelerometer.info,
|
ble = currentState.accelerometer.info,
|
||||||
accelMode = currentState.accelViewMode,
|
accelMode = currentState.accelViewMode,
|
||||||
fftAxis = currentState.fftAxis,
|
fftAxis = currentState.fftAxis,
|
||||||
|
|
@ -86,12 +86,28 @@ fun AccelerometerScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SheetPage.HISTORY -> launch {
|
SheetPage.ACCEL -> launch {
|
||||||
val currentState = viewModel.viewState.value
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
if (currentState is AccelerometerContract.State.Display) {
|
if (currentState is AccelerometerContract.State.Display) {
|
||||||
bottomDialog.show {
|
bottomDialog.show {
|
||||||
AccelerometerHistory(
|
AccelerometerAccel(
|
||||||
|
ble = currentState.accelerometer.info,
|
||||||
|
accelMode = currentState.accelViewMode,
|
||||||
|
fftAxis = currentState.fftAxis,
|
||||||
|
fftMode = currentState.fftViewMode,
|
||||||
|
frequency = currentState.fftFrequency,
|
||||||
|
accelScale = currentState.accelScale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SheetPage.SPECTRE -> launch {
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if (currentState is AccelerometerContract.State.Display) {
|
||||||
|
bottomDialog.show {
|
||||||
|
AccelerometerSpectre(
|
||||||
ble = currentState.accelerometer.info,
|
ble = currentState.accelerometer.info,
|
||||||
accelMode = currentState.accelViewMode,
|
accelMode = currentState.accelViewMode,
|
||||||
fftAxis = currentState.fftAxis,
|
fftAxis = currentState.fftAxis,
|
||||||
|
|
@ -221,9 +237,27 @@ fun AccelerometerScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SheetPage.INTERVAL_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
IntervalEdit(
|
||||||
|
state = currentState.accelerometer,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
null -> {
|
null -> {
|
||||||
bottomDialog.hide()
|
bottomDialog.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,10 +272,10 @@ fun AccelerometerScreen(
|
||||||
LaunchedEffect("effect"){
|
LaunchedEffect("effect"){
|
||||||
viewModel.effect.onEach {
|
viewModel.effect.onEach {
|
||||||
when(it){
|
when(it){
|
||||||
is AccelerometerContract.Effect.ShowAccelerometerMeasure -> launch {
|
is AccelerometerContract.Effect.ShowAccelerometerAccel -> launch {
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
delay(100)
|
delay(100)
|
||||||
sheetPage = SheetPage.MEASURE
|
sheetPage = SheetPage.ACCEL
|
||||||
}
|
}
|
||||||
is AccelerometerContract.Effect.HidePowerPicker -> launch {
|
is AccelerometerContract.Effect.HidePowerPicker -> launch {
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
|
|
@ -261,10 +295,10 @@ fun AccelerometerScreen(
|
||||||
delay(100)
|
delay(100)
|
||||||
sheetPage = SheetPage.WRITE
|
sheetPage = SheetPage.WRITE
|
||||||
}
|
}
|
||||||
is AccelerometerContract.Effect.ShowAccelerometerHistory -> launch {
|
is AccelerometerContract.Effect.ShowAccelerometerSpectre -> launch {
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
delay(100)
|
delay(100)
|
||||||
sheetPage = SheetPage.HISTORY
|
sheetPage = SheetPage.SPECTRE
|
||||||
}
|
}
|
||||||
is AccelerometerContract.Effect.ShowAccelEdit -> launch {
|
is AccelerometerContract.Effect.ShowAccelEdit -> launch {
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
|
|
@ -294,6 +328,25 @@ fun AccelerometerScreen(
|
||||||
delay(100)
|
delay(100)
|
||||||
sheetPage = SheetPage.FFT_MODE_EDIT
|
sheetPage = SheetPage.FFT_MODE_EDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AccelerometerContract.Effect.ShowAccelerometerHistory -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.HISTORY
|
||||||
|
}
|
||||||
|
|
||||||
|
AccelerometerContract.Effect.HideIntervalPicker -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
AccelerometerContract.Effect.ShowIntervalPicker -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.INTERVAL_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.Navigation -> {
|
||||||
|
onEvent(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import llc.arma.ble.app.ui.common.BaseViewModel
|
import llc.arma.ble.app.ui.common.BaseViewModel
|
||||||
import llc.arma.ble.app.ui.mapper.BleMapper
|
import llc.arma.ble.app.ui.mapper.BleMapper
|
||||||
import llc.arma.ble.app.ui.mapper.BleViewMapper
|
import llc.arma.ble.app.ui.mapper.BleViewMapper
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
import llc.arma.ble.domain.usecase.AccelScale
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
import llc.arma.ble.domain.usecase.AccelViewMode
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
|
@ -30,8 +30,8 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
override fun handleEvents(event: AccelerometerContract.Event) {
|
override fun handleEvents(event: AccelerometerContract.Event) {
|
||||||
when(event){
|
when(event){
|
||||||
is AccelerometerContract.Event.OnBleChanged -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnBleChanged -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnHideAccelerometerMeasure -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnHideAccelerometerAccel -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnShowAccelerometerMeasure -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnShowAccelerometerAccel -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnPowerChanged -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnPowerChanged -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnPowerEdit -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnPowerEdit -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event)
|
||||||
|
|
@ -48,9 +48,62 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
is AccelerometerContract.Event.OnFftModeChanged -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnFftModeChanged -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnFftAxisEdit -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnFftAxisEdit -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnFftModeEdit -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnFftModeEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnSaveHistoryChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHideAccelerometerHistory -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnShowAccelerometerHistory -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnChangePassword -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnSaveIntervalChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnSaveIntervalEdit -> reduce(viewState.value, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnSaveIntervalEdit
|
||||||
|
) {
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowIntervalPicker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnSaveIntervalChanged
|
||||||
|
) {
|
||||||
|
if(state is AccelerometerContract.State.Display) {
|
||||||
|
|
||||||
|
state.accelerometer.accelerometerState.historyInterval = event.interval
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.HideIntervalPicker
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnChangePassword
|
||||||
|
) {
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.Navigation.NavigateToChangePassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnSaveHistoryChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display) {
|
||||||
|
|
||||||
|
state.accelerometer.accelerometerState.saveHistory = event.save
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerContract.State,
|
state: AccelerometerContract.State,
|
||||||
event: AccelerometerContract.Event.OnFftModeEdit
|
event: AccelerometerContract.Event.OnFftModeEdit
|
||||||
|
|
@ -152,7 +205,7 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
setEffect {
|
setEffect {
|
||||||
AccelerometerContract.Effect.ShowAccelerometerHistory
|
AccelerometerContract.Effect.ShowAccelerometerSpectre
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +217,26 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnShowAccelerometerHistory
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowAccelerometerHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHideAccelerometerHistory
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
|
|
@ -197,8 +270,10 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer
|
val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer
|
||||||
|
|
||||||
val writeRequest = Ble.Accelerometer.WriteRequest(
|
val writeRequest = Ble.Accelerometer.WriteRequest(
|
||||||
tx = if(newBle.state.tx == state.origin.state.tx) null else newBle.state.tx
|
tx = if(newBle.state.tx == state.origin.state.tx) null else newBle.state.tx,
|
||||||
)
|
saveHistory = if(newBle.accelerometerState.saveHistory == state.origin.accelerometerState.saveHistory) null else newBle.accelerometerState.saveHistory,
|
||||||
|
historyInterval = if(newBle.accelerometerState.historyInterval == state.origin.accelerometerState.historyInterval) null else newBle.accelerometerState.historyInterval,
|
||||||
|
)
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
state.copy(
|
state.copy(
|
||||||
|
|
@ -274,7 +349,7 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerContract.State,
|
state: AccelerometerContract.State,
|
||||||
event: AccelerometerContract.Event.OnHideAccelerometerMeasure
|
event: AccelerometerContract.Event.OnHideAccelerometerAccel
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -283,13 +358,13 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerContract.State,
|
state: AccelerometerContract.State,
|
||||||
event: AccelerometerContract.Event.OnShowAccelerometerMeasure
|
event: AccelerometerContract.Event.OnShowAccelerometerAccel
|
||||||
) {
|
) {
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
||||||
setEffect {
|
setEffect {
|
||||||
AccelerometerContract.Effect.ShowAccelerometerMeasure
|
AccelerometerContract.Effect.ShowAccelerometerAccel
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -308,7 +383,8 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
state.copy(
|
state.copy(
|
||||||
origin = Ble.Accelerometer(
|
origin = Ble.Accelerometer(
|
||||||
info = event.ble.info,
|
info = event.ble.info,
|
||||||
state = event.ble.state
|
state = event.ble.state,
|
||||||
|
accelerometerState = state.origin.accelerometerState
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -361,6 +437,10 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
info = currentState.origin.info,
|
info = currentState.origin.info,
|
||||||
state = currentState.origin.state.copy(
|
state = currentState.origin.state.copy(
|
||||||
tx = request.writeRequest.tx ?: state.origin.state.tx
|
tx = request.writeRequest.tx ?: state.origin.state.tx
|
||||||
|
),
|
||||||
|
accelerometerState = currentState.origin.accelerometerState.copy(
|
||||||
|
saveHistory = request.writeRequest.saveHistory
|
||||||
|
?: currentState.origin.accelerometerState.saveHistory
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
|
||||||
import llc.arma.ble.domain.usecase.AccelViewMode
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
import llc.arma.ble.domain.usecase.AccelViewMode.*
|
import llc.arma.ble.domain.usecase.AccelViewMode.*
|
||||||
|
|
||||||
|
|
@ -90,7 +88,7 @@ fun AccelViewEdit(
|
||||||
onEvent(AccelerometerContract.Event.OnAccelViewModelChanged(value))
|
onEvent(AccelerometerContract.Event.OnAccelViewModelChanged(value))
|
||||||
when(next){
|
when(next){
|
||||||
AccelerometerContract.Event.OnAccelViewModeEdit.Next.ACCEL -> {
|
AccelerometerContract.Event.OnAccelViewModeEdit.Next.ACCEL -> {
|
||||||
onEvent(AccelerometerContract.Event.OnShowAccelerometerMeasure)
|
onEvent(AccelerometerContract.Event.OnShowAccelerometerAccel)
|
||||||
}
|
}
|
||||||
AccelerometerContract.Event.OnAccelViewModeEdit.Next.SPECTRE -> {
|
AccelerometerContract.Event.OnAccelViewModeEdit.Next.SPECTRE -> {
|
||||||
onEvent(AccelerometerContract.Event.OnSpectreEdit)
|
onEvent(AccelerometerContract.Event.OnSpectreEdit)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
||||||
|
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
|
||||||
|
import com.patrykandpatrick.vico.compose.chart.Chart
|
||||||
|
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import llc.arma.ble.app.ui.common.BaseViewModel
|
||||||
|
import llc.arma.ble.app.ui.common.ViewEvent
|
||||||
|
import llc.arma.ble.app.ui.common.ViewSideEffect
|
||||||
|
import llc.arma.ble.app.ui.common.ViewState
|
||||||
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
|
import javax.inject.Inject
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Refresh
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import com.patrykandpatrick.vico.compose.chart.column.columnChart
|
||||||
|
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.entry.ChartEntry
|
||||||
|
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
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.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.GetAccelerometerSpectreBySerial
|
||||||
|
|
||||||
|
class AccelerometerEntry(
|
||||||
|
val frequency: Long,
|
||||||
|
override val x: Float,
|
||||||
|
override val y: Float,
|
||||||
|
) : ChartEntry {
|
||||||
|
|
||||||
|
override fun withY(y: Float) = AccelerometerEntry(frequency, x, y)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelerometerSpectre(
|
||||||
|
ble: BleInfo,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency,
|
||||||
|
accelScale: AccelScale
|
||||||
|
) {
|
||||||
|
|
||||||
|
val viewModel = hiltViewModel<AccelerometerSpectreViewModel>()
|
||||||
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
|
LaunchedEffect(ble.serial, accelMode, fftAxis, fftMode, frequency) {
|
||||||
|
viewModel.setEvent(AccelerometerSpectreContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(key1 = "ble", effect = {
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
Log.d("history", "dispose")
|
||||||
|
viewModel.setEvent(AccelerometerSpectreContract.Event.StopMeasure)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxHeight(0.9f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
val title = when(state){
|
||||||
|
is AccelerometerSpectreContract.State.Display -> {
|
||||||
|
if (state.previousHistory !== null) {
|
||||||
|
"${fftMode.localized} (${state.previousHistory.size})"
|
||||||
|
}else {
|
||||||
|
fftMode.localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccelerometerSpectreContract.State.Exception -> fftMode.localized
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
if(state is AccelerometerSpectreContract.State.Display) {
|
||||||
|
|
||||||
|
when (state.loadingHistoryState) {
|
||||||
|
is ProgressState.Indeterminate -> {
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
is ProgressState.Progress -> {
|
||||||
|
|
||||||
|
val progressAnimDuration = 1500
|
||||||
|
val progressAnimation by animateFloatAsState(
|
||||||
|
targetValue = state.loadingHistoryState.value,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = progressAnimDuration,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
progress = progressAnimation,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(AccelerometerSpectreContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
|
||||||
|
},
|
||||||
|
enabled = when(state){
|
||||||
|
is AccelerometerSpectreContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
||||||
|
AccelerometerSpectreContract.State.Exception -> true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Refresh,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Box(modifier = Modifier) {
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
is AccelerometerSpectreContract.State.Display -> Display(state = state)
|
||||||
|
AccelerometerSpectreContract.State.Exception -> Exception()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
||||||
|
(chartValues.chartEntryModel.entries.firstOrNull()
|
||||||
|
?.getOrNull(value.toInt()) as? AccelerometerEntry)
|
||||||
|
?.frequency?.let { String.format("%.1f", (it.toFloat() / 256f)) }
|
||||||
|
.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Display(
|
||||||
|
state: AccelerometerSpectreContract.State.Display
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
val data = if(state.loadingHistoryState is ProgressState.Finished){
|
||||||
|
state.loadingHistoryState.data
|
||||||
|
} else {
|
||||||
|
state.previousHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
val producer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data != null){
|
||||||
|
|
||||||
|
if(data.isEmpty()){
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
text = "Нет данных"
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
LaunchedEffect(data){
|
||||||
|
producer.setEntries(
|
||||||
|
data.mapIndexed { index, measurePoint ->
|
||||||
|
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lineChart = columnChart(
|
||||||
|
spacing = 1.5.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
val scrollState = rememberChartScrollState()
|
||||||
|
|
||||||
|
Chart(
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
isZoomEnabled = true,
|
||||||
|
chartScrollState = scrollState,
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = producer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(
|
||||||
|
tickLength = 0.dp,
|
||||||
|
valueFormatter = axisValueFormatter,
|
||||||
|
labelRotationDegrees = -90f,
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
when (state.loadingHistoryState) {
|
||||||
|
is ProgressState.Indeterminate -> {
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
is ProgressState.Progress -> {
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Exception(
|
||||||
|
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(2f),
|
||||||
|
){
|
||||||
|
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
text = "Во время загрузки произошла ошибка",
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccelerometerSpectreContract {
|
||||||
|
|
||||||
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
|
object StopMeasure : Event()
|
||||||
|
|
||||||
|
data class OnStart(
|
||||||
|
val serial: String,
|
||||||
|
val accelMode: AccelViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftMode: FftViewMode,
|
||||||
|
val frequency: FftFrequency,
|
||||||
|
val accelScale: AccelScale
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnRefreshHistory(
|
||||||
|
val serial: String,
|
||||||
|
val accelMode: AccelViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftMode: FftViewMode,
|
||||||
|
val frequency: FftFrequency,
|
||||||
|
val accelScale: AccelScale
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class State : ViewState {
|
||||||
|
|
||||||
|
data class Display(
|
||||||
|
val previousHistory : List<Ble.Accelerometer.MeasurePoint>?,
|
||||||
|
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
|
||||||
|
) : State()
|
||||||
|
|
||||||
|
object Exception : State()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AccelerometerSpectreViewModel @Inject constructor(
|
||||||
|
private val getAccelerometerSpectreBySerial: GetAccelerometerSpectreBySerial
|
||||||
|
) : BaseViewModel<AccelerometerSpectreContract.State, AccelerometerSpectreContract.Event, AccelerometerSpectreContract.Effect>() {
|
||||||
|
|
||||||
|
private var job: Job? = null
|
||||||
|
private var lastSerial: String? = null
|
||||||
|
|
||||||
|
override fun setInitialState() = AccelerometerSpectreContract.State.Display(
|
||||||
|
loadingHistoryState = ProgressState.Indeterminate,
|
||||||
|
previousHistory = null
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun handleEvents(event: AccelerometerSpectreContract.Event) {
|
||||||
|
when(event){
|
||||||
|
is AccelerometerSpectreContract.Event.OnStart -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerSpectreContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerSpectreContract.Event.StopMeasure -> reduce(viewState.value, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerSpectreContract.State,
|
||||||
|
event: AccelerometerSpectreContract.Event.OnStart
|
||||||
|
) {
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
|
||||||
|
if(state is AccelerometerSpectreContract.State.Display) {
|
||||||
|
|
||||||
|
//if(lastSerial != event.serial) {
|
||||||
|
|
||||||
|
lastSerial = event.serial
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerSpectreContract.State.Display(
|
||||||
|
loadingHistoryState = ProgressState.Indeterminate,
|
||||||
|
previousHistory = when(state.loadingHistoryState){
|
||||||
|
is ProgressState.Finished -> state.loadingHistoryState.data
|
||||||
|
is ProgressState.Indeterminate -> null
|
||||||
|
is ProgressState.Progress -> null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setState {
|
||||||
|
AccelerometerSpectreContract.State.Display(
|
||||||
|
loadingHistoryState = ProgressState.Indeterminate,
|
||||||
|
previousHistory = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
job?.cancel()
|
||||||
|
job = getAccelerometerSpectreBySerial(
|
||||||
|
serial = event.serial,
|
||||||
|
accelMode = event.accelMode,
|
||||||
|
fftAxis = event.fftAxis,
|
||||||
|
fftMode = event.fftMode,
|
||||||
|
frequency = event.frequency,
|
||||||
|
accelScale = event.accelScale
|
||||||
|
).onEach {
|
||||||
|
|
||||||
|
val currentState = viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerSpectreContract.State.Display) {
|
||||||
|
|
||||||
|
it.fold(
|
||||||
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
|
||||||
|
AccelerometerSpectreContract.State.Display(
|
||||||
|
loadingHistoryState = it,
|
||||||
|
previousHistory = when (it) {
|
||||||
|
is ProgressState.Finished -> {
|
||||||
|
it.data
|
||||||
|
}
|
||||||
|
is ProgressState.Indeterminate -> currentState.previousHistory
|
||||||
|
is ProgressState.Progress -> currentState.previousHistory
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
AccelerometerSpectreContract.State.Exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerSpectreContract.State,
|
||||||
|
event: AccelerometerSpectreContract.Event.OnRefreshHistory
|
||||||
|
) {
|
||||||
|
/*viewModelScope.launch {
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccelerometerSpectreBySerial(
|
||||||
|
serial = event.serial,
|
||||||
|
accelMode = event.accelMode,
|
||||||
|
fftAxis = event.fftAxis,
|
||||||
|
fftMode = event.fftMode,
|
||||||
|
frequency = event.frequency,
|
||||||
|
accelScale = event.accelScale
|
||||||
|
).onEach {
|
||||||
|
it.fold(
|
||||||
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerSpectreContract.State,
|
||||||
|
event: AccelerometerSpectreContract.Event.StopMeasure
|
||||||
|
) {
|
||||||
|
job?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -45,7 +44,7 @@ import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccelerometerMeasure(
|
fun AccelerometerAccel(
|
||||||
ble: BleInfo,
|
ble: BleInfo,
|
||||||
accelScale: AccelScale,
|
accelScale: AccelScale,
|
||||||
accelMode: AccelViewMode,
|
accelMode: AccelViewMode,
|
||||||
|
|
@ -54,20 +53,15 @@ fun AccelerometerMeasure(
|
||||||
frequency: FftFrequency
|
frequency: FftFrequency
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val viewModel = hiltViewModel<AccelerometerMeasureViewModel>()
|
val viewModel = hiltViewModel<AccelerometerAccelViewModel>()
|
||||||
val state = viewModel.viewState.value
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
/*LaunchedEffect(ble.serial) {
|
viewModel.setEvent(AccelerometerAccelContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
|
||||||
|
|
||||||
DisposableEffect(key1 = "ble", effect = {
|
DisposableEffect(key1 = "ble", effect = {
|
||||||
|
|
||||||
onDispose {
|
onDispose {
|
||||||
Log.d("measure", "dispose")
|
viewModel.setEvent(AccelerometerAccelContract.Event.StopMeasure)
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
@ -89,7 +83,7 @@ fun AccelerometerMeasure(
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
viewModel.setEvent(AccelerometerAccelContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
},
|
},
|
||||||
enabled = true
|
enabled = true
|
||||||
) {
|
) {
|
||||||
|
|
@ -106,8 +100,8 @@ fun AccelerometerMeasure(
|
||||||
Box(modifier = Modifier) {
|
Box(modifier = Modifier) {
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is AccelerometerMeasureContract.State.Display -> Display(state = state)
|
is AccelerometerAccelContract.State.Display -> Display(state = state)
|
||||||
AccelerometerMeasureContract.State.Exception -> Exception()
|
is AccelerometerAccelContract.State.Exception -> Exception()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +113,7 @@ fun AccelerometerMeasure(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Display(
|
fun Display(
|
||||||
state: AccelerometerMeasureContract.State.Display
|
state: AccelerometerAccelContract.State.Display
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
|
|
@ -255,7 +249,7 @@ private fun Exception(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccelerometerMeasureContract {
|
class AccelerometerAccelContract {
|
||||||
|
|
||||||
sealed class Event : ViewEvent {
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
|
|
@ -300,29 +294,29 @@ class AccelerometerMeasureContract {
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AccelerometerMeasureViewModel @Inject constructor(
|
class AccelerometerAccelViewModel @Inject constructor(
|
||||||
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
||||||
) : BaseViewModel<AccelerometerMeasureContract.State, AccelerometerMeasureContract.Event, AccelerometerMeasureContract.Effect>() {
|
) : BaseViewModel<AccelerometerAccelContract.State, AccelerometerAccelContract.Event, AccelerometerAccelContract.Effect>() {
|
||||||
|
|
||||||
var measureJob: Job? = null
|
var measureJob: Job? = null
|
||||||
|
|
||||||
private var lastSerial: String? = null
|
private var lastSerial: String? = null
|
||||||
|
|
||||||
override fun setInitialState() = AccelerometerMeasureContract.State.Display(
|
override fun setInitialState() = AccelerometerAccelContract.State.Display(
|
||||||
emptyList()
|
emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun handleEvents(event: AccelerometerMeasureContract.Event) {
|
override fun handleEvents(event: AccelerometerAccelContract.Event) {
|
||||||
when(event){
|
when(event){
|
||||||
is AccelerometerMeasureContract.Event.OnStart -> reduce(viewState.value, event)
|
is AccelerometerAccelContract.Event.OnStart -> reduce(viewState.value, event)
|
||||||
is AccelerometerMeasureContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
is AccelerometerAccelContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
||||||
is AccelerometerMeasureContract.Event.StopMeasure -> reduce(viewState.value, event)
|
is AccelerometerAccelContract.Event.StopMeasure -> reduce(viewState.value, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerMeasureContract.State,
|
state: AccelerometerAccelContract.State,
|
||||||
event: AccelerometerMeasureContract.Event.StopMeasure
|
event: AccelerometerAccelContract.Event.StopMeasure
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -330,14 +324,14 @@ class AccelerometerMeasureViewModel @Inject constructor(
|
||||||
measureJob = null
|
measureJob = null
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
AccelerometerMeasureContract.State.Display(emptyList())
|
AccelerometerAccelContract.State.Display(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerMeasureContract.State,
|
state: AccelerometerAccelContract.State,
|
||||||
event: AccelerometerMeasureContract.Event.OnStart
|
event: AccelerometerAccelContract.Event.OnStart
|
||||||
) {
|
) {
|
||||||
|
|
||||||
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, false)
|
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, false)
|
||||||
|
|
@ -345,8 +339,8 @@ class AccelerometerMeasureViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerMeasureContract.State,
|
state: AccelerometerAccelContract.State,
|
||||||
event: AccelerometerMeasureContract.Event.OnRefreshHistory
|
event: AccelerometerAccelContract.Event.OnRefreshHistory
|
||||||
) {
|
) {
|
||||||
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, true)
|
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, true)
|
||||||
}
|
}
|
||||||
|
|
@ -367,7 +361,7 @@ class AccelerometerMeasureViewModel @Inject constructor(
|
||||||
measureJob = viewModelScope.launch {
|
measureJob = viewModelScope.launch {
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
AccelerometerMeasureContract.State.Display(emptyList())
|
AccelerometerAccelContract.State.Display(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency).onEach {
|
getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency).onEach {
|
||||||
|
|
@ -375,7 +369,7 @@ class AccelerometerMeasureViewModel @Inject constructor(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
setState {
|
setState {
|
||||||
when (this) {
|
when (this) {
|
||||||
is AccelerometerMeasureContract.State.Display -> {
|
is AccelerometerAccelContract.State.Display -> {
|
||||||
val dataList = this.measureHistory.toMutableList().apply {
|
val dataList = this.measureHistory.toMutableList().apply {
|
||||||
add(
|
add(
|
||||||
Accelerate(
|
Accelerate(
|
||||||
|
|
@ -385,18 +379,18 @@ class AccelerometerMeasureViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}.takeLast(10)
|
}.takeLast(10)
|
||||||
AccelerometerMeasureContract.State.Display(dataList)
|
AccelerometerAccelContract.State.Display(dataList)
|
||||||
}
|
}
|
||||||
|
|
||||||
AccelerometerMeasureContract.State.Exception -> {
|
AccelerometerAccelContract.State.Exception -> {
|
||||||
AccelerometerMeasureContract.State.Display(listOf(it))
|
AccelerometerAccelContract.State.Display(listOf(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
setState {
|
setState {
|
||||||
AccelerometerMeasureContract.State.Exception
|
AccelerometerAccelContract.State.Exception
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
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.*
|
||||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier
|
||||||
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 androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
|
||||||
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
|
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
|
||||||
import com.patrykandpatrick.vico.compose.chart.Chart
|
import com.patrykandpatrick.vico.compose.chart.Chart
|
||||||
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
|
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
|
||||||
|
|
@ -28,60 +27,68 @@ 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 androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import com.patrykandpatrick.vico.compose.chart.column.columnChart
|
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
||||||
|
import com.patrykandpatrick.vico.compose.chart.line.lineChart
|
||||||
|
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
|
||||||
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState
|
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.decoration.ThresholdLine
|
||||||
|
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
|
||||||
import com.patrykandpatrick.vico.core.entry.ChartEntry
|
import com.patrykandpatrick.vico.core.entry.ChartEntry
|
||||||
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
||||||
import com.patrykandpatrick.vico.core.extension.sumByFloat
|
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
||||||
|
import com.patrykandpatrick.vico.core.scroll.InitialScroll
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.TemperatureEntry
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.TemperatureHistoryContract
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.formatter
|
||||||
import llc.arma.ble.domain.common.ProgressState
|
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.usecase.AccelScale
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
import llc.arma.ble.domain.usecase.AccelViewMode
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.Accelerate
|
||||||
import llc.arma.ble.domain.usecase.FftAxis
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
import llc.arma.ble.domain.usecase.FftFrequency
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
import llc.arma.ble.domain.usecase.FftViewMode
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
import llc.arma.ble.domain.usecase.GetAccelerometerSpectreBySerial
|
import llc.arma.ble.domain.usecase.GetAccelerometerHistoryBySerial
|
||||||
|
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
class AccelerometerEntry(
|
class AccelEntry(
|
||||||
val frequency: Long,
|
val localDate: Long,
|
||||||
override val x: Float,
|
override val x: Float,
|
||||||
override val y: Float,
|
override val y: Float,
|
||||||
) : ChartEntry {
|
) : ChartEntry {
|
||||||
|
|
||||||
override fun withY(y: Float) = AccelerometerEntry(frequency, x, y)
|
override fun withY(y: Float) = AccelEntry(localDate, x, y)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccelerometerHistory(
|
fun AccelerometerHistory(
|
||||||
ble: BleInfo,
|
ble: BleInfo,
|
||||||
|
accelScale: AccelScale,
|
||||||
accelMode: AccelViewMode,
|
accelMode: AccelViewMode,
|
||||||
fftAxis: FftAxis,
|
fftAxis: FftAxis,
|
||||||
fftMode: FftViewMode,
|
fftMode: FftViewMode,
|
||||||
frequency: FftFrequency,
|
frequency: FftFrequency
|
||||||
accelScale: AccelScale
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
||||||
val state = viewModel.viewState.value
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
LaunchedEffect(ble.serial, accelMode, fftAxis, fftMode, frequency) {
|
LaunchedEffect(ble.serial) {
|
||||||
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
|
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(key1 = "ble", effect = {
|
DisposableEffect("ble") {
|
||||||
|
|
||||||
onDispose {
|
onDispose {
|
||||||
Log.d("history", "dispose")
|
|
||||||
viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure)
|
viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxHeight(0.9f)
|
modifier = Modifier.fillMaxHeight(0.9f)
|
||||||
|
|
@ -94,69 +101,26 @@ fun AccelerometerHistory(
|
||||||
|
|
||||||
val title = when(state){
|
val title = when(state){
|
||||||
is AccelerometerHistoryContract.State.Display -> {
|
is AccelerometerHistoryContract.State.Display -> {
|
||||||
if (state.previousHistory !== null) {
|
|
||||||
"${fftMode.localized} (${state.previousHistory.size})"
|
|
||||||
}else {
|
|
||||||
fftMode.localized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AccelerometerHistoryContract.State.Exception -> fftMode.localized
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(end = 16.dp),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
if(state is AccelerometerHistoryContract.State.Display) {
|
|
||||||
|
|
||||||
when (state.loadingHistoryState) {
|
when (state.loadingHistoryState) {
|
||||||
is ProgressState.Indeterminate -> {
|
is ProgressState.Finished -> "График измерений (${state.loadingHistoryState.data.size})"
|
||||||
|
is ProgressState.Indeterminate -> "График измерений"
|
||||||
CircularProgressIndicator(
|
is ProgressState.Progress -> "График измерений"
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
strokeWidth = 2.dp,
|
|
||||||
strokeCap = StrokeCap.Round,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
is ProgressState.Progress -> {
|
|
||||||
|
|
||||||
val progressAnimDuration = 1500
|
|
||||||
val progressAnimation by animateFloatAsState(
|
|
||||||
targetValue = state.loadingHistoryState.value,
|
|
||||||
animationSpec = tween(
|
|
||||||
durationMillis = progressAnimDuration,
|
|
||||||
easing = FastOutSlowInEasing
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
strokeWidth = 2.dp,
|
|
||||||
strokeCap = StrokeCap.Round,
|
|
||||||
progress = progressAnimation,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
AccelerometerHistoryContract.State.Exception -> "График измерений"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
|
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
},
|
},
|
||||||
enabled = when(state){
|
enabled = when(state){
|
||||||
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
||||||
AccelerometerHistoryContract.State.Exception -> true
|
AccelerometerHistoryContract.State.Exception -> true
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +139,7 @@ fun AccelerometerHistory(
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is AccelerometerHistoryContract.State.Display -> Display(state = state)
|
is AccelerometerHistoryContract.State.Display -> Display(state = state)
|
||||||
AccelerometerHistoryContract.State.Exception -> Exception()
|
is AccelerometerHistoryContract.State.Exception -> Exception()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -183,14 +147,7 @@ fun AccelerometerHistory(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
|
||||||
(chartValues.chartEntryModel.entries.firstOrNull()
|
|
||||||
?.getOrNull(value.toInt()) as? AccelerometerEntry)
|
|
||||||
?.frequency?.let { String.format("%.1f", (it.toFloat() / 256f)) }
|
|
||||||
.orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Display(
|
fun Display(
|
||||||
|
|
@ -202,98 +159,93 @@ fun Display(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val data = if(state.loadingHistoryState is ProgressState.Finished){
|
when (state.loadingHistoryState) {
|
||||||
state.loadingHistoryState.data
|
|
||||||
} else {
|
|
||||||
state.previousHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
val producer = remember {
|
is ProgressState.Finished -> {
|
||||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data != null){
|
if(state.loadingHistoryState.data.isEmpty()){
|
||||||
|
|
||||||
if(data.isEmpty()){
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
text = "Нет данных"
|
||||||
|
)
|
||||||
|
|
||||||
Text(
|
} else {
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
text = "Нет данных"
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
val producer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(data){
|
producer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||||
producer.setEntries(
|
AccelEntry(measurePoint.frequency, index.toFloat(), measurePoint.value )
|
||||||
data.mapIndexed { index, measurePoint ->
|
})
|
||||||
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
|
|
||||||
|
val axisValueFormatter =
|
||||||
|
AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
||||||
|
(chartValues.chartEntryModel.entries.firstOrNull()
|
||||||
|
?.getOrNull(value.toInt()) as? AccelEntry)
|
||||||
|
?.localDate
|
||||||
|
?.let { formatter.format(Date(it)) }
|
||||||
|
.orEmpty()
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val lineChart = columnChart(
|
val lineChart = lineChart()
|
||||||
spacing = 1.5.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
val scrollState = rememberChartScrollState()
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
Chart(
|
chartModelProducer = producer,
|
||||||
diffAnimationSpec = tween(0),
|
startAxis = startAxis(),
|
||||||
isZoomEnabled = true,
|
bottomAxis = bottomAxis(
|
||||||
chartScrollState = scrollState,
|
tickLength = 0.dp,
|
||||||
chart = lineChart,
|
valueFormatter = axisValueFormatter,
|
||||||
chartModelProducer = producer,
|
labelRotationDegrees = -90f,
|
||||||
startAxis = startAxis(),
|
),
|
||||||
bottomAxis = bottomAxis(
|
modifier = Modifier.fillMaxSize(),
|
||||||
tickLength = 0.dp,
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
valueFormatter = axisValueFormatter,
|
initialScroll = InitialScroll.End,
|
||||||
labelRotationDegrees = -90f,
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
),
|
autoScrollAnimationSpec = tween(0)
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
when (state.loadingHistoryState) {
|
|
||||||
is ProgressState.Indeterminate -> {
|
|
||||||
|
|
||||||
CircularProgressIndicator(
|
|
||||||
strokeCap = StrokeCap.Round,
|
|
||||||
modifier = Modifier.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
is ProgressState.Progress -> {
|
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
|
is ProgressState.Indeterminate -> {
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
is ProgressState.Progress -> {
|
||||||
|
|
||||||
|
val progressAnimDuration = 1500
|
||||||
|
val progressAnimation by animateFloatAsState(
|
||||||
|
targetValue = state.loadingHistoryState.value,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = progressAnimDuration,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
), label = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
progress = progressAnimation,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Exception(
|
private fun Exception() {
|
||||||
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
|
|
@ -319,20 +271,20 @@ class AccelerometerHistoryContract {
|
||||||
|
|
||||||
data class OnStart(
|
data class OnStart(
|
||||||
val serial: String,
|
val serial: String,
|
||||||
|
val accelScale: AccelScale,
|
||||||
val accelMode: AccelViewMode,
|
val accelMode: AccelViewMode,
|
||||||
val fftAxis: FftAxis,
|
val fftAxis: FftAxis,
|
||||||
val fftMode: FftViewMode,
|
val fftMode: FftViewMode,
|
||||||
val frequency: FftFrequency,
|
val frequency: FftFrequency
|
||||||
val accelScale: AccelScale
|
|
||||||
) : Event()
|
) : Event()
|
||||||
|
|
||||||
data class OnRefreshHistory(
|
data class OnRefreshHistory(
|
||||||
val serial: String,
|
val serial: String,
|
||||||
|
val accelScale: AccelScale,
|
||||||
val accelMode: AccelViewMode,
|
val accelMode: AccelViewMode,
|
||||||
val fftAxis: FftAxis,
|
val fftAxis: FftAxis,
|
||||||
val fftMode: FftViewMode,
|
val fftMode: FftViewMode,
|
||||||
val frequency: FftFrequency,
|
val frequency: FftFrequency
|
||||||
val accelScale: AccelScale
|
|
||||||
) : Event()
|
) : Event()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -340,7 +292,6 @@ class AccelerometerHistoryContract {
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
||||||
data class Display(
|
data class Display(
|
||||||
val previousHistory : List<Ble.Accelerometer.MeasurePoint>?,
|
|
||||||
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
|
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
|
||||||
) : State()
|
) : State()
|
||||||
|
|
||||||
|
|
@ -358,15 +309,15 @@ class AccelerometerHistoryContract {
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AccelerometerHistoryViewModel @Inject constructor(
|
class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
private val getAccelerometerSpectreBySerial: GetAccelerometerSpectreBySerial
|
private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial,
|
||||||
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
|
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
|
||||||
|
|
||||||
private var job: Job? = null
|
var measureJob: Job? = null
|
||||||
|
|
||||||
private var lastSerial: String? = null
|
private var lastSerial: String? = null
|
||||||
|
|
||||||
override fun setInitialState() = AccelerometerHistoryContract.State.Display(
|
override fun setInitialState() = AccelerometerHistoryContract.State.Display(
|
||||||
loadingHistoryState = ProgressState.Indeterminate,
|
ProgressState.Indeterminate
|
||||||
previousHistory = null
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun handleEvents(event: AccelerometerHistoryContract.Event) {
|
override fun handleEvents(event: AccelerometerHistoryContract.Event) {
|
||||||
|
|
@ -377,6 +328,21 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerHistoryContract.State,
|
||||||
|
event: AccelerometerHistoryContract.Event.StopMeasure
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Exception
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerHistoryContract.State,
|
state: AccelerometerHistoryContract.State,
|
||||||
event: AccelerometerHistoryContract.Event.OnStart
|
event: AccelerometerHistoryContract.Event.OnStart
|
||||||
|
|
@ -386,73 +352,36 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
|
|
||||||
if(state is AccelerometerHistoryContract.State.Display) {
|
if(state is AccelerometerHistoryContract.State.Display) {
|
||||||
|
|
||||||
//if(lastSerial != event.serial) {
|
if(lastSerial != event.serial) {
|
||||||
|
|
||||||
lastSerial = event.serial
|
lastSerial = event.serial
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
AccelerometerHistoryContract.State.Display(
|
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||||
loadingHistoryState = ProgressState.Indeterminate,
|
}
|
||||||
previousHistory = when(state.loadingHistoryState){
|
|
||||||
is ProgressState.Finished -> state.loadingHistoryState.data
|
measureJob?.cancel()
|
||||||
is ProgressState.Indeterminate -> null
|
measureJob = null
|
||||||
is ProgressState.Progress -> null
|
|
||||||
}
|
measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
|
||||||
)
|
it.fold(
|
||||||
}
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
} else {
|
|
||||||
setState {
|
|
||||||
AccelerometerHistoryContract.State.Display(
|
|
||||||
loadingHistoryState = ProgressState.Indeterminate,
|
|
||||||
previousHistory = null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
job?.cancel()
|
|
||||||
job = getAccelerometerSpectreBySerial(
|
|
||||||
serial = event.serial,
|
|
||||||
accelMode = event.accelMode,
|
|
||||||
fftAxis = event.fftAxis,
|
|
||||||
fftMode = event.fftMode,
|
|
||||||
frequency = event.frequency,
|
|
||||||
accelScale = event.accelScale
|
|
||||||
).onEach {
|
|
||||||
|
|
||||||
val currentState = viewState.value
|
|
||||||
|
|
||||||
if(currentState is AccelerometerHistoryContract.State.Display) {
|
|
||||||
|
|
||||||
it.fold(
|
|
||||||
onSuccess = {
|
|
||||||
setState {
|
|
||||||
|
|
||||||
AccelerometerHistoryContract.State.Display(
|
|
||||||
loadingHistoryState = it,
|
|
||||||
previousHistory = when (it) {
|
|
||||||
is ProgressState.Finished -> {
|
|
||||||
it.data
|
|
||||||
}
|
|
||||||
is ProgressState.Indeterminate -> currentState.previousHistory
|
|
||||||
is ProgressState.Progress -> currentState.previousHistory
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
setState {
|
|
||||||
AccelerometerHistoryContract.State.Exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}.launchIn(this)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -461,20 +390,16 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
state: AccelerometerHistoryContract.State,
|
state: AccelerometerHistoryContract.State,
|
||||||
event: AccelerometerHistoryContract.Event.OnRefreshHistory
|
event: AccelerometerHistoryContract.Event.OnRefreshHistory
|
||||||
) {
|
) {
|
||||||
/*viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccelerometerSpectreBySerial(
|
measureJob?.cancel()
|
||||||
serial = event.serial,
|
measureJob = null
|
||||||
accelMode = event.accelMode,
|
|
||||||
fftAxis = event.fftAxis,
|
measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
|
||||||
fftMode = event.fftMode,
|
|
||||||
frequency = event.frequency,
|
|
||||||
accelScale = event.accelScale
|
|
||||||
).onEach {
|
|
||||||
it.fold(
|
it.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
setState {
|
setState {
|
||||||
|
|
@ -489,14 +414,7 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
}*/
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun reduce(
|
|
||||||
state: AccelerometerHistoryContract.State,
|
|
||||||
event: AccelerometerHistoryContract.Event.StopMeasure
|
|
||||||
) {
|
|
||||||
job?.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
import llc.arma.ble.app.ui.screen.BleInfoView
|
import llc.arma.ble.app.ui.screen.BleInfoView
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -89,6 +90,123 @@ fun DisplayState(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable { }
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Сохранять историю измерений"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = ble.accelerometerState.saveHistory,
|
||||||
|
onCheckedChange = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnSaveHistoryChanged(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable {
|
||||||
|
onEvent(AccelerometerContract.Event.OnSaveIntervalEdit)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Интервал измерений"
|
||||||
|
)
|
||||||
|
|
||||||
|
val hours = ble.accelerometerState.historyInterval / 1000 / 60 / 60
|
||||||
|
val minutes = (ble.accelerometerState.historyInterval - ( hours * 1000 * 60 * 60 )) / 1000 / 60
|
||||||
|
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "$hours ч. $minutes мин."
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable {
|
||||||
|
onEvent(AccelerometerContract.Event.OnShowAccelerometerHistory)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "График измерений"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
vertical = 8.dp,
|
vertical = 8.dp,
|
||||||
|
|
@ -159,6 +277,42 @@ fun DisplayState(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable {
|
||||||
|
onEvent(AccelerometerContract.Event.OnChangePassword)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Изменить пароль"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowUp
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IntervalEdit(
|
||||||
|
state: BleView.Accelerometer,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var value by remember(state.accelerometerState.historyInterval) {
|
||||||
|
mutableStateOf((state.accelerometerState.historyInterval / 1000 / 60 / 60).toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxInterval = 240
|
||||||
|
|
||||||
|
if(value > maxInterval){
|
||||||
|
value = maxInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value < 1){
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxHours = maxInterval
|
||||||
|
val maxDays = maxInterval / 24
|
||||||
|
|
||||||
|
val dayValue = value / 24
|
||||||
|
val hourValue = value - (24 * dayValue)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Интервал измерений",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
|
||||||
|
NumberPicker(
|
||||||
|
range = -1..maxDays,
|
||||||
|
value = dayValue,
|
||||||
|
onValueChanged = { value = (it * 24) + hourValue }
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(text = "Дни")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
NumberPicker(
|
||||||
|
range = -1..maxHours,
|
||||||
|
value = hourValue,
|
||||||
|
onValueChanged = {
|
||||||
|
value = it + (dayValue * 24)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(text = "Часы")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onClick = {
|
||||||
|
onEvent(
|
||||||
|
AccelerometerContract.Event.OnSaveIntervalChanged(
|
||||||
|
value.toLong() * 1000 * 60 * 60
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Применить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NumberPicker(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
range: IntRange,
|
||||||
|
value: Int,
|
||||||
|
onValueChanged: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
LaunchedEffect(range){
|
||||||
|
|
||||||
|
if(value > range.last){
|
||||||
|
|
||||||
|
onValueChanged(range.last)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value < range.first){
|
||||||
|
|
||||||
|
onValueChanged(range.first)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
){
|
||||||
|
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = {
|
||||||
|
if(value < range.last) onValueChanged(value + 1)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowUp,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(36.dp))
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = value,
|
||||||
|
transitionSpec = {
|
||||||
|
if (targetState > initialState) {
|
||||||
|
(slideInVertically { height -> height } + fadeIn()).togetherWith(
|
||||||
|
slideOutVertically { height -> -height } + fadeOut())
|
||||||
|
} else {
|
||||||
|
(slideInVertically { height -> -height } + fadeIn()).togetherWith(
|
||||||
|
slideOutVertically { height -> height } + fadeOut())
|
||||||
|
}.using(
|
||||||
|
SizeTransform(clip = false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { targetCount ->
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.displaySmall,
|
||||||
|
text = "$targetCount"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(36.dp))
|
||||||
|
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = {
|
||||||
|
if(value > range.first) onValueChanged(value - 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ fun Write(
|
||||||
when (state) {
|
when (state) {
|
||||||
is AccelerometerContract.State.Display.WriteState.DisplayPreview -> {
|
is AccelerometerContract.State.Display.WriteState.DisplayPreview -> {
|
||||||
|
|
||||||
if(state.writeRequest.tx != null) {
|
if(state.writeRequest.tx != null || state.writeRequest.saveHistory != null || state.writeRequest.historyInterval != null) {
|
||||||
|
|
||||||
state.writeRequest.tx?.let {
|
state.writeRequest.tx?.let {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -81,6 +81,80 @@ fun Write(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.writeRequest.saveHistory?.let {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 0.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 = it.localizedName
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
state.writeRequest.historyInterval?.let {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 0.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 = "${it / 1000 / 60 / 60} ч."
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ fun Display(
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = progressAnimDuration,
|
durationMillis = progressAnimDuration,
|
||||||
easing = FastOutSlowInEasing
|
easing = FastOutSlowInEasing
|
||||||
)
|
), label = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
|
|
@ -221,9 +221,7 @@ fun Display(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Exception(
|
private fun Exception() {
|
||||||
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ fun Write(
|
||||||
Text(
|
Text(
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
text = "${it.localizedName}"
|
text = it.localizedName
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,11 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
private val app: Application
|
private val app: Application
|
||||||
) : BleRepository {
|
) : BleRepository {
|
||||||
|
|
||||||
|
private val ScanResult.timerEnabled: Boolean
|
||||||
|
get() {
|
||||||
|
return scanRecord?.manufacturerSpecificData?.get(89)?.get(2) == 1.toByte()
|
||||||
|
}
|
||||||
|
|
||||||
private val ScanResult.info: BleInfo
|
private val ScanResult.info: BleInfo
|
||||||
get() {
|
get() {
|
||||||
return BleInfo(
|
return BleInfo(
|
||||||
|
|
@ -135,15 +140,11 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
rssi = rssi,
|
rssi = rssi,
|
||||||
type = type,
|
type = type,
|
||||||
scanTime = timestampNanos / 1_000_000,
|
scanTime = timestampNanos / 1_000_000,
|
||||||
tx = scanRecord?.txPowerLevel ?: 0
|
tx = scanRecord?.txPowerLevel ?: 0,
|
||||||
|
recordEnabled = timerEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ScanResult.timerEnabled: Boolean
|
|
||||||
get() {
|
|
||||||
return scanRecord?.manufacturerSpecificData?.get(89)?.get(2) == 1.toByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val ScanResult.batteryLevel: Int?
|
private val ScanResult.batteryLevel: Int?
|
||||||
get() {
|
get() {
|
||||||
return scanRecord?.manufacturerSpecificData?.get(89)?.get(1)
|
return scanRecord?.manufacturerSpecificData?.get(89)?.get(1)
|
||||||
|
|
@ -300,6 +301,24 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
return when(result.info.type) {
|
return when(result.info.type) {
|
||||||
BleInfo.Type.ACCELEROMETER -> {
|
BleInfo.Type.ACCELEROMETER -> {
|
||||||
|
|
||||||
|
val tState = suspendCancellableCoroutine {
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|
||||||
|
it.resume(readAccelState(result))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}.fold(
|
||||||
|
onFailure = {
|
||||||
|
return Result.failure(it)
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Result.success(
|
Result.success(
|
||||||
flow {
|
flow {
|
||||||
|
|
||||||
|
|
@ -329,7 +348,8 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
4 -> Ble.BleState.TX.PLUS_4
|
4 -> Ble.BleState.TX.PLUS_4
|
||||||
else -> Ble.BleState.TX.ZERO
|
else -> Ble.BleState.TX.ZERO
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
|
accelerometerState = tState
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
@ -457,10 +477,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BleInfo.Type.ACCELEROMETER -> {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -497,6 +513,26 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun readAccelState(
|
||||||
|
record: ScanResult
|
||||||
|
): Result<Ble.Accelerometer.AccelerometerState, BleException> {
|
||||||
|
|
||||||
|
val history = readHistoryInterval(record).fold(
|
||||||
|
onFailure = {
|
||||||
|
return Result.failure(it)
|
||||||
|
},
|
||||||
|
onSuccess = { return@fold it }
|
||||||
|
)
|
||||||
|
|
||||||
|
return Result.success(
|
||||||
|
Ble.Accelerometer.AccelerometerState(
|
||||||
|
saveHistory = record.timerEnabled,
|
||||||
|
historyInterval = history
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
private suspend fun readTemperature(
|
private suspend fun readTemperature(
|
||||||
record: ScanResult
|
record: ScanResult
|
||||||
|
|
@ -620,7 +656,45 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
if (checkPermission()) {
|
if (checkPermission()) {
|
||||||
|
|
||||||
gatt = it.connectGatt(app, false, ReadHistoryCallback(app) {
|
gatt = it.connectGatt(app, false, ReadTemperatureHistoryCallback(app) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(Result.failure(BleException.PermissionDenied))
|
||||||
|
}
|
||||||
|
|
||||||
|
return@callbackFlow
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
gatt?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAccelerometerHistoryBySerial(
|
||||||
|
serial: String
|
||||||
|
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||||
|
|
||||||
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
|
return callbackFlow {
|
||||||
|
|
||||||
|
deviceCache[serial]?.device?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
gatt = it.connectGatt(app, false, ReadAccelerometerHistoryCallback(app) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
send(it)
|
send(it)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ class ReadAccelerometerCallback(
|
||||||
Log.d("accel", "notification")
|
Log.d("accel", "notification")
|
||||||
|
|
||||||
val data = value.toList().chunked(2).map {
|
val data = value.toList().chunked(2).map {
|
||||||
it.toByteArray().get2byteShortAt(0)
|
it.toByteArray().get2byteShortAt()
|
||||||
}
|
}
|
||||||
|
|
||||||
onResult(
|
onResult(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
package llc.arma.ble.data
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Application
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
|
import android.bluetooth.BluetoothGattCallback
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
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
|
||||||
|
|
||||||
|
class ReadAccelerometerHistoryCallback(
|
||||||
|
private val app: Application,
|
||||||
|
private val onResult: (Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>) -> Unit
|
||||||
|
) : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
enum class Property {
|
||||||
|
DATA_SIZE, PACKAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.get4byteUIntAt(idx: Int) =
|
||||||
|
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
|
||||||
|
((this[idx + 2].toUInt() and 0xFFu) shl 16) or
|
||||||
|
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
|
||||||
|
(this[idx].toUInt() and 0xFFu)
|
||||||
|
|
||||||
|
private fun ByteArray.get2byteUIntAt(idx: Int) =
|
||||||
|
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
|
||||||
|
(this[idx].toUInt() and 0xFFu)
|
||||||
|
|
||||||
|
private var readProperty: Property? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
onResult(Result.success(ProgressState.Indeterminate))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConnectionStateChange(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
status: Int,
|
||||||
|
newState: Int
|
||||||
|
) {
|
||||||
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
|
||||||
|
if(newState == BluetoothGatt.STATE_CONNECTED){
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
gatt.discoverServices()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServicesDiscovered(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onServicesDiscovered(gatt, status)
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerHistoryReadUUID)?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
readProperty = Property.DATA_SIZE
|
||||||
|
gatt.writeCharacteristic(it, byteArrayOf(2))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lastMeasureSystemTime: Long? = null
|
||||||
|
|
||||||
|
private var bleMeasureInterval: Long? = null
|
||||||
|
private var bleRealTime: Long? = null
|
||||||
|
private var bleLastMeasureTime: Long? = null
|
||||||
|
|
||||||
|
private val resultTemperaturePackage: MutableList<Float> = mutableListOf()
|
||||||
|
|
||||||
|
var expectedDataSize: Int? = null
|
||||||
|
|
||||||
|
override fun onCharacteristicRead(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
super.onCharacteristicRead(gatt, characteristic, status)
|
||||||
|
onCommonCharacteristicRead(gatt, characteristic, characteristic.value, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicRead(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
value: ByteArray,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onCharacteristicRead(gatt, characteristic, value, status)
|
||||||
|
onCommonCharacteristicRead(gatt, characteristic, value, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
private fun onCommonCharacteristicRead(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
value: ByteArray,
|
||||||
|
status: Int
|
||||||
|
){
|
||||||
|
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
when(readProperty){
|
||||||
|
Property.DATA_SIZE -> {
|
||||||
|
|
||||||
|
if(value.contentEquals(byteArrayOf(0, 0))) {
|
||||||
|
onResult(
|
||||||
|
Result.success(
|
||||||
|
ProgressState.Finished(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val writeData = mutableListOf(
|
||||||
|
1.toByte(),
|
||||||
|
0.toByte(),
|
||||||
|
0.toByte()
|
||||||
|
).apply {
|
||||||
|
addAll(value.toList())
|
||||||
|
}.toByteArray()
|
||||||
|
|
||||||
|
readProperty = Property.PACKAGE
|
||||||
|
gatt.writeCharacteristic(characteristic, writeData)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Property.PACKAGE -> {
|
||||||
|
|
||||||
|
if(value[0] == 250.toByte()){
|
||||||
|
|
||||||
|
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
|
||||||
|
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
|
||||||
|
bleRealTime = value.get4byteUIntAt(12).toLong()
|
||||||
|
|
||||||
|
lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
|
||||||
|
|
||||||
|
val temperatureDataArray = value.toUByteArray().asList().subList(16, value.size)
|
||||||
|
|
||||||
|
resultTemperaturePackage.addAll(
|
||||||
|
temperatureDataArray.chunked(2).map {
|
||||||
|
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
|
||||||
|
}.toMutableList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||||
|
expectedDataSize = nextPackageDataCount.toInt() + resultTemperaturePackage.size
|
||||||
|
|
||||||
|
onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
|
||||||
|
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))
|
||||||
|
|
||||||
|
if(nextPackageDataCount != 0.toUInt()){
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
gatt.writeCharacteristic(characteristic, byteArrayOf(5))
|
||||||
|
gatt.readCharacteristic(characteristic)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
onResult(
|
||||||
|
Result.success(
|
||||||
|
ProgressState.Finished(
|
||||||
|
resultTemperaturePackage.withIndex().map {
|
||||||
|
Ble.Accelerometer.MeasurePoint(
|
||||||
|
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
value = it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (value[0] == 251.toByte()) {
|
||||||
|
|
||||||
|
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||||
|
val temperatureDataArray = value.toUByteArray().toList().subList(4, value.size)
|
||||||
|
|
||||||
|
resultTemperaturePackage.addAll(
|
||||||
|
temperatureDataArray.chunked(2).map {
|
||||||
|
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))
|
||||||
|
|
||||||
|
if (nextPackageDataCount != 0.toUInt()) {
|
||||||
|
|
||||||
|
val writeData = byteArrayOf(5)
|
||||||
|
|
||||||
|
gatt.writeCharacteristic(characteristic, writeData)
|
||||||
|
gatt.readCharacteristic(characteristic)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
onResult(
|
||||||
|
Result.success(
|
||||||
|
ProgressState.Finished(
|
||||||
|
resultTemperaturePackage.withIndex().map {
|
||||||
|
Ble.Accelerometer.MeasurePoint(
|
||||||
|
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
value = it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicWrite(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
gatt.readCharacteristic(characteristic)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkPermission(): Boolean {
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED &&
|
||||||
|
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
} else {
|
||||||
|
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED &&
|
||||||
|
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BluetoothGatt.writeCharacteristic(
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
data: ByteArray
|
||||||
|
): Result<Unit, BleException>{
|
||||||
|
|
||||||
|
return if(checkPermission()){
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
||||||
|
}else{
|
||||||
|
characteristic.value = data
|
||||||
|
writeCharacteristic(characteristic)
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.success(Unit)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Result.failure(BleException.PermissionDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -23,13 +23,12 @@ import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder.LITTLE_ENDIAN
|
import java.nio.ByteOrder.LITTLE_ENDIAN
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
fun ByteArray.get2byteShortAt(idx: Int): Int {
|
fun ByteArray.get2byteShortAt(): Int {
|
||||||
val shorts = ShortArray(1)
|
val shorts = ShortArray(1)
|
||||||
ByteBuffer.wrap(this).order(LITTLE_ENDIAN).asShortBuffer()[shorts]
|
ByteBuffer.wrap(this).order(LITTLE_ENDIAN).asShortBuffer()[shorts]
|
||||||
return shorts[0].toInt()//(this[0].toInt() + (this[1].toInt() shl 8)).toShort()
|
return shorts[0].toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ReadAccelerometerSpectreCallback(
|
class ReadAccelerometerSpectreCallback(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val accelScale: AccelScale,
|
private val accelScale: AccelScale,
|
||||||
|
|
@ -67,11 +66,6 @@ class ReadAccelerometerSpectreCallback(
|
||||||
) {
|
) {
|
||||||
super.onConnectionStateChange(gatt, status, newState)
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
|
||||||
Log.d("spectre", "onConnectionStateChange")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
|
||||||
if(newState == BluetoothGatt.STATE_CONNECTED){
|
if(newState == BluetoothGatt.STATE_CONNECTED){
|
||||||
|
|
@ -98,10 +92,9 @@ class ReadAccelerometerSpectreCallback(
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
Log.d("spectre", "onServicesDiscovered")
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
enableNotifications(gatt)
|
enableNotifications(gatt)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +141,7 @@ class ReadAccelerometerSpectreCallback(
|
||||||
|
|
||||||
private val resultAccelerometerPackage: MutableList<Float> = mutableListOf()
|
private val resultAccelerometerPackage: MutableList<Float> = mutableListOf()
|
||||||
|
|
||||||
var expectedDataSize: Int? = null
|
private var expectedDataSize: Int? = null
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onCharacteristicRead(
|
override fun onCharacteristicRead(
|
||||||
|
|
@ -189,7 +182,7 @@ class ReadAccelerometerSpectreCallback(
|
||||||
){
|
){
|
||||||
|
|
||||||
if(characteristic.uuid == accelerometerReadUUID) {
|
if(characteristic.uuid == accelerometerReadUUID) {
|
||||||
Log.d("spectre", "changed")
|
|
||||||
readProperty = Property.DATA_SIZE
|
readProperty = Property.DATA_SIZE
|
||||||
gatt.getService(serviceUUID).getCharacteristic(accelerometerHistoryReadUUID)?.let {
|
gatt.getService(serviceUUID).getCharacteristic(accelerometerHistoryReadUUID)?.let {
|
||||||
gatt.writeCharacteristic(it, byteArrayOf(2))
|
gatt.writeCharacteristic(it, byteArrayOf(2))
|
||||||
|
|
@ -215,8 +208,6 @@ class ReadAccelerometerSpectreCallback(
|
||||||
status: Int
|
status: Int
|
||||||
){
|
){
|
||||||
|
|
||||||
Log.d("spectre", "onCharacteristicRead")
|
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
when(readProperty){
|
when(readProperty){
|
||||||
Property.DATA_SIZE -> {
|
Property.DATA_SIZE -> {
|
||||||
|
|
@ -257,7 +248,7 @@ class ReadAccelerometerSpectreCallback(
|
||||||
|
|
||||||
resultAccelerometerPackage.addAll(
|
resultAccelerometerPackage.addAll(
|
||||||
accelerometerDataArray.chunked(2).map {
|
accelerometerDataArray.chunked(2).map {
|
||||||
it.toByteArray().get2byteShortAt(0).toFloat()
|
it.toByteArray().get2byteShortAt().toFloat()
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -309,7 +300,7 @@ class ReadAccelerometerSpectreCallback(
|
||||||
|
|
||||||
resultAccelerometerPackage.addAll(
|
resultAccelerometerPackage.addAll(
|
||||||
temperatureDataArray.chunked(2).map {
|
temperatureDataArray.chunked(2).map {
|
||||||
it.toByteArray().get2byteShortAt(0).toFloat()
|
it.toByteArray().get2byteShortAt().toFloat()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -365,8 +356,6 @@ class ReadAccelerometerSpectreCallback(
|
||||||
) {
|
) {
|
||||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
|
||||||
Log.d("spectre", "request written $readProperty")
|
|
||||||
|
|
||||||
if(readProperty !== null) {
|
if(readProperty !== null) {
|
||||||
|
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
@ -390,29 +379,19 @@ class ReadAccelerometerSpectreCallback(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
|
|
||||||
super.onMtuChanged(gatt, mtu, status)
|
|
||||||
Log.d("spectre", "mtu $mtu")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDescriptorWrite(
|
override fun onDescriptorWrite(
|
||||||
gatt: BluetoothGatt,
|
gatt: BluetoothGatt,
|
||||||
descriptor: BluetoothGattDescriptor,
|
descriptor: BluetoothGattDescriptor,
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Log.d("spectre", "descriptor written")
|
|
||||||
super.onDescriptorWrite(gatt, descriptor, status)
|
super.onDescriptorWrite(gatt, descriptor, status)
|
||||||
start(gatt)
|
start(gatt)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(
|
private fun start(
|
||||||
gatt: BluetoothGatt,
|
gatt: BluetoothGatt,
|
||||||
){
|
){
|
||||||
|
|
||||||
Log.d("spectre", "start")
|
|
||||||
|
|
||||||
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||||
|
|
||||||
if (checkPermission()) {
|
if (checkPermission()) {
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,16 @@ import llc.arma.ble.domain.Result
|
||||||
import llc.arma.ble.domain.common.BleException
|
import llc.arma.ble.domain.common.BleException
|
||||||
import llc.arma.ble.domain.common.ProgressState
|
import llc.arma.ble.domain.common.ProgressState
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
enum class Property {
|
class ReadTemperatureHistoryCallback(
|
||||||
DATA_SIZE, PACKAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReadHistoryCallback(
|
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val onResult: (Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>) -> Unit
|
private val onResult: (Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>) -> Unit
|
||||||
) : BluetoothGattCallback() {
|
) : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
enum class Property {
|
||||||
|
DATA_SIZE, PACKAGE
|
||||||
|
}
|
||||||
|
|
||||||
private fun ByteArray.get4byteUIntAt(idx: Int) =
|
private fun ByteArray.get4byteUIntAt(idx: Int) =
|
||||||
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
|
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
|
||||||
((this[idx + 2].toUInt() and 0xFFu) shl 16) or
|
((this[idx + 2].toUInt() and 0xFFu) shl 16) or
|
||||||
|
|
@ -64,10 +64,43 @@ class WriteAccelerometerCallback(
|
||||||
status: Int
|
status: Int
|
||||||
){
|
){
|
||||||
|
|
||||||
if(request.tx != null) {
|
if(request.tx != null || request.saveHistory != null) {
|
||||||
|
|
||||||
|
fun UInt.to4ByteArrayInLittleEndian(): ByteArray =
|
||||||
|
(3 downTo 0).map {
|
||||||
|
(this shr (it * Byte.SIZE_BITS)).toByte()
|
||||||
|
}.toByteArray()
|
||||||
|
|
||||||
var uuid: Pair<UUID, ByteArray>? = null
|
var uuid: Pair<UUID, ByteArray>? = null
|
||||||
|
|
||||||
|
uuid = request.historyInterval?.let {
|
||||||
|
|
||||||
|
this.request = request.copy(
|
||||||
|
historyInterval = null
|
||||||
|
)
|
||||||
|
|
||||||
|
Pair(
|
||||||
|
intervalWriteUUID,
|
||||||
|
mutableListOf<Byte>(3).apply {
|
||||||
|
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
|
||||||
|
}.toByteArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = request.saveHistory?.let {
|
||||||
|
|
||||||
|
this.request = request.copy(
|
||||||
|
saveHistory = null
|
||||||
|
)
|
||||||
|
|
||||||
|
Pair(
|
||||||
|
saveEnabledWriteUUID,
|
||||||
|
mutableListOf<Byte>(4).apply {
|
||||||
|
add(if (it) 1 else 0)
|
||||||
|
}.toByteArray()
|
||||||
|
)
|
||||||
|
} ?: uuid
|
||||||
|
|
||||||
uuid = request.tx?.let {
|
uuid = request.tx?.let {
|
||||||
|
|
||||||
this.request = request.copy(
|
this.request = request.copy(
|
||||||
|
|
@ -143,8 +176,6 @@ class WriteAccelerometerCallback(
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Log.d("beacon", "onCharacteristicWrite $status")
|
|
||||||
|
|
||||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
|
||||||
if(checkPermission()) {
|
if(checkPermission()) {
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,6 @@ class WriteThermometerCallback(
|
||||||
) {
|
) {
|
||||||
super.onConnectionStateChange(gatt, status, newState)
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
|
||||||
Log.d("th", "onConnectionStateChange $status $newState")
|
|
||||||
|
|
||||||
if(checkPermission()) {
|
if(checkPermission()) {
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
|
@ -60,7 +58,6 @@ class WriteThermometerCallback(
|
||||||
gatt: BluetoothGatt,
|
gatt: BluetoothGatt,
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
Log.d("th", "onServicesDiscovered $status")
|
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
onCycle(gatt, status)
|
onCycle(gatt, status)
|
||||||
|
|
||||||
|
|
@ -183,8 +180,6 @@ class WriteThermometerCallback(
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Log.d("th", "onCharacteristicWrite $status")
|
|
||||||
|
|
||||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
|
||||||
if(checkPermission()) {
|
if(checkPermission()) {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@ sealed class Ble(
|
||||||
|
|
||||||
class Accelerometer(
|
class Accelerometer(
|
||||||
info: BleInfo,
|
info: BleInfo,
|
||||||
val state: BleState
|
val state: BleState,
|
||||||
|
val accelerometerState: AccelerometerState
|
||||||
): Ble(info) {
|
): Ble(info) {
|
||||||
|
|
||||||
data class WriteRequest(
|
data class WriteRequest(
|
||||||
val tx: BleState.TX?,
|
val tx: BleState.TX?,
|
||||||
|
val saveHistory: Boolean?,
|
||||||
|
val historyInterval: Long?
|
||||||
)
|
)
|
||||||
|
|
||||||
class MeasurePoint (
|
class MeasurePoint (
|
||||||
|
|
@ -18,6 +21,11 @@ sealed class Ble(
|
||||||
val value: Float
|
val value: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class AccelerometerState(
|
||||||
|
val saveHistory: Boolean,
|
||||||
|
val historyInterval: Long
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Beacon(
|
class Beacon(
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ data class BleInfo(
|
||||||
val rssi: Int?,
|
val rssi: Int?,
|
||||||
val type: Type,
|
val type: Type,
|
||||||
val scanTime: Long,
|
val scanTime: Long,
|
||||||
val tx: Int
|
val tx: Int,
|
||||||
|
val recordEnabled: Boolean
|
||||||
){
|
){
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
|
|
||||||
|
|
@ -50,4 +50,6 @@ interface BleRepository {
|
||||||
frequency: FftFrequency
|
frequency: FftFrequency
|
||||||
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
||||||
|
|
||||||
|
suspend fun getAccelerometerHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
class GetAccelerometerHistoryBySerial @Inject constructor(
|
||||||
|
private val bleRepository: BleRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||||
|
|
||||||
|
return bleRepository.getAccelerometerHistoryBySerial(serial)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue