improve beacon ui
This commit is contained in:
parent
2a43916ecc
commit
c19417e00f
|
|
@ -21,6 +21,7 @@ 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.beacon.view.DisplayState
|
import llc.arma.ble.app.ui.screen.beacon.view.DisplayState
|
||||||
import llc.arma.ble.app.ui.screen.beacon.view.PowerEdit
|
import llc.arma.ble.app.ui.screen.beacon.view.PowerEdit
|
||||||
|
import llc.arma.ble.app.ui.screen.beacon.view.Write
|
||||||
import llc.arma.ble.app.ui.screen.thermometer.localizedName
|
import llc.arma.ble.app.ui.screen.thermometer.localizedName
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
|
|
@ -75,257 +76,17 @@ fun BeaconScreen(
|
||||||
when(sheetPage){
|
when(sheetPage){
|
||||||
SheetPage.WRITE -> bottomDialog.show {
|
SheetPage.WRITE -> bottomDialog.show {
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val currentState = viewModel.viewState.value
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
if(currentState is BeaconContract.State.Display) {
|
if(currentState is BeaconContract.State.Display && currentState.writeState != null) {
|
||||||
|
|
||||||
Column() {
|
Write(
|
||||||
|
state = currentState.writeState,
|
||||||
when (currentState.writeState) {
|
onEvent = {
|
||||||
is BeaconContract.State.Display.WriteState.DisplayPreview -> {
|
viewModel.setEvent(it)
|
||||||
|
}
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
|
||||||
text = "Записать изменения?",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
)
|
||||||
|
|
||||||
currentState.writeState.writeRequest.tx?.let {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
vertical = 8.dp,
|
|
||||||
horizontal = 8.dp
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "Мощность"
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
text = "${currentState.origin.state.tx.localizedName} db -> ${it.localizedName} db"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
shape = CircleShape,
|
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
onClick = {
|
|
||||||
viewModel.setEvent(BeaconContract.Event.OnWriteBle)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
color = MaterialTheme.colorScheme.background,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
text = "Записать"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
shape = CircleShape,
|
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
text = "Отменить"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
is BeaconContract.State.Display.WriteState.Writing -> {
|
|
||||||
|
|
||||||
Box {
|
|
||||||
|
|
||||||
Column() {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
|
||||||
text = "Запись",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(bottom = 48.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
shape = CircleShape,
|
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
text = "Отменить"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
BeaconContract.State.Display.WriteState.Success -> {
|
|
||||||
|
|
||||||
Box {
|
|
||||||
|
|
||||||
Column {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
|
||||||
text = "Запись завершена",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
shape = CircleShape,
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
text = "Ок"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
BeaconContract.State.Display.WriteState.Failure -> {
|
|
||||||
|
|
||||||
Box {
|
|
||||||
|
|
||||||
Column {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
|
||||||
text = "Ошибка записи",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
shape = CircleShape,
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
text = "Ок"
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(48.dp))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +115,8 @@ fun BeaconScreen(
|
||||||
onEvent = {
|
onEvent = {
|
||||||
viewModel.setEvent(it)
|
viewModel.setEvent(it)
|
||||||
},
|
},
|
||||||
ble = state.beacon
|
ble = state.beacon,
|
||||||
|
origin = state.origin
|
||||||
)
|
)
|
||||||
is BeaconContract.State.Loading -> LoadingState()
|
is BeaconContract.State.Loading -> LoadingState()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,19 @@ class BeaconViewModel @Inject constructor(
|
||||||
state: BeaconContract.State,
|
state: BeaconContract.State,
|
||||||
event: BeaconContract.Event.OnBleChanged
|
event: BeaconContract.Event.OnBleChanged
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
when(state){
|
||||||
|
is BeaconContract.State.Display -> {
|
||||||
|
setState {
|
||||||
|
state.copy(
|
||||||
|
origin = Ble.Beacon(
|
||||||
|
info = event.ble.info,
|
||||||
|
state = state.origin.state
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is BeaconContract.State.Loading -> {
|
||||||
setState {
|
setState {
|
||||||
BeaconContract.State.Display(
|
BeaconContract.State.Display(
|
||||||
origin = event.ble,
|
origin = event.ble,
|
||||||
|
|
@ -92,6 +105,9 @@ class BeaconViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun reduce(
|
private fun reduce(
|
||||||
state: BeaconContract.State,
|
state: BeaconContract.State,
|
||||||
|
|
@ -147,22 +163,35 @@ class BeaconViewModel @Inject constructor(
|
||||||
|
|
||||||
if(state is BeaconContract.State.Display){
|
if(state is BeaconContract.State.Display){
|
||||||
|
|
||||||
state.writeState?.let {
|
state.writeState?.let { request ->
|
||||||
|
|
||||||
if(it is BeaconContract.State.Display.WriteState.DisplayPreview) {
|
if(request is BeaconContract.State.Display.WriteState.DisplayPreview) {
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
state.copy(
|
state.copy(
|
||||||
writeState = BeaconContract.State.Display.WriteState.Writing(it.writeRequest)
|
writeState = BeaconContract.State.Display.WriteState.Writing(request.writeRequest)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeBle(state.beacon.info.serial, it.writeRequest).fold(
|
val currentState = viewState.value
|
||||||
|
|
||||||
|
if(currentState is BeaconContract.State.Display) {
|
||||||
|
|
||||||
|
val newBleObject = Ble.Beacon(
|
||||||
|
info = currentState.origin.info,
|
||||||
|
state = currentState.origin.state.copy(
|
||||||
|
tx = request.writeRequest.tx ?: state.origin.state.tx
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
writeBle(state.beacon.info.serial, request.writeRequest).fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
setState {
|
setState {
|
||||||
state.copy(
|
currentState.copy(
|
||||||
|
origin = newBleObject,
|
||||||
|
beacon = bleMapper.map(newBleObject) as BleView.Beacon,
|
||||||
writeState = BeaconContract.State.Display.WriteState.Success
|
writeState = BeaconContract.State.Display.WriteState.Success
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -186,4 +215,6 @@ class BeaconViewModel @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -18,10 +18,12 @@ import androidx.compose.ui.unit.dp
|
||||||
import llc.arma.ble.app.ui.model.BleView
|
import llc.arma.ble.app.ui.model.BleView
|
||||||
import llc.arma.ble.app.ui.screen.BleInfoView
|
import llc.arma.ble.app.ui.screen.BleInfoView
|
||||||
import llc.arma.ble.app.ui.screen.beacon.BeaconContract
|
import llc.arma.ble.app.ui.screen.beacon.BeaconContract
|
||||||
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayState(
|
fun DisplayState(
|
||||||
onEvent: (BeaconContract.Event) -> Unit,
|
onEvent: (BeaconContract.Event) -> Unit,
|
||||||
|
origin: Ble.Beacon,
|
||||||
ble: BleView.Beacon
|
ble: BleView.Beacon
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -39,7 +41,7 @@ fun DisplayState(
|
||||||
horizontal = 8.dp
|
horizontal = 8.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
BleInfoView(bleInfo = ble.info)
|
BleInfoView(bleInfo = origin.info)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,346 @@
|
||||||
|
package llc.arma.ble.app.ui.screen.beacon.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.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 llc.arma.ble.R
|
||||||
|
import llc.arma.ble.app.ui.screen.beacon.BeaconContract
|
||||||
|
import llc.arma.ble.app.ui.screen.thermometer.localizedName
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Write(
|
||||||
|
state: BeaconContract.State.Display.WriteState,
|
||||||
|
onEvent: (BeaconContract.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 BeaconContract.State.Display.WriteState.DisplayPreview -> {
|
||||||
|
|
||||||
|
if(state.writeRequest.tx != 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onClick = {
|
||||||
|
onEvent(BeaconContract.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(BeaconContract.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(BeaconContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
is BeaconContract.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(BeaconContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Отменить"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
BeaconContract.State.Display.WriteState.Success -> {
|
||||||
|
|
||||||
|
Box {
|
||||||
|
|
||||||
|
Column {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(125.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
painter = painterResource(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(BeaconContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
BeaconContract.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(BeaconContract.Event.OnHideWriteBlePreview)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
text = "Ок"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -231,7 +231,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(500)
|
delay(1_000)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(500)
|
delay(1_000)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +310,7 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return llc.arma.ble.domain.Result.failure(BleException.UnexpectedResponse)
|
return Result.failure(BleException.UnexpectedResponse)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,37 +465,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*request.tx?.let {
|
|
||||||
Log.d("write", "tx")
|
|
||||||
writeTx(result.device, it)
|
|
||||||
}?.onFailure {
|
|
||||||
Log.d("write", "tx fail")
|
|
||||||
return Result.failure(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
request.historyInterval?.let {
|
|
||||||
Log.d("write", "in")
|
|
||||||
writeSaveInterval(result.device, it)
|
|
||||||
}?.onFailure {
|
|
||||||
Log.d("write", "in fail")
|
|
||||||
return Result.failure(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
request.saveHistory?.let {
|
|
||||||
Log.d("write", "hs")
|
|
||||||
writeSaveEnabled(result.device, it)
|
|
||||||
}?.onFailure {
|
|
||||||
Log.d("write", "hs fail")
|
|
||||||
return Result.failure(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("write", "fs")
|
|
||||||
|
|
||||||
writeToFlash(serial).onFailure {
|
|
||||||
Log.d("write", "fs fail")
|
|
||||||
return Result.failure(it)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -503,44 +472,37 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
override suspend fun writeBle(
|
override suspend fun writeBle(
|
||||||
serial: String,
|
serial: String,
|
||||||
request: Ble.Beacon.WriteRequest
|
request: Ble.Beacon.WriteRequest
|
||||||
): Result<Unit, BleException> {
|
): Result<Unit, BleException> = suspendCancellableCoroutine {
|
||||||
|
|
||||||
deviceCache[serial]?.let { result ->
|
deviceCache[serial]?.let { scanResult ->
|
||||||
|
|
||||||
request.tx?.let { writeTx(result.device, it) }?.onFailure {
|
if(checkPermission()) {
|
||||||
return Result.failure(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeToFlash(serial).onFailure {
|
var gatt: BluetoothGatt? = null
|
||||||
return Result.failure(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val callback = WriteBeaconCallback(app, request) { result ->
|
||||||
|
|
||||||
|
gatt?.close()
|
||||||
|
|
||||||
|
result.onSuccess {
|
||||||
deviceCache.remove(serial)
|
deviceCache.remove(serial)
|
||||||
resultList.remove(serial)
|
resultList.remove(serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.resume(result)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.success(Unit)
|
gatt = scanResult.device.connectGatt(app, false, callback)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
it.resume(Result.failure(BleException.PermissionDenied))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun writeToFlash(
|
|
||||||
serial: String
|
|
||||||
): Result<Unit, BleException>{
|
|
||||||
|
|
||||||
deviceCache[serial]?.device?.let { result ->
|
|
||||||
|
|
||||||
return writeCharacteristic(
|
|
||||||
device = result,
|
|
||||||
serviceId = serviceUUID,
|
|
||||||
characteristicId = flashWriteUUID,
|
|
||||||
writeData = byteArrayOf(9, 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.success(Unit)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun changeBlePassword(
|
override suspend fun changeBlePassword(
|
||||||
|
|
@ -567,69 +529,6 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun writeTx(
|
|
||||||
device: BluetoothDevice,
|
|
||||||
tx: Ble.BleState.TX
|
|
||||||
): Result<Unit, BleException> {
|
|
||||||
|
|
||||||
return writeCharacteristic(
|
|
||||||
device = device,
|
|
||||||
serviceId = serviceUUID,
|
|
||||||
characteristicId = txWriteUUID,
|
|
||||||
writeData = byteArrayOf(
|
|
||||||
when(tx) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun writeSaveInterval(
|
|
||||||
device: BluetoothDevice,
|
|
||||||
interval: Long
|
|
||||||
): Result<Unit, BleException> {
|
|
||||||
|
|
||||||
fun UInt.to4ByteArrayInBigEndian(): ByteArray =
|
|
||||||
(3 downTo 0).map {
|
|
||||||
(this shr (it * Byte.SIZE_BITS)).toByte()
|
|
||||||
}.reversed().toByteArray()
|
|
||||||
|
|
||||||
return writeCharacteristic(
|
|
||||||
device = device,
|
|
||||||
serviceId = serviceUUID,
|
|
||||||
characteristicId = intervalWriteUUID,
|
|
||||||
writeData = mutableListOf<Byte>(3).apply {
|
|
||||||
addAll((interval / 1_000).toUInt().to4ByteArrayInBigEndian().toList())
|
|
||||||
}.toByteArray()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun writeSaveEnabled(
|
|
||||||
device: BluetoothDevice,
|
|
||||||
enabled: Boolean
|
|
||||||
): Result<Unit, BleException> {
|
|
||||||
|
|
||||||
return writeCharacteristic(
|
|
||||||
device = device,
|
|
||||||
serviceId = serviceUUID,
|
|
||||||
characteristicId = saveEnabledWriteUUID,
|
|
||||||
writeData = mutableListOf<Byte>(4).apply {
|
|
||||||
add(if(enabled) 1 else 0)
|
|
||||||
}.toByteArray()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun readCharacteristic(
|
private suspend fun readCharacteristic(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
serviceId: UUID,
|
serviceId: UUID,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
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 WriteBeaconCallback(
|
||||||
|
private val app: Application,
|
||||||
|
private var request: Ble.Beacon.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)
|
||||||
|
|
||||||
|
Log.d("beacon", "onConnectionStateChange $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
|
||||||
|
) {
|
||||||
|
Log.d("beacon", "onServicesDiscovered $status")
|
||||||
|
super.onServicesDiscovered(gatt, status)
|
||||||
|
onCycle(gatt, status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCycle(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
status: Int
|
||||||
|
){
|
||||||
|
|
||||||
|
if(request.tx != null) {
|
||||||
|
|
||||||
|
var uuid: Pair<UUID, ByteArray>? = null
|
||||||
|
|
||||||
|
uuid = request.tx?.let {
|
||||||
|
|
||||||
|
this.request = request.copy(
|
||||||
|
tx = null
|
||||||
|
)
|
||||||
|
|
||||||
|
Pair(
|
||||||
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
} ?: uuid
|
||||||
|
|
||||||
|
uuid?.let { uuid ->
|
||||||
|
|
||||||
|
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||||
|
it.uuid == uuid.first
|
||||||
|
}?.let {
|
||||||
|
|
||||||
|
gatt.writeCharacteristic(it, uuid.second)
|
||||||
|
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
|
||||||
|
Log.d("beacon", "onCharacteristicWrite $status")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ sealed class Ble(
|
||||||
val state: BleState
|
val state: BleState
|
||||||
) : Ble(info){
|
) : Ble(info){
|
||||||
|
|
||||||
class WriteRequest(
|
data class WriteRequest(
|
||||||
val tx: BleState.TX?
|
val tx: BleState.TX?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue