Compare commits

..

62 commits

Author SHA1 Message Date
e2ecbc4df9 Merge pull request 'Доработка новых запросов для АПИ' (#12) from request-entities into master
Reviewed-on: #12
2023-06-22 21:45:02 +03:00
5d501d201d Merge branch 'master' into request-entities 2023-06-22 21:44:53 +03:00
0a6c3e703e Merge pull request 'Фикс #10' (#15) from issue-#10 into master
Reviewed-on: #15
2023-06-22 11:18:11 +03:00
55c583ba6a Рефакторинг параметров SensorsScreen 2023-06-22 11:27:03 +05:00
c774b8a402 В SettingsScreen добавлены кнопки "О приложении" и "Дебаг меню" 2023-06-21 15:30:05 +05:00
3c03aed629 Переименовал package ru.nm17.narodmon.ui.messages в ru.nm17.narodmon.ui.messagesScreen 2023-06-21 15:29:17 +05:00
f09f98e637 У настроек теперь есть своя внутренняя навигация 2023-06-21 15:29:05 +05:00
4f67569409 Почистил импорты 2023-06-21 14:39:25 +05:00
033aa34c38 Прибрался в build.gradle.kts + убрал ненужные библиотеки 2023-06-21 14:36:00 +05:00
d181b39c55 Status bar теперь должен быть прозрачным :) 2023-06-21 14:34:59 +05:00
4bbba45d49 Добавлена навигация 2023-06-21 14:34:43 +05:00
b9376be372 Иконки для навигации 2023-06-21 14:34:11 +05:00
1998931a98 Убрал AppHavHost в отдельный файл 2023-06-21 14:33:49 +05:00
80f8cc77b6 В SensorsScreen добавлена кнопка для перехода в SettingsScreen 2023-06-21 14:33:17 +05:00
628b168bcf Заглушки для SettingsScreen и MessagesScreen 2023-06-21 14:28:26 +05:00
9975db912e Новые строки в strings.xml 2023-06-21 14:28:06 +05:00
1a2612d4ba Поменял импорт в NearbyDeviceEntity.kt 2023-06-18 19:42:30 +05:00
a8aede84ff Поменял название класса в SensorEntity.kt и 2023-06-18 19:42:15 +05:00
e5e6e5dfb5 Добавлены дата классы для API Народного Мониторинга 2023-06-18 19:36:20 +05:00
7e5673f96e Merge pull request 'new-sensors-screen' (#6) from new-sensors-screen into master
Reviewed-on: nm17/narodmon#6
Reviewed-by: nm17 <nm17@riseup.net>
2023-06-18 13:23:06 +03:00
82c3797c90 Merge branch 'master' into new-sensors-screen 2023-06-18 13:19:13 +03:00
b1e5af538a Merge pull request 'dc09-sensors2' (#5) from dc09-sensors2 into master
Reviewed-on: nm17/narodmon#5
Reviewed-by: nm17 <nm17@riseup.net>
2023-06-18 13:15:52 +03:00
8c1c35afdd Новый экран датчиков 2023-06-12 16:08:09 +05:00
1811a22a5a Реорганизация файлов 2023-06-12 15:41:17 +05:00
0bdf64b7ed Добавлен Ui для веб-камер 2023-06-09 19:31:08 +05:00
2a6bfb205c Улучшен UX фильтров 2023-06-08 19:26:56 +05:00
9cd0bcddc8 Радио кнопки теперь прокликиваются по всей ширине 2023-06-08 18:54:41 +05:00
45c9101f9d Убрал ненужное из MainActivity.kt + удалил ненужный .xml файл 2023-06-08 18:51:09 +05:00
1c8f993dcb Упс. Очепятка 2023-06-08 18:48:47 +05:00
937f4890e1 Добавил иконки к чипам в Sensors.kt 2023-06-08 18:48:34 +05:00
01b362ce4e Поменял strings.xml 2023-06-08 18:34:41 +05:00
d82a4b28b7 Вынес data классы в отдельные файлы. Так принято делать. По крайней мере у себя я так стараюсь делать :) 2023-06-08 18:33:57 +05:00
9d024a0c69 Bottom Sheet'ы теперь живут в отдельных файлах, чтобы Sensors.kt был читабельнее 2023-06-08 18:32:24 +05:00
c32ea30772 Отступы у searchbar'а 2023-06-08 17:16:30 +05:00
b17e853103 Чекбоксы фильтров прокликиваются по всей ширине 2023-06-08 17:15:35 +05:00
be9b491c44 Убрал лишние библиотеки. Мы же не будем RxJava использовать? 2023-06-08 17:14:04 +05:00
4c3584d63c Шрифт Iosevka для значений датчиков 2023-06-07 21:42:38 +04:00
9739d1cdfe Разработан UI для списка датчиков, добавлены тестовые данные 2023-06-07 21:07:46 +04:00
9c6be5d8c5 Пофикшена прокрутка в BottomSheet-ах 2023-06-07 16:45:32 +04:00
12db4ba8df Заголовок для BottomSheet с сортировкой 2023-06-07 16:07:45 +04:00
95e849e27a Чекбокс/радио вынесены в отдельный composable, добавлены опции сортировки 2023-06-07 16:02:42 +04:00
965533c8cc Названия перемнных: filter/sortingShown -> ...Show 2023-06-07 15:42:40 +04:00
ebd8dadf7f Добавлена сортировка 2023-06-07 15:41:53 +04:00
e6f7ff8238 Обновление Gradle 2023-06-07 15:41:44 +04:00
5639ebd4cd Исправлены чекбоксы (забыл MutableState в датаклассе), центрирован заголовок BottomSheet 2023-06-06 21:14:50 +04:00
d1e7b60378 Фильтр: Card-ы заменены на CheckBox+Text, создан датакласс для фильтров 2023-06-06 20:56:15 +04:00
2a318d551c Добавлено содержимое в BottomSheet фильтрации 2023-06-06 17:50:37 +04:00
3a873725f2 Добавлен BottomSheet для фильтров и строки 2023-06-06 17:12:20 +04:00
1334014c04 Небольшие изменения в UI экрана сенсоров 2023-06-06 15:50:55 +04:00
06bc8a839e Fix: java.security.cert.CertificateException 2023-06-06 15:50:31 +04:00
2b2a2c2f5a IDE не видит @file:OptIn 2023-06-06 15:33:43 +04:00
0935cd88d4 Перенёс карту в отдельный компонент вместе со state 2023-06-06 14:57:49 +04:00
06bd65c4e4
fix: исправил недо-мерж 2023-06-05 22:50:01 +04:00
bc73528681 Merge pull request 'dc09-sensors' (#4) from dc09-sensors into master
Reviewed-on: nm17/narodmon#4
2023-06-05 21:37:06 +03:00
e384ffdf6c
Merge branch 'master' into dc09-sensors 2023-06-05 22:34:28 +04:00
72b82fa4e4
Merge branch 'nm17/wip' 2023-06-05 22:25:08 +04:00
8a9ddbc27c Изменён размер карты и зум, рендер ускорен в 100500 раз за счёт workerCount, добавлен код для автоустановки позиции на карте 2023-06-05 22:15:40 +04:00
03a758673b Карта работает, ура! URL.openStream заменён на Ktor, поправлены размеры блока с картой 2023-06-05 20:11:42 +04:00
266e2001f2 Нерабочий фикс карты 2023-06-05 18:59:23 +04:00
96d7819d00 Добавлена карта на экран сенсоров (пока подгрузка тайлов не работает), фильтр-чипы убраны в отдельный composable 2023-06-05 18:43:51 +04:00
35bd573cb0 Почищены импорты, hadlineText->Content (иначе не билдится) 2023-06-05 18:41:02 +04:00
ecb76ca594 Добавлен экран с сенсорами, убраны пустые строки 2023-06-05 17:13:13 +04:00
98 changed files with 1879 additions and 207 deletions

View file

@ -74,63 +74,44 @@ dependencies {
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
//-- Navigation
val nav_version = "2.5.3"
// Java language implementation
implementation("androidx.navigation:navigation-fragment:$nav_version")
implementation("androidx.navigation:navigation-ui:$nav_version")
// Kotlin
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
// Feature module Support
implementation("androidx.navigation:navigation-dynamic-features-fragment:$nav_version")
// Testing Navigation
androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
// Jetpack Compose Integration
implementation("androidx.navigation:navigation-compose:$nav_version")
val room_version = "2.5.1"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
//-- Room
// To use Kotlin annotation processing tool (kapt)
//kapt("androidx.room:room-compiler:$room_version")
// To use Kotlin Symbol Processing (KSP)
val room_version = "2.5.1"
ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
annotationProcessor("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-runtime:$room_version")
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")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
// optional - Paging 3 Integration
implementation("androidx.room:room-paging:$room_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
//-- Ktor
val ktor_version = "2.3.1"
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation(platform("dev.forkhandles:forkhandles-bom:2.6.0.0"))
implementation("dev.forkhandles:result4k")
implementation("io.ktor:ktor-client-core:2.3.1")
implementation("io.ktor:ktor-client-okhttp:2.3.1")
implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")
// For Identity Credential APIs
implementation("androidx.security:security-identity-credential:1.0.0-alpha03")
@ -144,4 +125,13 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
//-- Map Compose library
implementation("ovh.plrapps:mapcompose:2.7.1")
//-- Glide
implementation("com.github.bumptech.glide:glide:4.14.2")
implementation("com.github.bumptech.glide:compose:1.0.0-alpha.1")
}

View file

@ -1,6 +1,4 @@
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class,
ExperimentalMaterial3Api::class
)
package ru.nm17.narodmon
@ -25,74 +23,44 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.room.Room
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
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.navHost.AppNavHost
import ru.nm17.narodmon.ui.theme.NarodMonTheme
@Composable
fun AppNavHost() {
val navController = rememberNavController()
val coScope = rememberCoroutineScope()
NavHost(navController = navController, startDestination = "sensors") {
composable("agreement") {
}
composable("sensors") {
SensorsPage(navController)
}
/*...*/
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val db = Room.databaseBuilder(
applicationContext,
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 {
val coScope = rememberCoroutineScope()
//var asd = getPreferences()
NarodMonTheme {
@ -125,44 +93,15 @@ class MainActivity : ComponentActivity() {
Text(text = stringResource(R.string.waiting_for_user_agreement))
}
}
} else {
AppNavHost()
}
// A surface container using the 'background' color from the theme
}
}
}
}
@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")
}
}

