diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9a73f7f..153e5da 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -106,12 +106,6 @@ dependencies { // optional - Kotlin Extensions and Coroutines support for Room implementation("androidx.room:room-ktx:$room_version") - // optional - RxJava2 support for Room - implementation("androidx.room:room-rxjava2:$room_version") - - // optional - RxJava3 support for Room - implementation("androidx.room:room-rxjava3:$room_version") - // optional - Guava support for Room, including Optional and ListenableFuture implementation("androidx.room:room-guava:$room_version") @@ -151,4 +145,8 @@ dependencies { // Map Compose library implementation("ovh.plrapps:mapcompose:2.7.1") -} \ No newline at end of file + + // Glide + implementation ("com.github.bumptech.glide:glide:4.14.2") + implementation("com.github.bumptech.glide:compose:1.0.0-alpha.1") + } \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/MainActivity.kt b/app/src/main/java/ru/nm17/narodmon/MainActivity.kt index 63b65e5..baff5ae 100644 --- a/app/src/main/java/ru/nm17/narodmon/MainActivity.kt +++ b/app/src/main/java/ru/nm17/narodmon/MainActivity.kt @@ -41,6 +41,7 @@ import ru.nm17.narodmon.ui.pages.SensorsPage import ru.nm17.narodmon.ui.theme.NarodMonTheme +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AppNavHost() { val navController = rememberNavController() @@ -131,23 +132,7 @@ class MainActivity : ComponentActivity() { } } -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - @Composable fun NavHolderEl() { //NavHost(navController = NavHostController(N), graph =) } - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - NarodMonTheme { - Greeting("Android") - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/Font.kt b/app/src/main/java/ru/nm17/narodmon/ui/Font.kt new file mode 100644 index 0000000..a1c20f0 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/Font.kt @@ -0,0 +1,10 @@ +package ru.nm17.narodmon.ui + +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import ru.nm17.narodmon.R + +val iosevkaFamily = FontFamily( + Font(R.font.iosevka, FontWeight.Medium) +) \ 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/bottomSheets/FilterSensorsBottomSheet.kt new file mode 100644 index 0000000..ce8be73 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/FilterSensorsBottomSheet.kt @@ -0,0 +1,119 @@ +package ru.nm17.narodmon.ui.bottomSheets + +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.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.SheetValue +import androidx.compose.material3.Text +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.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.SensorFilterUiEntity +import ru.nm17.narodmon.ui.pages.FilterCheckbox +import ru.nm17.narodmon.ui.theme.NarodMonTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FilterSensorsBottomSheet( + onApply: (filters: List) -> Unit, + onDismissRequest: () -> Unit +) { + val filterItems = remember { + listOf( + /* TODO: + * Заменить `code` на настоящее значение + * либо динамически его подгружать из ответа АПИ + * (см. /appInit, ключ в жсоне: types.type) */ + SensorFilterUiEntity(R.string.filter_temp, 0), + SensorFilterUiEntity(R.string.filter_temp_water, 1), + SensorFilterUiEntity(R.string.filter_temp_ground, 2), + SensorFilterUiEntity(R.string.filter_temp_dew_point, 3), + SensorFilterUiEntity(R.string.filter_humidity, 4), + SensorFilterUiEntity(R.string.filter_pressure, 5), + SensorFilterUiEntity(R.string.filter_lightness, 6), + SensorFilterUiEntity(R.string.filter_uv, 7), + SensorFilterUiEntity(R.string.filter_radiation, 8), + SensorFilterUiEntity(R.string.filter_rainfall, 9), + SensorFilterUiEntity(R.string.filter_dust, 10), + SensorFilterUiEntity(R.string.filter_wind_speed, 11), + SensorFilterUiEntity(R.string.filter_wind_direction, 12), + SensorFilterUiEntity(R.string.filter_concentration, 13), + SensorFilterUiEntity(R.string.filter_power, 14), + SensorFilterUiEntity(R.string.filter_voltage, 15), + SensorFilterUiEntity(R.string.filter_amperage, 16), + SensorFilterUiEntity(R.string.filter_energy, 17), + SensorFilterUiEntity(R.string.filter_battery, 18), + SensorFilterUiEntity(R.string.filter_rxtx, 19), + SensorFilterUiEntity(R.string.filter_signal, 20), + SensorFilterUiEntity(R.string.filter_water_meter, 21), + SensorFilterUiEntity(R.string.filter_time, 22), + ) + } + + 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 } + } + } + 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() { + NarodMonTheme { + FilterSensorsBottomSheet({}) { + + } + } +} \ 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 new file mode 100644 index 0000000..8287226 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/bottomSheets/SortSensorsBottomSheet.kt @@ -0,0 +1,97 @@ +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/elements/TileMap.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/TileMap.kt new file mode 100644 index 0000000..3da671e --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/TileMap.kt @@ -0,0 +1,57 @@ +package ru.nm17.narodmon.ui.elements + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +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.scale +import ovh.plrapps.mapcompose.api.setScroll +import ovh.plrapps.mapcompose.core.TileStreamProvider +import ovh.plrapps.mapcompose.ui.MapUI +import ovh.plrapps.mapcompose.ui.state.MapState +import java.io.InputStream + +const val mapSize = 32768 + +val client = HttpClient(OkHttp) +val tileStreamProvider = TileStreamProvider { row, col, zoom -> + requestTile(row, col, zoom) +} + +suspend fun requestTile(row: Int, col: Int, zoom: Int): InputStream { + val response = client.get("https://tile.openstreetmap.org/${zoom}/${col}/${row}.png") + return response.bodyAsChannel().toInputStream() +} + +@Composable +fun TileMap(modifier: Modifier = Modifier) { + val state by remember { + mutableStateOf( + MapState( + levelCount = 8, + fullWidth = mapSize, + fullHeight = mapSize, + workerCount = 16, + ).apply { + addLayer(tileStreamProvider) + } + ) + } + + LaunchedEffect(state) { + // TODO: Подгружать сохранённую позицию + state.setScroll(Offset(28702.6F, 14787.6F)) + state.scale = 1.4658884F + } + + MapUI(modifier = modifier, state = state) +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorEntity.kt b/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorEntity.kt new file mode 100644 index 0000000..84aeb23 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorEntity.kt @@ -0,0 +1,21 @@ +package ru.nm17.narodmon.ui.entities + +import ru.nm17.narodmon.db.entities.SensorType + + +data class SensorEntity( + // TODO: Вынести в отдельный класс, и явно не в директорию `ui` + val id: Int, + val type: SensorType, + val deviceName: String, + val deviceOwner: Int, + val name: String, + val favorite: Boolean, + val public: Boolean, + val mine: Boolean, + val location: String, + val distance: Double, // километры + val value: Double, + val unit: String, + val changed: Int, +) \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorFilterUiEntity.kt b/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorFilterUiEntity.kt new file mode 100644 index 0000000..62277d8 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorFilterUiEntity.kt @@ -0,0 +1,11 @@ +package ru.nm17.narodmon.ui.entities + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf + +data class SensorFilterUiEntity( + // TODO: Можно попробовать объединить с db/SensorType.kt + val stringRes: Int, + val code: Int, + var enabled: MutableState = mutableStateOf(false), +) diff --git a/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorSortingUiEntity.kt b/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorSortingUiEntity.kt new file mode 100644 index 0000000..96e4604 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/entities/SensorSortingUiEntity.kt @@ -0,0 +1,7 @@ +package ru.nm17.narodmon.ui.entities + + +data class SensorSortingUiEntity( + val stringRes: Int, + val sortingType: SortingTypes, +) \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/entities/SortingSensorsEntity.kt b/app/src/main/java/ru/nm17/narodmon/ui/entities/SortingSensorsEntity.kt new file mode 100644 index 0000000..82e230c --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/entities/SortingSensorsEntity.kt @@ -0,0 +1 @@ +package ru.nm17.narodmon.ui.entities diff --git a/app/src/main/java/ru/nm17/narodmon/ui/entities/SortingTypesEnum.kt b/app/src/main/java/ru/nm17/narodmon/ui/entities/SortingTypesEnum.kt new file mode 100644 index 0000000..0cb7aa3 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/entities/SortingTypesEnum.kt @@ -0,0 +1,13 @@ +package ru.nm17.narodmon.ui.entities + +enum class SortingTypes { + DISTANCE, + DISTANCE_DESC, + TYPE, + TYPE_DESC, + UPD_TIME, + NAME, + NAME_DESC, + VALUE, + VALUE_DESC +} \ No newline at end of file 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 index 6bd37c1..b67adfc 100644 --- a/app/src/main/java/ru/nm17/narodmon/ui/pages/Sensors.kt +++ b/app/src/main/java/ru/nm17/narodmon/ui/pages/Sensors.kt @@ -1,97 +1,247 @@ 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.LaunchedEffect -import androidx.compose.runtime.getValue 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.geometry.Offset +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 ovh.plrapps.mapcompose.api.scale -import ovh.plrapps.mapcompose.api.setScroll -import ovh.plrapps.mapcompose.ui.MapUI 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.viewmodel.MapViewModel +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 -enum class SensorsFilter { - All, Thermometer, Camera, -} @ExperimentalMaterial3Api @Composable fun SensorsPage(navController: NavController) { - val mapVM by remember { mutableStateOf(MapViewModel()) } - var filter by remember { mutableStateOf(SensorsFilter.All) } + 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 - - LaunchedEffect(mapVM) { - // TODO: Подгружать сохранённую позицию - mapVM.state.setScroll(Offset(28702.6F, 14787.6F)) - mapVM.state.scale = 1.4658884F - } GenericNavScaffold( title = { Text(text = stringResource(R.string.sensors_page_title)) } ) { Column(modifier = Modifier.padding(it)) { - MapUI(state = mapVM.state, modifier = Modifier.height(mapHeight.dp)) + 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( - modifier = Modifier.padding(horizontal = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(horizontal = 8.dp), ) { - SensorsFilterChip( - name = stringResource(R.string.sensors_filter_all), - checkFilter = { filter == SensorsFilter.All }, - updateFilter = { filter = SensorsFilter.All }, + 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)) }, ) - SensorsFilterChip( - name = stringResource(R.string.sensors_filter_temp), - checkFilter = { filter == SensorsFilter.Thermometer }, - updateFilter = { filter = SensorsFilter.Thermometer }, + 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)) }, ) - SensorsFilterChip( - name = stringResource(R.string.sensors_filter_camera), - checkFilter = { filter == SensorsFilter.Camera }, - updateFilter = { filter = SensorsFilter.Camera }, + FilterChip( + selected = filterMine, + onClick = { filterMine = !filterMine }, + label = { Text(text = stringResource(R.string.sensors_mine)) }, ) } - //Text(mapVM.state.scroll.toString()) - //Text(mapVM.state.scale.toString()) + 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 }) + } } -@OptIn(ExperimentalMaterial3Api::class) +@ExperimentalMaterial3Api @Composable -fun SensorsFilterChip( - name: String, - checkFilter: () -> Boolean, - updateFilter: () -> Unit, -) { - FilterChip( - selected = checkFilter(), - onClick = updateFilter, - label = { Text(name) }, +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/viewmodel/MapViewModel.kt b/app/src/main/java/ru/nm17/narodmon/ui/viewmodel/MapViewModel.kt deleted file mode 100644 index 4d330ba..0000000 --- a/app/src/main/java/ru/nm17/narodmon/ui/viewmodel/MapViewModel.kt +++ /dev/null @@ -1,39 +0,0 @@ -package ru.nm17.narodmon.ui.viewmodel - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -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.core.TileStreamProvider -import ovh.plrapps.mapcompose.ui.state.MapState -import java.io.InputStream - -class MapViewModel : ViewModel() { - private val client = HttpClient(CIO) - - private val tileStreamProvider = TileStreamProvider { row, col, zoom -> - requestTile(row, col, zoom) - } - - private val mapSize = 32768 - val state: MapState by mutableStateOf( - MapState( - levelCount = 8, - fullWidth = mapSize, - fullHeight = mapSize, - workerCount = 16, - ).apply { - addLayer(tileStreamProvider) - } - ) - - private suspend fun requestTile(row: Int, col: Int, zoom: Int): InputStream { - val response = client.get("https://tile.openstreetmap.org/${zoom}/${col}/${row}.png") - return response.bodyAsChannel().toInputStream() - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamItem.kt b/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamItem.kt new file mode 100644 index 0000000..1bf1928 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamItem.kt @@ -0,0 +1,74 @@ +package ru.nm17.narodmon.ui.webCamsScreen + +import androidx.compose.foundation.background +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideImage +import ru.nm17.narodmon.ui.iosevkaFamily + +@OptIn(ExperimentalGlideComposeApi::class) +@Composable +fun WebCamItem(webCamEntity: WebCamUiEntity) { + Box( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + contentAlignment = Alignment.BottomStart + ) { + GlideImage( + model = webCamEntity.imageUrl, + contentDescription = webCamEntity.name, + contentScale = ContentScale.FillHeight, + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .height(240.dp) + .fillMaxWidth() + ) + Row( + modifier = Modifier + .clip( + RoundedCornerShape(bottomEnd = 8.dp, bottomStart = 8.dp) + ) + .background(Color(0f, 0f, 0f, 0.55f)) + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + text = webCamEntity.time, + color = Color.White, + fontFamily = iosevkaFamily + ) + Text( + text = webCamEntity.name, + color = Color.White, + maxLines = 1, + fontFamily = iosevkaFamily + ) + + } + Text( + text = "${webCamEntity.distance} км", + color = Color.White, + fontFamily = iosevkaFamily, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamUiEntity.kt b/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamUiEntity.kt new file mode 100644 index 0000000..e17a48e --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamUiEntity.kt @@ -0,0 +1,13 @@ +package ru.nm17.narodmon.ui.webCamsScreen + +import android.graphics.Bitmap +import androidx.compose.ui.graphics.ImageBitmap + +data class WebCamUiEntity( + val id: Int, + val name: String, + val distance: Int, + val location: String, + val time: String, + val imageUrl: String +) diff --git a/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamsScreen.kt b/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamsScreen.kt new file mode 100644 index 0000000..98298b9 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/ui/webCamsScreen/WebCamsScreen.kt @@ -0,0 +1,63 @@ +package ru.nm17.narodmon.ui.webCamsScreen + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +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.tooling.preview.Preview +import androidx.compose.ui.viewinterop.AndroidView +import ru.nm17.narodmon.ui.theme.NarodMonTheme + +@Composable +fun WebCamsScreen() { + + var webCams by remember { + mutableStateOf( + listOf( + WebCamUiEntity( + 1, + "Крутая камера", + 1, + "Улица Пушкина, дом Калатушкина, кватира под номером 5", + "12:45", + "https://images-webcams.windy.com/51/1559159251/current/preview/1559159251.jpg?1686320054" + ), + WebCamUiEntity( + 2, + "Крутая камера 2", + 2, + "Улица Пушкина, дом Калатушкина, кватира под номером 5", + "12:45", + "https://images-webcams.windy.com/51/1559159251/current/preview/1559159251.jpg?1686320054" + ), + WebCamUiEntity( + 3, + "Крутая камера 3", + 3, + "Улица Пушкина, дом Калатушкина, кватира под номером 5", + "12:45", + "https://images-webcams.windy.com/51/1559159251/current/preview/1559159251.jpg?1686320054" + ) + ) + ) + } // TODO источник камер + + LazyColumn() { + items(webCams) { + WebCamItem(webCamEntity = it) + } + } +} + +@Preview +@Composable +fun PreviewWebCams() { + NarodMonTheme { + WebCamsScreen() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 0000000..d574422 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml new file mode 100644 index 0000000..ed8927f --- /dev/null +++ b/app/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/font/iosevka.ttf b/app/src/main/res/font/iosevka.ttf new file mode 100644 index 0000000..bab9e51 Binary files /dev/null and b/app/src/main/res/font/iosevka.ttf differ diff --git a/app/src/main/res/layout/fragment_sensors.xml b/app/src/main/res/layout/fragment_sensors.xml deleted file mode 100644 index e076d3b..0000000 --- a/app/src/main/res/layout/fragment_sensors.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - \ 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 e6c67e6..21124ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,6 @@ Народный Мониторинг - Hello blank fragment Я принимаю соглашения Выйти @@ -15,7 +14,41 @@ Примите необходимые соглашения Сенсоры Ожидаю соглашение пользователя - Все - Термометры - Камеры + Поиск + Фильтр + Сортировка + Мои датчики + Температура точки росы + Температура воздуха + Температура воды + Температура почвы + Влажность + Давление + Освещённость + УФ-индекс + Радиация + Осадки + Запылённость + Скорость ветра + Направление ветра + Концентрация + Мощность + Напряжение + Сила тока + Энергия + % батареи + Rx/Tx трафик + Сигнал в dBm + Счётчик воды + Время работы + Тип датчиков + От ближних к дальним + По типу (от А до Я) + По типу (от Я до А) + По названию (от А до Я) + По названию (от Я до А) + Сортировка датчиков + По времени обновления + Применить + От дальних к ближним \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a952870..a1a48e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.2.0-alpha06" +agp = "8.2.0-alpha07" kotlin = "1.8.10" core-ktx = "1.9.0" junit = "4.13.2"