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 { + 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> { + val client = this.httpClient1Min + return flow> { + + 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> { + val client = this.httpClient1Min + return flow> { + + val resp = client.post("https://narodmon.ru/api") { + contentType(ContentType.Application.Json) + setBody( + getJSONRequestBody("appInit", request) + ) + } + + emit(deserializeResponse(resp)) + + }.flowOn(Dispatchers.IO) + + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/APIError.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/APIError.kt new file mode 100644 index 0000000..f20516b --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/APIError.kt @@ -0,0 +1,6 @@ +package ru.nm17.narodmon.appNarodMonApiClient.types + +data class APIError( + var errno: Int, + var error: String +) diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitRequest.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitRequest.kt index 5a5bd22..d83b3a9 100644 --- a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitRequest.kt +++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitRequest.kt @@ -3,17 +3,8 @@ package ru.nm17.narodmon.appNarodMonApiClient.types import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNames -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.jsonObject @Serializable data class AppInitRequest ( diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitResponse.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitResponse.kt new file mode 100644 index 0000000..289cfd9 --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/AppInitResponse.kt @@ -0,0 +1,66 @@ +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, + var favorites: Array +) { + 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 + } +} diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/MandatoryParams.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/MandatoryParams.kt index 598c8d5..45db650 100644 --- a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/MandatoryParams.kt +++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/types/MandatoryParams.kt @@ -6,13 +6,13 @@ import kotlinx.serialization.Serializable enum class NarodMonLanguages(val value: String) { Russian("ru"), English("en"), - Uk("uk") + Ukrainian("uk") } @Serializable data class MandatoryParams( - var cmd: String, - var lang: NarodMonLanguages, + var cmd: String?, + var lang: String, var uuid: String, - var apiKey: String, + var apiKey: String?, ) diff --git a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/utils.kt b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/utils.kt index 025dee1..cd42c90 100644 --- a/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/utils.kt +++ b/app/src/main/java/ru/nm17/narodmon/appNarodMonApiClient/utils.kt @@ -1,6 +1,11 @@ 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() @@ -9,4 +14,5 @@ fun mergeJsonObjects(json1: JsonObject, json2: JsonObject): JsonObject { result[key] = value } return JsonObject(result) -} \ No newline at end of file +} + diff --git a/app/src/main/java/ru/nm17/narodmon/db/entities/SensorType.kt b/app/src/main/java/ru/nm17/narodmon/db/entities/SensorType.kt new file mode 100644 index 0000000..006ecce --- /dev/null +++ b/app/src/main/java/ru/nm17/narodmon/db/entities/SensorType.kt @@ -0,0 +1,11 @@ +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, +) diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt index 0abf033..c50a53f 100644 --- a/app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/AgreementDialog.kt @@ -33,7 +33,7 @@ fun AgreementDialog(modifier: Modifier = Modifier, onClick: () -> Unit) { val uriHandler = LocalUriHandler.current AlertDialog( - onDismissRequest = { /* Ничего не делаем пока пользователь не примет соглашение. */ }, + onDismissRequest = { /* Делаем ничего пока пользователь не примет соглашение. */ }, title = { Text(text = stringResource(id = R.string.agreement_dialog_title)) }, text = { Column { diff --git a/app/src/main/java/ru/nm17/narodmon/ui/elements/Scaffolds.kt b/app/src/main/java/ru/nm17/narodmon/ui/elements/Scaffolds.kt index 28fa96c..72709c1 100644 --- a/app/src/main/java/ru/nm17/narodmon/ui/elements/Scaffolds.kt +++ b/app/src/main/java/ru/nm17/narodmon/ui/elements/Scaffolds.kt @@ -29,12 +29,14 @@ 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.rememberCoroutineScope 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.navigation.compose.rememberNavController +import kotlinx.coroutines.delay import kotlinx.coroutines.launch /** @@ -48,6 +50,10 @@ fun GenericNavScaffold(title: @Composable () -> Unit, content: @Composable (Padd val coScope = rememberCoroutineScope(); val navController = rememberNavController() // TODO: Используй меня + LaunchedEffect(key1 = Unit) { + delay(1000) + } + ModalNavigationDrawer(drawerState = expanded, drawerContent = { ModalDrawerSheet { ListItem( diff --git a/app/src/main/res/raw/isrgrootx1.pem b/app/src/main/res/raw/isrgrootx1.pem new file mode 100644 index 0000000..b85c803 --- /dev/null +++ b/app/src/main/res/raw/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..360b762 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + narodmon.ru + + + + + diff --git a/app/src/test/java/ru/nm17/narodmon/ExampleUnitTest.kt b/app/src/test/java/ru/nm17/narodmon/ExampleUnitTest.kt index 8660651..0a47494 100644 --- a/app/src/test/java/ru/nm17/narodmon/ExampleUnitTest.kt +++ b/app/src/test/java/ru/nm17/narodmon/ExampleUnitTest.kt @@ -3,6 +3,7 @@ package ru.nm17.narodmon import org.junit.Test import org.junit.Assert.* +import java.net.URL /** * Example local unit test, which will execute on the development machine (host). @@ -13,5 +14,6 @@ class ExampleUnitTest { @Test fun addition_isCorrect() { assertEquals(4, 2 + 2) + URL("https://tile.openstreetmap.org/${zoom}/${row}/${col}").openStream() } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b82cb02..05559cd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,4 +6,5 @@ plugins { 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 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f788ece..a952870 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ compose-bom = "2023.03.00" navigation-fragment = "2.5.3" datastore-core-android = "1.1.0-alpha04" room-common = "2.5.1" +security-crypto-ktx = "1.0.0" [libraries] androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigation-fragment" } @@ -30,6 +31,7 @@ ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } material3 = { group = "androidx.compose.material3", name = "material3" } 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-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "security-crypto-ktx" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index db4a857..718a0ce 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,5 +13,6 @@ dependencyResolutionManagement { } } + rootProject.name = "Народный Мониторинг" include(":app")