add ble sort
This commit is contained in:
parent
7652ccc135
commit
b915659217
|
|
@ -7,14 +7,14 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace 'llc.arma.ble'
|
||||
compileSdk 33
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "llc.arma.ble"
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
versionCode 15
|
||||
versionName "1.2.15"
|
||||
targetSdk 34
|
||||
versionCode 17
|
||||
versionName "1.2.17"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
|
@ -23,6 +23,10 @@ android {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
|
@ -59,6 +63,7 @@ dependencies {
|
|||
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0-alpha01'
|
||||
implementation 'androidx.activity:activity-compose:1.7.2'
|
||||
implementation "androidx.compose.ui:ui:1.5.0-beta01"
|
||||
implementation "androidx.compose.ui:ui-tooling-preview:1.5.0-beta01"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
package llc.arma.ble.app.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.BroadcastReceiver
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
|
|
@ -24,10 +25,16 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
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.content.ContextCompat
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityCompat.startActivityForResult
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -37,13 +44,17 @@ import llc.arma.ble.app.ui.common.LocalBottomDialogState
|
|||
import llc.arma.ble.app.ui.screen.main.MainScreen
|
||||
import llc.arma.ble.app.ui.theme.BleTheme
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterialApi::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val mBluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
installSplashScreen()
|
||||
|
|
@ -168,10 +179,33 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
var bleEnabled by remember {
|
||||
mutableStateOf(mBluetoothAdapter.isEnabled)
|
||||
}
|
||||
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsState()
|
||||
|
||||
LaunchedEffect(lifecycleState){
|
||||
bleEnabled = mBluetoothAdapter.isEnabled
|
||||
}
|
||||
|
||||
if (multiplePermissionsState.allPermissionsGranted) {
|
||||
|
||||
if(bleEnabled) {
|
||||
|
||||
MainScreen()
|
||||
|
||||
} else {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(mBluetoothAdapter.isEnabled){
|
||||
val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
||||
(context as Activity).startActivityForResult(intent, 1)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
LaunchedEffect(multiplePermissionsState) {
|
||||
|
|
@ -183,7 +217,9 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -193,3 +229,21 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) {
|
||||
val eventHandler = rememberUpdatedState(onEvent)
|
||||
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
|
||||
|
||||
DisposableEffect(lifecycleOwner.value) {
|
||||
val lifecycle = lifecycleOwner.value.lifecycle
|
||||
val observer = LifecycleEventObserver { owner, event ->
|
||||
eventHandler.value(owner, event)
|
||||
}
|
||||
|
||||
lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,14 @@ class BleListContract {
|
|||
val type: BleInfo.Type?
|
||||
) : Event()
|
||||
|
||||
data class OnSortFieldChanged(
|
||||
val field: State.Filter.Field
|
||||
) : Event()
|
||||
|
||||
data class OnSortOrderChanged(
|
||||
val order: State.Filter.Order
|
||||
) : Event()
|
||||
|
||||
}
|
||||
|
||||
data class State(
|
||||
|
|
@ -50,12 +58,24 @@ class BleListContract {
|
|||
) : ViewState {
|
||||
|
||||
data class Filter(
|
||||
val sortField: Field = Field.Name,
|
||||
val sortOrder: Order = Order.Asc,
|
||||
val name: String = "",
|
||||
val mac: String = "",
|
||||
val battery: ClosedFloatingPointRange<Float> = (0f)..(100f),
|
||||
val rssi: ClosedFloatingPointRange<Float> = (-100f)..(-10f),
|
||||
val bleType: BleInfo.Type? = null
|
||||
)
|
||||
){
|
||||
|
||||
enum class Field {
|
||||
Name, Mac, Distance, Dbm, Battery
|
||||
}
|
||||
|
||||
enum class Order {
|
||||
Asc, Desc
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,21 @@ fun BleListScreen(
|
|||
it.serial.contains(state.filter.mac) &&
|
||||
state.filter.rssi.contains(it.rssi?.toFloat() ?: Float.MIN_VALUE) &&
|
||||
state.filter.battery.contains(it.batteryLevel.toFloat())
|
||||
}.let {
|
||||
when(state.filter.sortField){
|
||||
BleListContract.State.Filter.Field.Name -> it.sortedBy { it.name }
|
||||
BleListContract.State.Filter.Field.Mac -> it.sortedBy { it.serial }
|
||||
BleListContract.State.Filter.Field.Distance -> it.sortedBy { 10.0.pow((it.tx.toDouble() - (it.rssi?.toDouble() ?: 0.0) - 74) / 20).toFloat() }
|
||||
BleListContract.State.Filter.Field.Dbm -> it.sortedBy { it.rssi ?: 0 }
|
||||
BleListContract.State.Filter.Field.Battery -> it.sortedBy { it.batteryLevel }
|
||||
}
|
||||
}.let {
|
||||
|
||||
when(state.filter.sortOrder){
|
||||
BleListContract.State.Filter.Order.Asc -> it
|
||||
BleListContract.State.Filter.Order.Desc -> it.reversed()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(filteredData.isEmpty()){
|
||||
|
|
@ -165,7 +180,7 @@ fun BleListScreen(
|
|||
|
||||
|
||||
|
||||
items(items = filteredData.sortedBy { it.name }.reversed()) {
|
||||
items(items = filteredData) {
|
||||
|
||||
BleItem(
|
||||
ble = it,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class BleListViewModel @Inject constructor(
|
|||
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
delay(6_000)
|
||||
delay(30_000)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +68,8 @@ class BleListViewModel @Inject constructor(
|
|||
is BleListContract.Event.OnShowFilter -> reduce(viewState.value, event)
|
||||
is BleListContract.Event.OnTypeChanged -> reduce(viewState.value, event)
|
||||
is BleListContract.Event.OnBatteryRangeChanged -> reduce(viewState.value, event)
|
||||
is BleListContract.Event.OnSortFieldChanged -> reduce(viewState.value, event)
|
||||
is BleListContract.Event.OnSortOrderChanged -> reduce(viewState.value, event)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +102,28 @@ class BleListViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: BleListContract.State,
|
||||
event: BleListContract.Event.OnSortOrderChanged
|
||||
) {
|
||||
setState {
|
||||
copy(
|
||||
filter = this.filter.copy(sortOrder = event.order)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: BleListContract.State,
|
||||
event: BleListContract.Event.OnSortFieldChanged
|
||||
) {
|
||||
setState {
|
||||
copy(
|
||||
filter = this.filter.copy(sortField = event.field)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: BleListContract.State,
|
||||
event: BleListContract.Event.OnNameFilterChanged
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ 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.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.BatteryFull
|
||||
import androidx.compose.material.icons.rounded.Bluetooth
|
||||
|
|
@ -17,6 +19,8 @@ import androidx.compose.material.icons.rounded.Close
|
|||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.icons.rounded.ShortText
|
||||
import androidx.compose.material.icons.rounded.SignalCellularAlt
|
||||
import androidx.compose.material.icons.rounded.Sort
|
||||
import androidx.compose.material.icons.rounded.SortByAlpha
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
|
|
@ -39,6 +43,25 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.dp
|
||||
import llc.arma.ble.domain.model.BleInfo
|
||||
|
||||
private val BleListContract.State.Filter.Order.localized: String
|
||||
get() {
|
||||
return when(this){
|
||||
BleListContract.State.Filter.Order.Asc -> "Прямой ↓"
|
||||
BleListContract.State.Filter.Order.Desc -> "Обратный ↑"
|
||||
}
|
||||
}
|
||||
|
||||
private val BleListContract.State.Filter.Field.localized: String
|
||||
get() {
|
||||
return when(this){
|
||||
BleListContract.State.Filter.Field.Name -> "Имя"
|
||||
BleListContract.State.Filter.Field.Mac -> "MAC"
|
||||
BleListContract.State.Filter.Field.Distance -> "Расстояние"
|
||||
BleListContract.State.Filter.Field.Dbm -> "dBm"
|
||||
BleListContract.State.Filter.Field.Battery -> "Заряд"
|
||||
}
|
||||
}
|
||||
|
||||
private val BleInfo.Type?.localized: String
|
||||
get() {
|
||||
return when(this){
|
||||
|
|
@ -69,11 +92,151 @@ fun Filter(
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 12.dp)
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Sort,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(12.dp)
|
||||
)
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 8.dp),
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
expanded = it
|
||||
}
|
||||
) {
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth(),
|
||||
readOnly = true,
|
||||
value = filter.sortField.localized,
|
||||
onValueChange = { },
|
||||
label = { Text("Сортировка") },
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxWidth(),
|
||||
expanded = expanded,
|
||||
onDismissRequest = {
|
||||
expanded = false
|
||||
}
|
||||
) {
|
||||
|
||||
BleListContract.State.Filter.Field.values().forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onEvent(
|
||||
BleListContract.Event.OnSortFieldChanged(
|
||||
selectionOption
|
||||
)
|
||||
)
|
||||
expanded = false
|
||||
},
|
||||
text = {
|
||||
Text(text = selectionOption.localized)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.SortByAlpha,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(12.dp)
|
||||
)
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 8.dp),
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
expanded = it
|
||||
}
|
||||
) {
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth(),
|
||||
readOnly = true,
|
||||
value = filter.sortOrder.localized,
|
||||
onValueChange = { },
|
||||
label = { Text("Напрвление сортировки") },
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxWidth(),
|
||||
expanded = expanded,
|
||||
onDismissRequest = {
|
||||
expanded = false
|
||||
}
|
||||
) {
|
||||
|
||||
BleListContract.State.Filter.Order.values().forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onEvent(
|
||||
BleListContract.Event.OnSortOrderChanged(
|
||||
selectionOption
|
||||
)
|
||||
)
|
||||
expanded = false
|
||||
},
|
||||
text = {
|
||||
Text(text = selectionOption.localized)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -87,7 +250,9 @@ fun Filter(
|
|||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
modifier = Modifier.fillMaxWidth().padding(end = 8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 8.dp),
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
expanded = it
|
||||
|
|
@ -95,7 +260,9 @@ fun Filter(
|
|||
) {
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.menuAnchor().fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth(),
|
||||
readOnly = true,
|
||||
value = filter.bleType.localized,
|
||||
onValueChange = { },
|
||||
|
|
@ -108,7 +275,9 @@ fun Filter(
|
|||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxWidth(),
|
||||
expanded = expanded,
|
||||
onDismissRequest = {
|
||||
expanded = false
|
||||
|
|
@ -137,6 +306,8 @@ fun Filter(
|
|||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
){
|
||||
|
|
|
|||
Loading…
Reference in New Issue