🚧 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 h2_version=2.3.232
kotlin_version=2.1.10 kotlin_version=2.1.10
ktor_version=3.1.3 ktor_version=3.1.3
logback_version=1.4.14 logback_version=1.5.13
+3 -1
View File
@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -27,4 +29,4 @@
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>
@@ -4,34 +4,13 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
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.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.*
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.*
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.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext 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 dev.svitan.antifed.ui.theme.AntiFedTheme
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
class AuthActivity : ComponentActivity() { class AuthActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
AntiFedTheme { AntiFedTheme {
var serverUrl by remember { mutableStateOf("") } var serverUrl by remember { mutableStateOf("") }
var token by remember { mutableStateOf("") } var token by remember { mutableStateOf("") }
var authIndex by remember { mutableIntStateOf(-1) }
var authId by remember { mutableStateOf("") } 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 needToCheckUrl by remember { mutableStateOf(false) }
var checkingUrl by remember { mutableStateOf(false) } var checkingUrl by remember { mutableStateOf(false) }
@@ -68,110 +53,99 @@ class AuthActivity : ComponentActivity() {
var checkingToken by remember { mutableStateOf(false) } var checkingToken by remember { mutableStateOf(false) }
var isTokenOk by remember { mutableStateOf(false) } var isTokenOk by remember { mutableStateOf(false) }
var auths by remember { mutableStateOf(listOf<AuthDTO>()) } val context = LocalContext.current
val prefs = context.getSharedPreferences(
val prefs = LocalContext.current.getSharedPreferences( stringResource(R.string.settings_prefs_key), MODE_PRIVATE
stringResource(R.string.settings_prefs_key),
MODE_PRIVATE
) )
val loadedServerUrl = prefs.getString(getString(R.string.server_url_key), "") ?: ""
if (loadedServerUrl.isNotBlank()) { LaunchedEffect(Unit) {
serverUrl = loadedServerUrl 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)
} }
val loadedToken = prefs.getString(getString(R.string.token_key), "") ?: ""
if (loadedToken.isNotBlank()) {
token = loadedToken
}
authId = prefs.getString(getString(R.string.auth_key), "") ?: ""
isServerUrlOk = prefs.getBoolean(getString(R.string.server_url_okay_key), false)
fun serverUrlMatches(): Boolean { fun serverUrlMatches(): Boolean {
var re = Regex( val regex = Regex("""https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?""")
"""https?://[a-zA-Z0-9\\.]+\\.[a-zA-Z0-9]+""", return regex.matches(serverUrl)
RegexOption.DOT_MATCHES_ALL
)
return re.containsMatchIn(serverUrl)
} }
LaunchedEffect(serverUrl) { LaunchedEffect(serverUrl) {
if (!needToCheckUrl) return@LaunchedEffect 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 isServerUrlOk = false
if (!serverUrlMatches()) return@LaunchedEffect prefs.edit {
putBoolean(getString(R.string.server_url_okay_key), false)
}
if (serverUrl.isBlank() || !serverUrlMatches()) return@LaunchedEffect
delay(1000) delay(1000)
checkingUrl = true checkingUrl = true
val response = client.get(serverUrl)
isServerUrlOk = try {
val response = client.get(serverUrl)
response.status == HttpStatusCode.OK
} catch (_: Exception) {
false
}
checkingUrl = false checkingUrl = false
needToCheckUrl = false
isServerUrlOk = response.status == HttpStatusCode.OK
prefs.edit { prefs.edit {
putBoolean(getString(R.string.server_url_okay_key), isServerUrlOk) putBoolean(
getString(R.string.server_url_okay_key), isServerUrlOk
)
} }
} }
LaunchedEffect(token) { LaunchedEffect(token) {
if (!needToCheckToken) return@LaunchedEffect if (!needToCheckToken) return@LaunchedEffect
else isTokenOk = false needToCheckToken = false
isTokenOk = false
if (!isServerUrlOk) return@LaunchedEffect if (!isServerUrlOk || token.isBlank()) return@LaunchedEffect
if (token.isBlank()) return@LaunchedEffect
delay(1000) delay(1000)
checkingToken = true checkingToken = true
val response = client.get("$serverUrl/auth") try {
if (response.status != HttpStatusCode.OK) { val response = client.get("$serverUrl/auth") {
checkingToken = false headers {
needToCheckToken = false append("Authorization", "Bearer $token")
isTokenOk = false }
return@LaunchedEffect }
}
auths = response.body<List<AuthDTO>>() if (response.status == HttpStatusCode.OK) {
if (authId.isNotBlank()) { auths = response.body()
authIndex = auths.indexOfFirst { it.id == authId } authIndex = auths.indexOfFirst { it.id == authId }
isTokenOk = true
}
} catch (_: Exception) {
isTokenOk = false
} }
checkingToken = false checkingToken = false
needToCheckToken = false
isTokenOk = true
} }
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
colors = TopAppBarDefaults.topAppBarColors( title = { Text(stringResource(R.string.auth)) },
titleContentColor = MaterialTheme.colorScheme.primary
),
title = {
Text(stringResource(R.string.auth))
},
navigationIcon = { navigationIcon = {
IconButton(onClick = { finish() }) { IconButton(onClick = { finish() }) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack, ( Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = stringResource(R.string.go_back) contentDescription = stringResource(R.string.go_back)
) )
} }
} })
) }) { padding ->
},
modifier = Modifier.fillMaxSize()
) { innerPadding ->
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(padding)
.fillMaxSize() .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) { ) {
Text(stringResource(R.string.auth), fontSize = 24.sp) Text(stringResource(R.string.auth), fontSize = 24.sp)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -179,69 +153,83 @@ class AuthActivity : ComponentActivity() {
TextField( TextField(
value = serverUrl, value = serverUrl,
onValueChange = { onValueChange = {
needToCheckUrl = true
serverUrl = it serverUrl = it
needToCheckUrl = true
prefs.edit { 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)) }, label = { Text(stringResource(R.string.server_url)) },
singleLine = true, singleLine = true,
isError = !isServerUrlOk, isError = !isServerUrlOk && serverUrl.isNotBlank(),
trailingIcon = { trailingIcon = {
if (checkingUrl) if (checkingUrl) {
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier.width(36.dp), modifier = Modifier.size(24.dp)
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant
) )
} }
) })
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
TextField( TextField(
value = token, value = token,
onValueChange = { onValueChange = {
needToCheckToken = true
token = it token = it
needToCheckToken = true
prefs.edit { prefs.edit {
putString(getString(R.string.token_key), it) putString(
getString(R.string.token_key), it
)
} }
}, },
label = { Text(stringResource(R.string.token)) }, label = { Text(stringResource(R.string.token)) },
singleLine = true, singleLine = true,
isError = isTokenOk, enabled = isServerUrlOk,
isError = !isTokenOk && token.isNotBlank(),
visualTransformation = PasswordVisualTransformation(), visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password
),
trailingIcon = { trailingIcon = {
if (checkingToken) { if (checkingToken) {
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier.width(36.dp), modifier = Modifier.size(24.dp)
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant
) )
} }
} })
)
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
if (auths.isNotEmpty()) if (auths.isNotEmpty()) {
DropdownList( DropdownList(
itemList = auths.map { it -> it.name }, itemList = auths.map { it.name },
selectedIndex = 0, selectedIndex = authIndex.coerceAtLeast(0),
modifier = Modifier,
onItemClick = { onItemClick = {
authIndex = it authIndex = it
authId = auths[it].id
prefs.edit { 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( FloatingActionButton(
onClick = { println("creating new auth!!") } onClick = {
) { println("creating new auth!!")
Icon(Icons.Default.Add, stringResource(R.string.create_auth)) }) {
Icon(
Icons.Default.Add,
contentDescription = stringResource(R.string.create_auth)
)
} }
} }
} }
@@ -83,7 +83,6 @@ fun DropdownList(
Text(text = item) Text(text = item)
} }
} }
} }
} }
} }
@@ -7,4 +7,5 @@
<string name="go_back">Ísť späť</string> <string name="go_back">Ísť späť</string>
<string name="token">Token</string> <string name="token">Token</string>
<string name="create_auth">Vytvoriť novú auth</string> <string name="create_auth">Vytvoriť novú auth</string>
<string name="bio">Biometrika</string>
</resources> </resources>
@@ -12,6 +12,7 @@
<string name="token_key" translatable="false">token</string> <string name="token_key" translatable="false">token</string>
<string name="auth_key" translatable="false">auth_id</string> <string name="auth_key" translatable="false">auth_id</string>
<string name="create_auth">Create new auth</string> <string name="create_auth">Create new auth</string>
<string name="bio">Biometrics</string>
<!-- Strings used for fragments for navigation --> <!-- Strings used for fragments for navigation -->
</resources> </resources>
+11 -1
View File
@@ -20,4 +20,14 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the # Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true 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] [versions]
agp = "8.10.0" agp = "9.1.1"
kotlin = "2.0.21" kotlin = "2.2.10"
coreKtx = "1.10.1" coreKtx = "1.10.1"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.1.5"
+1 -1
View File
@@ -1,6 +1,6 @@
#Wed May 14 20:14:06 CEST 2025 #Wed May 14 20:14:06 CEST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+3
View File
@@ -11,6 +11,9 @@ pluginManagement {
gradlePluginPortal() gradlePluginPortal()
} }
} }
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {