Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
cccc65153d
|
|
@ -1,6 +1,6 @@
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "llc.arma.ble"
|
applicationId "llc.arma.ble"
|
||||||
minSdk 24
|
minSdk 26
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 1
|
versionCode 12
|
||||||
versionName "1.0"
|
versionName "1.2.12"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
@ -46,6 +46,13 @@ android {
|
||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applicationVariants.configureEach { variant ->
|
||||||
|
variant.outputs.configureEach {
|
||||||
|
outputFileName = "Arma BLE v${defaultConfig.versionName}.apk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -76,8 +83,10 @@ dependencies {
|
||||||
|
|
||||||
implementation "com.google.accompanist:accompanist-permissions:0.26.3-beta"
|
implementation "com.google.accompanist:accompanist-permissions:0.26.3-beta"
|
||||||
|
|
||||||
implementation "com.patrykandpatrick.vico:core:1.6.6"
|
implementation "com.patrykandpatrick.vico:core:1.7.1"
|
||||||
implementation "com.patrykandpatrick.vico:compose:1.6.6"
|
implementation "com.patrykandpatrick.vico:compose:1.7.1"
|
||||||
implementation "com.patrykandpatrick.vico:compose-m3:1.6.6"
|
implementation "com.patrykandpatrick.vico:compose-m3:1.7.1"
|
||||||
|
|
||||||
|
implementation files('libs/poishadow-all.jar')
|
||||||
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
|
@ -18,6 +18,13 @@
|
||||||
<uses-feature android:name="android.hardware.location.gps" />
|
<uses-feature android:name="android.hardware.location.gps" />
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="29" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|
@ -27,7 +34,20 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.App.Starting"
|
android:theme="@style/Theme.App.Starting"
|
||||||
tools:targetApi="31"
|
tools:targetApi="31"
|
||||||
android:name=".app.framework.App">
|
android:name=".app.framework.App"
|
||||||
|
android:requestLegacyExternalStorage="true">
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="llc.arma.ble.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
|
||||||
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".app.ui.MainActivity"
|
android:name=".app.ui.MainActivity"
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,18 @@ import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
@HiltAndroidApp()
|
@HiltAndroidApp()
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Без этого apache poi на android не заводится
|
||||||
|
*@link https://github.com/centic9/poi-on-android
|
||||||
|
*/
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
System.setProperty("org.apache.poi.javax.xml.stream.XMLInputFactory", "com.fasterxml.aalto.stax.InputFactoryImpl");
|
||||||
|
System.setProperty("org.apache.poi.javax.xml.stream.XMLOutputFactory", "com.fasterxml.aalto.stax.OutputFactoryImpl");
|
||||||
|
System.setProperty("org.apache.poi.javax.xml.stream.XMLEventFactory", "com.fasterxml.aalto.stax.EventFactoryImpl");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,11 @@ import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import llc.arma.ble.data.BleRepositoryImpl
|
import llc.arma.ble.data.BleRepositoryImpl
|
||||||
|
import llc.arma.ble.data.EmailRepositoryImpl
|
||||||
|
import llc.arma.ble.data.XlsxRepositoryImpl
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
|
import llc.arma.ble.domain.repository.EmailRepository
|
||||||
|
import llc.arma.ble.domain.repository.XlsxRepository
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
|
|
@ -14,4 +18,10 @@ interface RepositoryBinding {
|
||||||
@Binds
|
@Binds
|
||||||
fun bindBleRepository(bleRepositoryImpl: BleRepositoryImpl): BleRepository
|
fun bindBleRepository(bleRepositoryImpl: BleRepositoryImpl): BleRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindEmailRepository(repository: EmailRepositoryImpl): EmailRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindXlsxRepository(repository: XlsxRepositoryImpl): XlsxRepository
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
package llc.arma.ble.app.ui
|
package llc.arma.ble.app.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.KeyEvent
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
|
@ -16,11 +22,10 @@ import androidx.compose.material.rememberModalBottomSheetState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
|
@ -34,6 +39,7 @@ import llc.arma.ble.app.ui.theme.BleTheme
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterialApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -56,6 +62,10 @@ class MainActivity : ComponentActivity() {
|
||||||
mutableStateOf<@Composable () -> Unit>({})
|
mutableStateOf<@Composable () -> Unit>({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(modalState.currentValue == ModalBottomSheetValue.Hidden){
|
||||||
|
sheetContent = {}
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalBottomDialogState provides BottomState(
|
LocalBottomDialogState provides BottomState(
|
||||||
sheetState = modalState,
|
sheetState = modalState,
|
||||||
|
|
@ -125,20 +135,37 @@ class MainActivity : ComponentActivity() {
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val multiplePermissionsState =
|
val multiplePermissionsState =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
|
||||||
rememberMultiplePermissionsState(
|
rememberMultiplePermissionsState(
|
||||||
listOf(
|
listOf(
|
||||||
|
Manifest.permission.READ_MEDIA_VIDEO,
|
||||||
|
Manifest.permission.READ_MEDIA_IMAGES,
|
||||||
Manifest.permission.BLUETOOTH_SCAN,
|
Manifest.permission.BLUETOOTH_SCAN,
|
||||||
Manifest.permission.BLUETOOTH_CONNECT
|
Manifest.permission.BLUETOOTH_CONNECT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
rememberMultiplePermissionsState(
|
}else{
|
||||||
listOf(
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
rememberMultiplePermissionsState(
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
listOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.BLUETOOTH_SCAN,
|
||||||
|
Manifest.permission.BLUETOOTH_CONNECT
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
} else {
|
||||||
|
rememberMultiplePermissionsState(
|
||||||
|
listOf(
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiplePermissionsState.allPermissionsGranted) {
|
if (multiplePermissionsState.allPermissionsGranted) {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class BottomDialogState @OptIn(ExperimentalMaterialApi::class) constructor(
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
){
|
){
|
||||||
setContent(content)
|
setContent(content)
|
||||||
if(sheetState?.currentValue != ModalBottomSheetValue.Expanded)
|
//if(sheetState?.currentValue != ModalBottomSheetValue.Expanded)
|
||||||
sheetState?.show()
|
sheetState?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package llc.arma.ble.app.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SignalLevel(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
maxLevel: Int = 5,
|
||||||
|
level: Int
|
||||||
|
){
|
||||||
|
|
||||||
|
val step = (16 - 4) / 4
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier.height(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
|
|
||||||
|
for(col in 0..4 step 1){
|
||||||
|
Surface(
|
||||||
|
color = LocalContentColor.current.copy(
|
||||||
|
alpha = if(col <= level + 1) ContentAlpha.high else ContentAlpha.disabled
|
||||||
|
),
|
||||||
|
shape = CircleShape,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(4.dp)
|
||||||
|
.defaultMinSize(minHeight = 4.dp)
|
||||||
|
.height(((col + 1) * step).dp)
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,13 @@ class BleMapper @Inject constructor(
|
||||||
is Ble.Accelerometer -> {
|
is Ble.Accelerometer -> {
|
||||||
BleView.Accelerometer(
|
BleView.Accelerometer(
|
||||||
info = input.info,
|
info = input.info,
|
||||||
|
state = BleView.BleState(
|
||||||
|
tx = txMapper.map(input.state.tx)
|
||||||
|
),
|
||||||
|
accelerometerState = BleView.Accelerometer.AccelerometerState(
|
||||||
|
saveHistory = input.accelerometerState.saveHistory,
|
||||||
|
historyInterval = input.accelerometerState.historyInterval
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,14 @@ class BleViewMapper @Inject constructor(
|
||||||
|
|
||||||
is BleView.Accelerometer -> {
|
is BleView.Accelerometer -> {
|
||||||
Ble.Accelerometer(
|
Ble.Accelerometer(
|
||||||
info = input.info
|
info = input.info,
|
||||||
|
state = Ble.BleState(
|
||||||
|
tx = txMapper.map(input.state.tx)
|
||||||
|
),
|
||||||
|
accelerometerState = Ble.Accelerometer.AccelerometerState(
|
||||||
|
saveHistory = input.accelerometerState.saveHistory,
|
||||||
|
historyInterval = input.accelerometerState.historyInterval,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,31 @@ package llc.arma.ble.app.ui.model
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
|
||||||
sealed class BleView(
|
sealed class BleView(
|
||||||
val info: BleInfo
|
val info: BleInfo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
class Accelerometer(
|
class Accelerometer(
|
||||||
info: BleInfo
|
info: BleInfo,
|
||||||
) : BleView(info)
|
val state: BleState,
|
||||||
|
val accelerometerState: AccelerometerState
|
||||||
|
) : BleView(info) {
|
||||||
|
|
||||||
|
class AccelerometerState(
|
||||||
|
saveHistory: Ble.Accelerometer.History,
|
||||||
|
historyInterval: Long,
|
||||||
|
) {
|
||||||
|
var saveHistory by mutableStateOf(saveHistory)
|
||||||
|
var historyInterval by mutableStateOf(historyInterval)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Beacon(
|
class Beacon(
|
||||||
info: BleInfo,
|
info: BleInfo,
|
||||||
|
|
@ -58,9 +74,27 @@ sealed class BleView(
|
||||||
MINUS_4(-4),
|
MINUS_4(-4),
|
||||||
ZERO(0),
|
ZERO(0),
|
||||||
PLUS_3(3),
|
PLUS_3(3),
|
||||||
PLUS_4(4)
|
PLUS_4(4);
|
||||||
|
|
||||||
|
val powerPercentage: Int
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
MINUS_40 -> 1
|
||||||
|
MINUS_20 -> 5
|
||||||
|
MINUS_16 -> 7
|
||||||
|
MINUS_12 -> 10
|
||||||
|
MINUS_8 -> 16
|
||||||
|
MINUS_4 -> 20
|
||||||
|
ZERO -> 40
|
||||||
|
PLUS_3 -> 80
|
||||||
|
PLUS_4 -> 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ class BleListContract {
|
||||||
data class Filter(
|
data class Filter(
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val mac: String = "",
|
val mac: String = "",
|
||||||
val rssi: ClosedFloatingPointRange<Float> = (-100f)..(-30f),
|
val rssi: ClosedFloatingPointRange<Float> = (-100f)..(-10f),
|
||||||
val bleType: BleInfo.Type? = null
|
val bleType: BleInfo.Type? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -29,9 +30,11 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import llc.arma.ble.app.ui.common.SignalLevel
|
||||||
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -119,7 +122,14 @@ fun BleListScreen(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(state.bleList.isEmpty()){
|
val filteredData = state.bleList.filter {
|
||||||
|
(it.type == state.filter.bleType || state.filter.bleType == null) &&
|
||||||
|
it.name.contains(state.filter.name) &&
|
||||||
|
it.serial.contains(state.filter.mac) &&
|
||||||
|
state.filter.rssi.contains(it.rssi?.toFloat() ?: Float.MIN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(filteredData.isEmpty()){
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
strokeCap = StrokeCap.Round,
|
strokeCap = StrokeCap.Round,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -128,37 +138,44 @@ fun BleListScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
if(filteredData.isEmpty()){
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
|
|
||||||
items(items = state.connectedBleList){
|
Box(modifier = Modifier.fillMaxSize()){
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
text = "Метки в области не найдены"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
items(items = state.connectedBleList){
|
||||||
|
|
||||||
|
ConnectedBleItem(ble = it) {
|
||||||
|
viewModel.setEvent(BleListContract.Event.OnConnectToBle(it.serial))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
items(items = filteredData.sortedBy { it.name }.reversed()) {
|
||||||
|
|
||||||
|
BleItem(
|
||||||
|
ble = it,
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(BleListContract.Event.OnConnectToBle(it.serial))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ConnectedBleItem(ble = it) {
|
|
||||||
viewModel.setEvent(BleListContract.Event.OnConnectToBle(it.serial))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val filteredData = state.bleList.filter {
|
|
||||||
(it.type == state.filter.bleType || state.filter.bleType == null) &&
|
|
||||||
it.name.contains(state.filter.name) &&
|
|
||||||
it.serial.contains(state.filter.mac) &&
|
|
||||||
state.filter.rssi.contains(it.rssi?.toFloat() ?: Float.MIN_VALUE)
|
|
||||||
}
|
|
||||||
|
|
||||||
items(items = filteredData.sortedBy { it.name }.reversed()) {
|
|
||||||
|
|
||||||
BleItem(
|
|
||||||
ble = it,
|
|
||||||
onClick = {
|
|
||||||
viewModel.setEvent(BleListContract.Event.OnConnectToBle(it.serial))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +201,16 @@ private fun ItemIcon(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Int.toSignalLevel(): Int {
|
||||||
|
return when(this){
|
||||||
|
in -30 downTo -52 -> 4
|
||||||
|
in -51 downTo -63 -> 3
|
||||||
|
in -62 downTo -75 -> 2
|
||||||
|
in -74 downTo -89 -> 1
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BleItem(
|
private fun BleItem(
|
||||||
ble: BleInfo,
|
ble: BleInfo,
|
||||||
|
|
@ -199,28 +226,27 @@ private fun BleItem(
|
||||||
val highAlpha = ContentAlpha.high
|
val highAlpha = ContentAlpha.high
|
||||||
val disabledAlpha = ContentAlpha.disabled
|
val disabledAlpha = ContentAlpha.disabled
|
||||||
|
|
||||||
var alpha by remember {
|
|
||||||
mutableStateOf(
|
var time by remember {
|
||||||
if(SystemClock.elapsedRealtime() - ble.scanTime > 10_000){
|
mutableLongStateOf(
|
||||||
disabledAlpha
|
SystemClock.elapsedRealtime()
|
||||||
} else {
|
|
||||||
highAlpha
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(ble.scanTime) {
|
LaunchedEffect(ble.scanTime) {
|
||||||
while(true) {
|
while(true) {
|
||||||
alpha = if(SystemClock.elapsedRealtime() - ble.scanTime > 10_000){
|
time = SystemClock.elapsedRealtime()
|
||||||
disabledAlpha
|
delay(100)
|
||||||
} else {
|
|
||||||
highAlpha
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(800)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var alpha = if(SystemClock.elapsedRealtime() - ble.scanTime > 10_000){
|
||||||
|
disabledAlpha
|
||||||
|
} else {
|
||||||
|
highAlpha
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
|
@ -234,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 {
|
||||||
|
|
@ -255,6 +305,26 @@ private fun BleItem(
|
||||||
text = ble.serial
|
text = ble.serial
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.alpha(0.7f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
imageVector = Icons.Rounded.CompareArrows,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20))) + " м."
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|
@ -264,11 +334,9 @@ private fun BleItem(
|
||||||
modifier = Modifier.alpha(0.7f)
|
modifier = Modifier.alpha(0.7f)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Icon(
|
SignalLevel(level = ble.rssi?.toSignalLevel() ?: 0)
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
imageVector = Icons.Rounded.NetworkCell,
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
|
||||||
|
|
@ -329,17 +397,10 @@ private fun BleItem(
|
||||||
modifier = Modifier.alpha(0.7f)
|
modifier = Modifier.alpha(0.7f)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val color = if(ble.batteryLevel < 100){
|
|
||||||
MaterialTheme.colorScheme.error
|
|
||||||
} else {
|
|
||||||
LocalContentColor.current
|
|
||||||
}
|
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
imageVector = Icons.Rounded.ArrowRightAlt,
|
imageVector = Icons.Rounded.ArrowRightAlt,
|
||||||
contentDescription = null,
|
contentDescription = null
|
||||||
tint = color
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
|
@ -356,7 +417,7 @@ private fun BleItem(
|
||||||
modifier = Modifier.alpha(0f)
|
modifier = Modifier.alpha(0f)
|
||||||
)
|
)
|
||||||
|
|
||||||
val lastAdv = ((SystemClock.elapsedRealtime() - ble.scanTime) / 1_000)
|
val lastAdv = ((time - ble.scanTime) / 1_000)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,17 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
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.domain.usecase.ExportToXlsx
|
||||||
import llc.arma.ble.domain.usecase.GetBleAroundFlow
|
import llc.arma.ble.domain.usecase.GetBleAroundFlow
|
||||||
import llc.arma.ble.domain.usecase.GetConnectedBleDevices
|
import llc.arma.ble.domain.usecase.GetConnectedBleDevices
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class BleListViewModel @Inject constructor(
|
class BleListViewModel @Inject constructor(
|
||||||
getBleAroundFlow: GetBleAroundFlow,
|
getBleAroundFlow: GetBleAroundFlow,
|
||||||
getConnectedBleDevices: GetConnectedBleDevices
|
getConnectedBleDevices: GetConnectedBleDevices,
|
||||||
|
exportToXlsx: ExportToXlsx
|
||||||
) : BaseViewModel<BleListContract.State, BleListContract.Event, BleListContract.Effect>() {
|
) : BaseViewModel<BleListContract.State, BleListContract.Event, BleListContract.Effect>() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
||||||
|
|
@ -244,8 +244,8 @@ fun Filter(
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
onEvent(BleListContract.Event.OnRssiRangeChanged(it))
|
onEvent(BleListContract.Event.OnRssiRangeChanged(it))
|
||||||
},
|
},
|
||||||
valueRange = (-100f)..(-30f),
|
valueRange = (-100f)..(-10f),
|
||||||
steps = 69,
|
steps = 89,
|
||||||
colors = SliderDefaults.colors(
|
colors = SliderDefaults.colors(
|
||||||
activeTickColor = MaterialTheme.colorScheme.primary,
|
activeTickColor = MaterialTheme.colorScheme.primary,
|
||||||
inactiveTickColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.38f)
|
inactiveTickColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.38f)
|
||||||
|
|
|
||||||
|
|
@ -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,21 +4,102 @@ 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.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.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
|
||||||
class AccelerometerContract {
|
class AccelerometerContract {
|
||||||
|
|
||||||
sealed class Event : ViewEvent {
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
object OnShowAccelerometerMeasure : Event()
|
object OnShowAccelerometerAccel : Event()
|
||||||
|
object OnHideAccelerometerAccel : Event()
|
||||||
|
|
||||||
object OnHideAccelerometerMeasure : Event()
|
object OnShowAccelerometerSpectre : Event()
|
||||||
|
object OnHideAccelerometerSpectre : Event()
|
||||||
|
|
||||||
|
object OnShowAccelerometerHistory : Event()
|
||||||
|
object OnHideAccelerometerHistory : Event()
|
||||||
|
|
||||||
|
data class OnAccelViewModeEdit(
|
||||||
|
val next: Next
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
enum class Next {
|
||||||
|
ACCEL, SPECTRE, HISTORY
|
||||||
|
}
|
||||||
|
|
||||||
|
data class OnAccelScaleEdit(
|
||||||
|
val next: Next
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
object OnAccelEdit : Event()
|
||||||
|
object OnSpectreEdit : Event()
|
||||||
|
object OnHistoryEdit : Event()
|
||||||
|
object OnFftFrequencyEdit : Event()
|
||||||
|
|
||||||
|
object OnFftAxisEdit : Event()
|
||||||
|
object OnFftModeEdit : Event()
|
||||||
|
|
||||||
|
object OnPowerEdit : Event()
|
||||||
|
|
||||||
|
object OnShowWriteBlePreview : Event()
|
||||||
|
|
||||||
|
object OnWriteBle : Event()
|
||||||
|
|
||||||
|
object OnHideWriteBlePreview : Event()
|
||||||
|
|
||||||
|
object OnChangePassword : Event()
|
||||||
|
object OnSaveIntervalEdit : Event()
|
||||||
|
object OnHideHistoryEdit : Event()
|
||||||
|
|
||||||
data class OnBleChanged(
|
data class OnBleChanged(
|
||||||
val ble: Ble.Accelerometer,
|
val ble: Ble.Accelerometer,
|
||||||
): Event()
|
): Event()
|
||||||
|
|
||||||
|
data class OnPowerChanged(
|
||||||
|
val tx: BleView.BleState.TX
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnAccelViewModelChanged(
|
||||||
|
val mode: AccelViewMode
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnHistoryViewModeChanged(
|
||||||
|
val mode: AccelViewMode
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnFftFrequencyChanged(
|
||||||
|
val frequency: FftFrequency
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnFftAxisChanged(
|
||||||
|
val axis: FftAxis
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnFftModeChanged(
|
||||||
|
val mode: FftViewMode
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnAccelScaleChanged(
|
||||||
|
val scale: AccelScale
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnHistoryScaleChanged(
|
||||||
|
val scale: AccelScale
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnSaveHistoryChanged(
|
||||||
|
val save: Boolean
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnSaveIntervalChanged(
|
||||||
|
val interval: Long
|
||||||
|
) : Event()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
@ -28,13 +109,73 @@ class AccelerometerContract {
|
||||||
data class Display(
|
data class Display(
|
||||||
val origin: Ble.Accelerometer,
|
val origin: Ble.Accelerometer,
|
||||||
val accelerometer: BleView.Accelerometer,
|
val accelerometer: BleView.Accelerometer,
|
||||||
) : State()
|
val writeState: WriteState?,
|
||||||
|
val accelViewMode: AccelViewMode,
|
||||||
|
val accelScale: AccelScale,
|
||||||
|
val fftViewMode: FftViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftFrequency: FftFrequency,
|
||||||
|
) : State() {
|
||||||
|
|
||||||
|
sealed class WriteState {
|
||||||
|
|
||||||
|
data class DisplayPreview(
|
||||||
|
val writeRequest: Ble.Accelerometer.WriteRequest
|
||||||
|
) : WriteState()
|
||||||
|
|
||||||
|
data class Writing(
|
||||||
|
val writeRequest: Ble.Accelerometer.WriteRequest
|
||||||
|
) : WriteState()
|
||||||
|
|
||||||
|
object Success : WriteState()
|
||||||
|
|
||||||
|
object Failure : WriteState()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Effect : ViewSideEffect {
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
object ShowAccelerometerMeasure : Effect()
|
object ShowAccelerometerAccel : Effect()
|
||||||
|
object ShowAccelerometerSpectre : Effect()
|
||||||
|
object ShowAccelerometerHistory : Effect()
|
||||||
|
|
||||||
|
object ShowPowerPicker : Effect()
|
||||||
|
object HidePowerPicker : Effect()
|
||||||
|
|
||||||
|
object ShowWriteBle : Effect()
|
||||||
|
object HideWriteBle : Effect()
|
||||||
|
|
||||||
|
object HideHistoryEdit : Effect()
|
||||||
|
|
||||||
|
data class ShowAccelViewEdit(
|
||||||
|
val next: Event.Next
|
||||||
|
) : Effect()
|
||||||
|
|
||||||
|
data class ShowAccelScaleEdit(
|
||||||
|
val next: Event.Next
|
||||||
|
) : Effect()
|
||||||
|
|
||||||
|
object ShowAccelEdit : Effect()
|
||||||
|
object ShowSpectreEdit : Effect()
|
||||||
|
object ShowFftFrequencyEdit : Effect()
|
||||||
|
object ShowFftAxisEdit : Effect()
|
||||||
|
object ShowFftModeEdit : Effect()
|
||||||
|
|
||||||
|
|
||||||
|
object HideIntervalPicker : Effect()
|
||||||
|
object ShowIntervalPicker : Effect()
|
||||||
|
|
||||||
|
object ShowHistoryEdit : Effect()
|
||||||
|
|
||||||
|
sealed class Navigation : Effect() {
|
||||||
|
|
||||||
|
object NavigateToChangePassword : Navigation()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ 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
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
|
@ -15,19 +17,33 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerMeasure
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelEdit
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFftAxisEdit
|
||||||
|
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.AccelScaleEdit
|
||||||
|
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.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.DisplayState
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.DisplayState
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.HistoryEdit
|
||||||
|
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.Write
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
enum class SheetPage {
|
enum class SheetPage {
|
||||||
MEASURE_HISTORY
|
HISTORY, ACCEL_SCALE, SPECTRE_SCALE, HISTORY_MODE_EDIT, HISTORY_SCALE, HISTORY_EDIT, ACCEL_EDIT, 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>()
|
||||||
|
|
@ -43,30 +59,402 @@ fun AccelerometerScreen(
|
||||||
mutableStateOf<SheetPage?>(null)
|
mutableStateOf<SheetPage?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(
|
||||||
|
key1 = bottomDialog.sheetState?.currentValue,
|
||||||
|
block = {
|
||||||
|
if(bottomDialog.sheetState?.currentValue == ModalBottomSheetValue.Hidden) {
|
||||||
|
bottomDialog.setContent({})
|
||||||
|
sheetPage = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(sheetPage) {
|
LaunchedEffect(sheetPage) {
|
||||||
when (sheetPage) {
|
when (sheetPage) {
|
||||||
SheetPage.MEASURE_HISTORY -> 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(ble = currentState.accelerometer.info)
|
AccelerometerHistory(
|
||||||
|
ble = currentState.accelerometer.info,
|
||||||
|
accelMode = currentState.accelViewMode,
|
||||||
|
fftAxis = currentState.fftAxis,
|
||||||
|
fftMode = currentState.fftViewMode,
|
||||||
|
frequency = currentState.fftFrequency,
|
||||||
|
accelScale = currentState.accelScale
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SheetPage.ACCEL -> launch {
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if (currentState is AccelerometerContract.State.Display) {
|
||||||
|
bottomDialog.show {
|
||||||
|
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,
|
||||||
|
accelMode = currentState.accelViewMode,
|
||||||
|
fftAxis = currentState.fftAxis,
|
||||||
|
fftMode = currentState.fftViewMode,
|
||||||
|
frequency = currentState.fftFrequency,
|
||||||
|
accelScale = currentState.accelScale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SheetPage.POWER -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
PowerEdit(
|
||||||
|
state = currentState.accelerometer,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.WRITE -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if (currentState is AccelerometerContract.State.Display) {
|
||||||
|
|
||||||
|
currentState.writeState?.let {
|
||||||
|
|
||||||
|
Write(
|
||||||
|
state = it,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SheetPage.ACCEL_MODE_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelViewEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.ACCEL,
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.SPECTRE_MODE_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelViewEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.SPECTRE,
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.HISTORY_MODE_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelViewEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.HISTORY,
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.SPECTRE_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelSpectreEdit(
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.FREQUENCY_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelFrequencyEdit(
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.AXIS_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelFftAxisEdit(
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.FFT_MODE_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelFftModeEdit(
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.INTERVAL_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
IntervalEdit(
|
||||||
|
state = currentState.accelerometer,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.ACCEL_SCALE -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelScaleEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.ACCEL,
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.SPECTRE_SCALE -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelScaleEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.SPECTRE,
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.HISTORY_SCALE -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelScaleEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.HISTORY,
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.ACCEL_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
AccelEdit(
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SheetPage.HISTORY_EDIT -> bottomDialog.show {
|
||||||
|
|
||||||
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
HistoryEdit(
|
||||||
|
state = currentState,
|
||||||
|
onEvent = {
|
||||||
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
null -> {
|
null -> {
|
||||||
bottomDialog.hide()
|
bottomDialog.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisposableEffect(key1 = Unit, effect = {
|
||||||
|
onDispose {
|
||||||
|
scope.launch {
|
||||||
|
bottomDialog.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
LaunchedEffect("effect"){
|
LaunchedEffect("effect"){
|
||||||
viewModel.effect.onEach {
|
viewModel.effect.onEach {
|
||||||
when(it){
|
when(it){
|
||||||
AccelerometerContract.Effect.ShowAccelerometerMeasure -> {
|
is AccelerometerContract.Effect.ShowAccelerometerAccel -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.ACCEL
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.HidePowerPicker -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowPowerPicker -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.POWER
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.HideWriteBle -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowWriteBle -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.WRITE
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowAccelerometerSpectre -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.SPECTRE
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowAccelViewEdit -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = when(it.next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL -> SheetPage.ACCEL_MODE_EDIT
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE -> SheetPage.SPECTRE_MODE_EDIT
|
||||||
|
AccelerometerContract.Event.Next.HISTORY -> SheetPage.HISTORY_MODE_EDIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowSpectreEdit -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.SPECTRE_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowFftFrequencyEdit -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.FREQUENCY_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowFftAxisEdit -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.AXIS_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowFftModeEdit -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.FFT_MODE_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowAccelerometerHistory -> launch {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.HISTORY
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.HideIntervalPicker -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowIntervalPicker -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.INTERVAL_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowAccelScaleEdit -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = when(it.next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL -> SheetPage.ACCEL_SCALE
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE -> SheetPage.SPECTRE_SCALE
|
||||||
|
AccelerometerContract.Event.Next.HISTORY -> SheetPage.HISTORY_SCALE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowAccelEdit -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.ACCEL_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.Navigation -> {
|
||||||
|
onEvent(it)
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.ShowHistoryEdit -> {
|
||||||
|
sheetPage = null
|
||||||
|
delay(100)
|
||||||
|
sheetPage = SheetPage.HISTORY_EDIT
|
||||||
|
}
|
||||||
|
is AccelerometerContract.Effect.HideHistoryEdit -> {
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
delay(100)
|
delay(100)
|
||||||
sheetPage = SheetPage.MEASURE_HISTORY
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,26 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.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.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
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.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.WriteBle
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AccelerometerViewModel @Inject constructor(
|
class AccelerometerViewModel @Inject constructor(
|
||||||
private val bleMapper: BleMapper
|
private val bleMapper: BleMapper,
|
||||||
|
private val bleViewMapper: BleViewMapper,
|
||||||
|
private val writeBle: WriteBle
|
||||||
) : BaseViewModel<AccelerometerContract.State, AccelerometerContract.Event, AccelerometerContract.Effect>() {
|
) : BaseViewModel<AccelerometerContract.State, AccelerometerContract.Event, AccelerometerContract.Effect>() {
|
||||||
|
|
||||||
override fun setInitialState() = AccelerometerContract.State.Loading
|
override fun setInitialState() = AccelerometerContract.State.Loading
|
||||||
|
|
@ -18,29 +28,475 @@ 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.OnPowerEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHideWriteBlePreview -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnWriteBle -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHideAccelerometerSpectre -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnShowAccelerometerSpectre -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnAccelViewModeEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnAccelViewModelChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnSpectreEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnFftFrequencyEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnFftAxisChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnFftFrequencyChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnFftModeChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnFftAxisEdit -> 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)
|
||||||
|
is AccelerometerContract.Event.OnAccelScaleChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnAccelScaleEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnAccelEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHistoryEdit -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHistoryScaleChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHistoryViewModeChanged -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerContract.Event.OnHideHistoryEdit -> reduce(viewState.value, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: AccelerometerContract.State,
|
state: AccelerometerContract.State,
|
||||||
event: AccelerometerContract.Event.OnHideAccelerometerMeasure
|
event: AccelerometerContract.Event.OnHideHistoryEdit
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reduce(
|
|
||||||
state: AccelerometerContract.State,
|
|
||||||
event: AccelerometerContract.Event.OnShowAccelerometerMeasure
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
setEffect {
|
setEffect {
|
||||||
AccelerometerContract.Effect.ShowAccelerometerMeasure
|
AccelerometerContract.Effect.HideHistoryEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHistoryViewModeChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
var saveHistory = state.accelerometer.accelerometerState.saveHistory
|
||||||
|
|
||||||
|
if(saveHistory is Ble.Accelerometer.History.Enabled){
|
||||||
|
saveHistory = Ble.Accelerometer.History.Enabled(
|
||||||
|
mode = event.mode,
|
||||||
|
scale = saveHistory.scale,
|
||||||
|
detailed = saveHistory.detailed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.accelerometer.accelerometerState.saveHistory = saveHistory
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHistoryScaleChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
var saveHistory = state.accelerometer.accelerometerState.saveHistory
|
||||||
|
|
||||||
|
if(saveHistory is Ble.Accelerometer.History.Enabled){
|
||||||
|
saveHistory = saveHistory.copy(scale = event.scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
state.accelerometer.accelerometerState.saveHistory = saveHistory
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHistoryEdit
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowHistoryEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnAccelScaleChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
accelScale = event.scale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnAccelScaleEdit
|
||||||
|
) {
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowAccelScaleEdit(
|
||||||
|
event.next
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
if(event.save){
|
||||||
|
|
||||||
|
state.accelerometer.accelerometerState.saveHistory = Ble.Accelerometer.History.Enabled(
|
||||||
|
scale = AccelScale.S_2,
|
||||||
|
mode = AccelViewMode.ACCELERATION,
|
||||||
|
detailed = true
|
||||||
|
)
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowHistoryEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
state.accelerometer.accelerometerState.saveHistory = Ble.Accelerometer.History.Disabled
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnFftModeEdit
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowFftModeEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnFftAxisEdit
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowFftAxisEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnFftAxisChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
fftAxis = event.axis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnFftModeChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
fftViewMode = event.mode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnFftFrequencyChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
fftFrequency = event.frequency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnFftFrequencyEdit
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowFftFrequencyEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnAccelEdit
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowAccelEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnSpectreEdit
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowSpectreEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnShowAccelerometerSpectre
|
||||||
|
) {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowAccelerometerSpectre
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHideAccelerometerSpectre
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHideWriteBlePreview
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
writeState = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.HideWriteBle
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnShowWriteBlePreview
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer
|
||||||
|
|
||||||
|
val writeRequest = Ble.Accelerometer.WriteRequest(
|
||||||
|
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 {
|
||||||
|
state.copy(
|
||||||
|
writeState = AccelerometerContract.State.Display.WriteState.DisplayPreview(
|
||||||
|
writeRequest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowWriteBle
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnPowerChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display) {
|
||||||
|
|
||||||
|
state.accelerometer.state.tx = event.tx
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.HidePowerPicker
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnAccelViewModelChanged
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display) {
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
accelViewMode = event.mode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.HidePowerPicker
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnPowerEdit
|
||||||
|
) {
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowPowerPicker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnAccelViewModeEdit
|
||||||
|
) {
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowAccelViewEdit(event.next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnHideAccelerometerAccel
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnShowAccelerometerAccel
|
||||||
|
) {
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
|
||||||
|
setEffect {
|
||||||
|
AccelerometerContract.Effect.ShowAccelerometerAccel
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
|
|
@ -52,7 +508,9 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
is AccelerometerContract.State.Display -> setState {
|
is AccelerometerContract.State.Display -> setState {
|
||||||
state.copy(
|
state.copy(
|
||||||
origin = Ble.Accelerometer(
|
origin = Ble.Accelerometer(
|
||||||
info = event.ble.info
|
info = event.ble.info,
|
||||||
|
state = event.ble.state,
|
||||||
|
accelerometerState = state.origin.accelerometerState
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -60,11 +518,85 @@ class AccelerometerViewModel @Inject constructor(
|
||||||
is AccelerometerContract.State.Loading -> setState {
|
is AccelerometerContract.State.Loading -> setState {
|
||||||
AccelerometerContract.State.Display(
|
AccelerometerContract.State.Display(
|
||||||
origin = event.ble,
|
origin = event.ble,
|
||||||
accelerometer = bleMapper.map(event.ble) as BleView.Accelerometer
|
accelerometer = bleMapper.map(event.ble) as BleView.Accelerometer,
|
||||||
|
writeState = null,
|
||||||
|
accelViewMode = AccelViewMode.ACCELERATION,
|
||||||
|
fftAxis = FftAxis.AUTO,
|
||||||
|
fftFrequency = FftFrequency.F_400,
|
||||||
|
fftViewMode = FftViewMode.SPECTRE,
|
||||||
|
accelScale = AccelScale.S_2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerContract.State,
|
||||||
|
event: AccelerometerContract.Event.OnWriteBle
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerContract.State.Display){
|
||||||
|
|
||||||
|
state.writeState?.let { request ->
|
||||||
|
|
||||||
|
if(request is AccelerometerContract.State.Display.WriteState.DisplayPreview) {
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
writeState = AccelerometerContract.State.Display.WriteState.Writing(
|
||||||
|
request.writeRequest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBle(state.accelerometer.info.serial, request.writeRequest).fold(
|
||||||
|
onSuccess = {
|
||||||
|
|
||||||
|
val currentState = viewState.value
|
||||||
|
|
||||||
|
if(currentState is AccelerometerContract.State.Display) {
|
||||||
|
|
||||||
|
val newBleObject = Ble.Accelerometer(
|
||||||
|
info = currentState.origin.info,
|
||||||
|
state = currentState.origin.state.copy(
|
||||||
|
tx = request.writeRequest.tx ?: state.origin.state.tx
|
||||||
|
),
|
||||||
|
accelerometerState = currentState.origin.accelerometerState.copy(
|
||||||
|
saveHistory = request.writeRequest.saveHistory
|
||||||
|
?: currentState.origin.accelerometerState.saveHistory
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
setState {
|
||||||
|
currentState.copy(
|
||||||
|
origin = newBleObject,
|
||||||
|
writeState = AccelerometerContract.State.Display.WriteState.Success
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
writeState = AccelerometerContract.State.Display.WriteState.Failure
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelEdit(
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
val accelMode = state.accelViewMode
|
||||||
|
val fftMode = state.fftViewMode
|
||||||
|
val fftAxis = state.fftAxis
|
||||||
|
val fftFrequency = state.fftFrequency
|
||||||
|
val accelScale = state.accelScale
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Ускорение",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable {
|
||||||
|
onEvent(
|
||||||
|
AccelerometerContract.Event.OnAccelViewModeEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.ACCEL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Accel view mode"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = accelMode.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnAccelScaleEdit(next = AccelerometerContract.Event.Next.ACCEL))
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Accel scale"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = accelScale.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnShowAccelerometerAccel)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Продолжить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelFftAxisEdit(
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var fftAxis = state.fftAxis
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Fft axis",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
FftAxis.values().forEach {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable { onEvent(AccelerometerContract.Event.OnFftAxisChanged(it)) }
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = it == fftAxis,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnFftAxisChanged(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = it.localized)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnSpectreEdit)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelFftModeEdit(
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var fftMode = state.fftViewMode
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Fft view mode",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
FftViewMode.values().forEach {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable { onEvent(AccelerometerContract.Event.OnFftModeChanged(it)) }
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = it == fftMode,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnFftModeChanged(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = it.localized)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnSpectreEdit)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
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
|
||||||
|
|
||||||
|
/*val AccelViewMode.localized: String
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
ACCELERATION -> "Ускорение"
|
||||||
|
PEAK_ACCELERATION -> "Пиковое ускорение"
|
||||||
|
RMS -> "Среднеквадратичное ускорение"
|
||||||
|
ANGLE -> "Угол"
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelFrequencyEdit(
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var fftFrequency = state.fftFrequency
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Fft frequency",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
FftFrequency.values().forEach {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable { onEvent(AccelerometerContract.Event.OnFftFrequencyChanged(it)) }
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = it == fftFrequency,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnFftFrequencyChanged(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = it.localized)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnSpectreEdit)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelScaleEdit(
|
||||||
|
next: AccelerometerContract.Event.Next,
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var fftMode = when(next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL,
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE ->
|
||||||
|
state.accelScale
|
||||||
|
AccelerometerContract.Event.Next.HISTORY -> {
|
||||||
|
val history = state.accelerometer.accelerometerState.saveHistory
|
||||||
|
if (history is Ble.Accelerometer.History.Enabled)
|
||||||
|
history.scale
|
||||||
|
else {
|
||||||
|
state.accelScale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Accel scale",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
AccelScale.values().forEach {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable {
|
||||||
|
when(next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL,
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnAccelScaleChanged(it))
|
||||||
|
AccelerometerContract.Event.Next.HISTORY ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnHistoryScaleChanged(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = it == fftMode,
|
||||||
|
onClick = {
|
||||||
|
when(next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL,
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnAccelScaleChanged(it))
|
||||||
|
AccelerometerContract.Event.Next.HISTORY ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnHistoryScaleChanged(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = it.localized)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onClick = {
|
||||||
|
when(next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnAccelEdit)
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnSpectreEdit)
|
||||||
|
AccelerometerContract.Event.Next.HISTORY ->
|
||||||
|
onEvent(AccelerometerContract.Event.OnHistoryEdit)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,294 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
|
||||||
|
val FftFrequency.localized: String
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
FftFrequency.OFF -> "откл"
|
||||||
|
FftFrequency.F_1 -> "1 Гц"
|
||||||
|
FftFrequency.F_10 -> "10 Гц"
|
||||||
|
FftFrequency.F_25 -> "25 Гц"
|
||||||
|
FftFrequency.F_50 -> "50 Гц"
|
||||||
|
FftFrequency.F_100 -> "100 Гц"
|
||||||
|
FftFrequency.F_200 -> "200 Гц"
|
||||||
|
FftFrequency.F_400 -> "400 Гц"
|
||||||
|
FftFrequency.F_1620 -> "1620 Гц"
|
||||||
|
FftFrequency.F_1344 -> "1344 Гц"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val FftAxis.localized: String
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
FftAxis.AUTO -> "Авто"
|
||||||
|
FftAxis.X -> "Ось X"
|
||||||
|
FftAxis.Y -> "Ось Y"
|
||||||
|
FftAxis.Z -> "Ось Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val FftViewMode.localized: String
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
FftViewMode.SPECTRE -> "Спектр"
|
||||||
|
FftViewMode.X -> "Ось X"
|
||||||
|
FftViewMode.Y -> "Ось Y"
|
||||||
|
FftViewMode.Z -> "Ось Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val AccelScale.localized: String
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
AccelScale.S_2 -> "2g"
|
||||||
|
AccelScale.S_4 -> "4g"
|
||||||
|
AccelScale.S_8 -> "8g"
|
||||||
|
AccelScale.S_16 -> "16g"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelSpectreEdit(
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
val accelMode = state.accelViewMode
|
||||||
|
val fftMode = state.fftViewMode
|
||||||
|
val fftAxis = state.fftAxis
|
||||||
|
val fftFrequency = state.fftFrequency
|
||||||
|
val accelScale = state.accelScale
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Спектр",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable {
|
||||||
|
onEvent(AccelerometerContract.Event.OnFftModeEdit)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Fft view mode"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = fftMode.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnFftAxisEdit) }
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Fft axis"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = fftAxis.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnFftFrequencyEdit)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Fft frequency"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = fftFrequency.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnAccelScaleEdit(next = AccelerometerContract.Event.Next.SPECTRE))
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Accel scale"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = accelScale.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnShowAccelerometerSpectre)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Продолжить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode.*
|
||||||
|
|
||||||
|
val AccelViewMode.localized: String
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
ACCELERATION -> "Ускорение"
|
||||||
|
PEAK_ACCELERATION -> "Пиковое ускорение"
|
||||||
|
RMS -> "Среднеквадратичное ускорение"
|
||||||
|
VIBRATION -> "Вибрация"
|
||||||
|
ANGLE -> "Угол"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelViewEdit(
|
||||||
|
next: AccelerometerContract.Event.Next,
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var value by remember(state.accelViewMode) {
|
||||||
|
mutableStateOf(state.accelViewMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Accel view mode",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
values().forEach {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable { value = it }
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = it == value,
|
||||||
|
onClick = {
|
||||||
|
value = it
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = it.localized)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onClick = {
|
||||||
|
|
||||||
|
when(next){
|
||||||
|
AccelerometerContract.Event.Next.ACCEL -> {
|
||||||
|
onEvent(AccelerometerContract.Event.OnAccelViewModelChanged(value))
|
||||||
|
onEvent(AccelerometerContract.Event.OnAccelEdit)
|
||||||
|
}
|
||||||
|
AccelerometerContract.Event.Next.SPECTRE -> {
|
||||||
|
onEvent(AccelerometerContract.Event.OnAccelViewModelChanged(value))
|
||||||
|
onEvent(AccelerometerContract.Event.OnSpectreEdit)
|
||||||
|
}
|
||||||
|
|
||||||
|
AccelerometerContract.Event.Next.HISTORY -> {
|
||||||
|
onEvent(AccelerometerContract.Event.OnHistoryViewModeChanged(value))
|
||||||
|
onEvent(AccelerometerContract.Event.OnHistoryEdit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,500 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,596 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
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.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.draw.rotate
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.TransformOrigin
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
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.core.chart.decoration.ThresholdLine
|
||||||
|
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
|
||||||
|
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
||||||
|
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
||||||
|
import com.patrykandpatrick.vico.core.scroll.InitialScroll
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
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.GetAccelerometerMeasureBySerialFlow
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelerometerAccel(
|
||||||
|
ble: BleInfo,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
) {
|
||||||
|
|
||||||
|
val viewModel = hiltViewModel<AccelerometerAccelViewModel>()
|
||||||
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
|
viewModel.setEvent(AccelerometerAccelContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
|
|
||||||
|
DisposableEffect(key1 = "ble", effect = {
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
viewModel.setEvent(AccelerometerAccelContract.Event.StopMeasure)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxHeight(0.9f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
text = accelMode.localized,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(AccelerometerAccelContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
|
},
|
||||||
|
enabled = true
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Refresh,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Box(modifier = Modifier) {
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
is AccelerometerAccelContract.State.Display -> Display(state = state)
|
||||||
|
is AccelerometerAccelContract.State.Exception -> Exception()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Display(
|
||||||
|
state: AccelerometerAccelContract.State.Display
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
if (state.measureHistory.isEmpty()) {
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
strokeCap = StrokeCap.Round
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
val xProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
val yProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
val zProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
xProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||||
|
when(measurePoint){
|
||||||
|
is MeasureData.Accelerate -> {
|
||||||
|
FloatEntry(index.toFloat(), measurePoint.x )
|
||||||
|
}
|
||||||
|
is MeasureData.Vibration -> FloatEntry(index.toFloat(), measurePoint.value)
|
||||||
|
is MeasureData.Angle -> {
|
||||||
|
FloatEntry(index.toFloat(), measurePoint.xAngle )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
yProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||||
|
when(measurePoint){
|
||||||
|
is MeasureData.Accelerate -> {
|
||||||
|
FloatEntry(index.toFloat(), measurePoint.y )
|
||||||
|
}
|
||||||
|
is MeasureData.Vibration -> FloatEntry(index.toFloat(), measurePoint.value)
|
||||||
|
is MeasureData.Angle -> {
|
||||||
|
FloatEntry(index.toFloat(), measurePoint.yAngle )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
zProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||||
|
when(measurePoint){
|
||||||
|
is MeasureData.Accelerate -> {
|
||||||
|
FloatEntry(index.toFloat(), measurePoint.z )
|
||||||
|
}
|
||||||
|
is MeasureData.Vibration -> FloatEntry(index.toFloat(), measurePoint.value)
|
||||||
|
is MeasureData.Angle -> {
|
||||||
|
FloatEntry(index.toFloat(), measurePoint.zAngle )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val lineChart = lineChart(
|
||||||
|
decorations = listOf(
|
||||||
|
ThresholdLine(
|
||||||
|
thresholdValue = 0f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val lastMeasure = state.measureHistory.lastOrNull()
|
||||||
|
|
||||||
|
if(lastMeasure is MeasureData.Accelerate) {
|
||||||
|
|
||||||
|
if(state.mode == AccelViewMode.ANGLE){
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(text = "Ось X: ${lastMeasure.x}")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Angle(
|
||||||
|
angle = lastMeasure.x,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(text = "Ось Y: ${lastMeasure.y}")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Angle(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
angle = lastMeasure.y
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(text = "Ось Z: ${lastMeasure.z}")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Angle(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
angle = lastMeasure.z
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
|
||||||
|
Text(text = "Ось X:")
|
||||||
|
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = xProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = "Ось Y:")
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = yProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = "Ось Z:")
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = zProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Column {
|
||||||
|
|
||||||
|
Text(text = "Вибрация:")
|
||||||
|
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = xProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun Angle(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
angle: Float
|
||||||
|
) {
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(4.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(text = "0°C")
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(text = "-90°C")
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
shape = CircleShape
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier,
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.rotate(-90f + angle),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Divider(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = "90°C")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = "±180°C")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Exception(
|
||||||
|
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(2f),
|
||||||
|
){
|
||||||
|
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
text = "Во время загрузки произошла ошибка",
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccelerometerAccelContract {
|
||||||
|
|
||||||
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
|
object StopMeasure : Event()
|
||||||
|
|
||||||
|
data class OnStart(
|
||||||
|
val serial: String,
|
||||||
|
val accelScale: AccelScale,
|
||||||
|
val accelMode: AccelViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftMode: FftViewMode,
|
||||||
|
val frequency: FftFrequency
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnRefreshHistory(
|
||||||
|
val serial: String,
|
||||||
|
val accelScale: AccelScale,
|
||||||
|
val accelMode: AccelViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftMode: FftViewMode,
|
||||||
|
val frequency: FftFrequency
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class State : ViewState {
|
||||||
|
|
||||||
|
data class Display(
|
||||||
|
val mode: AccelViewMode,
|
||||||
|
val measureHistory : List<MeasureData>
|
||||||
|
) : State()
|
||||||
|
|
||||||
|
object Exception : State()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AccelerometerAccelViewModel @Inject constructor(
|
||||||
|
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
||||||
|
) : BaseViewModel<AccelerometerAccelContract.State, AccelerometerAccelContract.Event, AccelerometerAccelContract.Effect>() {
|
||||||
|
|
||||||
|
var measureJob: Job? = null
|
||||||
|
|
||||||
|
private var lastSerial: String? = null
|
||||||
|
|
||||||
|
override fun setInitialState() = AccelerometerAccelContract.State.Display(
|
||||||
|
mode = AccelViewMode.ACCELERATION,
|
||||||
|
measureHistory = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun handleEvents(event: AccelerometerAccelContract.Event) {
|
||||||
|
when(event){
|
||||||
|
is AccelerometerAccelContract.Event.OnStart -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerAccelContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerAccelContract.Event.StopMeasure -> reduce(viewState.value, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerAccelContract.State,
|
||||||
|
event: AccelerometerAccelContract.Event.StopMeasure
|
||||||
|
) {
|
||||||
|
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerAccelContract.State.Display(
|
||||||
|
mode = AccelViewMode.ACCELERATION,
|
||||||
|
measureHistory = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerAccelContract.State,
|
||||||
|
event: AccelerometerAccelContract.Event.OnStart
|
||||||
|
) {
|
||||||
|
|
||||||
|
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerAccelContract.State,
|
||||||
|
event: AccelerometerAccelContract.Event.OnRefreshHistory
|
||||||
|
) {
|
||||||
|
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startReadMeasure(
|
||||||
|
serial: String,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency,
|
||||||
|
restartJob: Boolean
|
||||||
|
){
|
||||||
|
|
||||||
|
if(restartJob || measureJob == null) {
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
measureJob = viewModelScope.launch {
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerAccelContract.State.Display(
|
||||||
|
mode = AccelViewMode.ACCELERATION,
|
||||||
|
measureHistory = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency).onEach {
|
||||||
|
it.fold(
|
||||||
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
when (this) {
|
||||||
|
is AccelerometerAccelContract.State.Display -> {
|
||||||
|
var dataList = this.measureHistory.toMutableList().apply {
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
|
if(accelMode != AccelViewMode.ANGLE) {
|
||||||
|
dataList = dataList.takeLast(10).toMutableList()
|
||||||
|
}
|
||||||
|
AccelerometerAccelContract.State.Display(accelMode, dataList)
|
||||||
|
}
|
||||||
|
|
||||||
|
AccelerometerAccelContract.State.Exception -> {
|
||||||
|
AccelerometerAccelContract.State.Display(accelMode, listOf(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
AccelerometerAccelContract.State.Exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,589 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
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.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.CloudUpload
|
||||||
|
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.axis.horizontal.bottomAxis
|
||||||
|
import com.patrykandpatrick.vico.compose.chart.line.lineChart
|
||||||
|
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
|
||||||
|
import com.patrykandpatrick.vico.core.axis.AxisPosition
|
||||||
|
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.FloatEntry
|
||||||
|
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
||||||
|
import com.patrykandpatrick.vico.core.scroll.InitialScroll
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.formatter
|
||||||
|
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.ExportToXlsx
|
||||||
|
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.GetAccelerometerHistoryBySerial
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class AccelEntry(
|
||||||
|
val localDate: Long,
|
||||||
|
override val x: Float,
|
||||||
|
override val y: Float,
|
||||||
|
) : ChartEntry {
|
||||||
|
|
||||||
|
override fun withY(y: Float) = AccelEntry(localDate, x, y)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccelerometerHistory(
|
||||||
|
ble: BleInfo,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
) {
|
||||||
|
|
||||||
|
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
||||||
|
val state = viewModel.viewState.value
|
||||||
|
|
||||||
|
LaunchedEffect(ble.serial) {
|
||||||
|
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*DisposableEffect("ble") {
|
||||||
|
onDispose {
|
||||||
|
viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxHeight(0.9f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
|
||||||
|
val title = when(state){
|
||||||
|
is AccelerometerHistoryContract.State.Display -> {
|
||||||
|
when (state.loadingHistoryState) {
|
||||||
|
is ProgressState.Finished -> "График измерений (${state.loadingHistoryState.data.size})"
|
||||||
|
is ProgressState.Indeterminate -> "График измерений"
|
||||||
|
is ProgressState.Progress -> "График измерений"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccelerometerHistoryContract.State.Exception -> "График измерений"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(AccelerometerHistoryContract.Event.OnExport)
|
||||||
|
},
|
||||||
|
enabled = when(state){
|
||||||
|
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
||||||
|
AccelerometerHistoryContract.State.Exception -> false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.CloudUpload,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
|
||||||
|
},
|
||||||
|
enabled = when(state){
|
||||||
|
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
||||||
|
AccelerometerHistoryContract.State.Exception -> true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Refresh,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Box(modifier = Modifier) {
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
is AccelerometerHistoryContract.State.Display -> Display(state = state)
|
||||||
|
is AccelerometerHistoryContract.State.Exception -> Exception()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Display(
|
||||||
|
state: AccelerometerHistoryContract.State.Display
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
when (state.loadingHistoryState) {
|
||||||
|
|
||||||
|
is ProgressState.Finished -> {
|
||||||
|
|
||||||
|
if(state.loadingHistoryState.data.isEmpty()){
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
text = "Нет данных"
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
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 xProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
val yProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
val zProducer = remember {
|
||||||
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
|
}
|
||||||
|
|
||||||
|
xProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||||
|
when(measurePoint){
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
yProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||||
|
when(measurePoint){
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.y )
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.y )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
zProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||||
|
when(measurePoint){
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.z )
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||||
|
AccelEntry(measurePoint.date, index.toFloat(), measurePoint.z )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val lineChart = lineChart(
|
||||||
|
decorations = listOf(
|
||||||
|
ThresholdLine(
|
||||||
|
thresholdValue = 0f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val lastMeasure = state.loadingHistoryState.data.lastOrNull()
|
||||||
|
|
||||||
|
if((lastMeasure is Ble.Accelerometer.HistoryPoint.Vibration).not()) {
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
|
||||||
|
Text(text = "Ось X:")
|
||||||
|
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = xProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(
|
||||||
|
tickLength = 0.dp,
|
||||||
|
valueFormatter = axisValueFormatter,
|
||||||
|
labelRotationDegrees = -90f,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = "Ось Y:")
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = yProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(
|
||||||
|
tickLength = 0.dp,
|
||||||
|
valueFormatter = axisValueFormatter,
|
||||||
|
labelRotationDegrees = -90f,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = "Ось Z:")
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = zProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(
|
||||||
|
tickLength = 0.dp,
|
||||||
|
valueFormatter = axisValueFormatter,
|
||||||
|
labelRotationDegrees = -90f,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Column {
|
||||||
|
|
||||||
|
Text(text = "Вибрация:")
|
||||||
|
|
||||||
|
Chart(
|
||||||
|
chart = lineChart,
|
||||||
|
chartModelProducer = xProducer,
|
||||||
|
startAxis = startAxis(),
|
||||||
|
bottomAxis = bottomAxis(
|
||||||
|
tickLength = 0.dp,
|
||||||
|
valueFormatter = axisValueFormatter,
|
||||||
|
labelRotationDegrees = -90f,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
autoScaleUp = AutoScaleUp.None,
|
||||||
|
diffAnimationSpec = tween(0),
|
||||||
|
chartScrollSpec = rememberChartScrollSpec(
|
||||||
|
initialScroll = InitialScroll.End,
|
||||||
|
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||||
|
autoScrollAnimationSpec = tween(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
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
|
||||||
|
private fun Exception() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(2f),
|
||||||
|
){
|
||||||
|
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
text = "Во время загрузки произошла ошибка",
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccelerometerHistoryContract {
|
||||||
|
|
||||||
|
sealed class Event : ViewEvent {
|
||||||
|
|
||||||
|
object StopMeasure : Event()
|
||||||
|
|
||||||
|
object OnExport : Event()
|
||||||
|
|
||||||
|
data class OnStart(
|
||||||
|
val serial: String,
|
||||||
|
val accelScale: AccelScale,
|
||||||
|
val accelMode: AccelViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftMode: FftViewMode,
|
||||||
|
val frequency: FftFrequency
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
data class OnRefreshHistory(
|
||||||
|
val serial: String,
|
||||||
|
val accelScale: AccelScale,
|
||||||
|
val accelMode: AccelViewMode,
|
||||||
|
val fftAxis: FftAxis,
|
||||||
|
val fftMode: FftViewMode,
|
||||||
|
val frequency: FftFrequency
|
||||||
|
) : Event()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class State : ViewState {
|
||||||
|
|
||||||
|
data class Display(
|
||||||
|
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.HistoryPoint>>
|
||||||
|
) : State()
|
||||||
|
|
||||||
|
object Exception : State()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Effect : ViewSideEffect {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
|
private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial,
|
||||||
|
private val exportToXlsx: ExportToXlsx
|
||||||
|
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
|
||||||
|
|
||||||
|
var measureJob: Job? = null
|
||||||
|
|
||||||
|
private var lastSerial: String? = null
|
||||||
|
|
||||||
|
override fun setInitialState() = AccelerometerHistoryContract.State.Display(
|
||||||
|
ProgressState.Indeterminate
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun handleEvents(event: AccelerometerHistoryContract.Event) {
|
||||||
|
when(event){
|
||||||
|
is AccelerometerHistoryContract.Event.OnStart -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerHistoryContract.Event.StopMeasure -> reduce(viewState.value, event)
|
||||||
|
is AccelerometerHistoryContract.Event.OnExport -> reduce(viewState.value, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerHistoryContract.State,
|
||||||
|
event: AccelerometerHistoryContract.Event.OnExport
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(state is AccelerometerHistoryContract.State.Display){
|
||||||
|
if(state.loadingHistoryState is ProgressState.Finished){
|
||||||
|
exportToXlsx.invoke(state.loadingHistoryState.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerHistoryContract.State,
|
||||||
|
event: AccelerometerHistoryContract.Event.StopMeasure
|
||||||
|
) {
|
||||||
|
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Exception
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerHistoryContract.State,
|
||||||
|
event: AccelerometerHistoryContract.Event.OnStart
|
||||||
|
) {
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
|
||||||
|
if(state is AccelerometerHistoryContract.State.Display) {
|
||||||
|
|
||||||
|
if(lastSerial != event.serial) {
|
||||||
|
|
||||||
|
lastSerial = event.serial
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
|
||||||
|
measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
|
||||||
|
it.fold(
|
||||||
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reduce(
|
||||||
|
state: AccelerometerHistoryContract.State,
|
||||||
|
event: AccelerometerHistoryContract.Event.OnRefreshHistory
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
|
||||||
|
measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
|
||||||
|
it.fold(
|
||||||
|
onSuccess = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Display(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
setState {
|
||||||
|
AccelerometerHistoryContract.State.Exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,301 +0,0 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
|
||||||
|
|
||||||
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.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.text.style.TextAlign
|
|
||||||
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
|
||||||
import com.patrykandpatrick.vico.compose.chart.column.columnChart
|
|
||||||
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
|
|
||||||
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
|
|
||||||
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
|
||||||
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
|
||||||
import com.patrykandpatrick.vico.core.scroll.InitialScroll
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AccelerometerMeasure(
|
|
||||||
ble: BleInfo
|
|
||||||
) {
|
|
||||||
|
|
||||||
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
|
||||||
val state = viewModel.viewState.value
|
|
||||||
|
|
||||||
LaunchedEffect(ble.serial) {
|
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial))
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposableEffect(key1 = ble.serial, effect = {
|
|
||||||
onDispose {
|
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxHeight(0.9f)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
|
|
||||||
val title = when(state){
|
|
||||||
is AccelerometerMeasureContract.State.Display -> {
|
|
||||||
"История измерений"
|
|
||||||
}
|
|
||||||
AccelerometerMeasureContract.State.Exception -> "История измерений"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnRefreshHistory(ble.serial))
|
|
||||||
},
|
|
||||||
enabled = true
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Refresh,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Box(modifier = Modifier) {
|
|
||||||
|
|
||||||
when (state) {
|
|
||||||
is AccelerometerMeasureContract.State.Display -> Display(state = state)
|
|
||||||
AccelerometerMeasureContract.State.Exception -> Exception()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Display(
|
|
||||||
state: AccelerometerMeasureContract.State.Display
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
if (state.measureHistory.isEmpty()) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
text = "Нет данных"
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
val producer = remember {
|
|
||||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
|
||||||
}
|
|
||||||
|
|
||||||
producer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
|
||||||
FloatEntry(index.toFloat(), measurePoint)
|
|
||||||
})
|
|
||||||
|
|
||||||
val lineChart = columnChart()
|
|
||||||
|
|
||||||
Chart(
|
|
||||||
chart = lineChart,
|
|
||||||
chartModelProducer = producer,
|
|
||||||
startAxis = startAxis(),
|
|
||||||
bottomAxis = bottomAxis(),
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
autoScaleUp = AutoScaleUp.None,
|
|
||||||
diffAnimationSpec = tween(0),
|
|
||||||
chartScrollSpec = rememberChartScrollSpec(
|
|
||||||
initialScroll = InitialScroll.End,
|
|
||||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Exception(
|
|
||||||
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(2f),
|
|
||||||
){
|
|
||||||
|
|
||||||
Text(
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
text = "Во время загрузки произошла ошибка",
|
|
||||||
modifier = Modifier.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccelerometerMeasureContract {
|
|
||||||
|
|
||||||
sealed class Event : ViewEvent {
|
|
||||||
|
|
||||||
object StopMeasure : Event()
|
|
||||||
|
|
||||||
data class OnStart(
|
|
||||||
val serial: String
|
|
||||||
) : Event()
|
|
||||||
|
|
||||||
data class OnRefreshHistory(
|
|
||||||
val serial: String
|
|
||||||
) : Event()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class State : ViewState {
|
|
||||||
|
|
||||||
data class Display(
|
|
||||||
val measureHistory : List<Float>
|
|
||||||
) : State()
|
|
||||||
|
|
||||||
object Exception : State()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Effect : ViewSideEffect {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class AccelerometerHistoryViewModel @Inject constructor(
|
|
||||||
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
|
||||||
) : BaseViewModel<AccelerometerMeasureContract.State, AccelerometerMeasureContract.Event, AccelerometerMeasureContract.Effect>() {
|
|
||||||
|
|
||||||
var measureJob: Job? = null
|
|
||||||
|
|
||||||
private var lastSerial: String? = null
|
|
||||||
|
|
||||||
override fun setInitialState() = AccelerometerMeasureContract.State.Display(
|
|
||||||
emptyList()
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun handleEvents(event: AccelerometerMeasureContract.Event) {
|
|
||||||
when(event){
|
|
||||||
is AccelerometerMeasureContract.Event.OnStart -> reduce(viewState.value, event)
|
|
||||||
is AccelerometerMeasureContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
|
||||||
is AccelerometerMeasureContract.Event.StopMeasure -> reduce(viewState.value, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reduce(
|
|
||||||
state: AccelerometerMeasureContract.State,
|
|
||||||
event: AccelerometerMeasureContract.Event.StopMeasure
|
|
||||||
) {
|
|
||||||
|
|
||||||
measureJob?.cancel()
|
|
||||||
setState {
|
|
||||||
AccelerometerMeasureContract.State.Display(emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reduce(
|
|
||||||
state: AccelerometerMeasureContract.State,
|
|
||||||
event: AccelerometerMeasureContract.Event.OnStart
|
|
||||||
) {
|
|
||||||
|
|
||||||
startReadMeasure(event.serial, false)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reduce(
|
|
||||||
state: AccelerometerMeasureContract.State,
|
|
||||||
event: AccelerometerMeasureContract.Event.OnRefreshHistory
|
|
||||||
) {
|
|
||||||
startReadMeasure(event.serial, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startReadMeasure(serial: String, restartJob: Boolean){
|
|
||||||
|
|
||||||
if(restartJob || measureJob == null) {
|
|
||||||
measureJob = viewModelScope.launch {
|
|
||||||
|
|
||||||
setState {
|
|
||||||
AccelerometerMeasureContract.State.Display(emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
getAccelerometerMeasureBySerialFlow(serial).onEach {
|
|
||||||
it.fold(
|
|
||||||
onSuccess = {
|
|
||||||
setState {
|
|
||||||
when (this) {
|
|
||||||
is AccelerometerMeasureContract.State.Display -> {
|
|
||||||
val dataList = this.measureHistory.toMutableList().apply {
|
|
||||||
add(it)
|
|
||||||
}
|
|
||||||
AccelerometerMeasureContract.State.Display(dataList)
|
|
||||||
}
|
|
||||||
|
|
||||||
AccelerometerMeasureContract.State.Exception -> {
|
|
||||||
AccelerometerMeasureContract.State.Display(listOf(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
setState {
|
|
||||||
AccelerometerMeasureContract.State.Exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}.launchIn(this)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
import androidx.compose.material.icons.rounded.KeyboardArrowRight
|
import androidx.compose.material.icons.rounded.KeyboardArrowRight
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -62,7 +61,146 @@ fun DisplayState(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.clickable {
|
.clickable {
|
||||||
onEvent(AccelerometerContract.Event.OnShowAccelerometerMeasure)
|
onEvent(AccelerometerContract.Event.OnPowerEdit)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Мощность"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "${ble.state.tx.value} db"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { }
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Сохранять историю измерений"
|
||||||
|
)
|
||||||
|
|
||||||
|
val history = ble.accelerometerState.saveHistory
|
||||||
|
|
||||||
|
if(history is Ble.Accelerometer.History.Enabled){
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "View mode ${history.mode.localized}"
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "Scale: ${history.scale.localized}"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = ble.accelerometerState.saveHistory is Ble.Accelerometer.History.Enabled,
|
||||||
|
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)
|
.padding(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|
@ -86,6 +224,112 @@ 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.OnAccelEdit) }
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Ускорение"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||||
|
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.OnSpectreEdit)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Спектр"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||||
|
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.OnChangePassword)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Изменить пароль"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -99,7 +343,7 @@ fun DisplayState(
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnShowWriteBlePreview)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HistoryEdit(
|
||||||
|
state: AccelerometerContract.State.Display,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
val history = state.accelerometer.accelerometerState.saveHistory
|
||||||
|
|
||||||
|
val detailed = if (history is Ble.Accelerometer.History.Enabled) history.detailed else false
|
||||||
|
val accelMode = if (history is Ble.Accelerometer.History.Enabled) history.mode else state.accelViewMode
|
||||||
|
val accelScale = if (history is Ble.Accelerometer.History.Enabled) history.scale else state.accelScale
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "История измерений",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
vertical = 8.dp,
|
||||||
|
horizontal = 8.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.clickable {
|
||||||
|
onEvent(
|
||||||
|
AccelerometerContract.Event.OnAccelViewModeEdit(
|
||||||
|
next = AccelerometerContract.Event.Next.HISTORY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Accel view mode"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = accelMode.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnAccelScaleEdit(next = AccelerometerContract.Event.Next.HISTORY))
|
||||||
|
}
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Accel scale"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = accelScale.localized
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnHideHistoryEdit)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
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) {
|
||||||
|
mutableIntStateOf((state.accelerometerState.historyInterval).toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxInterval = 10 * 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
if(value > maxInterval){
|
||||||
|
value = maxInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value < 1){
|
||||||
|
value = 1 * 60 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxMinutes = maxInterval / millisInMinute
|
||||||
|
val maxHours = maxInterval / millisInHour
|
||||||
|
val maxDays = maxInterval / millisInDay
|
||||||
|
|
||||||
|
val dayValue = value / millisInDay
|
||||||
|
val hourValue = (value - (dayValue * millisInDay)) / millisInHour
|
||||||
|
val minutesValue = (value - (dayValue * millisInDay) - (hourValue * millisInHour)) / millisInMinute
|
||||||
|
|
||||||
|
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 * millisInDay) + (hourValue * millisInHour) + (minutesValue * millisInMinute)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(text = "Дни")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
NumberPicker(
|
||||||
|
range = -1..maxHours,
|
||||||
|
value = hourValue,
|
||||||
|
onValueChanged = {
|
||||||
|
value = (it * millisInHour) + (dayValue * millisInDay) + (minutesValue * millisInMinute)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(text = "Часы")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
NumberPicker(
|
||||||
|
range = -1..maxMinutes,
|
||||||
|
value = minutesValue,
|
||||||
|
onValueChanged = {
|
||||||
|
value = (it * millisInMinute) + (dayValue * millisInDay) + (hourValue * millisInHour)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Применить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const val millisInMinute = 1000 * 60
|
||||||
|
const val millisInHour = millisInMinute * 60
|
||||||
|
const val millisInDay = millisInHour * 24
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
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 PowerEdit(
|
||||||
|
state: BleView.Accelerometer,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||||
|
){
|
||||||
|
|
||||||
|
var value by remember(state.state.tx) {
|
||||||
|
mutableStateOf(state.state.tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Мощность",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
BleView.BleState.TX.values().forEach {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.clickable { value = it }
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
selected = it == value,
|
||||||
|
onClick = { value = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnPowerChanged(
|
||||||
|
value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Применить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,431 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import llc.arma.ble.R
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.localizedName
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Write(
|
||||||
|
state: AccelerometerContract.State.Display.WriteState,
|
||||||
|
onEvent: (AccelerometerContract.Event) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.animateContentSize()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
text = "Запись изменений",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
is AccelerometerContract.State.Display.WriteState.DisplayPreview -> {
|
||||||
|
|
||||||
|
if(state.writeRequest.tx != null || state.writeRequest.saveHistory != null || state.writeRequest.historyInterval != null) {
|
||||||
|
|
||||||
|
state.writeRequest.tx?.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} db"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = when(it){
|
||||||
|
Ble.Accelerometer.History.Disabled -> "Выключено"
|
||||||
|
is Ble.Accelerometer.History.Enabled -> "Включено"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
) {
|
||||||
|
|
||||||
|
val hours = it / 1000 / 60 / 60
|
||||||
|
val minutes = (it - ( hours * 1000 * 60 * 60 )) / 1000 / 60
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Интервал измерений"
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "$hours ч. $minutes мин."
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnWriteBle)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Записать"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Отменить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(38.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Нет изменений",
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(64.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
is AccelerometerContract.State.Display.WriteState.Writing -> {
|
||||||
|
|
||||||
|
Box {
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(28.dp))
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(48.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Отменить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
AccelerometerContract.State.Display.WriteState.Success -> {
|
||||||
|
|
||||||
|
Box {
|
||||||
|
|
||||||
|
Column {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(125.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
painter = painterResource(llc.arma.ble.R.drawable.ic_done),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
text = "Успешно завершено"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
AccelerometerContract.State.Display.WriteState.Failure -> {
|
||||||
|
|
||||||
|
Box {
|
||||||
|
|
||||||
|
Column {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(125.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
painter = painterResource(R.drawable.ic_error),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
text = "Ошибка записи"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(50.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
onClick = {
|
||||||
|
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ fun PowerEdit(
|
||||||
onClick = { value = it }
|
onClick = { value = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(text = it.value.toString() + " db")
|
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ val Ble.BleState.TX.localizedName: String
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThermometerScreen(
|
fun ThermometerScreen(
|
||||||
ble: Ble.Thermometer,
|
ble: Ble.Thermometer,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ fun PowerEdit(
|
||||||
onClick = { value = it }
|
onClick = { value = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(text = it.value.toString() + " db")
|
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -137,13 +137,17 @@ fun Write(
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val hours = it / 1000 / 60 / 60
|
||||||
|
val minutes = (it - ( hours * 1000 * 60 * 60 )) / 1000 / 60
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Интервал измерний"
|
text = "Интервал измерений"
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
text = "${it / 1000 / 60 / 60} ч."
|
text = "$hours ч. $minutes мин."
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import android.os.Build
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.util.toRange
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -24,17 +23,80 @@ import llc.arma.ble.domain.model.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
|
val FftFrequency.sendData: Byte
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
FftFrequency.OFF -> 0
|
||||||
|
FftFrequency.F_1 -> 1
|
||||||
|
FftFrequency.F_10 -> 2
|
||||||
|
FftFrequency.F_25 -> 3
|
||||||
|
FftFrequency.F_50 -> 4
|
||||||
|
FftFrequency.F_100 -> 5
|
||||||
|
FftFrequency.F_200 -> 6
|
||||||
|
FftFrequency.F_400 -> 7
|
||||||
|
FftFrequency.F_1620 -> 8
|
||||||
|
FftFrequency.F_1344 -> 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val FftAxis.sendData: Byte
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
FftAxis.AUTO -> 0
|
||||||
|
FftAxis.X -> 1
|
||||||
|
FftAxis.Y -> 2
|
||||||
|
FftAxis.Z -> 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val FftViewMode.sendData: Byte
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
FftViewMode.SPECTRE -> 0
|
||||||
|
FftViewMode.X -> 1
|
||||||
|
FftViewMode.Y -> 2
|
||||||
|
FftViewMode.Z -> 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val AccelViewMode.sendData: Byte
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
AccelViewMode.ACCELERATION -> 0
|
||||||
|
AccelViewMode.PEAK_ACCELERATION -> 1
|
||||||
|
AccelViewMode.RMS -> 2
|
||||||
|
AccelViewMode.VIBRATION -> 3
|
||||||
|
AccelViewMode.ANGLE -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val AccelScale.sendData: Byte
|
||||||
|
get() {
|
||||||
|
return when(this){
|
||||||
|
AccelScale.S_2 -> 0
|
||||||
|
AccelScale.S_4 -> 1
|
||||||
|
AccelScale.S_8 -> 2
|
||||||
|
AccelScale.S_16 -> 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002")
|
val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002")
|
||||||
|
|
||||||
val accelerometerDescriptorUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
|
|
||||||
val accelerometerReadUUID: UUID = UUID.fromString("00002713-0000-1000-8000-00805f9b34fb")
|
val accelerometerReadUUID: UUID = UUID.fromString("00002713-0000-1000-8000-00805f9b34fb")
|
||||||
val temperatureHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
val temperatureHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||||
|
val accelerometerHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||||
val temperatureReadUUID: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
|
val temperatureReadUUID: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
|
||||||
val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||||
val intervalWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
|
val intervalWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
|
||||||
|
|
@ -56,11 +118,20 @@ fun UByteArray.toTemperature(): Float {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ByteArray.get2byteUIntAt(idx: Int) =
|
||||||
|
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
|
||||||
|
(this[idx].toUInt() and 0xFFu)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class BleRepositoryImpl @Inject constructor(
|
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(
|
||||||
|
|
@ -69,15 +140,12 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
batteryLevel = batteryLevel ?: 0,
|
batteryLevel = batteryLevel ?: 0,
|
||||||
rssi = rssi,
|
rssi = rssi,
|
||||||
type = type,
|
type = type,
|
||||||
scanTime = timestampNanos / 1_000_000
|
scanTime = timestampNanos / 1_000_000,
|
||||||
|
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)
|
||||||
|
|
@ -234,6 +302,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 {
|
||||||
|
|
||||||
|
|
@ -250,8 +336,21 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
newResult.rssi
|
newResult.rssi
|
||||||
}
|
}
|
||||||
|
),
|
||||||
)
|
state = Ble.BleState(
|
||||||
|
tx = when (result.scanRecord?.txPowerLevel) {
|
||||||
|
-40 -> Ble.BleState.TX.MINUS_40
|
||||||
|
-20 -> Ble.BleState.TX.MINUS_20
|
||||||
|
-16 -> Ble.BleState.TX.MINUS_16
|
||||||
|
-12 -> Ble.BleState.TX.MINUS_12
|
||||||
|
-8 -> Ble.BleState.TX.MINUS_8
|
||||||
|
-4 -> Ble.BleState.TX.MINUS_4
|
||||||
|
3 -> Ble.BleState.TX.PLUS_3
|
||||||
|
4 -> Ble.BleState.TX.PLUS_4
|
||||||
|
else -> Ble.BleState.TX.ZERO
|
||||||
|
}
|
||||||
|
),
|
||||||
|
accelerometerState = tState
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
@ -379,10 +478,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BleInfo.Type.ACCELEROMETER -> {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -419,6 +514,78 @@ 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 }
|
||||||
|
)
|
||||||
|
|
||||||
|
val historyParams = when(record.timerEnabled){
|
||||||
|
true -> {
|
||||||
|
|
||||||
|
writeCharacteristic(
|
||||||
|
device = record.device,
|
||||||
|
serviceId = serviceUUID,
|
||||||
|
characteristicId = accelerometerReadUUID,
|
||||||
|
writeData = byteArrayOf(4)
|
||||||
|
).onFailure {
|
||||||
|
return Result.failure(BleException.UnexpectedResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
readCharacteristic(
|
||||||
|
device = record.device,
|
||||||
|
serviceId = serviceUUID,
|
||||||
|
characteristicId = accelerometerReadUUID,
|
||||||
|
).fold(
|
||||||
|
onSuccess = {
|
||||||
|
Log.d("history", it.joinToString { it.toString() })
|
||||||
|
val scale = when(it[1].toInt()){
|
||||||
|
0 -> AccelScale.S_2
|
||||||
|
1 -> AccelScale.S_4
|
||||||
|
2 -> AccelScale.S_8
|
||||||
|
3 -> AccelScale.S_16
|
||||||
|
else -> {
|
||||||
|
return Result.failure(BleException.UnexpectedResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val mode = when(it[0].toInt()){
|
||||||
|
0 -> AccelViewMode.ACCELERATION
|
||||||
|
1 -> AccelViewMode.PEAK_ACCELERATION
|
||||||
|
2 -> AccelViewMode.RMS
|
||||||
|
3 -> AccelViewMode.VIBRATION
|
||||||
|
4 -> AccelViewMode.ANGLE
|
||||||
|
else -> {
|
||||||
|
return Result.failure(BleException.UnexpectedResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ble.Accelerometer.History.Enabled(
|
||||||
|
scale = scale,
|
||||||
|
mode = mode,
|
||||||
|
detailed = false //TODO
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
return Result.failure(BleException.UnexpectedResponse)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
false -> Ble.Accelerometer.History.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(
|
||||||
|
Ble.Accelerometer.AccelerometerState(
|
||||||
|
saveHistory = historyParams,
|
||||||
|
historyInterval = history
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
private suspend fun readTemperature(
|
private suspend fun readTemperature(
|
||||||
record: ScanResult
|
record: ScanResult
|
||||||
|
|
@ -484,9 +651,14 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTemperatureHistoryBySerial(
|
override suspend fun getAccelerometerSpectreBySerial(
|
||||||
serial: String
|
serial: String,
|
||||||
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||||
|
|
||||||
var gatt: BluetoothGatt? = null
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
|
|
@ -496,7 +668,9 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
if (checkPermission()) {
|
if (checkPermission()) {
|
||||||
|
|
||||||
gatt = it.connectGatt(app, false, ReadHistoryCallback(app) {
|
gatt = it.connectGatt(app, false, ReadAccelerometerSpectreCallback(
|
||||||
|
app, accelScale, accelMode, fftAxis, fftMode, frequency
|
||||||
|
) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
send(it)
|
send(it)
|
||||||
}
|
}
|
||||||
|
|
@ -514,6 +688,143 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
Log.d("measure", "close")
|
||||||
|
gatt?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTemperatureHistoryBySerial(
|
||||||
|
serial: String
|
||||||
|
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
|
||||||
|
|
||||||
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
|
return callbackFlow {
|
||||||
|
|
||||||
|
deviceCache[serial]?.device?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
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.HistoryPoint>>, BleException>> {
|
||||||
|
|
||||||
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
|
return callbackFlow {
|
||||||
|
|
||||||
|
deviceCache[serial]?.let { result ->
|
||||||
|
|
||||||
|
val device = result.device
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
val state = readAccelState(result).fold(
|
||||||
|
onSuccess = {
|
||||||
|
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var scale: AccelScale? = null
|
||||||
|
var mode: AccelViewMode? = null
|
||||||
|
|
||||||
|
writeCharacteristic(
|
||||||
|
device = device,
|
||||||
|
serviceId = serviceUUID,
|
||||||
|
characteristicId = accelerometerReadUUID,
|
||||||
|
writeData = byteArrayOf(4)
|
||||||
|
).fold(
|
||||||
|
onSuccess = {
|
||||||
|
readCharacteristic(
|
||||||
|
device = device,
|
||||||
|
serviceId = serviceUUID,
|
||||||
|
characteristicId = accelerometerReadUUID,
|
||||||
|
).fold(
|
||||||
|
onSuccess = {
|
||||||
|
scale = when(it[1].toInt()){
|
||||||
|
0 -> AccelScale.S_2
|
||||||
|
1 -> AccelScale.S_4
|
||||||
|
2 -> AccelScale.S_8
|
||||||
|
3 -> AccelScale.S_16
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
mode = when(it[0].toInt()){
|
||||||
|
0 -> AccelViewMode.ACCELERATION
|
||||||
|
1 -> AccelViewMode.PEAK_ACCELERATION
|
||||||
|
2 -> AccelViewMode.RMS
|
||||||
|
3 -> AccelViewMode.VIBRATION
|
||||||
|
4 -> AccelViewMode.ANGLE
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { null }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(scale != null && mode != null) {
|
||||||
|
|
||||||
|
gatt = device.connectGatt(
|
||||||
|
app,
|
||||||
|
false,
|
||||||
|
ReadAccelerometerHistoryCallback(mode!!, scale!!, app) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(Result.failure(BleException.PermissionDenied))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
awaitClose {
|
awaitClose {
|
||||||
gatt?.close()
|
gatt?.close()
|
||||||
}
|
}
|
||||||
|
|
@ -594,6 +905,42 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun writeBle(
|
||||||
|
serial: String,
|
||||||
|
request: Ble.Accelerometer.WriteRequest
|
||||||
|
): Result<Unit, BleException> = suspendCancellableCoroutine {
|
||||||
|
|
||||||
|
deviceCache[serial]?.let { scanResult ->
|
||||||
|
|
||||||
|
if(checkPermission()) {
|
||||||
|
|
||||||
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
|
val callback = WriteAccelerometerCallback(app, request) { result ->
|
||||||
|
|
||||||
|
gatt?.close()
|
||||||
|
|
||||||
|
result.onSuccess {
|
||||||
|
deviceCache.remove(serial)
|
||||||
|
resultList.remove(serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.resume(result)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
gatt = scanResult.device.connectGatt(app, false, callback)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun changeBlePassword(
|
override suspend fun changeBlePassword(
|
||||||
password: String,
|
password: String,
|
||||||
serial: String
|
serial: String
|
||||||
|
|
@ -618,52 +965,51 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Float, BleException>> {
|
override fun getAccelerometerMeasureBySerialFlow(
|
||||||
|
serial: String,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency,
|
||||||
|
): Flow<Result<MeasureData, BleException>> {
|
||||||
|
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
|
|
||||||
deviceCache[serial]?.let {
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
it.device.connectGatt(app, false, object : BluetoothGattCallback() {
|
if(checkPermission()) {
|
||||||
|
|
||||||
override fun onConnectionStateChange(
|
deviceCache[serial]?.let {
|
||||||
gatt: BluetoothGatt,
|
|
||||||
status: Int,
|
|
||||||
newState: Int
|
|
||||||
) {
|
|
||||||
gatt.discoverServices()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
gatt = it.device.connectGatt(
|
||||||
super.onServicesDiscovered(gatt, status)
|
app,
|
||||||
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
false,
|
||||||
gatt.setCharacteristicNotification(it, true)
|
ReadAccelerometerCallback(
|
||||||
gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
|
app = app,
|
||||||
|
accelScale = accelScale,
|
||||||
|
accelMode = accelMode,
|
||||||
|
fftAxis = fftAxis,
|
||||||
|
fftMode = fftMode,
|
||||||
|
frequency = frequency
|
||||||
|
) { result ->
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
override fun onCharacteristicChanged(
|
}
|
||||||
gatt: BluetoothGatt,
|
|
||||||
characteristic: BluetoothGattCharacteristic,
|
|
||||||
value: ByteArray
|
|
||||||
) {
|
|
||||||
|
|
||||||
Log.d("new", value.toString())
|
awaitClose {
|
||||||
|
gatt?.close()
|
||||||
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
} else {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|
||||||
send(
|
send(Result.failure(BleException.PermissionDenied))
|
||||||
Result.success(((-256)..256).random().toFloat())
|
}
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
awaitClose {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -688,8 +1034,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
newState: Int
|
newState: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Log.d("read", "onConnectionStateChange $newState $status")
|
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS) {
|
if(status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
||||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
|
@ -699,16 +1043,18 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
gatt.discoverServices()
|
gatt.discoverServices()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
gatt.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.disconnect()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,8 +1066,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
) {
|
) {
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
|
|
||||||
Log.d("read", "onServicesDiscovered $status")
|
|
||||||
|
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
||||||
gatt.services?.firstOrNull { service ->
|
gatt.services?.firstOrNull { service ->
|
||||||
|
|
@ -736,6 +1080,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
gatt.disconnect()
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -744,10 +1089,14 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gatt.disconnect()
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
|
|
||||||
|
gatt.disconnect()
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -787,8 +1136,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS) {
|
if(status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
||||||
if (checkPermission()) {
|
if (checkPermission()) {
|
||||||
|
|
||||||
gatt.close()
|
|
||||||
result = value
|
result = value
|
||||||
it.resume(Result.success(result!!))
|
it.resume(Result.success(result!!))
|
||||||
|
|
||||||
|
|
@ -796,11 +1143,11 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
gatt.disconnect()
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
gatt.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -832,8 +1179,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
newState: Int
|
newState: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Log.d("write", "onConnectionStateChange $status $newState")
|
|
||||||
|
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
||||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
|
@ -844,7 +1189,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bleGatt?.close()
|
bleGatt?.disconnect()
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -852,13 +1197,12 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bleGatt?.close()
|
bleGatt?.close()
|
||||||
it.resume(Result.success(Unit))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bleGatt?.close()
|
bleGatt?.disconnect()
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -871,8 +1215,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
) {
|
) {
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
|
|
||||||
Log.d("write", "onServicesDiscovered $status")
|
|
||||||
|
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
||||||
gatt.services?.firstOrNull { service ->
|
gatt.services?.firstOrNull { service ->
|
||||||
|
|
@ -887,7 +1229,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bleGatt?.close()
|
bleGatt?.disconnect()
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -898,12 +1240,12 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
Log.d("write", "service not found")
|
Log.d("write", "service not found")
|
||||||
|
|
||||||
bleGatt?.close()
|
bleGatt?.disconnect()
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
bleGatt?.close()
|
bleGatt?.disconnect()
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -917,17 +1259,13 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
) {
|
) {
|
||||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
|
||||||
Log.d("write", "onCharacteristicWrite $status")
|
|
||||||
|
|
||||||
if (checkPermission()) {
|
if (checkPermission()) {
|
||||||
|
|
||||||
gatt.close()
|
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS) {
|
if(status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
|
||||||
it.resume(Result.success(Unit))
|
it.resume(Result.success(Unit))
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
|
|
||||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
|
|
@ -935,11 +1273,12 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
gatt.close()
|
|
||||||
it.resume(Result.failure(BleException.PermissionDenied))
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gatt.disconnect()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package llc.arma.ble.data
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import llc.arma.ble.R
|
||||||
|
import llc.arma.ble.domain.repository.EmailRepository
|
||||||
|
import llc.arma.ble.domain.repository.XlsxRepository
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
import org.apache.poi.ss.SpreadsheetVersion
|
||||||
|
import org.apache.poi.ss.usermodel.WorkbookFactory
|
||||||
|
import org.apache.poi.ss.util.AreaReference
|
||||||
|
import org.apache.poi.ss.util.CellReference
|
||||||
|
import org.apache.poi.util.IOUtils
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSheet
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class EmailRepositoryImpl @Inject constructor(
|
||||||
|
private val application: Application
|
||||||
|
) : EmailRepository {
|
||||||
|
|
||||||
|
override fun sendFile(file: File) {
|
||||||
|
val uri = FileProvider.getUriForFile(application, "llc.arma.ble.fileprovider", file)
|
||||||
|
|
||||||
|
val sendIntent = Intent(Intent.ACTION_SEND)
|
||||||
|
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
sendIntent.type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Measure history")
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
application.startActivity(
|
||||||
|
Intent.createChooser(sendIntent, "Send email...").apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
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.bluetooth.BluetoothGattDescriptor
|
||||||
|
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.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.atan
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
|
||||||
|
class ReadAccelerometerCallback(
|
||||||
|
private val app: Application,
|
||||||
|
private val accelScale: AccelScale,
|
||||||
|
private val accelMode: AccelViewMode,
|
||||||
|
private val fftAxis: FftAxis,
|
||||||
|
private val fftMode: FftViewMode,
|
||||||
|
private val frequency: FftFrequency,
|
||||||
|
private val onResult: (Result<MeasureData, BleException>) -> Unit
|
||||||
|
) : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
Log.d("accel", "onServicesDiscovered")
|
||||||
|
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
|
||||||
|
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
val payload = byteArrayOf(
|
||||||
|
4,
|
||||||
|
accelMode.sendData,
|
||||||
|
accelScale.sendData,
|
||||||
|
fftMode.sendData,
|
||||||
|
fftAxis.sendData,
|
||||||
|
frequency.sendData,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
gatt.writeCharacteristic(it, payload)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicWrite(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
|
||||||
|
Log.d("accel", "request written")
|
||||||
|
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
|
|
||||||
|
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
gatt.setCharacteristicNotification(it, true)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
|
||||||
|
} else {
|
||||||
|
val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
||||||
|
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||||
|
gatt.writeDescriptor(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun onCharacteristicChanged(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic
|
||||||
|
) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
super.onCharacteristicChanged(gatt, characteristic)
|
||||||
|
onCommonCharacteristicRead(characteristic.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicChanged(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
value: ByteArray
|
||||||
|
) {
|
||||||
|
super.onCharacteristicChanged(gatt, characteristic, value)
|
||||||
|
onCommonCharacteristicRead(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCommonCharacteristicRead(
|
||||||
|
value: ByteArray,
|
||||||
|
){
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val result = if(accelMode == AccelViewMode.VIBRATION){
|
||||||
|
|
||||||
|
MeasureData.Vibration((value.get2byteShortAt().toFloat() * accelScale.k) / Short.MAX_VALUE)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val data = value.toList().chunked(2).map {
|
||||||
|
it.toByteArray().get2byteShortAt()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("accel", "x: ${data[0]} y: ${data[1]} z: ${data[2]} bytes: ${value.joinToString { it.toString() }}")
|
||||||
|
|
||||||
|
val x: Float
|
||||||
|
val y: Float
|
||||||
|
val z: Float
|
||||||
|
|
||||||
|
if (accelMode == AccelViewMode.ANGLE) {
|
||||||
|
|
||||||
|
x = calculateZAngle(data[2].toFloat(), data[1].toFloat()) * 180f / Math.PI.toFloat()
|
||||||
|
y = calculateZAngle(data[2].toFloat(), data[0].toFloat()) * 180f / Math.PI.toFloat()
|
||||||
|
z = calculateZAngle(data[0].toFloat(), data[1].toFloat()) * 180f / Math.PI.toFloat()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
x = (data[0].toFloat() * accelScale.k) / Short.MAX_VALUE
|
||||||
|
y = (data[1].toFloat() * accelScale.k) / Short.MAX_VALUE
|
||||||
|
z = (data[2].toFloat() * accelScale.k) / Short.MAX_VALUE
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val state = MeasureData.Accelerate(
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
z = z
|
||||||
|
)
|
||||||
|
|
||||||
|
state
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult(Result.success(result))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateAngle(
|
||||||
|
targetAxis: Float,
|
||||||
|
firstAxis: Float,
|
||||||
|
secondAxis: Float
|
||||||
|
): Float {
|
||||||
|
|
||||||
|
return atan(targetAxis.div(sqrt(firstAxis.pow(2) + secondAxis.pow(2))))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun calculateZAngle(
|
||||||
|
x: Float,
|
||||||
|
y: Float
|
||||||
|
): Float {
|
||||||
|
|
||||||
|
var x = x
|
||||||
|
|
||||||
|
if(x == 0f && y == 0f){
|
||||||
|
x = 0.0001f
|
||||||
|
}
|
||||||
|
|
||||||
|
if(x > 0){
|
||||||
|
return atan(y/x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(x < 0 && y >= 0){
|
||||||
|
return atan(y/x) + PI.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if(x < 0 && y < 0){
|
||||||
|
return atan(y/x) - PI.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if(x == 0f && y > 0){
|
||||||
|
return PI.toFloat() / 2f
|
||||||
|
}
|
||||||
|
|
||||||
|
if(x == 0f && y < 0){
|
||||||
|
return -PI.toFloat() / 2f
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0f
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,410 @@
|
||||||
|
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
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
|
||||||
|
class ReadAccelerometerHistoryCallback(
|
||||||
|
private val mode: AccelViewMode,
|
||||||
|
private val scale: AccelScale,
|
||||||
|
private val app: Application,
|
||||||
|
private val onResult: (Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, 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 {
|
||||||
|
Log.d("history", scale.name)
|
||||||
|
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()
|
||||||
|
|
||||||
|
private val result = mutableListOf<List<UByte>>()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
Log.d("expected data size", value.get2byteUIntAt(0).toString())
|
||||||
|
|
||||||
|
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()){
|
||||||
|
|
||||||
|
result.add(value.toUByteArray().toList())
|
||||||
|
|
||||||
|
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
|
||||||
|
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
|
||||||
|
bleRealTime = value.get4byteUIntAt(12).toLong()
|
||||||
|
|
||||||
|
lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
|
||||||
|
|
||||||
|
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||||
|
val temperatureDataArray = value.toUByteArray().asList().subList(16, value.size)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
resultTemperaturePackage.addAll(
|
||||||
|
temperatureDataArray.chunked(2).map {
|
||||||
|
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
|
||||||
|
}.toMutableList()
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d("received data size", (temperatureDataArray.chunked(2).size).toString())
|
||||||
|
Log.d("next data size", nextPackageDataCount.toString())
|
||||||
|
|
||||||
|
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(
|
||||||
|
when(mode){
|
||||||
|
AccelViewMode.ACCELERATION,
|
||||||
|
AccelViewMode.PEAK_ACCELERATION,
|
||||||
|
AccelViewMode.RMS -> {
|
||||||
|
resultTemperaturePackage.chunked(3).withIndex().map {
|
||||||
|
Ble.Accelerometer.HistoryPoint.Angle(
|
||||||
|
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
x = (it.value[0] * scale.k) / Short.MAX_VALUE,
|
||||||
|
y = (it.value[1] * scale.k) / Short.MAX_VALUE,
|
||||||
|
z = (it.value[2] * scale.k) / Short.MAX_VALUE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccelViewMode.ANGLE -> {
|
||||||
|
resultTemperaturePackage.chunked(3).withIndex().map {
|
||||||
|
Ble.Accelerometer.HistoryPoint.Angle(
|
||||||
|
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
x = calculateZAngle(it.value[2], it.value[1]) * 180f / Math.PI.toFloat(),
|
||||||
|
y = calculateZAngle(it.value[2], it.value[0]) * 180f / Math.PI.toFloat(),
|
||||||
|
z = calculateZAngle(it.value[0], it.value[1]) * 180f / Math.PI.toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccelViewMode.VIBRATION -> {
|
||||||
|
resultTemperaturePackage.withIndex().map {
|
||||||
|
Ble.Accelerometer.HistoryPoint.Vibration(
|
||||||
|
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
value = (it.value * scale.k) / Short.MAX_VALUE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (value[0] == 251.toByte()) {
|
||||||
|
|
||||||
|
result.add(value.toUByteArray().toList())
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d("received data size", (temperatureDataArray.chunked(2).size).toString())
|
||||||
|
Log.d("next data size", nextPackageDataCount.toString())
|
||||||
|
|
||||||
|
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(
|
||||||
|
when(mode){
|
||||||
|
AccelViewMode.ACCELERATION,
|
||||||
|
AccelViewMode.PEAK_ACCELERATION,
|
||||||
|
AccelViewMode.RMS -> {
|
||||||
|
resultTemperaturePackage.chunked(3).withIndex().map {
|
||||||
|
Ble.Accelerometer.HistoryPoint.Angle(
|
||||||
|
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
x = (it.value[0] * scale.k) / Short.MAX_VALUE,
|
||||||
|
y = (it.value[1] * scale.k) / Short.MAX_VALUE,
|
||||||
|
z = (it.value[2] * scale.k) / Short.MAX_VALUE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccelViewMode.ANGLE -> {
|
||||||
|
resultTemperaturePackage.chunked(3).withIndex().map {
|
||||||
|
Ble.Accelerometer.HistoryPoint.Angle(
|
||||||
|
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
x = calculateZAngle(it.value[2], it.value[1]) * 180f / Math.PI.toFloat(),
|
||||||
|
y = calculateZAngle(it.value[2], it.value[0]) * 180f / Math.PI.toFloat(),
|
||||||
|
z = calculateZAngle(it.value[0], it.value[1]) * 180f / Math.PI.toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccelViewMode.VIBRATION -> {
|
||||||
|
resultTemperaturePackage.withIndex().map {
|
||||||
|
Ble.Accelerometer.HistoryPoint.Vibration(
|
||||||
|
date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||||
|
value = (it.value * scale.k) / Short.MAX_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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,464 @@
|
||||||
|
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.bluetooth.BluetoothGattDescriptor
|
||||||
|
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
|
||||||
|
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 java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder.LITTLE_ENDIAN
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
fun ByteArray.get2byteShortAt(): Int {
|
||||||
|
val shorts = ShortArray(1)
|
||||||
|
ByteBuffer.wrap(this).order(LITTLE_ENDIAN).asShortBuffer()[shorts]
|
||||||
|
return shorts[0].toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReadAccelerometerSpectreCallback(
|
||||||
|
private val app: Application,
|
||||||
|
private val accelScale: AccelScale,
|
||||||
|
private val accelMode: AccelViewMode,
|
||||||
|
private val fftAxis: FftAxis,
|
||||||
|
private val fftMode: FftViewMode,
|
||||||
|
private val frequency: FftFrequency,
|
||||||
|
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){
|
||||||
|
enableNotifications(gatt)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableNotifications(
|
||||||
|
gatt: BluetoothGatt
|
||||||
|
){
|
||||||
|
|
||||||
|
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
gatt.setCharacteristicNotification(it, true)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")),
|
||||||
|
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
|
||||||
|
} else {
|
||||||
|
val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
||||||
|
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||||
|
gatt.writeDescriptor(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("spectre", "enable notification")
|
||||||
|
|
||||||
|
onResult(Result.success(ProgressState.Indeterminate))
|
||||||
|
resultAccelerometerPackage.clear()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private var initialValue: Long? = null
|
||||||
|
private var frequencyInterval: Long? = null
|
||||||
|
|
||||||
|
private val resultAccelerometerPackage: MutableList<Float> = mutableListOf()
|
||||||
|
|
||||||
|
private var expectedDataSize: Int? = null
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun onCharacteristicChanged(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic
|
||||||
|
) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
super.onCharacteristicChanged(gatt, characteristic)
|
||||||
|
onCommonCharacteristicChanged(gatt, characteristic, characteristic.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicChanged(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
value: ByteArray
|
||||||
|
) {
|
||||||
|
super.onCharacteristicChanged(gatt, characteristic, value)
|
||||||
|
onCommonCharacteristicChanged(gatt, characteristic, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCommonCharacteristicChanged(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
value: ByteArray,
|
||||||
|
){
|
||||||
|
|
||||||
|
if(characteristic.uuid == accelerometerReadUUID) {
|
||||||
|
|
||||||
|
readProperty = Property.DATA_SIZE
|
||||||
|
gatt.getService(serviceUUID).getCharacteristic(accelerometerHistoryReadUUID)?.let {
|
||||||
|
gatt.writeCharacteristic(it, byteArrayOf(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicRead(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
value: ByteArray,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onCharacteristicRead(gatt, characteristic, value, status)
|
||||||
|
onCommonCharacteristicRead(gatt, characteristic, value, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()){
|
||||||
|
|
||||||
|
initialValue = value.get4byteUIntAt(8).toLong()
|
||||||
|
frequencyInterval = value.get4byteUIntAt(4).toLong()
|
||||||
|
|
||||||
|
val accelerometerDataArray = value.asList().subList(16, value.size)
|
||||||
|
|
||||||
|
resultAccelerometerPackage.addAll(
|
||||||
|
accelerometerDataArray.chunked(2).map {
|
||||||
|
it.toByteArray().get2byteShortAt().toFloat()
|
||||||
|
}.toMutableList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||||
|
expectedDataSize = nextPackageDataCount.toInt() + resultAccelerometerPackage.size
|
||||||
|
|
||||||
|
onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
|
||||||
|
onResult(Result.success(ProgressState.Progress(resultAccelerometerPackage.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(
|
||||||
|
resultAccelerometerPackage.withIndex().map {
|
||||||
|
Ble.Accelerometer.MeasurePoint(
|
||||||
|
frequency = frequencyInterval!! * it.index + initialValue!!,
|
||||||
|
value = (it.value * accelScale.k) / Short.MAX_VALUE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
start(gatt)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (value[0] == 251.toByte()) {
|
||||||
|
|
||||||
|
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||||
|
val temperatureDataArray = value.toList().subList(4, value.size)
|
||||||
|
|
||||||
|
resultAccelerometerPackage.addAll(
|
||||||
|
temperatureDataArray.chunked(2).map {
|
||||||
|
it.toByteArray().get2byteShortAt().toFloat()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onResult(Result.success(ProgressState.Progress(resultAccelerometerPackage.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(
|
||||||
|
resultAccelerometerPackage.withIndex().map {
|
||||||
|
Ble.Accelerometer.MeasurePoint(
|
||||||
|
frequency = frequencyInterval!! * it.index + initialValue!!,
|
||||||
|
value = (it.value * accelScale.k) / Short.MAX_VALUE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
start(gatt)
|
||||||
|
|
||||||
|
}
|
||||||
|
} 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(readProperty !== null) {
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDescriptorWrite(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
descriptor: BluetoothGattDescriptor,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onDescriptorWrite(gatt, descriptor, status)
|
||||||
|
start(gatt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun start(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
){
|
||||||
|
|
||||||
|
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||||
|
|
||||||
|
if (checkPermission()) {
|
||||||
|
|
||||||
|
val payload = byteArrayOf(
|
||||||
|
4,
|
||||||
|
accelMode.sendData,
|
||||||
|
accelScale.sendData,
|
||||||
|
fftMode.sendData,
|
||||||
|
fftAxis.sendData,
|
||||||
|
frequency.sendData,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
readProperty = null
|
||||||
|
gatt.writeCharacteristic(it, payload)
|
||||||
|
|
||||||
|
onResult(Result.success(ProgressState.Indeterminate))
|
||||||
|
resultAccelerometerPackage.clear()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
gatt.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -133,7 +132,6 @@ class ReadHistoryCallback(
|
||||||
value: ByteArray,
|
value: ByteArray,
|
||||||
status: Int
|
status: Int
|
||||||
){
|
){
|
||||||
Log.d("read", value[0].toString())
|
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||||
when(readProperty){
|
when(readProperty){
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
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.bluetooth.BluetoothProfile
|
||||||
|
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.model.Ble
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class WriteAccelerometerCallback(
|
||||||
|
private val app: Application,
|
||||||
|
private var request: Ble.Accelerometer.WriteRequest,
|
||||||
|
private val onResult: (Result<Unit, BleException>) -> Unit
|
||||||
|
) : BluetoothGattCallback() {
|
||||||
|
|
||||||
|
private var flashed = false
|
||||||
|
|
||||||
|
override fun onConnectionStateChange(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
status: Int,
|
||||||
|
newState: Int
|
||||||
|
) {
|
||||||
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
|
||||||
|
if(checkPermission()) {
|
||||||
|
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
|
||||||
|
gatt.discoverServices()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServicesDiscovered(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onServicesDiscovered(gatt, status)
|
||||||
|
onCycle(gatt, status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCycle(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
status: Int
|
||||||
|
){
|
||||||
|
|
||||||
|
Log.d("write", "${request.tx != null} ${request.saveHistory != null} ${request.historyInterval != null}")
|
||||||
|
|
||||||
|
if(request.tx != null || request.saveHistory != null || request.historyInterval != null) {
|
||||||
|
|
||||||
|
fun UInt.to4ByteArrayInLittleEndian(): ByteArray =
|
||||||
|
(3 downTo 0).map {
|
||||||
|
(this shr (it * Byte.SIZE_BITS)).toByte()
|
||||||
|
}.toByteArray()
|
||||||
|
|
||||||
|
var uuid: Triple<UUID, ByteArray, Ble.Accelerometer.WriteRequest>? = null
|
||||||
|
|
||||||
|
uuid = request.historyInterval?.let {
|
||||||
|
|
||||||
|
Triple(
|
||||||
|
intervalWriteUUID,
|
||||||
|
mutableListOf<Byte>(3).apply {
|
||||||
|
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
|
||||||
|
}.toByteArray(),
|
||||||
|
request.copy(
|
||||||
|
historyInterval = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = request.saveHistory?.let {
|
||||||
|
|
||||||
|
Triple(
|
||||||
|
saveEnabledWriteUUID,
|
||||||
|
mutableListOf<Byte>(4).apply {
|
||||||
|
add(if (it is Ble.Accelerometer.History.Enabled) 1 else 0)
|
||||||
|
if(it is Ble.Accelerometer.History.Enabled) {
|
||||||
|
add(it.mode.sendData)
|
||||||
|
add(it.scale.sendData)
|
||||||
|
}
|
||||||
|
}.toByteArray(),
|
||||||
|
request.copy(
|
||||||
|
saveHistory = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} ?: uuid
|
||||||
|
|
||||||
|
uuid = request.tx?.let {
|
||||||
|
|
||||||
|
Triple(
|
||||||
|
txWriteUUID,
|
||||||
|
byteArrayOf(
|
||||||
|
when (it) {
|
||||||
|
Ble.BleState.TX.MINUS_40 -> -40
|
||||||
|
Ble.BleState.TX.MINUS_20 -> -20
|
||||||
|
Ble.BleState.TX.MINUS_16 -> -16
|
||||||
|
Ble.BleState.TX.MINUS_12 -> -12
|
||||||
|
Ble.BleState.TX.MINUS_8 -> -8
|
||||||
|
Ble.BleState.TX.MINUS_4 -> -4
|
||||||
|
Ble.BleState.TX.ZERO -> 0
|
||||||
|
Ble.BleState.TX.PLUS_3 -> 3
|
||||||
|
Ble.BleState.TX.PLUS_4 -> 4
|
||||||
|
}
|
||||||
|
),
|
||||||
|
request.copy(
|
||||||
|
tx = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
} ?: uuid
|
||||||
|
|
||||||
|
uuid?.let { uuid ->
|
||||||
|
|
||||||
|
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||||
|
it.uuid == uuid.first
|
||||||
|
}?.let {
|
||||||
|
|
||||||
|
gatt.writeCharacteristic(it, uuid.second)
|
||||||
|
|
||||||
|
request = uuid.third
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if(flashed.not()){
|
||||||
|
|
||||||
|
flashed = true
|
||||||
|
|
||||||
|
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||||
|
it.uuid == flashWriteUUID
|
||||||
|
}?.let {
|
||||||
|
|
||||||
|
gatt.writeCharacteristic(it, byteArrayOf(9))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.success(Unit))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicWrite(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
|
||||||
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
|
|
||||||
|
if(checkPermission()) {
|
||||||
|
|
||||||
|
if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
|
||||||
|
|
||||||
|
onCycle(gatt, status)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
onResult(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.writeType
|
||||||
|
characteristic.value = data
|
||||||
|
writeCharacteristic(characteristic)
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.success(Unit)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Result.failure(BleException.PermissionDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -30,8 +30,6 @@ class WriteBeaconCallback(
|
||||||
) {
|
) {
|
||||||
super.onConnectionStateChange(gatt, status, newState)
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
|
||||||
Log.d("beacon", "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) {
|
||||||
|
|
@ -56,7 +54,6 @@ class WriteBeaconCallback(
|
||||||
gatt: BluetoothGatt,
|
gatt: BluetoothGatt,
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
Log.d("beacon", "onServicesDiscovered $status")
|
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
onCycle(gatt, status)
|
onCycle(gatt, status)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
@ -78,43 +75,37 @@ class WriteThermometerCallback(
|
||||||
(this shr (it * Byte.SIZE_BITS)).toByte()
|
(this shr (it * Byte.SIZE_BITS)).toByte()
|
||||||
}.toByteArray()
|
}.toByteArray()
|
||||||
|
|
||||||
var uuid: Pair<UUID, ByteArray>? = null
|
var uuid: Triple<UUID, ByteArray, Ble.Thermometer.WriteRequest>? = null
|
||||||
|
|
||||||
uuid = request.historyInterval?.let {
|
uuid = request.historyInterval?.let {
|
||||||
|
|
||||||
this.request = request.copy(
|
Triple(
|
||||||
historyInterval = null
|
|
||||||
)
|
|
||||||
|
|
||||||
Pair(
|
|
||||||
intervalWriteUUID,
|
intervalWriteUUID,
|
||||||
mutableListOf<Byte>(3).apply {
|
mutableListOf<Byte>(3).apply {
|
||||||
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
|
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
|
||||||
}.toByteArray()
|
}.toByteArray(),
|
||||||
|
request.copy(
|
||||||
|
historyInterval = null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
uuid = request.saveHistory?.let {
|
uuid = request.saveHistory?.let {
|
||||||
|
|
||||||
this.request = request.copy(
|
Triple(
|
||||||
saveHistory = null
|
|
||||||
)
|
|
||||||
|
|
||||||
Pair(
|
|
||||||
saveEnabledWriteUUID,
|
saveEnabledWriteUUID,
|
||||||
mutableListOf<Byte>(4).apply {
|
mutableListOf<Byte>(4).apply {
|
||||||
add(if (it) 1 else 0)
|
add(if (it) 1 else 0)
|
||||||
}.toByteArray()
|
}.toByteArray(),
|
||||||
|
request.copy(
|
||||||
|
saveHistory = null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} ?: uuid
|
} ?: uuid
|
||||||
|
|
||||||
uuid = request.tx?.let {
|
uuid = request.tx?.let {
|
||||||
|
|
||||||
this.request = request.copy(
|
Triple(
|
||||||
tx = null
|
|
||||||
)
|
|
||||||
|
|
||||||
Pair(
|
|
||||||
txWriteUUID,
|
txWriteUUID,
|
||||||
byteArrayOf(
|
byteArrayOf(
|
||||||
when (it) {
|
when (it) {
|
||||||
|
|
@ -128,6 +119,9 @@ class WriteThermometerCallback(
|
||||||
Ble.BleState.TX.PLUS_3 -> 3
|
Ble.BleState.TX.PLUS_3 -> 3
|
||||||
Ble.BleState.TX.PLUS_4 -> 4
|
Ble.BleState.TX.PLUS_4 -> 4
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
request.copy(
|
||||||
|
tx = null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -140,7 +134,7 @@ class WriteThermometerCallback(
|
||||||
}?.let {
|
}?.let {
|
||||||
|
|
||||||
gatt.writeCharacteristic(it, uuid.second)
|
gatt.writeCharacteristic(it, uuid.second)
|
||||||
|
request = uuid.third
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -183,8 +177,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()) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package llc.arma.ble.data
|
||||||
|
|
||||||
|
import android.R.attr.src
|
||||||
|
import android.app.Application
|
||||||
|
import android.icu.text.SimpleDateFormat
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.FileUtils
|
||||||
|
import llc.arma.ble.R
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
import llc.arma.ble.domain.repository.XlsxRepository
|
||||||
|
import org.apache.poi.ss.usermodel.WorkbookFactory
|
||||||
|
import org.apache.poi.util.IOUtils
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSheet
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class XlsxRepositoryImpl @Inject constructor(
|
||||||
|
private val application: Application
|
||||||
|
) : XlsxRepository {
|
||||||
|
|
||||||
|
override fun exportToXls(data: List<Ble.Accelerometer.HistoryPoint>): File {
|
||||||
|
|
||||||
|
val formatter = SimpleDateFormat("dd.MM.yyyy HH:mm")
|
||||||
|
|
||||||
|
val mailFile = File(application.filesDir, "${UUID.randomUUID()}.xlsx")
|
||||||
|
mailFile.createNewFile()
|
||||||
|
|
||||||
|
when(data.firstOrNull()){
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||||
|
IOUtils.copy(application.resources.openRawResource(R.raw.measure_single_axis), FileOutputStream(mailFile))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
IOUtils.copy(application.resources.openRawResource(R.raw.measure_multiple_axis), FileOutputStream(mailFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileIn = FileInputStream(mailFile)
|
||||||
|
val workbook = WorkbookFactory.create(fileIn)
|
||||||
|
val worksheet = workbook.getSheetAt(0) as XSSFSheet
|
||||||
|
|
||||||
|
data.withIndex().forEach {
|
||||||
|
|
||||||
|
val row = worksheet.createRow(it.index + 1)
|
||||||
|
val dateX = row.createCell(0)
|
||||||
|
val dateY = row.createCell(2)
|
||||||
|
val dateZ = row.createCell(4)
|
||||||
|
val x = row.createCell(1)
|
||||||
|
val y = row.createCell(3)
|
||||||
|
val z = row.createCell(5)
|
||||||
|
|
||||||
|
when(val value = it.value){
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Angle -> {
|
||||||
|
dateX.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
dateY.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
dateZ.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
x.setCellValue(value.x.toDouble())
|
||||||
|
y.setCellValue(value.y.toDouble())
|
||||||
|
z.setCellValue(value.z.toDouble())
|
||||||
|
}
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Vibration -> {
|
||||||
|
dateX.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
x.setCellValue(value.value.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
is Ble.Accelerometer.HistoryPoint.Accelerate -> {
|
||||||
|
dateX.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
dateY.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
dateZ.setCellValue(formatter.format(Date(value.date)))
|
||||||
|
x.setCellValue(value.x.toDouble())
|
||||||
|
y.setCellValue(value.y.toDouble())
|
||||||
|
z.setCellValue(value.z.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileIn.close()
|
||||||
|
val saveFos = FileOutputStream(mailFile)
|
||||||
|
workbook.write(saveFos)
|
||||||
|
saveFos.close()
|
||||||
|
|
||||||
|
val sharedFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "${UUID.randomUUID()}.xlsx")
|
||||||
|
sharedFile.createNewFile()
|
||||||
|
|
||||||
|
val sharedSaveFos = FileOutputStream(sharedFile)
|
||||||
|
workbook.write(sharedSaveFos)
|
||||||
|
sharedSaveFos.close()
|
||||||
|
|
||||||
|
workbook.close()
|
||||||
|
|
||||||
|
return mailFile
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,68 @@
|
||||||
package llc.arma.ble.domain.model
|
package llc.arma.ble.domain.model
|
||||||
|
|
||||||
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
|
||||||
sealed class Ble(
|
sealed class Ble(
|
||||||
val info: BleInfo
|
val info: BleInfo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
class Accelerometer(
|
class Accelerometer(
|
||||||
info: BleInfo
|
info: BleInfo,
|
||||||
): Ble(info){
|
val state: BleState,
|
||||||
|
val accelerometerState: AccelerometerState
|
||||||
|
): Ble(info) {
|
||||||
|
|
||||||
|
sealed class History {
|
||||||
|
|
||||||
|
data class Enabled(
|
||||||
|
val scale: AccelScale,
|
||||||
|
val mode: AccelViewMode,
|
||||||
|
val detailed: Boolean
|
||||||
|
) : History()
|
||||||
|
|
||||||
|
object Disabled : History()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WriteRequest(
|
||||||
|
val tx: BleState.TX?,
|
||||||
|
val saveHistory: History?,
|
||||||
|
val historyInterval: Long?
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class HistoryPoint {
|
||||||
|
|
||||||
|
class Vibration (
|
||||||
|
val date: Long,
|
||||||
|
val value: Float
|
||||||
|
) : HistoryPoint()
|
||||||
|
|
||||||
|
class Accelerate (
|
||||||
|
val date: Long,
|
||||||
|
val x: Float,
|
||||||
|
val y: Float,
|
||||||
|
val z: Float
|
||||||
|
) : HistoryPoint()
|
||||||
|
|
||||||
|
class Angle (
|
||||||
|
val date: Long,
|
||||||
|
val x: Float,
|
||||||
|
val y: Float,
|
||||||
|
val z: Float
|
||||||
|
) : HistoryPoint()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeasurePoint (
|
||||||
|
val frequency: Long,
|
||||||
|
val value: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AccelerometerState(
|
||||||
|
val saveHistory: History,
|
||||||
|
val historyInterval: Long
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ data class BleInfo(
|
||||||
val batteryLevel: Int,
|
val batteryLevel: Int,
|
||||||
val rssi: Int?,
|
val rssi: Int?,
|
||||||
val type: Type,
|
val type: Type,
|
||||||
val scanTime: Long
|
val scanTime: Long,
|
||||||
|
val tx: Int,
|
||||||
|
val recordEnabled: Boolean
|
||||||
){
|
){
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ import llc.arma.ble.domain.common.ProgressState
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||||
import llc.arma.ble.domain.usecase.GetBleBySerial
|
import llc.arma.ble.domain.usecase.AccelScale
|
||||||
|
import llc.arma.ble.domain.usecase.AccelViewMode
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
import llc.arma.ble.domain.usecase.FftAxis
|
||||||
|
import llc.arma.ble.domain.usecase.FftFrequency
|
||||||
|
import llc.arma.ble.domain.usecase.FftViewMode
|
||||||
|
|
||||||
interface BleRepository {
|
interface BleRepository {
|
||||||
|
|
||||||
|
|
@ -23,7 +28,28 @@ interface BleRepository {
|
||||||
|
|
||||||
suspend fun writeBle(serial: String, request: Ble.Beacon.WriteRequest): Result<Unit, BleException>
|
suspend fun writeBle(serial: String, request: Ble.Beacon.WriteRequest): Result<Unit, BleException>
|
||||||
|
|
||||||
|
suspend fun writeBle(serial: String, request: Ble.Accelerometer.WriteRequest): Result<Unit, BleException>
|
||||||
|
|
||||||
suspend fun changeBlePassword(password: String, serial: String): Result<Unit, BleException>
|
suspend fun changeBlePassword(password: String, serial: String): Result<Unit, BleException>
|
||||||
fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Float, BleException>>
|
|
||||||
|
fun getAccelerometerMeasureBySerialFlow(
|
||||||
|
serial: String,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
): Flow<Result<MeasureData, BleException>>
|
||||||
|
|
||||||
|
suspend fun getAccelerometerSpectreBySerial(
|
||||||
|
serial: String,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
||||||
|
|
||||||
|
suspend fun getAccelerometerHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package llc.arma.ble.domain.repository
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface EmailRepository {
|
||||||
|
|
||||||
|
fun sendFile(file: File)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package llc.arma.ble.domain.repository
|
||||||
|
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
import llc.arma.ble.domain.usecase.MeasureData
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface XlsxRepository {
|
||||||
|
|
||||||
|
fun exportToXls(data: List<Ble.Accelerometer.HistoryPoint>): File
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package llc.arma.ble.domain.usecase
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import llc.arma.ble.R
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
import llc.arma.ble.domain.repository.EmailRepository
|
||||||
|
import llc.arma.ble.domain.repository.XlsxRepository
|
||||||
|
import org.apache.poi.ss.SpreadsheetVersion
|
||||||
|
import org.apache.poi.ss.usermodel.WorkbookFactory
|
||||||
|
import org.apache.poi.ss.util.AreaReference
|
||||||
|
import org.apache.poi.ss.util.CellReference
|
||||||
|
import org.apache.poi.util.IOUtils
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCell
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSheet
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFTable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ExportToXlsx @Inject constructor(
|
||||||
|
private val xlsxRepository: XlsxRepository,
|
||||||
|
private val emailRepository: EmailRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(data: List<Ble.Accelerometer.HistoryPoint>){
|
||||||
|
|
||||||
|
val file = xlsxRepository.exportToXls(data)
|
||||||
|
emailRepository.sendFile(file)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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.HistoryPoint>>, BleException>> {
|
||||||
|
|
||||||
|
return bleRepository.getAccelerometerHistoryBySerial(serial)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,6 @@ package llc.arma.ble.domain.usecase
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import llc.arma.ble.domain.Result
|
import llc.arma.ble.domain.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.model.Ble
|
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -12,10 +10,54 @@ class GetAccelerometerMeasureBySerialFlow @Inject constructor(
|
||||||
private val bleRepository: BleRepository
|
private val bleRepository: BleRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(serial: String): Flow<Result<Float, BleException>> {
|
operator fun invoke(
|
||||||
|
serial: String,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
): Flow<Result<MeasureData, BleException>> {
|
||||||
|
|
||||||
return bleRepository.getAccelerometerMeasureBySerialFlow(serial)
|
return bleRepository.getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class AccelViewMode {
|
||||||
|
ACCELERATION, PEAK_ACCELERATION, RMS, VIBRATION, ANGLE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class AccelScale(val k: Int) {
|
||||||
|
S_2(2_000), S_4(4_000), S_8(8_000), S_16(16_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class MeasureData {
|
||||||
|
|
||||||
|
data class Angle(
|
||||||
|
val xAngle: Float,
|
||||||
|
val yAngle: Float,
|
||||||
|
val zAngle: Float,
|
||||||
|
val xAccelerate: Float,
|
||||||
|
val yAccelerate: Float,
|
||||||
|
val zAccelerate: Float,
|
||||||
|
) : MeasureData()
|
||||||
|
|
||||||
|
data class Accelerate(
|
||||||
|
val x: Float,
|
||||||
|
val y: Float,
|
||||||
|
val z: Float,
|
||||||
|
) : MeasureData()
|
||||||
|
|
||||||
|
data class Vibration(
|
||||||
|
val value: Float
|
||||||
|
) : MeasureData()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Accelerate(
|
||||||
|
val x: Float,
|
||||||
|
val y: Float,
|
||||||
|
val z: Float,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
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 GetAccelerometerSpectreBySerial @Inject constructor(
|
||||||
|
private val bleRepository: BleRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
serial: String,
|
||||||
|
accelScale: AccelScale,
|
||||||
|
accelMode: AccelViewMode,
|
||||||
|
fftAxis: FftAxis,
|
||||||
|
fftMode: FftViewMode,
|
||||||
|
frequency: FftFrequency
|
||||||
|
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||||
|
|
||||||
|
return bleRepository.getAccelerometerSpectreBySerial(serial, accelScale, accelMode, fftAxis, fftMode, frequency)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FftFrequency {
|
||||||
|
OFF, F_1, F_10, F_25, F_50, F_100, F_200, F_400, F_1620, F_1344
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FftViewMode {
|
||||||
|
SPECTRE, X, Y, Z
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FftAxis {
|
||||||
|
AUTO, X, Y, Z
|
||||||
|
}
|
||||||
|
|
@ -24,4 +24,11 @@ class WriteBle @Inject constructor(
|
||||||
return bleRepository.writeBle(serial, request)
|
return bleRepository.writeBle(serial, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
serial: String,
|
||||||
|
request: Ble.Accelerometer.WriteRequest
|
||||||
|
): llc.arma.ble.domain.Result<Unit, BleException>{
|
||||||
|
return bleRepository.writeBle(serial, request)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<files-path name="xlsx" path="/"/>
|
||||||
|
</paths>
|
||||||
|
|
@ -14,7 +14,7 @@ buildscript {
|
||||||
|
|
||||||
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.0.2' apply false
|
id 'com.android.application' version '8.1.1' apply false
|
||||||
id 'com.android.library' version '8.0.2' apply false
|
id 'com.android.library' version '8.1.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
|
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue