rotations save

This commit is contained in:
Vineyro 2024-07-05 17:00:32 +07:00
parent 55b6807081
commit 549b625820
37 changed files with 1514 additions and 129 deletions

View File

@ -96,6 +96,10 @@ dependencies {
implementation "com.patrykandpatrick.vico:compose:1.7.1"
implementation "com.patrykandpatrick.vico:compose-m3:1.7.1"
implementation "androidx.room:room-runtime:2.6.1"
kapt("androidx.room:room-compiler:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
implementation files('libs/poishadow-all.jar')
}

View File

@ -0,0 +1,30 @@
package llc.arma.ble.app.framework.di
import android.app.Application
import androidx.room.Room
import androidx.room.RoomDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import llc.arma.ble.data.db.AppDatabase
import llc.arma.ble.data.db.RotationsDao
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class DatabaseModule {
@Provides
@Singleton
fun provideDatabase(app: Application): AppDatabase {
return Room.databaseBuilder(app, AppDatabase::class.java, "app-database").build()
}
@Provides
@Singleton
fun provideRotationsDao(db: AppDatabase): RotationsDao {
return db.getRotationsDao()
}
}

View File

@ -4,11 +4,13 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import llc.arma.ble.data.BleRepositoryImpl
import llc.arma.ble.data.EmailRepositoryImpl
import llc.arma.ble.data.XlsxRepositoryImpl
import llc.arma.ble.data.repository.BleRepositoryImpl
import llc.arma.ble.data.repository.EmailRepositoryImpl
import llc.arma.ble.data.repository.RotationsRepositoryImpl
import llc.arma.ble.data.repository.XlsxRepositoryImpl
import llc.arma.ble.domain.repository.BleRepository
import llc.arma.ble.domain.repository.EmailRepository
import llc.arma.ble.domain.repository.RotationsRepository
import llc.arma.ble.domain.repository.XlsxRepository
@Module
@ -21,6 +23,9 @@ interface RepositoryBinding {
@Binds
fun bindEmailRepository(repository: EmailRepositoryImpl): EmailRepository
@Binds
fun bindRotationsRepository(repository: RotationsRepositoryImpl): RotationsRepository
@Binds
fun bindXlsxRepository(repository: XlsxRepositoryImpl): XlsxRepository

View File

@ -7,15 +7,19 @@ import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.Bundle
import android.view.SurfaceView
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
@ -28,8 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityCompat.startActivityForResult
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
@ -231,19 +234,20 @@ class MainActivity : ComponentActivity() {
}
@Composable
fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) {
val eventHandler = rememberUpdatedState(onEvent)
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
fun Camera(){
DisposableEffect(lifecycleOwner.value) {
val lifecycle = lifecycleOwner.value.lifecycle
val observer = LifecycleEventObserver { owner, event ->
eventHandler.value(owner, event)
}
AndroidView(
factory = {
val cameraManager = it.getSystemService(CameraManager::class.java)
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
cameraManager.cameraIdList.forEach {
println("$it is front: ${cameraManager.getCameraCharacteristics(it).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT}")
}
return@AndroidView SurfaceView(it)
}
}
)
}

View File

@ -66,6 +66,10 @@ class ConnectionContract {
val serial: String
) : Navigation()
data class NavigateToRotationsStatistic(
val serial: String
) : Navigation()
}
sealed class InnerNavigation : Effect() {

View File

@ -184,7 +184,9 @@ fun ConnectionScreen(
onDismiss = {
innerScreen = null
}
)
){
onNavigationEvent(ConnectionContract.Effect.Navigation.NavigateToRotationsStatistic(it.ble.serial))
}
}

View File