View file

@ -16,8 +16,10 @@ import io.ktor.http.headers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.take
import kotlinx.datetime.Clock
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
@ -120,7 +122,7 @@ class Client private constructor(
val client = this.httpClient1Min
return flow<Result4k<AppInitResponse, APIError>> {
val resp = client.post("https://narodmon.ru/api") {
val resp = this@Client.httpClient1Min.take(1).first().post("https://narodmon.ru/api") {
contentType(ContentType.Application.Json)
setBody(
getJSONRequestBody("appInit", request)
@ -132,7 +134,7 @@ class Client private constructor(
}.flowOn(Dispatchers.IO)
}
public fun appInit(request: AppInitRequest): Flow<Result4k<AppInitResponse, APIError>> {
/*public fun jjhgjhg(request: AppInitRequest): Flow<Result4k<AppInitResponse, APIError>> {
val client = this.httpClient1Min
return flow<Result4k<AppInitResponse, APIError>> {
@ -147,5 +149,5 @@ class Client private constructor(
}.flowOn(Dispatchers.IO)
}
}*/
}

View file

@ -0,0 +1,16 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities
data class SensorEntity(
val changed: Int,
val fav: Int,
val id: Int,
val mac: String,
val name: String,
val pub: Int,
val time: Int,
val trend: Int,
val type: Int,
val unit: String,
val value: Double
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.addLike
data class AddLikeRequestEntity(
val api_key: String,
val cmd: String,
val id: Int,
val uuid: String
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.addLike
data class AddLikeResponseEntity(
val id: Int,
val liked: Int,
val time: Int
)

View file

@ -0,0 +1,11 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.appInit
data class AppInitRequestEntity(
val api_key: String,
val cmd: String,
val lang: String,
val platform: String,
val utc: Int,
val uuid: String,
val version: String
)

View file

@ -0,0 +1,14 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.appInit
data class AppInitResponseEntity(
val addr: String,
val favorites: List<Any>,
val lat: Double,
val latest: String,
val login: String,
val lon: Double,
val timestamp: Int,
val types: List<AppInitTypeEntity>,
val url: String,
val vip: Int
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.appInit
data class AppInitTypeEntity(
val name: String,
val type: Int,
val unit: String
)

View file

@ -0,0 +1,12 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.bugReport
data class BugReportRequestEntity(
val api_key: String,
val cmd: String,
val email: String,
val logs: String,
val mess: String,
val name: String,
val time: Int,
val uuid: String
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.disLike
data class DisLikeRequestEntity(
val api_key: String,
val cmd: String,
val id: Int,
val uuid: String
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.disLike
data class DisLikeResponseEntity(
val id: Int,
val liked: Int,
val time: Int
)

View file

@ -0,0 +1,12 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.mapBounds
data class MapBoundsDeviceEntity(
val id: Int,
val lat: Double,
val lon: Double,
val name: String,
val time: Int,
val type: Int,
val unit: String,
val value: Double
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.mapBounds
data class MapBoundsRequestEntity(
val api_key: String,
val bounds: List<Int>,
val cmd: String,
val lang: String,
val limit: Int,
val uuid: String
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.mapBounds
data class MapBoundsResponseEntity(
val devices: List<MapBoundsDeviceEntity>,
val webcams: List<MapBoundsWebcamEntity>
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.mapBounds
data class MapBoundsWebcamEntity(
val id: Int,
val image: String,
val lat: Double,
val lon: Double,
val name: String,
val time: Int
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.nameSensor
data class NameSensorRequestEntity(
val api_key: String,
val cmd: String,
val id: Int,
val name: String,
val uuid: String
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.pubSensor
data class PubSensorRequestEntity(
val api_key: String,
val cmd: String,
val id: Int,
val uuid: String
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.pubSensor
data class PubSensorResponseEntity(
val code: Int,
val id: Int
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sendCommand
data class SendCommandRequestEntity(
val api_key: String,
val cmd: String,
val command: String,
val id: Int,
val uuid: String
)

View file

@ -0,0 +1,13 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sendComplaint
data class SendComplaintRequestEntity(
val api_key: String,
val cmd: String,
val email: String,
val id: Int,
val name: String,
val problem: String,
val time: Int,
val uuid: String,
val value: Int
)

View file

@ -0,0 +1,12 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sendMessage
data class SendMessageRequestEntity(
val api_key: String,
val cmd: String,
val email: String,
val mess: String,
val name: String,
val subj: String,
val uid: Int,
val uuid: String
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsHistory
data class HistoryDataEntity(
val id: Int,
val time: Int,
val value: Double
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsHistory
data class HistorySensorEntity(
val id: Int,
val name: String,
val type: Int,
val unit: String
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsHistory
data class SensorsHistoryRequestEntity(
val api_key: String,
val cmd: String,
val id: Int,
val offset: Int,
val period: String,
val uuid: String
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsHistory
data class SensorsHistoryResponseEntity(
val `data`: List<HistoryDataEntity>,
val sensors: List<HistorySensorEntity>
)

View file

@ -0,0 +1,19 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsNearby
import ru.nm17.narodmon.appNarodMonApiClient.entities.SensorEntity
data class NearbyDeviceEntity(
val cmd: Int,
val distance: Double,
val id: Int,
val lat: Double,
val location: String,
val lon: Double,
val mac: String,
val my: Int,
val name: String,
val owner: String,
val sensors: List<SensorEntity>,
val time: Int
)

View file

@ -0,0 +1,12 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsNearby
data class SensorsNearbyRequestEntity(
val api_key: String,
val cmd: String,
val lang: String,
val lat: Double,
val lon: Double,
val radius: Int,
val types: List<Int>,
val uuid: String
)

View file

@ -0,0 +1,5 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsNearby
data class SensorsNearbyResponseEntity(
val devices: List<NearbyDeviceEntity>
)

View file

@ -0,0 +1,19 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsOnDevice
import ru.nm17.narodmon.ui.entities.SensorEntity
data class SensorOnDeviceEntity(
val cmd: Int,
val distance: Double,
val id: Int,
val info: String,
val location: String,
val mac: String,
val my: Int,
val name: String,
val owner: String,
val photo: String,
val sensors: List<SensorEntity>,
val site: String,
val time: Int
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsOnDevice
data class SensorsOnDeviceRequestEntity(
val api_key: String,
val cmd: String,
val devices: List<Int>,
val lang: String,
val uuid: String
)

View file

@ -0,0 +1,5 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsOnDevice
data class SensorsOnDeviceResponseEntity(
val devices: List<SensorOnDeviceEntity>
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsValues
data class SensorValueEntity(
val changed: Int,
val id: Int,
val time: Int,
val trend: Int,
val type: Int,
val value: Int
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsValues
data class SensorsValuesRequestEntity(
val api_key: String,
val cmd: String,
val sensors: List<Int>,
val uuid: String
)

View file

@ -0,0 +1,5 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.sensorsValues
data class SensorsValuesResponseEntity(
val sensors: List<SensorValueEntity>
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userFavorites
data class FavoriteSensorEntity(
val id: Int,
val name: String,
val time: Int,
val type: Int,
val value: Double
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userFavorites
data class FavoriteWebcamEntity(
val id: Int,
val image: String,
val name: String,
val time: Int
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userFavorites
data class UserFavoritesRequestEntity(
val api_key: String,
val cmd: String,
val lang: String,
val sensors: List<Int>,
val uuid: String,
val webcams: List<Int>
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userFavorites
data class UserFavoritesResponseEntity(
val sensors: List<FavoriteSensorEntity>,
val webcams: List<FavoriteWebcamEntity>
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class CellEntity(
val bssid: String,
val rssi: Int
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class UserLocationByAddrRequestEntity(
val addr: String,
val api_key: String,
val cmd: String,
val lang: String,
val uuid: String
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class UserLocationByCellRequestEntity(
val api_key: String,
val cells: List<CellEntity>,
val cmd: String,
val lang: String,
val uuid: String
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class UserLocationByCoordRequestEntity(
val api_key: String,
val cmd: String,
val lang: String,
val lat: Double,
val lon: Double,
val uuid: String
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class UserLocationByWifiRequestEntity(
val api_key: String,
val cmd: String,
val lang: String,
val uuid: String,
val wifi: List<WifiEntity>
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class UserLocationResponseEntity(
val addr: String,
val lat: Double,
val lon: Double
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLocation
data class WifiEntity(
val bssid: String,
val rssi: Int
)

View file

@ -0,0 +1,10 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLogon
data class UserLogonRequestEntity(
val api_key: String,
val cmd: String,
val hash: String,
val lang: String,
val login: String,
val uuid: String
)

View file

@ -0,0 +1,8 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLogon
data class UserLogonResponseEntity(
val login: String,
val tz: Int,
val uid: Int,
val vip: Int
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLogout
data class UserLogoutRequestEntity(
val api_key: String,
val cmd: String,
val uuid: String
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.userLogout
data class UserLogoutResponseEntity(
val login: String,
val uid: Int
)

View file

@ -0,0 +1,14 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.weatherReport
data class WeatherReportRequestEntity(
val api_key: String,
val cmd: String,
val humid: String,
val lang: String,
val lat: Double,
val lon: Double,
val press: String,
val temp: String,
val uuid: String,
val wind: String
)

View file

@ -0,0 +1,5 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.weatherReport
data class WeatherReportResponseEntity(
val result: String
)

View file

@ -0,0 +1,6 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.webcamImages
data class WebcamImageEntity(
val image: String,
val time: Int
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.webcamImages
data class WebcamImagesRequestEntity(
val api_key: String,
val cmd: String,
val id: Int,
val limit: Int,
val uuid: String
)

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.webcamImages
data class WebcamImagesResponseEntity(
val distance: Double,
val id: Int,
val images: List<WebcamImageEntity>,
val location: String,
val name: String
)

View file

@ -0,0 +1,15 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.webcamsNearby
data class WebcamNearbyEntity(
val distance: Double,
val fav: Int,
val id: Int,
val image: String,
val lat: Double,
val location: String,
val lon: Double,
val my: Int,
val name: String,
val owner: String,
val time: Int
)

View file

@ -0,0 +1,11 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.webcamsNearby
data class WebcamsNearbyRequestEntity(
val api_key: String,
val cmd: String,
val lang: String,
val lat: Double,
val lon: Double,
val radius: Int,
val uuid: String
)

View file

@ -0,0 +1,5 @@
package ru.nm17.narodmon.appNarodMonApiClient.entities.webcamsNearby
data class WebcamsResponseEntity(
val webcams: List<WebcamNearbyEntity>
)

View file

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

View file

@ -0,0 +1,9 @@
package ru.nm17.narodmon.ui
fun String.toChipTitle(): String {
return if (length >= 20) {
this.slice(0..16) + ".."
} else {
this
}
}

View file

@ -1,10 +1,7 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package ru.nm17.narodmon.ui.elements
package ru.nm17.narodmon.ui.dialogs
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@ -22,12 +19,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.activity.ComponentActivity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.core.content.ContextCompat.startActivity
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)
@Composable
fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
val uriHandler = LocalUriHandler.current
@ -40,7 +38,7 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
Text(text = stringResource(id = R.string.agreement_dialog_text))
Divider(Modifier.padding(vertical = 8.dp))
ListItem(
headlineText = {
headlineContent = {
Text(
text = stringResource(id = R.string.privacy_policy),
style = MaterialTheme.typography.titleSmall
@ -56,7 +54,7 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
)
ListItem(
headlineText = {
headlineContent = {
Text(
text = stringResource(id = R.string.user_agreement),
style = MaterialTheme.typography.titleSmall
@ -87,4 +85,14 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
modifier = modifier
)
}
@Preview
@Composable
fun PreviewAgreementDialog(){
NarodMonTheme {
AgreementDialog {
}
}
}

View file

@ -0,0 +1,127 @@
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.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.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.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.FilterCheckbox
import ru.nm17.narodmon.ui.entities.SensorFilterUiEntity
import ru.nm17.narodmon.ui.theme.NarodMonTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilterSensorsDialog(
onApply: (filters: List<SensorFilterUiEntity>) -> 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),
)
}
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))
}
}
}
}
}
}
@Preview
@Composable
fun PreviewFilterSensorsDialog() {
NarodMonTheme {
FilterSensorsDialog({}) {}
}
}

View file

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

View file

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

View file

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

View file

@ -47,7 +47,7 @@ import kotlinx.coroutines.launch
@Composable
fun GenericNavScaffold(title: @Composable () -> Unit, content: @Composable (PaddingValues) -> Unit) {
val expanded = rememberDrawerState(initialValue = DrawerValue.Closed)
val coScope = rememberCoroutineScope();
val coScope = rememberCoroutineScope()
val navController = rememberNavController() // TODO: Используй меня
LaunchedEffect(key1 = Unit) {
@ -60,7 +60,7 @@ fun GenericNavScaffold(title: @Composable () -> Unit, content: @Composable (Padd
leadingContent = {
Icon(Icons.Default.AccountCircle, contentDescription = "")
},
headlineText = { Text(text = "Гость", style = MaterialTheme.typography.titleLarge)},
headlineContent = { Text(text = "Гость", style = MaterialTheme.typography.titleLarge)},
modifier = Modifier.height(72.dp)
)
Divider()
@ -75,29 +75,27 @@ fun GenericNavScaffold(title: @Composable () -> Unit, content: @Composable (Padd
}
}) {
Scaffold(
topBar = {
TopAppBar(
title = title,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
),
navigationIcon = {
IconButton(onClick = { coScope.launch { expanded.open() } }) {
Icon(Icons.Filled.Menu, contentDescription = null)
}
},
)
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
FloatingActionButton(onClick = {}) {
Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
}
},
//drawerContent = { Text(text = "Drawer Menu 1") },
content = content,
topBar = {
TopAppBar(
title = title,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
),
navigationIcon = {
IconButton(onClick = { coScope.launch { expanded.open() } }) {
Icon(Icons.Filled.Menu, contentDescription = null)
}
},
)
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
FloatingActionButton(onClick = {}) {
Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
}
},
//drawerContent = { Text(text = "Drawer Menu 1") },
content = content,
)
}
}

View 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))
}

View file

@ -0,0 +1,50 @@
package ru.nm17.narodmon.ui.elements
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
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.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import ru.nm17.narodmon.R
/**
* Кнопка, которая нужна для настроек.
* @param titleId Id заголовка кнопки
* @param leadingItem Заполнить, когда нужно вставить Composable перед заголовком(например [Icon], [FilterCheckbox] или [Switch]
*/
@Composable
fun SettingsItem(
@StringRes titleId: Int,
leadingItem: @Composable (() -> Unit)? = null,
onClick: () -> Unit = {}
) {
Row(modifier = Modifier.padding(16.dp)) {
if (leadingItem != null) {
leadingItem.invoke()
Spacer(modifier = Modifier.size(16.dp))
}
Column(modifier = Modifier
.fillMaxWidth()
.clickable { onClick.invoke() }) {
Text(text = stringResource(id = titleId))
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewSettingsItem() {
SettingsItem(R.string.about_app) {}
}

View file

@ -0,0 +1,61 @@
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.onTouchDown
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, onTap: () -> Unit) {
val state by remember {
mutableStateOf(
MapState(
levelCount = 8,
fullWidth = mapSize,
fullHeight = mapSize,
workerCount = 16,
).apply {
addLayer(tileStreamProvider)
onTouchDown {
onTap.invoke()
}
}
)
}
LaunchedEffect(state) {
// TODO: Подгружать сохранённую позицию
state.setScroll(Offset(28702.6F, 14787.6F))
state.scale = 1.4658884F
}
MapUI(modifier = modifier, state = state)
}

View file

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

View file

@ -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<Boolean> = mutableStateOf(false),
)

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.ui.entities
data class SensorSortingUiEntity(
val stringRes: Int,
val sortingType: SortingTypes,
)

View file

@ -0,0 +1 @@
package ru.nm17.narodmon.ui.entities

View file

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

View file

@ -0,0 +1,16 @@
package ru.nm17.narodmon.ui.messagesScreen
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun MessagesScreen() {
Text(text = "todo")
}
@Preview
@Composable
fun PreviewMessagesScreen() {
MessagesScreen()
}

View file

@ -0,0 +1,30 @@
package ru.nm17.narodmon.ui.navHost
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import ru.nm17.narodmon.ui.settings.SettingsNavigation
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppNavHost() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "main") {
composable("main") {
MainScreen(navController)
}
composable("settings") {
SettingsNavigation()
}
}
}
@Preview
@Composable
fun PreviewAppNavHost() {
AppNavHost()
}

View file

@ -0,0 +1,83 @@
package ru.nm17.narodmon.ui.navHost
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import ru.nm17.narodmon.ui.sensorsScreen.SensorsScreen
import ru.nm17.narodmon.ui.theme.NarodMonTheme
import ru.nm17.narodmon.ui.webCamsScreen.WebCamsScreen
val items = listOf(
MainScreenSealed.Sensors,
MainScreenSealed.Webcams,
MainScreenSealed.Messages
)
@Composable
fun MainScreen(outerNavController: NavController) {
val navController = rememberNavController()
Scaffold(
bottomBar = {
NavigationBar {
items.forEach { screen ->
NavigationBarItem(
selected = navController.currentDestination?.route == screen.route,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
label = { Text(text = stringResource(id = screen.resourceId)) },
icon = {
Icon(
painter = painterResource(id = screen.iconId),
contentDescription = ""
)
})
}
}
},
modifier = Modifier.fillMaxSize()
) {
NavHost(
navController,
startDestination = MainScreenSealed.Sensors.route,
Modifier.padding(it)
) {
composable(MainScreenSealed.Sensors.route) {
SensorsScreen(outerNavController)
}
composable(MainScreenSealed.Webcams.route) { WebCamsScreen(navController) }
composable(MainScreenSealed.Messages.route) { }
}
}
}
@Preview
@Composable
fun PreviewMainScreen() {
NarodMonTheme {
MainScreen(rememberNavController())
}
}

View file

@ -0,0 +1,18 @@
package ru.nm17.narodmon.ui.navHost
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import ru.nm17.narodmon.R
sealed class MainScreenSealed(
val route: String,
@StringRes val resourceId: Int,
@DrawableRes val iconId: Int
) {
object Sensors : MainScreenSealed("sensors", R.string.sensors_page_title, R.drawable.ic_home)
object Webcams : MainScreenSealed("webcams", R.string.webcams, R.drawable.ic_webcam)
object Messages : MainScreenSealed("messages", R.string.messages, R.drawable.ic_message)
object Settings : MainScreenSealed("settings", R.string.settings, R.drawable.ic_settings)
}

View file

@ -1,28 +0,0 @@
package ru.nm17.narodmon.ui.pages
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import ru.nm17.narodmon.Greeting
import ru.nm17.narodmon.R
import ru.nm17.narodmon.ui.elements.GenericNavScaffold
@ExperimentalMaterial3Api
@Composable
fun SensorsPage(navController: NavController) {
GenericNavScaffold(
title = { Text(text = stringResource(R.string.sensors_page_title))}
) {
Greeting(name = "world", modifier = Modifier.padding(it))
}
}

View file

@ -0,0 +1,260 @@
package ru.nm17.narodmon.ui.sensorsScreen
import androidx.compose.animation.AnimatedVisibility
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.outlined.Settings
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.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.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 ru.nm17.narodmon.R
import ru.nm17.narodmon.db.entities.SensorType
import ru.nm17.narodmon.ui.dialogs.FilterSensorsDialog
import ru.nm17.narodmon.ui.dialogs.SortSensorsDialog
import ru.nm17.narodmon.ui.elements.SensorItem
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.navHost.MainScreenSealed
import ru.nm17.narodmon.ui.theme.NarodMonTheme
import ru.nm17.narodmon.ui.toChipTitle
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SensorsScreen(outerNavController: NavController) {
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_sensors)) },
trailingIcon = {
IconButton(onClick = { outerNavController.navigate(MainScreenSealed.Settings.route) }) {
Icon(
Icons.Outlined.Settings,
contentDescription = stringResource(R.string.settings)
)
}
},
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = if (!searchActive) 8.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())
}
}

View file

@ -0,0 +1,7 @@
package ru.nm17.narodmon.ui.sensorsScreen
enum class SheetHeight {
ExtraExpanded,
Expanded,
Hidden
}

View file

@ -0,0 +1,12 @@
package ru.nm17.narodmon.ui.settings
import androidx.annotation.StringRes
import ru.nm17.narodmon.R
sealed class Settings(val route: String, @StringRes val resourceId: Int) {
object Main : Settings("settings_main", R.string.settings)
object AboutApp : Settings("settings_about_app", R.string.about_app)
object Debug : Settings("settings_debug_menu", R.string.debug_menu)
}

View file

@ -0,0 +1,39 @@
package ru.nm17.narodmon.ui.settings
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsNavigation() {
val navController = rememberNavController()
Scaffold(topBar = {
TopAppBar(title = { Text(text = navController.currentDestination?.route ?: "") })
}) {
NavHost(
navController = navController,
startDestination = Settings.Main.route,
modifier = Modifier.padding(it)
) {
composable(Settings.Main.route) { SettingsScreen(navController) }
composable(Settings.AboutApp.route) { }
composable(Settings.Debug.route) { }
}
}
}
@Preview
@Composable
fun PreviewSettingsNavigation() {
SettingsNavigation()
}

View file

@ -0,0 +1,37 @@
package ru.nm17.narodmon.ui.settings
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import ru.nm17.narodmon.R
import ru.nm17.narodmon.ui.elements.SettingsItem
import ru.nm17.narodmon.ui.theme.NarodMonTheme
@Composable
fun SettingsScreen(navController: NavController) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
SettingsItem(titleId = R.string.debug_menu) {
navController.navigate(Settings.Debug.route)
}
}
item {
SettingsItem(R.string.about_app) {
navController.navigate(Settings.AboutApp.route)
}
}
}
}
@Preview(showBackground = true, showSystemUi = false)
@Composable
fun PreviewSettingsScreen() {
NarodMonTheme {
SettingsScreen(rememberNavController())
}
}

View file

@ -10,15 +10,16 @@ import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
@ -26,23 +27,23 @@ private val LightColorScheme = lightColorScheme(
secondary = PurpleGrey80,
tertiary = Pink80
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun NarodMonTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
@ -57,14 +58,16 @@ fun NarodMonTheme(
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View file

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

View file

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

View file

@ -0,0 +1,62 @@
package ru.nm17.narodmon.ui.webCamsScreen
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.tooling.preview.Preview
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import ru.nm17.narodmon.ui.theme.NarodMonTheme
@Composable
fun WebCamsScreen(navController: NavController) {
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(rememberNavController())
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,5.69l5,4.5V18h-2v-6H9v6H7v-7.81l5,-4.5M12,3L2,12h3v8h6v-6h2v6h6v-8h3L12,3z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4,4h16v12L5.17,16L4,17.17L4,4m0,-2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2L4,2zM6,12h12v2L6,14v-2zM6,9h12v2L6,11L6,9zM6,6h12v2L6,8L6,6z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98 0,-0.34 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.09,-0.16 -0.26,-0.25 -0.44,-0.25 -0.06,0 -0.12,0.01 -0.17,0.03l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.06,-0.02 -0.12,-0.03 -0.18,-0.03 -0.17,0 -0.34,0.09 -0.43,0.25l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98 0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.09,0.16 0.26,0.25 0.44,0.25 0.06,0 0.12,-0.01 0.17,-0.03l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.06,0.02 0.12,0.03 0.18,0.03 0.17,0 0.34,-0.09 0.43,-0.25l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM17.45,11.27c0.04,0.31 0.05,0.52 0.05,0.73 0,0.21 -0.02,0.43 -0.05,0.73l-0.14,1.13 0.89,0.7 1.08,0.84 -0.7,1.21 -1.27,-0.51 -1.04,-0.42 -0.9,0.68c-0.43,0.32 -0.84,0.56 -1.25,0.73l-1.06,0.43 -0.16,1.13 -0.2,1.35h-1.4l-0.19,-1.35 -0.16,-1.13 -1.06,-0.43c-0.43,-0.18 -0.83,-0.41 -1.23,-0.71l-0.91,-0.7 -1.06,0.43 -1.27,0.51 -0.7,-1.21 1.08,-0.84 0.89,-0.7 -0.14,-1.13c-0.03,-0.31 -0.05,-0.54 -0.05,-0.74s0.02,-0.43 0.05,-0.73l0.14,-1.13 -0.89,-0.7 -1.08,-0.84 0.7,-1.21 1.27,0.51 1.04,0.42 0.9,-0.68c0.43,-0.32 0.84,-0.56 1.25,-0.73l1.06,-0.43 0.16,-1.13 0.2,-1.35h1.39l0.19,1.35 0.16,1.13 1.06,0.43c0.43,0.18 0.83,0.41 1.23,0.71l0.91,0.7 1.06,-0.43 1.27,-0.51 0.7,1.21 -1.07,0.85 -0.89,0.7 0.14,1.13zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15,8v8H5V8h10m1,-2H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4V7c0,-0.55 -0.45,-1 -1,-1z"/>
</vector>

Binary file not shown.

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Sensors">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>

View file

@ -1,7 +1,6 @@
<resources>
<string name="app_name">Народный Мониторинг</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="accept_agreements">Я принимаю соглашения</string>
<string name="exit">Выйти</string>
<string name="agreement_dialog_text">
@ -15,4 +14,48 @@
<string name="agreement_dialog_title">Примите необходимые соглашения</string>
<string name="sensors_page_title">Сенсоры</string>
<string name="waiting_for_user_agreement">Ожидаю соглашение пользователя</string>
<string name="search">Поиск</string>
<string name="sensors_filter">Тип датчиков</string>
<string name="sensors_sorting">Сортировка</string>
<string name="sensors_mine">Мои датчики</string>
<string name="filter_temp_dew_point">Температура точки росы</string>
<string name="filter_temp">Температура воздуха</string>
<string name="filter_temp_water">Температура воды</string>
<string name="filter_temp_ground">Температура почвы</string>
<string name="filter_humidity">Влажность</string>
<string name="filter_pressure">Давление</string>
<string name="filter_lightness">Освещённость</string>
<string name="filter_uv">УФ-индекс</string>
<string name="filter_radiation">Радиация</string>
<string name="filter_rainfall">Осадки</string>
<string name="filter_dust">Запылённость</string>
<string name="filter_wind_speed">Скорость ветра</string>
<string name="filter_wind_direction">Направление ветра</string>
<string name="filter_concentration">Концентрация</string>
<string name="filter_power">Мощность</string>
<string name="filter_voltage">Напряжение</string>
<string name="filter_amperage">Сила тока</string>
<string name="filter_energy">Энергия</string>
<string name="filter_battery">% батареи</string>
<string name="filter_rxtx">Rx/Tx трафик</string>
<string name="filter_signal">Сигнал в dBm</string>
<string name="filter_water_meter">Счётчик воды</string>
<string name="filter_time">Время работы</string>
<string name="sensors_filter_title">Тип датчиков</string>
<string name="sort_by_distance">От ближних к дальним</string>
<string name="sort_by_type">По типу (от А до Я)</string>
<string name="sort_by_type_desc">По типу (от Я до А)</string>
<string name="sort_by_name">По названию (от А до Я)</string>
<string name="sort_by_name_desc">По названию (от Я до А)</string>
<string name="sensors_sort_title">Сортировка датчиков</string>
<string name="sort_update_time">По времени обновления</string>
<string name="apply">Применить</string>
<string name="sort_by_distance_desc">От дальних к ближним</string>
<string name="cancel1">Отменить</string>
<string name="webcams">Веб-камеры</string>
<string name="messages">Сообщения</string>
<string name="settings">Настройки</string>
<string name="search_sensors">Поиск датчиков</string>
<string name="about_app">О приложении</string>
<string name="debug_menu">Debug-меню</string>
</resources>

View file

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