🚧 Works on authentication

This commit is contained in:
2026-04-16 14:48:31 +02:00
parent 36c6c50626
commit 67b5b6ac6c
11 changed files with 138 additions and 121 deletions
+1 -1
View File
@@ -3,4 +3,4 @@ exposed_version=0.61.0
h2_version=2.3.232
kotlin_version=2.1.10
ktor_version=3.1.3
logback_version=1.4.14
logback_version=1.5.13
@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -4,34 +4,13 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -45,20 +24,26 @@ import dev.svitan.antifed.ui.components.DropdownList
import dev.svitan.antifed.ui.theme.AntiFedTheme
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class)
class AuthActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AntiFedTheme {
var serverUrl by remember { mutableStateOf("") }
var token by remember { mutableStateOf("") }
var authIndex by remember { mutableIntStateOf(-1) }
var authId by remember { mutableStateOf("") }
var authIndex by remember { mutableIntStateOf(-1) }
var auths by remember { mutableStateOf<List<AuthDTO>>(emptyList()) }
var needToCheckUrl by remember { mutableStateOf(false) }
var checkingUrl by remember { mutableStateOf(false) }
@@ -68,110 +53,99 @@ class AuthActivity : ComponentActivity() {
var checkingToken by remember { mutableStateOf(false) }
var isTokenOk by remember { mutableStateOf(false) }
var auths by remember { mutableStateOf(listOf<AuthDTO>()) }
val prefs = LocalContext.current.getSharedPreferences(
stringResource(R.string.settings_prefs_key),
MODE_PRIVATE
val context = LocalContext.current
val prefs = context.getSharedPreferences(
stringResource(R.string.settings_prefs_key), MODE_PRIVATE
)
val loadedServerUrl = prefs.getString(getString(R.string.server_url_key), "") ?: ""
if (loadedServerUrl.isNotBlank()) {
serverUrl = loadedServerUrl
}
val loadedToken = prefs.getString(getString(R.string.token_key), "") ?: ""
if (loadedToken.isNotBlank()) {
token = loadedToken
}
LaunchedEffect(Unit) {
serverUrl = prefs.getString(getString(R.string.server_url_key), "") ?: ""
token = prefs.getString(getString(R.string.token_key), "") ?: ""
authId = prefs.getString(getString(R.string.auth_key), "") ?: ""
isServerUrlOk = prefs.getBoolean(getString(R.string.server_url_okay_key), false)
}
fun serverUrlMatches(): Boolean {
var re = Regex(
"""https?://[a-zA-Z0-9\\.]+\\.[a-zA-Z0-9]+""",
RegexOption.DOT_MATCHES_ALL
)
return re.containsMatchIn(serverUrl)
val regex = Regex("""https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?""")
return regex.matches(serverUrl)
}
LaunchedEffect(serverUrl) {
if (!needToCheckUrl) return@LaunchedEffect
else {
needToCheckUrl = false
isServerUrlOk = false
prefs.edit {
putBoolean(getString(R.string.server_url_okay_key), false)
}
}
if (serverUrl.isBlank()) return@LaunchedEffect
if (!serverUrlMatches()) return@LaunchedEffect
if (serverUrl.isBlank() || !serverUrlMatches()) return@LaunchedEffect
delay(1000)
checkingUrl = true
isServerUrlOk = try {
val response = client.get(serverUrl)
response.status == HttpStatusCode.OK
} catch (_: Exception) {
false
}
checkingUrl = false
needToCheckUrl = false
isServerUrlOk = response.status == HttpStatusCode.OK
prefs.edit {
putBoolean(getString(R.string.server_url_okay_key), isServerUrlOk)
putBoolean(
getString(R.string.server_url_okay_key), isServerUrlOk
)
}
}
LaunchedEffect(token) {
if (!needToCheckToken) return@LaunchedEffect
else isTokenOk = false
needToCheckToken = false
isTokenOk = false
if (!isServerUrlOk) return@LaunchedEffect
if (token.isBlank()) return@LaunchedEffect
if (!isServerUrlOk || token.isBlank()) return@LaunchedEffect
delay(1000)
checkingToken = true
val response = client.get("$serverUrl/auth")
if (response.status != HttpStatusCode.OK) {
checkingToken = false
needToCheckToken = false
isTokenOk = false
return@LaunchedEffect
try {
val response = client.get("$serverUrl/auth") {
headers {
append("Authorization", "Bearer $token")
}
}
auths = response.body<List<AuthDTO>>()
if (authId.isNotBlank()) {
if (response.status == HttpStatusCode.OK) {
auths = response.body()
authIndex = auths.indexOfFirst { it.id == authId }
isTokenOk = true
}
} catch (_: Exception) {
isTokenOk = false
}
checkingToken = false
needToCheckToken = false
isTokenOk = true
}
Scaffold(
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
titleContentColor = MaterialTheme.colorScheme.primary
),
title = {
Text(stringResource(R.string.auth))
},
title = { Text(stringResource(R.string.auth)) },
navigationIcon = {
IconButton(onClick = { finish() }) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
( Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = stringResource(R.string.go_back)
)
}
}
)
},
modifier = Modifier.fillMaxSize()
) { innerPadding ->
})
}) { padding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.padding(padding)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(stringResource(R.string.auth), fontSize = 24.sp)
Spacer(modifier = Modifier.height(16.dp))
@@ -179,69 +153,83 @@ class AuthActivity : ComponentActivity() {
TextField(
value = serverUrl,
onValueChange = {
needToCheckUrl = true
serverUrl = it
needToCheckUrl = true
prefs.edit {
putString(getString(R.string.server_url_key), it)
putString(
getString(R.string.server_url_key), it
)
}
},
label = { Text(stringResource(R.string.server_url)) },
singleLine = true,
isError = !isServerUrlOk,
isError = !isServerUrlOk && serverUrl.isNotBlank(),
trailingIcon = {
if (checkingUrl)
if (checkingUrl) {
CircularProgressIndicator(
modifier = Modifier.width(36.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant
modifier = Modifier.size(24.dp)
)
}
)
})
Spacer(modifier = Modifier.height(12.dp))
TextField(
value = token,
onValueChange = {
needToCheckToken = true
token = it
needToCheckToken = true
prefs.edit {
putString(getString(R.string.token_key), it)
putString(
getString(R.string.token_key), it
)
}
},
label = { Text(stringResource(R.string.token)) },
singleLine = true,
isError = isTokenOk,
enabled = isServerUrlOk,
isError = !isTokenOk && token.isNotBlank(),
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password
),
trailingIcon = {
if (checkingToken) {
CircularProgressIndicator(
modifier = Modifier.width(36.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant
modifier = Modifier.size(24.dp)
)
}
}
)
})
Spacer(modifier = Modifier.height(12.dp))
if (auths.isNotEmpty())
if (auths.isNotEmpty()) {
DropdownList(
itemList = auths.map { it -> it.name },
selectedIndex = 0,
modifier = Modifier,
itemList = auths.map { it.name },
selectedIndex = authIndex.coerceAtLeast(0),
onItemClick = {
authIndex = it
authId = auths[it].id
prefs.edit {
putInt(getString(R.string.auth_key), it)
}
}
putString(
getString(R.string.auth_key), authId
)
}
},
modifier = Modifier
)
}
Spacer(modifier = Modifier.height(16.dp))
FloatingActionButton(
onClick = { println("creating new auth!!") }
) {
Icon(Icons.Default.Add, stringResource(R.string.create_auth))
onClick = {
println("creating new auth!!")
}) {
Icon(
Icons.Default.Add,
contentDescription = stringResource(R.string.create_auth)
)
}
}
}
@@ -83,7 +83,6 @@ fun DropdownList(
Text(text = item)
}
}
}
}
}
@@ -7,4 +7,5 @@
<string name="go_back">Ísť späť</string>
<string name="token">Token</string>
<string name="create_auth">Vytvoriť novú auth</string>
<string name="bio">Biometrika</string>
</resources>
@@ -12,6 +12,7 @@
<string name="token_key" translatable="false">token</string>
<string name="auth_key" translatable="false">auth_id</string>
<string name="create_auth">Create new auth</string>
<string name="bio">Biometrics</string>
<!-- Strings used for fragments for navigation -->
</resources>
+10
View File
@@ -21,3 +21,13 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.defaults.buildfeatures.resvalues=true
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.enableAppCompileTimeRClass=false
android.usesSdkInManifest.disallowed=false
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
android.builtInKotlin=false
android.newDsl=false
@@ -0,0 +1,13 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect
toolchainVendor=JETBRAINS
toolchainVersion=21
+2 -2
View File
@@ -1,6 +1,6 @@
[versions]
agp = "8.10.0"
kotlin = "2.0.21"
agp = "9.1.1"
kotlin = "2.2.10"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
+1 -1
View File
@@ -1,6 +1,6 @@
#Wed May 14 20:14:06 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3
View File
@@ -11,6 +11,9 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {