From 1811a22a5a2acca2f9e4366dee1333b71c7cad1e Mon Sep 17 00:00:00 2001 From: mezhendosina Date: Mon, 12 Jun 2023 15:41:17 +0500 Subject: [PATCH 1/2] =?UTF-8?q?=D0=A0=D0=B5=D0=BE=D1=80=D0=B3=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{elements => dialogs}/AgreementDialog.kt | 14 ++++- .../narodmon/ui/elements/FilterCheckbox.kt | 31 ++++++++++ .../narodmon/ui/elements/FilterRadioButton.kt | 29 ++++++++++ .../nm17/narodmon/ui/elements/SensorItem.kt | 56 +++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) rename app/src/main/java/ru/nm17/narodmon/ui/{elements => dialogs}/AgreementDialog.kt (92%) create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/elements/FilterCheckbox.kt create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/elements/FilterRadioButton.kt create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/elements/SensorItem.kt diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt b/app/src/main/java/ru/nm17/narodmon/ui/dialogs/AgreementDialog.kt similarity index 92% rename from app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt rename to app/src/main/java/ru/nm17/narodmon/ui/dialogs/AgreementDialog.kt index fe97557..0daf706 100644 --- a/app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt +++ b/app/src/main/java/ru/nm17/narodmon/ui/dialogs/AgreementDialog.kt @@ -1,6 +1,6 @@ @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.layout.Column @@ -20,7 +20,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.tooling.preview.Preview import ru.nm17.narodmon.R +import ru.nm17.narodmon.ui.theme.NarodMonTheme import kotlin.system.exitProcess @OptIn(ExperimentalMaterial3Api::class) @@ -83,4 +85,14 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) { modifier = modifier ) +} + +@Preview +@Composable +fun PreviewAgreementDialog(){ + NarodMonTheme { + AgreementDialog { + + } + } } \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/FilterCheckbox.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/FilterCheckbox.kt new file mode 100644 index 0000000..5f39e69 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/FilterCheckbox.kt @@ -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), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/FilterRadioButton.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/FilterRadioButton.kt new file mode 100644 index 0000000..86d90a4 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/FilterRadioButton.kt @@ -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)) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/SensorItem.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/SensorItem.kt new file mode 100644 index 0000000..8662d3f --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/SensorItem.kt @@ -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)) +} \ No newline at end of file From 8c1c35afdd3eee6fa94a3e0613dcea1c06f22321 Mon Sep 17 00:00:00 2001 From: mezhendosina Date: Mon, 12 Jun 2023 16:08:09 +0500 Subject: [PATCH 2/2] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=8D?= =?UTF-8?q?=D0=BA=D1=80=D0=B0=D0=BD=20=D0=B4=D0=B0=D1=82=D1=87=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ru/nm17/narodmon/MainActivity.kt | 69 +++-- .../main/java/ru/nm17/narodmon/ui/Utils.kt | 9 + .../ui/bottomSheets/SortSensorsBottomSheet.kt | 97 ------ .../FilterSensorsDialog.kt} | 106 ++++--- .../narodmon/ui/dialogs/SortSensorsDialog.kt | 122 ++++++++ .../ru/nm17/narodmon/ui/elements/TileMap.kt | 6 +- .../java/ru/nm17/narodmon/ui/pages/Sensors.kt | 247 --------------- .../ui/sensorsScreen/SensorsScreen.kt | 282 ++++++++++++++++++ .../narodmon/ui/sensorsScreen/SheetHeight.kt | 7 + app/src/main/res/values/strings.xml | 3 +- 10 files changed, 534 insertions(+), 414 deletions(-) create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/Utils.kt delete mode 100644 app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/SortSensorsBottomSheet.kt rename app/src/main/java/ru/nm17/narodmon/ui/{bottomSheets/FilterSensorsBottomSheet.kt => dialogs/FilterSensorsDialog.kt} (55%) create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/dialogs/SortSensorsDialog.kt delete mode 100644 app/src/main/java/ru/nm17/narodmon/ui/pages/Sensors.kt create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SensorsScreen.kt create mode 100644 app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SheetHeight.kt diff --git a/app/src/main/java/ru/nm17/narodmon/MainActivity.kt b/app/src/main/java/ru/nm17/narodmon/MainActivity.kt index baff5ae..72011a6 100644 --- a/app/src/main/java/ru/nm17/narodmon/MainActivity.kt +++ b/app/src/main/java/ru/nm17/narodmon/MainActivity.kt @@ -1,4 +1,5 @@ -@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class, +@file:OptIn( + ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class ) @@ -7,12 +8,20 @@ package ru.nm17.narodmon import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize 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.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -24,8 +33,9 @@ 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.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -36,8 +46,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import ru.nm17.narodmon.db.AppDatabase import ru.nm17.narodmon.db.entities.KVSetting -import ru.nm17.narodmon.ui.elements.AgreementDialog -import ru.nm17.narodmon.ui.pages.SensorsPage +import ru.nm17.narodmon.ui.dialogs.AgreementDialog +import ru.nm17.narodmon.ui.sensorsScreen.SensorsScreen import ru.nm17.narodmon.ui.theme.NarodMonTheme @@ -46,18 +56,39 @@ import ru.nm17.narodmon.ui.theme.NarodMonTheme fun AppNavHost() { val navController = rememberNavController() val coScope = rememberCoroutineScope() - NavHost(navController = navController, startDestination = "sensors") { composable("agreement") { } 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() { @@ -69,20 +100,20 @@ class MainActivity : ComponentActivity() { AppDatabase::class.java, "data" ).build() - val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) - - val sharedPreferences = EncryptedSharedPreferences.create( - "secret_shared_prefs", - masterKeyAlias, - createDeviceProtectedStorageContext(), - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) +// val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) +// +// val sharedPreferences = EncryptedSharedPreferences.create( +// "secret_shared_prefs", +// masterKeyAlias, +// createDeviceProtectedStorageContext(), +// EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, +// EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM +// ) // 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 { @@ -120,9 +151,9 @@ class MainActivity : ComponentActivity() { Text(text = stringResource(R.string.waiting_for_user_agreement)) } } - } else { AppNavHost() + } // A surface container using the 'background' color from the theme diff --git a/app/src/main/java/ru/nm17/narodmon/ui/Utils.kt b/app/src/main/java/ru/nm17/narodmon/ui/Utils.kt new file mode 100644 index 0000000..7b20986 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/Utils.kt @@ -0,0 +1,9 @@ +package ru.nm17.narodmon.ui + +fun String.toChipTitle(): String { + return if (length >= 20) { + this.slice(0..16) + ".." + } else { + this + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/SortSensorsBottomSheet.kt b/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/SortSensorsBottomSheet.kt deleted file mode 100644 index 8287226..0000000 --- a/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/SortSensorsBottomSheet.kt +++ /dev/null @@ -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 = {}) {} - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/FilterSensorsBottomSheet.kt b/app/src/main/java/ru/nm17/narodmon/ui/dialogs/FilterSensorsDialog.kt similarity index 55% rename from app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/FilterSensorsBottomSheet.kt rename to app/src/main/java/ru/nm17/narodmon/ui/dialogs/FilterSensorsDialog.kt index ce8be73..cd07cff 100644 --- a/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/FilterSensorsBottomSheet.kt +++ b/app/src/main/java/ru/nm17/narodmon/ui/dialogs/FilterSensorsDialog.kt @@ -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.Box import androidx.compose.foundation.layout.Column 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.heightIn import androidx.compose.foundation.layout.padding 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.ModalBottomSheet -import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue +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.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.unit.dp -import androidx.compose.ui.unit.sp import ru.nm17.narodmon.R +import ru.nm17.narodmon.ui.elements.FilterCheckbox import ru.nm17.narodmon.ui.entities.SensorFilterUiEntity -import ru.nm17.narodmon.ui.pages.FilterCheckbox import ru.nm17.narodmon.ui.theme.NarodMonTheme @OptIn(ExperimentalMaterial3Api::class) @Composable -fun FilterSensorsBottomSheet( +fun FilterSensorsDialog( onApply: (filters: List) -> Unit, onDismissRequest: () -> Unit ) { @@ -67,53 +67,61 @@ fun FilterSensorsBottomSheet( ) } - ModalBottomSheet( - onDismissRequest = { onDismissRequest.invoke() }, - sheetState = SheetState(true) - ) { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - Text( - text = stringResource(R.string.sensors_filter_title), - fontSize = 24.sp, - fontWeight = FontWeight(500), - ) - } - Box(contentAlignment = Alignment.BottomCenter) { - LazyColumn( - modifier = Modifier - .padding(horizontal = 4.dp) - .fillMaxWidth(), - ) { - items(filterItems) { - FilterCheckbox( - checked = it.enabled.value, - stringRes = it.stringRes, - ) { it.enabled.value = !it.enabled.value } + AlertDialog( + onDismissRequest = { + onDismissRequest.invoke() + }) { + Surface( + shape = MaterialTheme.shapes.large, + tonalElevation = AlertDialogDefaults.TonalElevation + ) { + Column(modifier = Modifier.padding(vertical = 16.dp)) { + Text( + text = stringResource(id = R.string.sensors_filter_title), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + ) + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .heightIn(128.dp, 312.dp) + .padding(horizontal = 4.dp), + ) { + items(filterItems) { + FilterCheckbox( + checked = it.enabled.value, + stringRes = it.stringRes, + ) { it.enabled.value = !it.enabled.value } + } + } + 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(filterItems) }) { + Text(text = stringResource(id = R.string.apply)) + } } } - Button( - onClick = { onApply.invoke(filterItems) }, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp, horizontal = 64.dp) - ) { - Text(text = stringResource(id = R.string.apply)) - } - } + } } } @Preview @Composable -fun PreviewSensorFilterBottomSheet() { +fun PreviewFilterSensorsDialog() { NarodMonTheme { - FilterSensorsBottomSheet({}) { - - } + FilterSensorsDialog({}) {} } } \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/dialogs/SortSensorsDialog.kt b/app/src/main/java/ru/nm17/narodmon/ui/dialogs/SortSensorsDialog.kt new file mode 100644 index 0000000..eb0f74e --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/dialogs/SortSensorsDialog.kt @@ -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? = 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 = {}) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/TileMap.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/TileMap.kt index 3da671e..15f479d 100644 --- a/app/src/main/java/ru/nm17/narodmon/ui/elements/TileMap.kt +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/TileMap.kt @@ -13,6 +13,7 @@ import io.ktor.client.request.get import io.ktor.client.statement.bodyAsChannel import io.ktor.utils.io.jvm.javaio.toInputStream import ovh.plrapps.mapcompose.api.addLayer +import ovh.plrapps.mapcompose.api.onTouchDown import ovh.plrapps.mapcompose.api.scale import ovh.plrapps.mapcompose.api.setScroll import ovh.plrapps.mapcompose.core.TileStreamProvider @@ -33,7 +34,7 @@ suspend fun requestTile(row: Int, col: Int, zoom: Int): InputStream { } @Composable -fun TileMap(modifier: Modifier = Modifier) { +fun TileMap(modifier: Modifier = Modifier, onTap: () -> Unit) { val state by remember { mutableStateOf( MapState( @@ -43,6 +44,9 @@ fun TileMap(modifier: Modifier = Modifier) { workerCount = 16, ).apply { addLayer(tileStreamProvider) + onTouchDown { + onTap.invoke() + } } ) } diff --git a/app/src/main/java/ru/nm17/narodmon/ui/pages/Sensors.kt b/app/src/main/java/ru/nm17/narodmon/ui/pages/Sensors.kt deleted file mode 100644 index b67adfc..0000000 --- a/app/src/main/java/ru/nm17/narodmon/ui/pages/Sensors.kt +++ /dev/null @@ -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)) - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SensorsScreen.kt b/app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SensorsScreen.kt new file mode 100644 index 0000000..d40161b --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SensorsScreen.kt @@ -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()) + } +} diff --git a/app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SheetHeight.kt b/app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SheetHeight.kt new file mode 100644 index 0000000..1b2fe7c --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/sensorsScreen/SheetHeight.kt @@ -0,0 +1,7 @@ +package ru.nm17.narodmon.ui.sensorsScreen + +enum class SheetHeight { + ExtraExpanded, + Expanded, + Hidden +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21124ef..ac3adf6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,7 +15,7 @@ Сенсоры Ожидаю соглашение пользователя Поиск - Фильтр + Тип датчиков Сортировка Мои датчики Температура точки росы @@ -51,4 +51,5 @@ По времени обновления Применить От дальних к ближним + Отменить \ No newline at end of file