@ -26,6 +26,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.CloudUpload
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.TableView
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextAlign
import com.patrykandpatrick.vico.compose.axis.axisGuidelineComponent
@ -71,6 +72,7 @@ class AccelEntry(
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AccelerometerHistory(
ble: BleInfo,
@ -79,7 +81,8 @@ fun AccelerometerHistory(
fftAxis: FftAxis,
fftMode: FftViewMode,
frequency: FftFrequency,
onDismiss: (() -> Unit)? = null
onDismiss: (() -> Unit)? = null,
onShowStatistic: () -> Unit,
) {
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
@ -95,74 +98,85 @@ fun AccelerometerHistory(
}
}*/
Column(
modifier = Modifier.fillMaxHeight(0.9f)
) {
Column() {
Row(
modifier = Modifier.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
TopAppBar(
navigationIcon = {
onDismiss?.let {
onDismiss?.let {
IconButton(onClick = it) {
Icon(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = null
)
}
IconButton(onClick = it) {
}
},
title = {
val title = when(state){
is AccelerometerHistoryContract.State.Display -> {
when (state.loadingHistoryState) {
is ProgressState.Finished -> "${accelMode.localized} (${state.loadingHistoryState.data.size})"
is ProgressState.Indeterminate -> accelMode.localized
is ProgressState.Progress -> accelMode.localized
}
}
AccelerometerHistoryContract.State.Exception -> accelMode.localized
}
Text(
modifier = Modifier.weight(1f),
text = title,
style = MaterialTheme.typography.titleLarge
)
},
actions = {
IconButton(
onClick = onShowStatistic,
enabled = when(state){
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
AccelerometerHistoryContract.State.Exception -> false
}
) {
Icon(
imageVector = Icons.Rounded.ArrowBack,
imageVector = Icons.Rounded.TableView,
contentDescription = null
)
}
}
val title = when(state){
is AccelerometerHistoryContract.State.Display -> {
when (state.loadingHistoryState) {
is ProgressState.Finished -> "${accelMode.localized} (${state.loadingHistoryState.data.size})"
is ProgressState.Indeterminate -> accelMode.localized
is ProgressState.Progress -> accelMode.localized
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
)
}
AccelerometerHistoryContract.State.Exception -> accelMode.localized
}
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
IconButton(
onClick = {
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.name, 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
)
}
) {
Icon(
imageVector = Icons.Rounded.CloudUpload,
contentDescription = null
)
}
IconButton(
onClick = {
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.name, 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))

View File

@ -15,6 +15,10 @@ import llc.arma.ble.app.ui.screen.connection.ConnectionContract
import llc.arma.ble.app.ui.screen.connection.ConnectionScreen
import llc.arma.ble.app.ui.screen.password.ChangePasswordContract
import llc.arma.ble.app.ui.screen.password.ChangePasswordScreen
import llc.arma.ble.app.ui.screen.rotation.delete.RotationDeleteContract
import llc.arma.ble.app.ui.screen.rotation.delete.RotationDeleteScreen
import llc.arma.ble.app.ui.screen.rotation.statistic.RotationStatisticContract
import llc.arma.ble.app.ui.screen.rotation.statistic.RotationStatisticScreen
@Composable
fun MainScreen() {
@ -49,7 +53,10 @@ fun MainScreen() {
onNavigationEvent = {
when(it){
ConnectionContract.Effect.Navigation.NavigateUp -> controller.navigateUp()
is ConnectionContract.Effect.Navigation.NavigateToChangePassword -> controller.navigate("change_password/${it.serial}")
is ConnectionContract.Effect.Navigation.NavigateToChangePassword ->
controller.navigate("change_password/${it.serial}")
is ConnectionContract.Effect.Navigation.NavigateToRotationsStatistic ->
controller.navigate("rotation_statistic/${it.serial}")
}
}
)
@ -57,6 +64,36 @@ fun MainScreen() {
}
)
composable(
route = "rotation_statistic/{serial}",
content = {
RotationStatisticScreen {
when(it){
is RotationStatisticContract.Effect.Navigation.NavigateToDelete -> {
controller.navigate("rotation_delete/${it.serial}")
}
RotationStatisticContract.Effect.Navigation.NavigateUp -> {
controller.navigateUp()
}
}
}
}
)
dialog(
route = "rotation_delete/{serial}",
dialogProperties = DialogProperties(usePlatformDefaultWidth = false),
content = {
RotationDeleteScreen {
when(it){
RotationDeleteContract.Effect.Navigation.NavigateUp -> controller.navigateUp()
}
}
}
)
dialog(
route = "change_password/{serial}",
dialogProperties = DialogProperties(usePlatformDefaultWidth = false),

View File

@ -0,0 +1,30 @@
package llc.arma.ble.app.ui.screen.rotation.delete
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.Rotation
class RotationDeleteContract {
sealed class Event : ViewEvent {
object OnNavigateUp : Event()
object OnDelete : Event()
}
object State : ViewState
sealed class Effect : ViewSideEffect {
sealed class Navigation : Effect(){
object NavigateUp : Navigation()
}
}
}

View File

@ -0,0 +1,118 @@
package llc.arma.ble.app.ui.screen.rotation.delete
import android.icu.text.SimpleDateFormat
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material3.Card
import androidx.compose.material3.DatePicker
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import llc.arma.ble.app.ui.screen.rotation.statistic.RotationStatisticContract
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlin.math.absoluteValue
import kotlin.math.pow
import kotlin.math.roundToInt
@Composable
fun RotationDeleteScreen(
onNavigation: (RotationDeleteContract.Effect.Navigation) -> Unit
) {
val viewModel = hiltViewModel<RotationDeleteViewModel>()
LaunchedEffect(Unit) {
viewModel.effect.onEach {
when(it){
is RotationDeleteContract.Effect.Navigation -> onNavigation(it)
}
}.launchIn(this)
}
Surface(
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
style = MaterialTheme.typography.titleLarge,
text = "Удалить сохраненные записи?"
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
modifier = Modifier
.align(Alignment.End)
) {
TextButton(
onClick = {
viewModel.setEvent(
RotationDeleteContract.Event.OnNavigateUp
)
}
) {
Text(text = "Отмена")
}
Button(
onClick = {
viewModel.setEvent(
RotationDeleteContract.Event.OnDelete
)
}
) {
Text(text = "Удалить")
}
}
}
}
}

View File

@ -0,0 +1,67 @@
package llc.arma.ble.app.ui.screen.rotation.delete
import android.icu.util.GregorianCalendar
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.domain.usecase.DeleteRotationsBySerial
import llc.arma.ble.domain.usecase.GetRotationsBySerial
import llc.arma.ble.domain.usecase.GetWheelRadiusBySerial
import llc.arma.ble.domain.usecase.SetWheelRadius
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.absoluteValue
@HiltViewModel
class RotationDeleteViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val deleteRotationsBySerial: DeleteRotationsBySerial
) : BaseViewModel<RotationDeleteContract.State, RotationDeleteContract.Event, RotationDeleteContract.Effect>() {
override fun setInitialState() = RotationDeleteContract.State
override fun handleEvents(event: RotationDeleteContract.Event) {
when(event){
is RotationDeleteContract.Event.OnDelete -> reduce(viewState.value, event)
is RotationDeleteContract.Event.OnNavigateUp -> reduce(viewState.value, event)
}
}
private fun reduce(
state: RotationDeleteContract.State,
event: RotationDeleteContract.Event.OnNavigateUp
) {
setEffect {
RotationDeleteContract.Effect.Navigation.NavigateUp
}
}
private fun reduce(
state: RotationDeleteContract.State,
event: RotationDeleteContract.Event.OnDelete
) {
viewModelScope.launch {
deleteRotationsBySerial(savedStateHandle.get<String>("serial")!!)
}
setEffect {
RotationDeleteContract.Effect.Navigation.NavigateUp
}
}
}

View File

@ -0,0 +1,69 @@
package llc.arma.ble.app.ui.screen.rotation.statistic
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.Rotation
class RotationStatisticContract {
sealed class Event : ViewEvent {
object OnNavigateUp : Event()
object OnNavigateToDelete : Event()
data class OnRadiusChanged(
val radius: Long?
): Event()
data class OnStartDateChanged(
val start: Long?
): Event()
data class OnStopDateChanged(
val stop: Long?
): Event()
}
sealed class State : ViewState {
object Loading : State()
data class Display(
val rotations: List<Rotation>,
val ranges: List<RotationRange>,
val radius: Long?,
val startDate: Long?,
val endDate: Long?
) : State() {
data class RotationRange(
val distance: Double,
val startDate: Long,
val endDate: Long,
val maxSpeed: Double,
val avgSpeed: Double,
val rotations: Float,
)
}
}
sealed class Effect : ViewSideEffect {
sealed class Navigation : Effect(){
object NavigateUp : Navigation()
data class NavigateToDelete(
val serial: String
) : Navigation()
}
}
}

View File

@ -0,0 +1,496 @@
package llc.arma.ble.app.ui.screen.rotation.statistic
import android.icu.text.SimpleDateFormat
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.ArrowForward
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material3.Card
import androidx.compose.material3.DatePicker
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlin.math.absoluteValue
import kotlin.math.pow
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RotationStatisticScreen(
onNavigation: (RotationStatisticContract.Effect.Navigation) -> Unit
) {
val viewModel = hiltViewModel<RotationStatisticViewModel>()
val state = viewModel.viewState.value
LaunchedEffect(Unit) {
viewModel.effect.onEach {
when(it){
is RotationStatisticContract.Effect.Navigation -> onNavigation(it)
}
}.launchIn(this)
}
Column(
modifier = Modifier.fillMaxSize()
) {
TopAppBar(
navigationIcon = {
IconButton(
onClick = {
viewModel.setEvent(RotationStatisticContract.Event.OnNavigateUp)
}
) {
Icon(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = null
)
}
},
title = {
Text(
modifier = Modifier.weight(1f),
text = "Обороты",
style = MaterialTheme.typography.titleLarge
)
},
actions = {
IconButton(
onClick = {
viewModel.setEvent(RotationStatisticContract.Event.OnNavigateToDelete)
}
) {
Icon(
imageVector = Icons.Rounded.Delete,
contentDescription = null
)
}
}
)
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(12.dp)
) {
if(state is RotationStatisticContract.State.Display){
var showStartDateSelector by remember {
mutableStateOf(false)
}
var showEndDateSelector by remember {
mutableStateOf(false)
}
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.width(IntrinsicSize.Max)
.height(IntrinsicSize.Max)
) {
TextField(
readOnly = true,
value = dateFormat.format(Date(state.startDate ?: state.ranges.minOfOrNull { it.startDate } ?: 0)),
onValueChange = {},
label = {
Text(text = "Начало")
},
)
Box(
modifier = Modifier
.fillMaxSize()
.clickable { showStartDateSelector = true }
) {}
}
Box(
modifier = Modifier
.weight(1f)
.width(IntrinsicSize.Max)
.height(IntrinsicSize.Max)
) {
TextField(
readOnly = true,
value = dateFormat.format(Date(state.endDate ?: state.ranges.maxOfOrNull { it.endDate } ?: 0)),
onValueChange = {},
label = {
Text(text = "Завершение")
}
)
Box(
modifier = Modifier
.fillMaxSize()
.clickable { showEndDateSelector = true }
) {}
}
}
TextField(
singleLine = true,
value = state.radius?.toString() ?: "",
onValueChange = {
viewModel.setEvent(
RotationStatisticContract.Event.OnRadiusChanged(
it.filter { it.isDigit() }.toLongOrNull()
)
)
},
label = {
Text(text = "Радиус колеса (мм.)")
},
modifier = Modifier.fillMaxWidth()
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Column {
Text(text = "Пройденное расстояние")
Text(text = "Движение вперед")
Text(text = "Движение назад")
Text(text = "Стоянка")
}
Column {
val duration = state.ranges.filter { it.distance == 0.0 }.sumOf { it.endDate - it.startDate }
val hours = duration / TimeUnit.HOURS.toMillis(1)
val minutes = (duration - (hours * TimeUnit.HOURS.toMillis(1))) / TimeUnit.MINUTES.toMillis(1)
val seconds = (duration - (hours * TimeUnit.HOURS.toMillis(1)) - (minutes * TimeUnit.MINUTES.toMillis(1))) / TimeUnit.SECONDS.toMillis(1)
Text(text = state.ranges.sumOf { it.distance.absoluteValue }.roundTo().toString() + " м.")
Text(text = state.ranges.filter { it.distance > 0 }.sumOf { it.distance.absoluteValue }.roundTo().toString() + " м.")
Text(text = state.ranges.filter { it.distance < 0 }.sumOf { it.distance.absoluteValue }.roundTo().toString() + " м.")
Text(text = "${hours}ч ${minutes}м. ${seconds}с.")
}
}
var reversed by remember {
mutableStateOf(true)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Порядок")
IconButton(
onClick = { reversed = reversed.not() }
) {
Icon(
imageVector = if (reversed) Icons.Rounded.ArrowBack else Icons.Rounded.ArrowForward,
contentDescription = null
)
}
}
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(items = state.ranges.let { if (reversed) it.reversed() else it}){
DataRange(range = it)
}
}
if(showStartDateSelector) {
Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest = {
showEndDateSelector = false
}
) {
Surface(
shape = RoundedCornerShape(16.dp)
) {
val datePickerState = rememberDatePickerState(
initialSelectedDateMillis = state.startDate
)
Column{
DatePicker(state = datePickerState)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
modifier = Modifier
.align(Alignment.End)
.padding(12.dp)
) {
TextButton(
onClick = {
showStartDateSelector = false
}
) {
Text(text = "Отмена")
}
Button(
onClick = {
viewModel.setEvent(
RotationStatisticContract.Event.OnStartDateChanged(
datePickerState.selectedDateMillis ?: state.startDate
)
)
showStartDateSelector = false
}
) {
Text(text = "ок")
}
}
}
}
}
}
if(showEndDateSelector) {
Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest = {
showEndDateSelector = false
}
) {
Surface(
shape = RoundedCornerShape(16.dp)
) {
val datePickerState = rememberDatePickerState(
initialSelectedDateMillis = state.endDate
)
Column {
DatePicker(state = datePickerState)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
modifier = Modifier
.align(Alignment.End)
.padding(12.dp)
) {
TextButton(
onClick = {
showEndDateSelector = false
}
) {
Text(text = "Отмена")
}
Button(
onClick = {
viewModel.setEvent(
RotationStatisticContract.Event.OnStopDateChanged(
datePickerState.selectedDateMillis ?: state.endDate
)
)
showEndDateSelector = false
}
) {
Text(text = "ок")
}
}
}
}
}
}
}
}
}
}
fun Double.roundTo(numFractionDigits: Int = 2): Double {
val factor = 10.0.pow(numFractionDigits.toDouble())
return (this * factor).roundToInt() / factor
}
private fun Double.round(decimals: Int = 2): Double = "%.${decimals}f".format(this).toDouble()
private val dateTimeFormat = SimpleDateFormat("dd.MM HH:mm:ss")
private val dateFormat = SimpleDateFormat("dd.MM HH:mm:ss")
@Composable
private fun DataRange(range: RotationStatisticContract.State.Display.RotationRange){
Card {
Column(
modifier = Modifier
.padding(12.dp)
.width(IntrinsicSize.Max)
) {
val duration = range.endDate - range.startDate
val hours = duration / TimeUnit.HOURS.toMillis(1)
val minutes = (duration - (hours * TimeUnit.HOURS.toMillis(1))) / TimeUnit.MINUTES.toMillis(1)
val seconds = (duration - (hours * TimeUnit.HOURS.toMillis(1)) - (minutes * TimeUnit.MINUTES.toMillis(1))) / TimeUnit.SECONDS.toMillis(1)
val directionText = if(range.distance > 0){
"-->"
}else{
if(range.distance < 0) {
"<--"
} else {
"<<P>>"
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Column {
Text(text = "Начало")
Text(text = "Конец")
}
Column {
Text(text = dateTimeFormat.format(Date(range.startDate)))
Text(text = dateTimeFormat.format(Date(range.endDate)))
}
}
Text(text = "Направление: " + directionText)
HorizontalDivider()
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Column {
Text(text = "Время")
if(range.distance != 0.0) {
Text(text = "Расстояние")
Text(text = "Vmax.")
Text(text = "Vср.")
Text(text = "Обороты")
}
}
Column {
Text(text = "${hours}ч ${minutes}м. ${seconds}с.")
if(range.distance != 0.0) {
Text(text = range.distance.absoluteValue.roundTo().toString() + " м.")
Text(text = range.maxSpeed.roundTo().toString() + " Км/ч")
Text(text = range.avgSpeed.roundTo().toString() + " Км/ч")
Text(text = range.rotations.absoluteValue.toDouble().roundTo().toString())
}
}
}
}
}
}

