add ble sort

This commit is contained in:
Vineyro 2024-06-11 10:19:15 +07:00
parent 7652ccc135
commit b915659217
6 changed files with 306 additions and 17 deletions

View File

@ -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"

View File

@ -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,8 +217,8 @@ class MainActivity : ComponentActivity() {
}
}
)
}
}
@ -193,3 +227,23 @@ 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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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,

View File

@ -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

View File

@ -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
){