diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 217e093..baa4f27 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,4 +1,3 @@
-
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.androidApplication)
@@ -7,6 +6,7 @@ plugins {
kotlin("plugin.serialization") version "1.8.21"
}
+
android {
namespace = "ru.nm17.narodmon"
compileSdk = 33
@@ -27,7 +27,10 @@ android {
buildTypes {
release {
isMinifyEnabled = false
- proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
}
}
compileOptions {
@@ -62,6 +65,7 @@ dependencies {
implementation(libs.material3)
implementation(libs.androidx.datastore.core.android)
implementation(libs.androidx.room.common)
+ implementation(libs.androidx.security.crypto.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
@@ -119,5 +123,25 @@ dependencies {
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")
+
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5f5c06a..4a6a90d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,9 @@
+
+
+
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ru/nm17/narodmon/MainActivity.kt b/app/src/main/java/ru/nm17/narodmon/MainActivity.kt
index 02ea5ff..4a14373 100644
--- a/app/src/main/java/ru/nm17/narodmon/MainActivity.kt
+++ b/app/src/main/java/ru/nm17/narodmon/MainActivity.kt
@@ -4,47 +4,19 @@
package ru.nm17.narodmon
-import android.content.Intent
-import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.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.Divider
-import androidx.compose.material3.DrawerValue
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.Surface
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.LaunchedEffect
-import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -52,34 +24,23 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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.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.composable
import androidx.navigation.compose.rememberNavController
import androidx.room.Room
-import kotlinx.coroutines.CoroutineScope
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKeys
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 ru.nm17.narodmon.db.AppDatabase
import ru.nm17.narodmon.db.entities.KVSetting
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.theme.NarodMonTheme
+
@Composable
fun AppNavHost() {
val navController = rememberNavController()
@@ -110,6 +71,20 @@ class MainActivity : ComponentActivity() {
AppDatabase::class.java, "data"
).build()
+ val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
+
+ val sharedPreferences = EncryptedSharedPreferences.create(
+ "secret_shared_prefs",
+ masterKeyAlias,
+ createDeviceProtectedStorageContext(),
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+
+ // use the shared preferences and editor as you normally would
+
+ // use the shared preferences and editor as you normally would
+ val credSharedPreferences = sharedPreferences
@@ -118,9 +93,6 @@ class MainActivity : ComponentActivity() {
val coScope = rememberCoroutineScope()
-
-
-
//var asd = getPreferences()
NarodMonTheme {
@@ -145,7 +117,9 @@ class MainActivity : ComponentActivity() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
- modifier = Modifier.fillMaxSize().padding(it)
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(it)
) {
CircularProgressIndicator()
Text(text = stringResource(R.string.waiting_for_user_agreement))
diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/RateLimitingSemaphore.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/RateLimitingSemaphore.kt
new file mode 100644
index 0000000..f35881a
--- /dev/null
+++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/RateLimitingSemaphore.kt
@@ -0,0 +1,33 @@
+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(
+ 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")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/api_key.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/api_key.kt
new file mode 100644
index 0000000..20e4c2e
--- /dev/null
+++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/api_key.kt
@@ -0,0 +1,7 @@
+package ru.nm17.narodmon.appNarodMonApiClient
+
+/**
+ * Прятать данное значение нет смысла, ибо так или иначе
+ * кто-нибудь догадается его зареверсить в готовых сборках.
+ */
+const val API_KEY = "45z3du2MZY0vW"
\ No newline at end of file
diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/client.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/client.kt
new file mode 100644
index 0000000..ec594fe
--- /dev/null
+++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/client.kt
@@ -0,0 +1,151 @@
+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 {
+ 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 deserializeResponse(resp: HttpResponse): Result4k