View File

@ -0,0 +1,256 @@
package llc.arma.ble.app.ui.screen.rotation.statistic
import android.icu.util.GregorianCalendar
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.patrykandpatrick.vico.core.extension.sumOf
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.domain.usecase.GetRotationsBySerial
import llc.arma.ble.domain.usecase.GetWheelRadiusBySerial
import llc.arma.ble.domain.usecase.SetWheelRadius
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.absoluteValue
fun <T> Collection<T>.splitBy(separator: (T) -> Any?): List<List<T>> {
val result = mutableListOf<MutableList<T>>()
var newSublist = true
for (item in this) {
newSublist = (separator(item) != result.lastOrNull()?.lastOrNull()?.let { separator(it) })
if (newSublist || result.lastOrNull() == null)
result += mutableListOf<T>()
result.last() += item
}
return result
}
@HiltViewModel
class RotationStatisticViewModel @Inject constructor(
private val setWheelRadius: SetWheelRadius,
private val getWheelRadius: GetWheelRadiusBySerial,
private val getRotationsBySerial: GetRotationsBySerial,
private val savedStateHandle: SavedStateHandle
) : BaseViewModel<RotationStatisticContract.State, RotationStatisticContract.Event, RotationStatisticContract.Effect>() {
init {
viewModelScope.launch {
load()
}
}
override fun setInitialState() = RotationStatisticContract.State.Loading
override fun handleEvents(event: RotationStatisticContract.Event) {
when(event){
is RotationStatisticContract.Event.OnRadiusChanged -> reduce(viewState.value, event)
is RotationStatisticContract.Event.OnStartDateChanged -> reduce(viewState.value, event)
is RotationStatisticContract.Event.OnStopDateChanged -> reduce(viewState.value, event)
is RotationStatisticContract.Event.OnNavigateToDelete -> reduce(viewState.value, event)
is RotationStatisticContract.Event.OnNavigateUp -> reduce(viewState.value, event)
}
}
private suspend fun load(){
savedStateHandle.get<String>("serial")?.let {
val state = viewState.value
val r = getWheelRadius(it)?.toFloat() ?: 500f
val rCm = r / 10f
val circumference = 2 * Math.PI * rCm
Log.d("circumference", circumference.toString())
var rotations = getRotationsBySerial(it)
if(state is RotationStatisticContract.State.Display){
rotations = rotations.filter {
it.date >= (state.startDate ?: 0) && it.date <= (state.endDate ?: Long.MAX_VALUE)
}
}
val split: List<RotationStatisticContract.State.Display.RotationRange> = if(rotations.size < 2) {
emptyList()
}else {
val pointDuration = rotations[1].date - rotations[0].date
rotations.splitBy {
Log.d("rDate", it.date.toString())
if (it.rotations > 0) {
true
} else {
if (it.rotations < 0) {
false
} else {
null
}
}
}.map { range ->
val start = range.minOf { it.date } - pointDuration
val end = range.maxOf { it.date }
val speeds = range.map {
val km = ((it.rotations.absoluteValue / 8f) * circumference) / 100_000f
km / (pointDuration.toFloat() / TimeUnit.HOURS.toMillis(1).toFloat())
}
RotationStatisticContract.State.Display.RotationRange(
distance = ((range.sumOf { it.rotations.toFloat() } / 8f) * circumference) / 100f,
startDate = start,
endDate = end,
maxSpeed = speeds.max(),
avgSpeed = speeds.average(),
rotations = range.sumOf { it.rotations.toFloat() } / 8f
)
}
}
setState {
when(this){
is RotationStatisticContract.State.Display -> {
copy(
rotations = rotations,
ranges = split
)
}
RotationStatisticContract.State.Loading -> {
RotationStatisticContract.State.Display(
rotations = rotations,
ranges = split,
radius = r.toLong(),
startDate = null,
endDate = null
)
}
}
}
} ?: throw IllegalArgumentException()
}
private fun reduce(
state: RotationStatisticContract.State,
event: RotationStatisticContract.Event.OnRadiusChanged
) {
if(state is RotationStatisticContract.State.Display){
setState {
state.copy(radius = event.radius)
}
viewModelScope.launch {
setWheelRadius(savedStateHandle.get<String>("serial")!!, event.radius ?: 100L)
load()
}
}
}
private fun reduce(
state: RotationStatisticContract.State,
event: RotationStatisticContract.Event.OnStartDateChanged
) {
if(state is RotationStatisticContract.State.Display){
val mCalendar = GregorianCalendar()
val mTimeZone = mCalendar.timeZone
val mGMTOffset: Int = mTimeZone.rawOffset
val start = event.start?.let {
TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(event.start)) - mGMTOffset
}
Log.d("date", start.toString() + " " + event.start)
setState {
state.copy(startDate = start)
}
viewModelScope.launch {
load()
}
}
}
private fun reduce(
state: RotationStatisticContract.State,
event: RotationStatisticContract.Event.OnStopDateChanged
) {
if(state is RotationStatisticContract.State.Display){
val mCalendar = GregorianCalendar()
val mTimeZone = mCalendar.timeZone
val mGMTOffset: Int = mTimeZone.rawOffset
val stop = event.stop?.let {
TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(event.stop) + 1) - mGMTOffset - 1_000
}
setState {
state.copy(endDate = stop)
}
viewModelScope.launch {
load()
}
}
}
private fun reduce(
state: RotationStatisticContract.State,
event: RotationStatisticContract.Event.OnNavigateUp
) {
setEffect {
RotationStatisticContract.Effect.Navigation.NavigateUp
}
}
private fun reduce(
state: RotationStatisticContract.State,
event: RotationStatisticContract.Event.OnNavigateToDelete
) {
setEffect {
RotationStatisticContract.Effect.Navigation.NavigateToDelete(savedStateHandle.get<String>("serial")!!)
}
}
}

