Compare commits
2 commits
2a6bfb205c
...
8c1c35afdd
Author | SHA1 | Date | |
---|---|---|---|
8c1c35afdd | |||
1811a22a5a |
14 changed files with 663 additions and 415 deletions
|
@ -1,4 +1,5 @@
|
||||||
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class,
|
@file:OptIn(
|
||||||
|
ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class,
|
||||||
ExperimentalMaterial3Api::class
|
ExperimentalMaterial3Api::class
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -7,12 +8,20 @@ package ru.nm17.narodmon
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
import androidx.compose.material.icons.rounded.Menu
|
||||||
|
import androidx.compose.material3.BottomAppBar
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -24,8 +33,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
@ -36,8 +46,8 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.nm17.narodmon.db.AppDatabase
|
import ru.nm17.narodmon.db.AppDatabase
|
||||||
import ru.nm17.narodmon.db.entities.KVSetting
|
import ru.nm17.narodmon.db.entities.KVSetting
|
||||||
import ru.nm17.narodmon.ui.elements.AgreementDialog
|
import ru.nm17.narodmon.ui.dialogs.AgreementDialog
|
||||||
import ru.nm17.narodmon.ui.pages.SensorsPage
|
import ru.nm17.narodmon.ui.sensorsScreen.SensorsScreen
|
||||||
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,18 +56,39 @@ import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
fun AppNavHost() {
|
fun AppNavHost() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val coScope = rememberCoroutineScope()
|
val coScope = rememberCoroutineScope()
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = "sensors") {
|
NavHost(navController = navController, startDestination = "sensors") {
|
||||||
composable("agreement") {
|
composable("agreement") {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composable("sensors") {
|
composable("sensors") {
|
||||||
SensorsPage(navController)
|
Scaffold(bottomBar = {
|
||||||
|
BottomAppBar(actions = {
|
||||||
|
Image(
|
||||||
|
Icons.Rounded.Menu,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}, floatingActionButton = {
|
||||||
|
FloatingActionButton(onClick = { /*TODO*/ }) {
|
||||||
|
Image(
|
||||||
|
Icons.Rounded.Add,
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentPadding = PaddingValues(start = 16.dp)
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Column(modifier = Modifier.padding(it)) {
|
||||||
|
SensorsScreen(navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*...*/
|
/*...*/
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@ -69,20 +100,20 @@ class MainActivity : ComponentActivity() {
|
||||||
AppDatabase::class.java, "data"
|
AppDatabase::class.java, "data"
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
// val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||||
|
//
|
||||||
val sharedPreferences = EncryptedSharedPreferences.create(
|
// val sharedPreferences = EncryptedSharedPreferences.create(
|
||||||
"secret_shared_prefs",
|
// "secret_shared_prefs",
|
||||||
masterKeyAlias,
|
// masterKeyAlias,
|
||||||
createDeviceProtectedStorageContext(),
|
// createDeviceProtectedStorageContext(),
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
// EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
// EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||||
)
|
// )
|
||||||
|
|
||||||
// use the shared preferences and editor as you normally would
|
// use the shared preferences and editor as you normally would
|
||||||
|
|
||||||
// use the shared preferences and editor as you normally would
|
// use the shared preferences and editor as you normally would
|
||||||
val credSharedPreferences = sharedPreferences
|
// val credSharedPreferences = sharedPreferences
|
||||||
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
@ -120,9 +151,9 @@ class MainActivity : ComponentActivity() {
|
||||||
Text(text = stringResource(R.string.waiting_for_user_agreement))
|
Text(text = stringResource(R.string.waiting_for_user_agreement))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
AppNavHost()
|
AppNavHost()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A surface container using the 'background' color from the theme
|
// A surface container using the 'background' color from the theme
|
||||||
|
|
9
app/src/main/java/ru/nm17/narodmon/ui/Utils.kt
Normal file
9
app/src/main/java/ru/nm17/narodmon/ui/Utils.kt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package ru.nm17.narodmon.ui
|
||||||
|
|
||||||
|
fun String.toChipTitle(): String {
|
||||||
|
return if (length >= 20) {
|
||||||
|
this.slice(0..16) + ".."
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,97 +0,0 @@
|
||||||
package ru.nm17.narodmon.ui.bottomSheets
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.SheetState
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import ru.nm17.narodmon.R
|
|
||||||
import ru.nm17.narodmon.ui.entities.SensorSortingUiEntity
|
|
||||||
import ru.nm17.narodmon.ui.entities.SortingTypes
|
|
||||||
import ru.nm17.narodmon.ui.pages.FilterRadioButton
|
|
||||||
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun SortSensorsBottomSheet(
|
|
||||||
onApply: (sortingType: SortingTypes) -> Unit,
|
|
||||||
onDismissRequest: () -> Unit
|
|
||||||
) {
|
|
||||||
var sortingType by remember { mutableStateOf(SortingTypes.DISTANCE) }
|
|
||||||
|
|
||||||
val sortingTypes = remember {
|
|
||||||
listOf(
|
|
||||||
SensorSortingUiEntity(R.string.sort_by_name, SortingTypes.NAME),
|
|
||||||
SensorSortingUiEntity(R.string.sort_by_name_desc, SortingTypes.NAME_DESC),
|
|
||||||
SensorSortingUiEntity(R.string.sort_by_distance, SortingTypes.DISTANCE),
|
|
||||||
SensorSortingUiEntity(R.string.sort_by_distance_desc, SortingTypes.DISTANCE_DESC),
|
|
||||||
SensorSortingUiEntity(R.string.sort_by_type, SortingTypes.TYPE),
|
|
||||||
SensorSortingUiEntity(R.string.sort_by_type_desc, SortingTypes.TYPE_DESC),
|
|
||||||
SensorSortingUiEntity(R.string.sort_update_time, SortingTypes.UPD_TIME),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { onDismissRequest.invoke() },
|
|
||||||
sheetState = SheetState(true)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sensors_sort_title),
|
|
||||||
fontSize = 24.sp,
|
|
||||||
fontWeight = FontWeight(500),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 4.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
items(sortingTypes) {
|
|
||||||
FilterRadioButton(
|
|
||||||
selected = (sortingType == it.sortingType),
|
|
||||||
onClick = { sortingType = it.sortingType },
|
|
||||||
stringRes = it.stringRes,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 64.dp, vertical = 8.dp),
|
|
||||||
onClick = {
|
|
||||||
onApply.invoke(sortingType)
|
|
||||||
}) {
|
|
||||||
Text(text = stringResource(R.string.apply))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun PreviewSortBottomSheet() {
|
|
||||||
NarodMonTheme {
|
|
||||||
SortSensorsBottomSheet(onApply = {}) {}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
package ru.nm17.narodmon.ui.elements
|
package ru.nm17.narodmon.ui.dialogs
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -20,7 +20,9 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import ru.nm17.narodmon.R
|
import ru.nm17.narodmon.R
|
||||||
|
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@ -84,3 +86,13 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewAgreementDialog(){
|
||||||
|
NarodMonTheme {
|
||||||
|
AgreementDialog {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,37 @@
|
||||||
package ru.nm17.narodmon.ui.bottomSheets
|
package ru.nm17.narodmon.ui.dialogs
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SheetState
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.SheetValue
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import ru.nm17.narodmon.R
|
import ru.nm17.narodmon.R
|
||||||
|
import ru.nm17.narodmon.ui.elements.FilterCheckbox
|
||||||
import ru.nm17.narodmon.ui.entities.SensorFilterUiEntity
|
import ru.nm17.narodmon.ui.entities.SensorFilterUiEntity
|
||||||
import ru.nm17.narodmon.ui.pages.FilterCheckbox
|
|
||||||
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FilterSensorsBottomSheet(
|
fun FilterSensorsDialog(
|
||||||
onApply: (filters: List<SensorFilterUiEntity>) -> Unit,
|
onApply: (filters: List<SensorFilterUiEntity>) -> Unit,
|
||||||
onDismissRequest: () -> Unit
|
onDismissRequest: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
@ -67,26 +67,29 @@ fun FilterSensorsBottomSheet(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { onDismissRequest.invoke() },
|
|
||||||
sheetState = SheetState(true)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
onDismissRequest.invoke()
|
||||||
|
}) {
|
||||||
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.sensors_filter_title),
|
text = stringResource(id = R.string.sensors_filter_title),
|
||||||
fontSize = 24.sp,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight(500),
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
|
||||||
Box(contentAlignment = Alignment.BottomCenter) {
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 4.dp)
|
.fillMaxWidth()
|
||||||
.fillMaxWidth(),
|
.heightIn(128.dp, 312.dp)
|
||||||
|
.padding(horizontal = 4.dp),
|
||||||
) {
|
) {
|
||||||
items(filterItems) {
|
items(filterItems) {
|
||||||
FilterCheckbox(
|
FilterCheckbox(
|
||||||
|
@ -95,25 +98,30 @@ fun FilterSensorsBottomSheet(
|
||||||
) { it.enabled.value = !it.enabled.value }
|
) { it.enabled.value = !it.enabled.value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button(
|
Row(
|
||||||
onClick = { onApply.invoke(filterItems) },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp, horizontal = 64.dp)
|
.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
) {
|
) {
|
||||||
|
TextButton(onClick = { onDismissRequest.invoke() }) {
|
||||||
|
Text(text = "Отмена")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.padding(horizontal = 8.dp))
|
||||||
|
Button(onClick = { onApply.invoke(filterItems) }) {
|
||||||
Text(text = stringResource(id = R.string.apply))
|
Text(text = stringResource(id = R.string.apply))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewSensorFilterBottomSheet() {
|
fun PreviewFilterSensorsDialog() {
|
||||||
NarodMonTheme {
|
NarodMonTheme {
|
||||||
FilterSensorsBottomSheet({}) {
|
FilterSensorsDialog({}) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package ru.nm17.narodmon.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ru.nm17.narodmon.R
|
||||||
|
import ru.nm17.narodmon.ui.elements.FilterRadioButton
|
||||||
|
import ru.nm17.narodmon.ui.entities.SensorSortingUiEntity
|
||||||
|
import ru.nm17.narodmon.ui.entities.SortingTypes
|
||||||
|
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SortSensorsDialog(
|
||||||
|
selected: SensorSortingUiEntity,
|
||||||
|
sortingTypes: List<SensorSortingUiEntity>? = null,
|
||||||
|
onApply: (sortingType: SensorSortingUiEntity) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
var selectedType by remember {
|
||||||
|
mutableStateOf(selected)
|
||||||
|
}
|
||||||
|
val sensorsSortingTypes = listOf(
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_name, SortingTypes.NAME),
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_name_desc, SortingTypes.NAME_DESC),
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_distance, SortingTypes.DISTANCE),
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_distance_desc, SortingTypes.DISTANCE_DESC),
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_type, SortingTypes.TYPE),
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_type_desc, SortingTypes.TYPE_DESC),
|
||||||
|
SensorSortingUiEntity(R.string.sort_update_time, SortingTypes.UPD_TIME),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
onDismissRequest.invoke()
|
||||||
|
}) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.sensors_sort_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 4.dp),
|
||||||
|
) {
|
||||||
|
items(sortingTypes ?: sensorsSortingTypes) {
|
||||||
|
FilterRadioButton(
|
||||||
|
selected = (selectedType == it),
|
||||||
|
onClick = { selectedType = it },
|
||||||
|
stringRes = it.stringRes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
TextButton(onClick = { onDismissRequest.invoke() }) {
|
||||||
|
Text(text = "Отмена")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.padding(horizontal = 8.dp))
|
||||||
|
Button(onClick = { onApply.invoke(selectedType) }) {
|
||||||
|
Text(text = stringResource(id = R.string.apply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SortSensorsPreview() {
|
||||||
|
NarodMonTheme {
|
||||||
|
SortSensorsDialog(
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_distance, SortingTypes.DISTANCE),
|
||||||
|
onApply = {}) {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package ru.nm17.narodmon.ui.elements
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
||||||
|
@ExperimentalMaterial3Api
|
||||||
|
@Composable
|
||||||
|
fun FilterCheckbox(checked: Boolean, stringRes: Int, onCheckedChange: () -> Unit) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onCheckedChange() }
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = { onCheckedChange() },
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = stringRes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package ru.nm17.narodmon.ui.elements
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
||||||
|
@ExperimentalMaterial3Api
|
||||||
|
@Composable
|
||||||
|
fun FilterRadioButton(selected: Boolean, onClick: () -> Unit, stringRes: Int) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick.invoke() }
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
Text(text = stringResource(id = stringRes))
|
||||||
|
}
|
||||||
|
}
|
56
app/src/main/java/ru/nm17/narodmon/ui/elements/SensorItem.kt
Normal file
56
app/src/main/java/ru/nm17/narodmon/ui/elements/SensorItem.kt
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package ru.nm17.narodmon.ui.elements
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.ElevatedCard
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import ru.nm17.narodmon.ui.entities.SensorEntity
|
||||||
|
import ru.nm17.narodmon.ui.iosevkaFamily
|
||||||
|
|
||||||
|
@ExperimentalMaterial3Api
|
||||||
|
@Composable
|
||||||
|
fun SensorItem(sensorEntity: SensorEntity) {
|
||||||
|
ListItem(
|
||||||
|
overlineContent = { Text(text = "${sensorEntity.deviceName} от ${sensorEntity.deviceOwner}") },
|
||||||
|
headlineContent = { Text(text = sensorEntity.type.name) },
|
||||||
|
supportingContent = { Text(text = sensorEntity.name) },
|
||||||
|
trailingContent = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.End,
|
||||||
|
) {
|
||||||
|
Text(text = "${sensorEntity.distance} km")
|
||||||
|
Spacer(modifier = Modifier.size(2.dp))
|
||||||
|
ElevatedCard(
|
||||||
|
shape = RectangleShape,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${sensorEntity.value} ${sensorEntity.unit}",
|
||||||
|
fontFamily = iosevkaFamily,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp, vertical = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewSensorItem() {
|
||||||
|
// SensorItem(SensorEntity(0, Se))
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.bodyAsChannel
|
import io.ktor.client.statement.bodyAsChannel
|
||||||
import io.ktor.utils.io.jvm.javaio.toInputStream
|
import io.ktor.utils.io.jvm.javaio.toInputStream
|
||||||
import ovh.plrapps.mapcompose.api.addLayer
|
import ovh.plrapps.mapcompose.api.addLayer
|
||||||
|
import ovh.plrapps.mapcompose.api.onTouchDown
|
||||||
import ovh.plrapps.mapcompose.api.scale
|
import ovh.plrapps.mapcompose.api.scale
|
||||||
import ovh.plrapps.mapcompose.api.setScroll
|
import ovh.plrapps.mapcompose.api.setScroll
|
||||||
import ovh.plrapps.mapcompose.core.TileStreamProvider
|
import ovh.plrapps.mapcompose.core.TileStreamProvider
|
||||||
|
@ -33,7 +34,7 @@ suspend fun requestTile(row: Int, col: Int, zoom: Int): InputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TileMap(modifier: Modifier = Modifier) {
|
fun TileMap(modifier: Modifier = Modifier, onTap: () -> Unit) {
|
||||||
val state by remember {
|
val state by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
MapState(
|
MapState(
|
||||||
|
@ -43,6 +44,9 @@ fun TileMap(modifier: Modifier = Modifier) {
|
||||||
workerCount = 16,
|
workerCount = 16,
|
||||||
).apply {
|
).apply {
|
||||||
addLayer(tileStreamProvider)
|
addLayer(tileStreamProvider)
|
||||||
|
onTouchDown {
|
||||||
|
onTap.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
package ru.nm17.narodmon.ui.pages
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material3.AssistChip
|
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
import androidx.compose.material3.ElevatedCard
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.SearchBar
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import ru.nm17.narodmon.R
|
|
||||||
import ru.nm17.narodmon.db.entities.SensorType
|
|
||||||
import ru.nm17.narodmon.ui.bottomSheets.FilterSensorsBottomSheet
|
|
||||||
import ru.nm17.narodmon.ui.bottomSheets.SortSensorsBottomSheet
|
|
||||||
import ru.nm17.narodmon.ui.elements.GenericNavScaffold
|
|
||||||
import ru.nm17.narodmon.ui.elements.TileMap
|
|
||||||
import ru.nm17.narodmon.ui.entities.SensorEntity
|
|
||||||
import ru.nm17.narodmon.ui.entities.SensorFilterUiEntity
|
|
||||||
import ru.nm17.narodmon.ui.entities.SortingTypes
|
|
||||||
import ru.nm17.narodmon.ui.iosevkaFamily
|
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalMaterial3Api
|
|
||||||
@Composable
|
|
||||||
fun SensorsPage(navController: NavController) {
|
|
||||||
var searchQuery by remember { mutableStateOf("") }
|
|
||||||
var searchActive by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
|
|
||||||
var sortingShow by remember { mutableStateOf(false) }
|
|
||||||
var sortingType by remember { mutableStateOf(SortingTypes.DISTANCE) }
|
|
||||||
|
|
||||||
var filterShow by remember { mutableStateOf(false) }
|
|
||||||
var filterMine by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
|
|
||||||
val sensorEntities = remember {
|
|
||||||
mutableListOf(
|
|
||||||
// TODO: загружать датчики с сервера. Этот список -- для макета
|
|
||||||
SensorEntity(
|
|
||||||
0,
|
|
||||||
SensorType(0, "temp", "C"),
|
|
||||||
"device0", 0,
|
|
||||||
"sensor0", favorite = true,
|
|
||||||
public = true, mine = false,
|
|
||||||
"Москва", 0.4,
|
|
||||||
20.0, "C", 1686142800,
|
|
||||||
),
|
|
||||||
SensorEntity(
|
|
||||||
1,
|
|
||||||
SensorType(4, "humidity", "%"),
|
|
||||||
"device1", 0,
|
|
||||||
"sensor1", favorite = true,
|
|
||||||
public = false, mine = false,
|
|
||||||
"Подмосковье", 1.1,
|
|
||||||
39.0, "%", 1686142800,
|
|
||||||
),
|
|
||||||
SensorEntity(
|
|
||||||
2,
|
|
||||||
SensorType(11, "wind speed", "m/s"),
|
|
||||||
"device2", 1,
|
|
||||||
"sensor2", favorite = false,
|
|
||||||
public = true, mine = true,
|
|
||||||
"Москва", 0.01,
|
|
||||||
3.2, "m/s", 1686142800,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrConfig = LocalConfiguration.current
|
|
||||||
val mapHeight = scrConfig.screenHeightDp / 3
|
|
||||||
|
|
||||||
GenericNavScaffold(
|
|
||||||
title = { Text(text = stringResource(R.string.sensors_page_title)) }
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(it)) {
|
|
||||||
|
|
||||||
TileMap(modifier = Modifier.height(mapHeight.dp))
|
|
||||||
|
|
||||||
SearchBar(
|
|
||||||
query = searchQuery,
|
|
||||||
active = searchActive,
|
|
||||||
onActiveChange = { active -> searchActive = active },
|
|
||||||
onQueryChange = { query -> searchQuery = query },
|
|
||||||
onSearch = { searchActive = false },
|
|
||||||
placeholder = { Text(stringResource(R.string.search)) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = if (!searchActive) 8.dp else 0.dp)
|
|
||||||
) {}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
|
||||||
) {
|
|
||||||
AssistChip(
|
|
||||||
onClick = { filterShow = true },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_filter),
|
|
||||||
contentDescription = stringResource(id = R.string.sensors_filter)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = { Text(text = stringResource(R.string.sensors_filter)) },
|
|
||||||
)
|
|
||||||
|
|
||||||
AssistChip(
|
|
||||||
onClick = { sortingShow = true },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_sort),
|
|
||||||
contentDescription = stringResource(id = R.string.sensors_sorting)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = { Text(text = stringResource(R.string.sensors_sorting)) },
|
|
||||||
)
|
|
||||||
|
|
||||||
FilterChip(
|
|
||||||
selected = filterMine,
|
|
||||||
onClick = { filterMine = !filterMine },
|
|
||||||
label = { Text(text = stringResource(R.string.sensors_mine)) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
) {
|
|
||||||
items(sensorEntities) { sensor ->
|
|
||||||
SensorItem(sensor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterShow) {
|
|
||||||
FilterSensorsBottomSheet(
|
|
||||||
onApply = {
|
|
||||||
// TODO применение фильтров
|
|
||||||
filterShow = false
|
|
||||||
},
|
|
||||||
onDismissRequest = { filterShow = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortingShow) {
|
|
||||||
SortSensorsBottomSheet(
|
|
||||||
onApply = { s ->
|
|
||||||
sortingType = s
|
|
||||||
sortingShow = false
|
|
||||||
},
|
|
||||||
onDismissRequest = { sortingShow = false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalMaterial3Api
|
|
||||||
@Composable
|
|
||||||
fun SensorItem(sensorEntity: SensorEntity) {
|
|
||||||
ListItem(
|
|
||||||
overlineContent = { Text(text = "${sensorEntity.deviceName} от ${sensorEntity.deviceOwner}") },
|
|
||||||
headlineContent = { Text(text = sensorEntity.type.name) },
|
|
||||||
supportingContent = { Text(text = sensorEntity.name) },
|
|
||||||
trailingContent = {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.End,
|
|
||||||
) {
|
|
||||||
Text(text = "${sensorEntity.distance} km")
|
|
||||||
ElevatedCard(
|
|
||||||
shape = RectangleShape,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "${sensorEntity.value} ${sensorEntity.unit}",
|
|
||||||
fontFamily = iosevkaFamily,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
modifier = Modifier.padding(horizontal = 3.dp, vertical = 1.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalMaterial3Api
|
|
||||||
@Composable
|
|
||||||
fun FilterCheckbox(checked: Boolean, stringRes: Int, onCheckedChange: () -> Unit) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onCheckedChange() }
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = checked,
|
|
||||||
onCheckedChange = { onCheckedChange() },
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = stringRes),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalMaterial3Api
|
|
||||||
@Composable
|
|
||||||
fun FilterRadioButton(selected: Boolean, onClick: () -> Unit, stringRes: Int) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onClick.invoke() }
|
|
||||||
) {
|
|
||||||
RadioButton(
|
|
||||||
selected = selected,
|
|
||||||
onClick = onClick,
|
|
||||||
)
|
|
||||||
Text(text = stringResource(id = stringRes))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
package ru.nm17.narodmon.ui.sensorsScreen
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||||
|
import androidx.compose.material.icons.rounded.ArrowDropDown
|
||||||
|
import androidx.compose.material.icons.rounded.Check
|
||||||
|
import androidx.compose.material.icons.rounded.Person
|
||||||
|
import androidx.compose.material3.BottomSheetScaffold
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.SearchBar
|
||||||
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
|
import androidx.compose.material3.SheetValue
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldColors
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ovh.plrapps.mapcompose.core.debounce
|
||||||
|
import ru.nm17.narodmon.R
|
||||||
|
import ru.nm17.narodmon.db.entities.SensorType
|
||||||
|
import ru.nm17.narodmon.ui.dialogs.FilterSensorsDialog
|
||||||
|
import ru.nm17.narodmon.ui.elements.SensorItem
|
||||||
|
import ru.nm17.narodmon.ui.dialogs.SortSensorsDialog
|
||||||
|
import ru.nm17.narodmon.ui.elements.TileMap
|
||||||
|
import ru.nm17.narodmon.ui.entities.SensorEntity
|
||||||
|
import ru.nm17.narodmon.ui.entities.SensorSortingUiEntity
|
||||||
|
import ru.nm17.narodmon.ui.entities.SortingTypes
|
||||||
|
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
|
import ru.nm17.narodmon.ui.toChipTitle
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun SensorsScreen(navController: NavController) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
|
var searchActive by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||||
|
val sensorEntities = remember {
|
||||||
|
mutableListOf(
|
||||||
|
// TODO: загружать датчики с сервера. Этот список -- для макета
|
||||||
|
SensorEntity(
|
||||||
|
0,
|
||||||
|
SensorType(0, "temp", "C"),
|
||||||
|
"device0", 0,
|
||||||
|
"sensor0", favorite = true,
|
||||||
|
public = true, mine = false,
|
||||||
|
"Москва", 0.4,
|
||||||
|
20.0, "C", 1686142800,
|
||||||
|
),
|
||||||
|
SensorEntity(
|
||||||
|
1,
|
||||||
|
SensorType(4, "humidity", "%"),
|
||||||
|
"device1", 0,
|
||||||
|
"sensor1", favorite = true,
|
||||||
|
public = false, mine = false,
|
||||||
|
"Подмосковье", 1.1,
|
||||||
|
39.0, "%", 1686142800,
|
||||||
|
),
|
||||||
|
SensorEntity(
|
||||||
|
2,
|
||||||
|
SensorType(11, "wind speed", "m/s"),
|
||||||
|
"device2", 1,
|
||||||
|
"sensor2", favorite = false,
|
||||||
|
public = true, mine = true,
|
||||||
|
"Москва", 0.01,
|
||||||
|
3.2, "m/s", 1686142800,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterShow by remember { mutableStateOf(false) }
|
||||||
|
var filterMine by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var sortingShow by remember { mutableStateOf(false) }
|
||||||
|
var sortingType by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
SensorSortingUiEntity(R.string.sort_by_distance, SortingTypes.DISTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sheetHeight by remember {
|
||||||
|
mutableStateOf(SheetHeight.ExtraExpanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
BottomSheetScaffold(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
sheetPeekHeight = when (sheetHeight) {
|
||||||
|
SheetHeight.ExtraExpanded -> 256.dp
|
||||||
|
SheetHeight.Expanded -> 128.dp
|
||||||
|
SheetHeight.Hidden -> 0.dp
|
||||||
|
},
|
||||||
|
scaffoldState = scaffoldState,
|
||||||
|
sheetContent = {
|
||||||
|
AnimatedVisibility(visible = scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = searchQuery,
|
||||||
|
onValueChange = { searchQuery = it },
|
||||||
|
placeholder = { Text(stringResource(R.string.search)) },
|
||||||
|
shape = SearchBarDefaults.inputFieldShape,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
FilterChip(
|
||||||
|
selected = false,
|
||||||
|
onClick = { filterShow = true },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_filter),
|
||||||
|
contentDescription = stringResource(id = R.string.sensors_filter)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
// Icon(
|
||||||
|
// Icons.Filled.ArrowDropDown,
|
||||||
|
// "",
|
||||||
|
// tint = MaterialTheme.colorScheme.onBackground
|
||||||
|
// )
|
||||||
|
},
|
||||||
|
|
||||||
|
label = { Text(text = stringResource(R.string.sensors_filter)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
FilterChip(
|
||||||
|
selected = sortingType.sortingType != SortingTypes.DISTANCE,
|
||||||
|
onClick = { sortingShow = true },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_sort),
|
||||||
|
contentDescription = stringResource(id = R.string.sensors_sorting)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
// Icon(
|
||||||
|
// Icons.Filled.ArrowDropDown,
|
||||||
|
// "",
|
||||||
|
// tint = MaterialTheme.colorScheme.onBackground
|
||||||
|
// )
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
if (sortingType.sortingType == SortingTypes.DISTANCE) R.string.sensors_sorting
|
||||||
|
else sortingType.stringRes
|
||||||
|
).toChipTitle(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
FilterChip(
|
||||||
|
selected = filterMine,
|
||||||
|
onClick = { filterMine = !filterMine },
|
||||||
|
leadingIcon = {
|
||||||
|
if (filterMine) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Check,
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text(text = stringResource(R.string.sensors_mine)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
) {
|
||||||
|
items(sensorEntities) { sensor ->
|
||||||
|
SensorItem(sensor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
SearchBar(
|
||||||
|
query = searchQuery,
|
||||||
|
active = searchActive,
|
||||||
|
onActiveChange = { active ->
|
||||||
|
searchActive = active
|
||||||
|
sheetHeight = if (active) SheetHeight.Hidden else SheetHeight.ExtraExpanded
|
||||||
|
},
|
||||||
|
onQueryChange = { query -> searchQuery = query },
|
||||||
|
onSearch = { searchActive = false },
|
||||||
|
placeholder = { Text(stringResource(R.string.search)) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
horizontal = if (!searchActive) 8.dp else 0.dp,
|
||||||
|
vertical = if (!searchActive) 16.dp else 0.dp
|
||||||
|
)
|
||||||
|
) {}
|
||||||
|
|
||||||
|
TileMap(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
sheetHeight =
|
||||||
|
SheetHeight.Expanded // TODO придумать, чтобы менялось на SheetHeight.ExtraExpanded после взаимодействия с картой
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sortingShow) {
|
||||||
|
SortSensorsDialog(
|
||||||
|
sortingType,
|
||||||
|
onApply = {
|
||||||
|
sortingType = it
|
||||||
|
sortingShow = false
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
sortingShow = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (filterShow) {
|
||||||
|
FilterSensorsDialog(
|
||||||
|
onApply = {
|
||||||
|
// TODO применение фильтров
|
||||||
|
filterShow = false
|
||||||
|
},
|
||||||
|
onDismissRequest = { filterShow = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewNewSensors() {
|
||||||
|
NarodMonTheme {
|
||||||
|
SensorsScreen(rememberNavController())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ru.nm17.narodmon.ui.sensorsScreen
|
||||||
|
|
||||||
|
enum class SheetHeight {
|
||||||
|
ExtraExpanded,
|
||||||
|
Expanded,
|
||||||
|
Hidden
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
<string name="sensors_page_title">Сенсоры</string>
|
<string name="sensors_page_title">Сенсоры</string>
|
||||||
<string name="waiting_for_user_agreement">Ожидаю соглашение пользователя</string>
|
<string name="waiting_for_user_agreement">Ожидаю соглашение пользователя</string>
|
||||||
<string name="search">Поиск</string>
|
<string name="search">Поиск</string>
|
||||||
<string name="sensors_filter">Фильтр</string>
|
<string name="sensors_filter">Тип датчиков</string>
|
||||||
<string name="sensors_sorting">Сортировка</string>
|
<string name="sensors_sorting">Сортировка</string>
|
||||||
<string name="sensors_mine">Мои датчики</string>
|
<string name="sensors_mine">Мои датчики</string>
|
||||||
<string name="filter_temp_dew_point">Температура точки росы</string>
|
<string name="filter_temp_dew_point">Температура точки росы</string>
|
||||||
|
@ -51,4 +51,5 @@
|
||||||
<string name="sort_update_time">По времени обновления</string>
|
<string name="sort_update_time">По времени обновления</string>
|
||||||
<string name="apply">Применить</string>
|
<string name="apply">Применить</string>
|
||||||
<string name="sort_by_distance_desc">От дальних к ближним</string>
|
<string name="sort_by_distance_desc">От дальних к ближним</string>
|
||||||
|
<string name="cancel1">Отменить</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Reference in a new issue