Compare commits
No commits in common. "72b82fa4e4f846c7e33ce9f45b0938b9f1bb2920" and "ecb76ca5949e1200fa3e7f56287df5919a2dd710" have entirely different histories.
72b82fa4e4
...
ecb76ca594
20 changed files with 59 additions and 461 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.androidApplication)
|
alias(libs.plugins.androidApplication)
|
||||||
|
@ -6,7 +7,6 @@ plugins {
|
||||||
kotlin("plugin.serialization") version "1.8.21"
|
kotlin("plugin.serialization") version "1.8.21"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "ru.nm17.narodmon"
|
namespace = "ru.nm17.narodmon"
|
||||||
compileSdk = 33
|
compileSdk = 33
|
||||||
|
@ -27,10 +27,7 @@ android {
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
||||||
"proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
@ -65,7 +62,6 @@ dependencies {
|
||||||
implementation(libs.material3)
|
implementation(libs.material3)
|
||||||
implementation(libs.androidx.datastore.core.android)
|
implementation(libs.androidx.datastore.core.android)
|
||||||
implementation(libs.androidx.room.common)
|
implementation(libs.androidx.room.common)
|
||||||
implementation(libs.androidx.security.crypto.ktx)
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
|
@ -123,25 +119,5 @@ dependencies {
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
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")
|
|
||||||
|
|
||||||
// For App Authentication APIs
|
|
||||||
implementation("androidx.security:security-app-authenticator:1.0.0-alpha02")
|
|
||||||
|
|
||||||
// For App Authentication API testing
|
|
||||||
androidTestImplementation("androidx.security:security-app-authenticator:1.0.0-alpha01")
|
|
||||||
|
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,9 +2,6 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
@ -14,8 +11,12 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.NarodMon"
|
android:theme="@style/Theme.NarodMon"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
<service
|
||||||
|
android:name=".DataStoreService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:process=":data_store_process"
|
||||||
|
android:exported="false"></service>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
@ -28,9 +29,6 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -4,19 +4,47 @@
|
||||||
|
|
||||||
package ru.nm17.narodmon
|
package ru.nm17.narodmon
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.DrawerValue
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FabPosition
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
|
import androidx.compose.material3.NavigationDrawerItem
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -24,23 +52,34 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.security.crypto.EncryptedSharedPreferences
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import androidx.security.crypto.MasterKeys
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.nm17.narodmon.db.AppDatabase
|
import ru.nm17.narodmon.db.AppDatabase
|
||||||
import ru.nm17.narodmon.db.entities.KVSetting
|
import ru.nm17.narodmon.db.entities.KVSetting
|
||||||
import ru.nm17.narodmon.ui.elements.AgreementDialog
|
import ru.nm17.narodmon.ui.elements.AgreementDialog
|
||||||
|
import ru.nm17.narodmon.ui.elements.GenericNavScaffold
|
||||||
import ru.nm17.narodmon.ui.pages.SensorsPage
|
import ru.nm17.narodmon.ui.pages.SensorsPage
|
||||||
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
import ru.nm17.narodmon.ui.theme.NarodMonTheme
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavHost() {
|
fun AppNavHost() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
@ -68,22 +107,6 @@ class MainActivity : ComponentActivity() {
|
||||||
AppDatabase::class.java, "data"
|
AppDatabase::class.java, "data"
|
||||||
).build()
|
).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
|
|
||||||
)
|
|
||||||
|
|
||||||
// use the shared preferences and editor as you normally would
|
|
||||||
|
|
||||||
// use the shared preferences and editor as you normally would
|
|
||||||
val credSharedPreferences = sharedPreferences
|
|
||||||
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val coScope = rememberCoroutineScope()
|
val coScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
@ -111,9 +134,7 @@ class MainActivity : ComponentActivity() {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize().padding(it)
|
||||||
.fillMaxSize()
|
|
||||||
.padding(it)
|
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
Text(text = stringResource(R.string.waiting_for_user_agreement))
|
Text(text = stringResource(R.string.waiting_for_user_agreement))
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
private class PermissionSlip
|
|
||||||
|
|
||||||
class RateLimitingSemaphore<T>(
|
|
||||||
val availablePermits: AtomicInteger,
|
|
||||||
var lastPermitHanded: AtomicInteger,
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun acquire() {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
|
||||||
fun release() {
|
|
||||||
val current = availablePermits.getAcquire()
|
|
||||||
if (current > 0) {
|
|
||||||
availablePermits.setRelease(current - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun tryAcquire(): Boolean {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Прятать данное значение нет смысла, ибо так или иначе
|
|
||||||
* кто-нибудь догадается его зареверсить в готовых сборках.
|
|
||||||
*/
|
|
||||||
const val API_KEY = "45z3du2MZY0vW"
|
|
|
@ -1,151 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient
|
|
||||||
|
|
||||||
import dev.forkhandles.result4k.Failure
|
|
||||||
import dev.forkhandles.result4k.Result4k
|
|
||||||
import dev.forkhandles.result4k.Success
|
|
||||||
import io.ktor.client.HttpClient
|
|
||||||
import io.ktor.client.call.body
|
|
||||||
import io.ktor.client.engine.okhttp.OkHttp
|
|
||||||
import io.ktor.client.request.post
|
|
||||||
import io.ktor.client.request.setBody
|
|
||||||
import io.ktor.client.statement.HttpResponse
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.http.HttpHeaders
|
|
||||||
import io.ktor.http.contentType
|
|
||||||
import io.ktor.http.headers
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import ru.nm17.narodmon.appNarodMonApiClient.types.APIError
|
|
||||||
import ru.nm17.narodmon.appNarodMonApiClient.types.AppInitRequest
|
|
||||||
import ru.nm17.narodmon.appNarodMonApiClient.types.AppInitResponse
|
|
||||||
import ru.nm17.narodmon.appNarodMonApiClient.types.MandatoryParams
|
|
||||||
import ru.nm17.narodmon.db.AppDatabase
|
|
||||||
import ru.nm17.narodmon.db.entities.KVSetting
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
const val allowedUuidChars = "abcdef0123456789"
|
|
||||||
const val uuidCharLength = 16
|
|
||||||
|
|
||||||
private val apiHttpClient = HttpClient(OkHttp) {
|
|
||||||
headers {
|
|
||||||
append(HttpHeaders.UserAgent, "IoTMonitor")
|
|
||||||
append(HttpHeaders.AcceptEncoding, "br;q=1.0, gzip;q=0.6, deflate;q=0.6")
|
|
||||||
}
|
|
||||||
engine {
|
|
||||||
// this: OkHttpConfig
|
|
||||||
config {
|
|
||||||
// this: OkHttpClient.Builder
|
|
||||||
followRedirects(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Client private constructor(
|
|
||||||
private var uuid: String,
|
|
||||||
private var _rawHttpClient: HttpClient
|
|
||||||
) {
|
|
||||||
private val lang: String = Locale.getDefault().language
|
|
||||||
|
|
||||||
val httpClient1Min = flow {
|
|
||||||
while (true) {
|
|
||||||
emit(_rawHttpClient)
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
}.flowOn(Dispatchers.Default)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromDb(db: AppDatabase): Flow<Client> {
|
|
||||||
return flow {
|
|
||||||
val uuid = db.kvDao().getByKey("current_user_uuid")?.value
|
|
||||||
|
|
||||||
if (uuid == null) {
|
|
||||||
val newUuid =
|
|
||||||
List(uuidCharLength) { allowedUuidChars.random() }.joinToString("")
|
|
||||||
db.kvDao().setAll(KVSetting("current_user_uuid", newUuid))
|
|
||||||
db.kvDao().setAll(
|
|
||||||
KVSetting(
|
|
||||||
"current_user_uuid_generated_at_unix_ms_utc",
|
|
||||||
Json.encodeToString(Clock.System.now().epochSeconds)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
emit(Client(newUuid, apiHttpClient))
|
|
||||||
} else {
|
|
||||||
emit(Client(uuid, apiHttpClient))
|
|
||||||
}
|
|
||||||
}.flowOn(Dispatchers.IO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMandatoryParams(cmd: String): MandatoryParams {
|
|
||||||
return MandatoryParams(
|
|
||||||
cmd,
|
|
||||||
this@Client.lang,
|
|
||||||
this@Client.uuid,
|
|
||||||
API_KEY
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getJSONRequestBody(cmd: String, request: @Serializable Any) {
|
|
||||||
mergeJsonObjects(
|
|
||||||
Json.encodeToJsonElement(request).jsonObject,
|
|
||||||
Json.encodeToJsonElement(
|
|
||||||
getMandatoryParams(cmd)
|
|
||||||
).jsonObject
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend inline fun <reified RT> deserializeResponse(resp: HttpResponse): Result4k<RT, APIError> {
|
|
||||||
val body: String = resp.body()
|
|
||||||
return try {
|
|
||||||
Success(Json.decodeFromString(body))
|
|
||||||
} catch (err: SerializationException) {
|
|
||||||
Failure(Json.decodeFromString(body))
|
|
||||||
} catch (err: Exception) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun appInit(request: AppInitRequest): Flow<Result4k<AppInitResponse, APIError>> {
|
|
||||||
val client = this.httpClient1Min
|
|
||||||
return flow<Result4k<AppInitResponse, APIError>> {
|
|
||||||
|
|
||||||
val resp = client.post("https://narodmon.ru/api") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(
|
|
||||||
getJSONRequestBody("appInit", request)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(deserializeResponse(resp))
|
|
||||||
|
|
||||||
}.flowOn(Dispatchers.IO)
|
|
||||||
|
|
||||||
}
|
|
||||||
public fun appInit(request: AppInitRequest): Flow<Result4k<AppInitResponse, APIError>> {
|
|
||||||
val client = this.httpClient1Min
|
|
||||||
return flow<Result4k<AppInitResponse, APIError>> {
|
|
||||||
|
|
||||||
val resp = client.post("https://narodmon.ru/api") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(
|
|
||||||
getJSONRequestBody("appInit", request)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(deserializeResponse(resp))
|
|
||||||
|
|
||||||
}.flowOn(Dispatchers.IO)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient.types
|
|
||||||
|
|
||||||
data class APIError(
|
|
||||||
var errno: Int,
|
|
||||||
var error: String
|
|
||||||
)
|
|
|
@ -1,18 +0,0 @@
|
||||||
@file:OptIn(ExperimentalSerializationApi::class)
|
|
||||||
|
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient.types
|
|
||||||
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.JsonNames
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AppInitRequest (
|
|
||||||
@JsonNames("version")
|
|
||||||
val appVersion: String,
|
|
||||||
val platform: String,
|
|
||||||
val model: String,
|
|
||||||
val width: Int,
|
|
||||||
val utc: Int
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient.types
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
enum class VipStatus(var value: Int) {
|
|
||||||
IsVip(1),
|
|
||||||
NotVip(0)
|
|
||||||
}
|
|
||||||
@Serializable
|
|
||||||
data class SensorTypes(
|
|
||||||
@SerialName("type")
|
|
||||||
var typeCode: Long,
|
|
||||||
var name: String,
|
|
||||||
var types: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AppInitResponse(
|
|
||||||
var latest: String?,
|
|
||||||
var url: String?,
|
|
||||||
var login: String,
|
|
||||||
var vip: Boolean, // TODO: Change this if it doesn't work
|
|
||||||
var lat: Long,
|
|
||||||
var long: Double,
|
|
||||||
var addr: Double,
|
|
||||||
/**
|
|
||||||
* Timestamp в секундах спустя Epoch по часовой зоне пользователя
|
|
||||||
*/
|
|
||||||
var timestamp: Long,
|
|
||||||
var types: Array<SensorTypes>,
|
|
||||||
var favorites: Array<Long>
|
|
||||||
) {
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as AppInitResponse
|
|
||||||
|
|
||||||
if (latest != other.latest) return false
|
|
||||||
if (url != other.url) return false
|
|
||||||
if (login != other.login) return false
|
|
||||||
if (vip != other.vip) return false
|
|
||||||
if (lat != other.lat) return false
|
|
||||||
if (long != other.long) return false
|
|
||||||
if (addr != other.addr) return false
|
|
||||||
if (timestamp != other.timestamp) return false
|
|
||||||
if (!types.contentEquals(other.types)) return false
|
|
||||||
return favorites.contentEquals(other.favorites)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = latest.hashCode()
|
|
||||||
result = 31 * result + url.hashCode()
|
|
||||||
result = 31 * result + login.hashCode()
|
|
||||||
result = 31 * result + vip.hashCode()
|
|
||||||
result = 31 * result + lat.hashCode()
|
|
||||||
result = 31 * result + long.hashCode()
|
|
||||||
result = 31 * result + addr.hashCode()
|
|
||||||
result = 31 * result + timestamp.hashCode()
|
|
||||||
result = 31 * result + types.contentHashCode()
|
|
||||||
result = 31 * result + favorites.contentHashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient.types
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
enum class NarodMonLanguages(val value: String) {
|
|
||||||
Russian("ru"),
|
|
||||||
English("en"),
|
|
||||||
Ukrainian("uk")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MandatoryParams(
|
|
||||||
var cmd: String?,
|
|
||||||
var lang: String,
|
|
||||||
var uuid: String,
|
|
||||||
var apiKey: String?,
|
|
||||||
)
|
|
|
@ -1,18 +0,0 @@
|
||||||
package ru.nm17.narodmon.appNarodMonApiClient
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import ru.nm17.narodmon.appNarodMonApiClient.types.MandatoryParams
|
|
||||||
|
|
||||||
fun mergeJsonObjects(json1: JsonObject, json2: JsonObject): JsonObject {
|
|
||||||
val result = mutableMapOf<String, kotlinx.serialization.json.JsonElement>()
|
|
||||||
result.putAll(json1)
|
|
||||||
json2.forEach { (key, value) ->
|
|
||||||
result[key] = value
|
|
||||||
}
|
|
||||||
return JsonObject(result)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package ru.nm17.narodmon.db.entities
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(tableName = "settings")
|
|
||||||
data class SensorType(
|
|
||||||
@PrimaryKey val code: Long,
|
|
||||||
val name: String,
|
|
||||||
val unit: String,
|
|
||||||
)
|
|
|
@ -29,11 +29,11 @@ import ru.nm17.narodmon.R
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
fun AgreementDialog(onClick: () -> Unit) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { /* Делаем ничего пока пользователь не примет соглашение. */ },
|
onDismissRequest = { /* Ничего не делаем пока пользователь не примет соглашение. */ },
|
||||||
title = { Text(text = stringResource(id = R.string.agreement_dialog_title)) },
|
title = { Text(text = stringResource(id = R.string.agreement_dialog_title)) },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
|
@ -83,8 +83,7 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||||
onClick = { exitProcess(0) }) {
|
onClick = { exitProcess(0) }) {
|
||||||
Text(text = stringResource(id = R.string.exit))
|
Text(text = stringResource(id = R.string.exit))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
modifier = modifier
|
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -2,13 +2,9 @@
|
||||||
|
|
||||||
package ru.nm17.narodmon.ui.elements
|
package ru.nm17.narodmon.ui.elements
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AccountCircle
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material3.Divider
|
import androidx.compose.material3.Divider
|
||||||
|
@ -18,7 +14,6 @@ import androidx.compose.material3.FabPosition
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalDrawerSheet
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
import androidx.compose.material3.ModalNavigationDrawer
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
|
@ -29,15 +24,11 @@ import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,29 +42,16 @@ fun GenericNavScaffold(title: @Composable () -> Unit, content: @Composable (Padd
|
||||||
val coScope = rememberCoroutineScope()
|
val coScope = rememberCoroutineScope()
|
||||||
val navController = rememberNavController() // TODO: Используй меня
|
val navController = rememberNavController() // TODO: Используй меня
|
||||||
|
|
||||||
LaunchedEffect(key1 = Unit) {
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
ModalNavigationDrawer(drawerState = expanded, drawerContent = {
|
ModalNavigationDrawer(drawerState = expanded, drawerContent = {
|
||||||
ModalDrawerSheet {
|
ModalDrawerSheet {
|
||||||
ListItem(
|
Text("Drawer title", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleLarge)
|
||||||
leadingContent = {
|
|
||||||
Icon(Icons.Default.AccountCircle, contentDescription = "")
|
|
||||||
},
|
|
||||||
headlineText = { Text(text = "Гость", style = MaterialTheme.typography.titleLarge)},
|
|
||||||
modifier = Modifier.height(72.dp)
|
|
||||||
)
|
|
||||||
Divider()
|
Divider()
|
||||||
Column(modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp)) {
|
|
||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
label = { Text(text = "Drawer Item") },
|
label = { Text(text = "Drawer Item") },
|
||||||
selected = true,
|
selected = true,
|
||||||
onClick = { /*TODO*/ }
|
onClick = { navController.navigate("sensors") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
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
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<network-security-config>
|
|
||||||
<domain-config>
|
|
||||||
<domain includeSubdomains="true">narodmon.ru</domain>
|
|
||||||
<trust-anchors>
|
|
||||||
<certificates src="@raw/isrgrootx1"/>
|
|
||||||
</trust-anchors>
|
|
||||||
</domain-config>
|
|
||||||
</network-security-config>
|
|
|
@ -3,7 +3,6 @@ package ru.nm17.narodmon
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
@ -14,6 +13,5 @@ class ExampleUnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun addition_isCorrect() {
|
fun addition_isCorrect() {
|
||||||
assertEquals(4, 2 + 2)
|
assertEquals(4, 2 + 2)
|
||||||
URL("https://tile.openstreetmap.org/${zoom}/${row}/${col}").openStream()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,5 +6,4 @@ plugins {
|
||||||
id("com.google.devtools.ksp") version "1.8.21-1.0.11" apply false
|
id("com.google.devtools.ksp") version "1.8.21-1.0.11" apply false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true // Needed to make the Suppress annotation work for the plugins block
|
true // Needed to make the Suppress annotation work for the plugins block
|
|
@ -11,7 +11,6 @@ compose-bom = "2023.03.00"
|
||||||
navigation-fragment = "2.5.3"
|
navigation-fragment = "2.5.3"
|
||||||
datastore-core-android = "1.1.0-alpha04"
|
datastore-core-android = "1.1.0-alpha04"
|
||||||
room-common = "2.5.1"
|
room-common = "2.5.1"
|
||||||
security-crypto-ktx = "1.0.0"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigation-fragment" }
|
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigation-fragment" }
|
||||||
|
@ -31,7 +30,6 @@ ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
material3 = { group = "androidx.compose.material3", name = "material3" }
|
material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastore-core-android" }
|
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastore-core-android" }
|
||||||
androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "room-common" }
|
androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "room-common" }
|
||||||
androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "security-crypto-ktx" }
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
@ -13,6 +13,5 @@ dependencyResolutionManagement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
rootProject.name = "Народный Мониторинг"
|
rootProject.name = "Народный Мониторинг"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|
Loading…
Add table
Reference in a new issue