View File

@ -0,0 +1,16 @@
package llc.arma.ble.data.db
import androidx.room.Database
import androidx.room.RoomDatabase
import llc.arma.ble.data.model.RotationEntity
import llc.arma.ble.data.model.WheelEntity
@Database(
entities = [RotationEntity::class, WheelEntity::class],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun getRotationsDao(): RotationsDao
}

View File

@ -0,0 +1,31 @@
package llc.arma.ble.data.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import llc.arma.ble.data.model.RotationEntity
import llc.arma.ble.data.model.WheelEntity
@Dao
interface RotationsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(rotations: WheelEntity)
@Query("select * from wheel where bleId = :serial")
suspend fun getWheelBySerial(serial: String): WheelEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(rotations: List<RotationEntity>)
@Query("delete from rotation where bleId = :serial")
suspend fun deleteBySerial(serial: String)
@Query("delete from rotation where bleId = :serial and date > :date")
suspend fun deleteBySerialAndDateMoreThan(serial: String, date: Long)
@Query("select * from rotation where bleId = :serial order by date asc")
suspend fun getBySerialSortedByDate(serial: String): List<RotationEntity>
}

View File

@ -0,0 +1,19 @@
package llc.arma.ble.data.model
import androidx.room.Entity
import androidx.room.Index
@Entity(
tableName = "rotation",
indices = [Index(unique = true, value = ["bleId", "date"])],
primaryKeys = ["bleId", "date"]
)
class RotationEntity(
val bleId: String,
val date: Long,
val rotations: Long
)

View File

@ -0,0 +1,18 @@
package llc.arma.ble.data.model
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "wheel",
indices = [Index(unique = true, value = ["bleId"])]
)
class WheelEntity(
@PrimaryKey
val bleId: String,
val radius: Long
)

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.os.SystemClock
@ -14,12 +14,13 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import llc.arma.ble.data.extensions.checkPermission
import llc.arma.ble.data.extensions.fromByte
import llc.arma.ble.data.extensions.get4byteUIntAt
import llc.arma.ble.data.extensions.info
import llc.arma.ble.data.extensions.sendData
import llc.arma.ble.data.extensions.toTemperature
import llc.arma.ble.data.db.RotationsDao
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.fromByte
import llc.arma.ble.data.repository.extensions.get4byteUIntAt
import llc.arma.ble.data.repository.extensions.info
import llc.arma.ble.data.repository.extensions.sendData
import llc.arma.ble.data.repository.extensions.toTemperature
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
@ -67,7 +68,8 @@ val flashWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002
@Singleton
class BleRepositoryImpl @Inject constructor(
private val app: Application
private val app: Application,
private val rotationsDao: RotationsDao
) : BleRepository {
val resultList: MutableMap<String, BleInfo> = Collections.synchronizedMap(mutableMapOf<String, BleInfo>())
@ -536,13 +538,17 @@ class BleRepositoryImpl @Inject constructor(
request.tx?.let {
connection.discoverServices().findService(serviceUUID)?.findCharacteristic(txWriteUUID)?.write(
connection.discoverServices().findService(serviceUUID)?.findCharacteristic(
txWriteUUID
)?.write(
DataByteArray.from(it.sendData)
) ?: return Result.failure(BleException.UnexpectedResponse)
}
connection.discoverServices().findService(serviceUUID)?.findCharacteristic(flashWriteUUID)!!.write(
connection.discoverServices().findService(serviceUUID)?.findCharacteristic(
flashWriteUUID
)!!.write(
DataByteArray.from(9)
)
@ -579,6 +585,8 @@ class BleRepositoryImpl @Inject constructor(
(this shr (it * Byte.SIZE_BITS)).toByte()
}.toByteArray()
rotationsDao.deleteBySerial(serial)
return if(app.checkPermission()) {
val connection =

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.content.Intent

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.util.Log
@ -6,11 +6,11 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.data.extensions.checkPermission
import llc.arma.ble.data.extensions.fromByte
import llc.arma.ble.data.extensions.get2byteShortAt
import llc.arma.ble.data.extensions.get2byteUIntAt
import llc.arma.ble.data.extensions.get4byteUIntAt
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.fromByte
import llc.arma.ble.data.repository.extensions.get2byteShortAt
import llc.arma.ble.data.repository.extensions.get2byteUIntAt
import llc.arma.ble.data.repository.extensions.get4byteUIntAt
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState

View File

@ -1,14 +1,13 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.data.extensions.checkPermission
import llc.arma.ble.data.extensions.get2byteShortAt
import llc.arma.ble.data.extensions.sendData
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.get2byteShortAt
import llc.arma.ble.data.repository.extensions.sendData
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.model.Ble

View File

@ -1,21 +1,16 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.os.Build
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.data.extensions.checkPermission
import llc.arma.ble.data.extensions.get2byteShortAt
import llc.arma.ble.data.extensions.get2byteUIntAt
import llc.arma.ble.data.extensions.get4byteUIntAt
import llc.arma.ble.data.extensions.sendData
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.get2byteShortAt
import llc.arma.ble.data.repository.extensions.get2byteUIntAt
import llc.arma.ble.data.repository.extensions.get4byteUIntAt
import llc.arma.ble.data.repository.extensions.sendData
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
@ -27,8 +22,6 @@ import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import no.nordicsemi.android.common.core.DataByteArray
import no.nordicsemi.android.kotlin.ble.client.main.callback.ClientBleGatt
import java.nio.ByteBuffer
import java.nio.ByteOrder.LITTLE_ENDIAN
import java.util.UUID

View File

@ -1,19 +1,14 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.os.Build
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.data.extensions.checkPermission
import llc.arma.ble.data.extensions.get2byteUIntAt
import llc.arma.ble.data.extensions.get4byteUIntAt
import llc.arma.ble.data.extensions.toTemperature
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.get2byteUIntAt
import llc.arma.ble.data.repository.extensions.get4byteUIntAt
import llc.arma.ble.data.repository.extensions.toTemperature
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState

View File

@ -0,0 +1,49 @@
package llc.arma.ble.data.repository
import llc.arma.ble.data.db.RotationsDao
import llc.arma.ble.data.model.RotationEntity
import llc.arma.ble.data.model.WheelEntity
import llc.arma.ble.domain.model.Rotation
import llc.arma.ble.domain.repository.RotationsRepository
import javax.inject.Inject
class RotationsRepositoryImpl @Inject constructor(
private val dao: RotationsDao
) : RotationsRepository {
override suspend fun saveRotations(rotations: List<Rotation>) {
dao.deleteBySerialAndDateMoreThan(rotations.first().bleId, rotations.minOf { it.date } - 8)
dao.save(
rotations.map {
RotationEntity(
bleId = it.bleId,
rotations = it.rotations,
date = it.date
)
}
)
}
override suspend fun getRotationsBySerial(serial: String): List<Rotation> {
return dao.getBySerialSortedByDate(serial).map {
Rotation(
bleId = it.bleId,
rotations = it.rotations,
date = it.date
)
}
}
override suspend fun deleteRotationsBySerial(serial: String) {
dao.deleteBySerial(serial)
}
override suspend fun getWheelRadiusBySerial(serial: String): Long? {
return dao.getWheelBySerial(serial)?.radius
}
override suspend fun setWheelRadius(serial: String, radius: Long) {
dao.save(WheelEntity(serial, radius))
}
}

View File

@ -1,9 +1,8 @@
package llc.arma.ble.data
package llc.arma.ble.data.repository
import android.app.Application
import android.icu.text.SimpleDateFormat
import android.os.Environment
import android.util.Log
import llc.arma.ble.R
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.repository.XlsxRepository

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data.extensions
package llc.arma.ble.data.repository.extensions
import android.Manifest
import android.app.Application

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data.extensions
package llc.arma.ble.data.repository.extensions
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data.extensions
package llc.arma.ble.data.repository.extensions
import llc.arma.ble.domain.model.BleInfo
import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResult

View File

@ -1,4 +1,4 @@
package llc.arma.ble.data.extensions
package llc.arma.ble.data.repository.extensions
import java.nio.ByteBuffer
import java.nio.ByteOrder

View File

@ -0,0 +1,14 @@
package llc.arma.ble.domain.model
import androidx.room.Entity
import androidx.room.Index
data class Rotation(
val bleId: String,
val date: Long,
val rotations: Long
)

View File

@ -0,0 +1,17 @@
package llc.arma.ble.domain.repository
import llc.arma.ble.domain.model.Rotation
interface RotationsRepository {
suspend fun saveRotations(rotations: List<Rotation>)
suspend fun getRotationsBySerial(serial: String): List<Rotation>
suspend fun deleteRotationsBySerial(serial: String)
suspend fun getWheelRadiusBySerial(serial: String): Long?
suspend fun setWheelRadius(serial: String, radius: Long)
}

View File

@ -0,0 +1,17 @@
package llc.arma.ble.domain.usecase
import llc.arma.ble.domain.model.Rotation
import llc.arma.ble.domain.repository.RotationsRepository
import javax.inject.Inject
class DeleteRotationsBySerial @Inject constructor (
private val rotationsRepository: RotationsRepository
) {
suspend operator fun invoke(serial: String) {
rotationsRepository.deleteRotationsBySerial(serial)
}
}

View File

@ -1,20 +1,49 @@
package llc.arma.ble.domain.usecase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.model.Rotation
import llc.arma.ble.domain.repository.BleRepository
import llc.arma.ble.domain.repository.RotationsRepository
import java.time.LocalDateTime
import java.util.Date
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class GetAccelerometerHistoryBySerial @Inject constructor(
private val bleRepository: BleRepository
private val bleRepository: BleRepository,
private val rotationsRepository: RotationsRepository
) {
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {
return bleRepository.getAccelerometerHistoryBySerial(serial)
return bleRepository.getAccelerometerHistoryBySerial(serial).onEach {
it.onSuccess { progress ->
if(progress is ProgressState.Finished){
val rotations = progress.data.mapNotNull { point ->
if(point is Ble.Accelerometer.HistoryPoint.Rotation){
Rotation(
bleId = serial,
date = TimeUnit.SECONDS.toMillis(TimeUnit.MILLISECONDS.toSeconds(point.date)),
rotations = point.value
)
}else{
null
}
}
if(rotations.isNotEmpty()){
rotationsRepository.saveRotations(rotations)
}
}
}
}
}

View File

@ -0,0 +1,17 @@
package llc.arma.ble.domain.usecase
import llc.arma.ble.domain.model.Rotation
import llc.arma.ble.domain.repository.RotationsRepository
import javax.inject.Inject
class GetRotationsBySerial @Inject constructor (
private val rotationsRepository: RotationsRepository
) {
suspend operator fun invoke(serial: String): List<Rotation> {
return rotationsRepository.getRotationsBySerial(serial)
}
}

View File

@ -0,0 +1,14 @@
package llc.arma.ble.domain.usecase
import llc.arma.ble.domain.repository.RotationsRepository
import javax.inject.Inject
class GetWheelRadiusBySerial @Inject constructor(
private val rotationsRepository: RotationsRepository
) {
suspend operator fun invoke(serial: String): Long? {
return rotationsRepository.getWheelRadiusBySerial(serial)
}
}

View File

@ -0,0 +1,14 @@
package llc.arma.ble.domain.usecase
import llc.arma.ble.domain.repository.RotationsRepository
import javax.inject.Inject
class SetWheelRadius @Inject constructor(
private val rotationsRepository: RotationsRepository
) {
suspend operator fun invoke(serial: String, radius: Long){
rotationsRepository.setWheelRadius(serial, radius)
}
}