Implement sign in with api key
This commit is contained in:
+4
-6
@@ -1,14 +1,12 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp
|
package com.digitoolsolutions.app.torquevaultkmp
|
||||||
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.AuthApi
|
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.AuthApi
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.ReferKeys
|
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.Endpoints
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
|
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.okhttp.OkHttp
|
import io.ktor.client.engine.okhttp.OkHttp
|
||||||
import io.ktor.client.plugins.DefaultRequest
|
|
||||||
import io.ktor.client.plugins.HttpSend
|
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
import io.ktor.client.plugins.auth.Auth
|
import io.ktor.client.plugins.auth.Auth
|
||||||
import io.ktor.client.plugins.auth.providers.BearerTokens
|
import io.ktor.client.plugins.auth.providers.BearerTokens
|
||||||
@@ -17,9 +15,6 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
|||||||
import io.ktor.client.plugins.logging.LogLevel
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
import io.ktor.client.plugins.logging.Logging
|
||||||
import io.ktor.client.plugins.plugin
|
|
||||||
import io.ktor.client.request.header
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
@@ -45,6 +40,9 @@ actual fun createPlatformHttpClient(
|
|||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
refreshTokens {
|
refreshTokens {
|
||||||
|
if (response.call.request.url.encodedPath.contains(Endpoints.AUTH_TOKEN)) {
|
||||||
|
return@refreshTokens null
|
||||||
|
}
|
||||||
Napier.d(">>>>> [NetworkEngine.android.kt] Access token expired, auto request refresh token...")
|
Napier.d(">>>>> [NetworkEngine.android.kt] Access token expired, auto request refresh token...")
|
||||||
val newAccess = authApi?.refreshToken()
|
val newAccess = authApi?.refreshToken()
|
||||||
if (newAccess != null) {
|
if (newAccess != null) {
|
||||||
|
|||||||
@@ -19,11 +19,15 @@
|
|||||||
<string name="dark_theme">Dark theme</string>
|
<string name="dark_theme">Dark theme</string>
|
||||||
<string name="auto_connect">Auto-connect device</string>
|
<string name="auto_connect">Auto-connect device</string>
|
||||||
|
|
||||||
<string name="lbl_server_url">Server URL</string>
|
<string name="lbl_server_url">Server Address</string>
|
||||||
<string name="server_url_placeholder">Ex: https://example.com</string>
|
<string name="server_url_placeholder">Ex: https://example.com</string>
|
||||||
<string name="lbl_username">Username</string>
|
<string name="lbl_username">Username</string>
|
||||||
<string name="lbl_password">Password</string>
|
<string name="lbl_password">Password</string>
|
||||||
<string name="btn_login">Login</string>
|
<string name="btn_login">Sign In</string>
|
||||||
|
<string name="api_key">API Key</string>
|
||||||
|
<string name="use_api_key_instead">Use API key instead?</string>
|
||||||
|
<string name="use_password_instead">Use password instead?</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="bonded_title">Bonded Devices</string>
|
<string name="bonded_title">Bonded Devices</string>
|
||||||
|
|
||||||
|
|||||||
+21
@@ -8,9 +8,12 @@ import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
|||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.call.body
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.header
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
import io.ktor.client.request.setBody
|
import io.ktor.client.request.setBody
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
|
|
||||||
|
|
||||||
@@ -42,6 +45,24 @@ class AuthApi(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to authenticate the user using an API Key.
|
||||||
|
* Calls Endpoints.AUTH_REFRESH and stores the API Key as the access token.
|
||||||
|
*/
|
||||||
|
suspend fun loginWithApiKey(apiKey: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val res: RefreshResponseDto = client.post("$baseUrl${Endpoints.AUTH_REFRESH}") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(RefreshRequestDto(apiKey))
|
||||||
|
}.body()
|
||||||
|
tokenManager.saveTokens(res.access, apiKey)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Napier.d(">>>>> [AuthApi.kt] loginWithApiKey Exception: $e")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Attempts to refresh the access token using the stored refresh token.
|
* Attempts to refresh the access token using the stored refresh token.
|
||||||
*
|
*
|
||||||
|
|||||||
+21
-1
@@ -60,7 +60,27 @@ class AuthViewModel(
|
|||||||
if (success) {
|
if (success) {
|
||||||
_loginState.value = LoginUiState.Success
|
_loginState.value = LoginUiState.Success
|
||||||
} else {
|
} else {
|
||||||
_loginState.value = LoginUiState.Error("Login failed: Invalid credentials")
|
_loginState.value = LoginUiState.Error("Incorrect username or password")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_loginState.value = LoginUiState.Error("Error: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to log in the user using an API Key.
|
||||||
|
*/
|
||||||
|
fun loginWithApiKey(apiKey: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_loginState.value = LoginUiState.Loading
|
||||||
|
tokenManager.saveServerUrl(_serverUrl.value)
|
||||||
|
try {
|
||||||
|
val success = authApi.loginWithApiKey(apiKey)
|
||||||
|
if (success) {
|
||||||
|
_loginState.value = LoginUiState.Success
|
||||||
|
} else {
|
||||||
|
_loginState.value = LoginUiState.Error("Invalid API Key")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_loginState.value = LoginUiState.Error("Error: ${e.message}")
|
_loginState.value = LoginUiState.Error("Error: ${e.message}")
|
||||||
|
|||||||
+113
-56
@@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
import androidx.compose.material.icons.filled.VisibilityOff
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -33,6 +34,7 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -53,7 +55,6 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeDestination
|
import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeDestination
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -61,12 +62,15 @@ import org.jetbrains.compose.resources.painterResource
|
|||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
import torquevaultkmp.composeapp.generated.resources.Res
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.api_key
|
||||||
import torquevaultkmp.composeapp.generated.resources.btn_login
|
import torquevaultkmp.composeapp.generated.resources.btn_login
|
||||||
import torquevaultkmp.composeapp.generated.resources.dts_text
|
import torquevaultkmp.composeapp.generated.resources.dts_text
|
||||||
import torquevaultkmp.composeapp.generated.resources.lbl_password
|
import torquevaultkmp.composeapp.generated.resources.lbl_password
|
||||||
import torquevaultkmp.composeapp.generated.resources.lbl_server_url
|
import torquevaultkmp.composeapp.generated.resources.lbl_server_url
|
||||||
import torquevaultkmp.composeapp.generated.resources.lbl_username
|
import torquevaultkmp.composeapp.generated.resources.lbl_username
|
||||||
import torquevaultkmp.composeapp.generated.resources.server_url_placeholder
|
import torquevaultkmp.composeapp.generated.resources.server_url_placeholder
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.use_api_key_instead
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.use_password_instead
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen for user authentication.
|
* Screen for user authentication.
|
||||||
@@ -80,12 +84,15 @@ fun LoginScreen(
|
|||||||
val serverUrl by viewModel.serverUrl.collectAsState()
|
val serverUrl by viewModel.serverUrl.collectAsState()
|
||||||
|
|
||||||
LoginScreenContent(
|
LoginScreenContent(
|
||||||
viewModel = viewModel,
|
|
||||||
loginState = loginState,
|
loginState = loginState,
|
||||||
serverUrl = serverUrl,
|
serverUrl = serverUrl,
|
||||||
|
onServerUrlChange = { viewModel.onChangeServerUrl(it) },
|
||||||
onLogin = { username, password ->
|
onLogin = { username, password ->
|
||||||
viewModel.login(username, password)
|
viewModel.login(username, password)
|
||||||
},
|
},
|
||||||
|
onLoginWithApiKey = { apiKey ->
|
||||||
|
viewModel.loginWithApiKey(apiKey)
|
||||||
|
},
|
||||||
onLoginSuccess = {
|
onLoginSuccess = {
|
||||||
navController.navigate(HomeDestination.route) {
|
navController.navigate(HomeDestination.route) {
|
||||||
popUpTo(AuthDestination.route) { inclusive = true }
|
popUpTo(AuthDestination.route) { inclusive = true }
|
||||||
@@ -101,16 +108,19 @@ fun LoginScreen(
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreenContent(
|
fun LoginScreenContent(
|
||||||
viewModel: AuthViewModel,
|
|
||||||
loginState: LoginUiState,
|
loginState: LoginUiState,
|
||||||
serverUrl: String,
|
serverUrl: String,
|
||||||
|
onServerUrlChange: (String) -> Unit,
|
||||||
onLogin: (String, String) -> Unit,
|
onLogin: (String, String) -> Unit,
|
||||||
|
onLoginWithApiKey: (String) -> Unit,
|
||||||
onLoginSuccess: () -> Unit
|
onLoginSuccess: () -> Unit
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
var username by remember { mutableStateOf("admin") }
|
var username by remember { mutableStateOf("admin") }
|
||||||
var password by remember { mutableStateOf("admin") }
|
var password by remember { mutableStateOf("admin") }
|
||||||
|
var apiKey by remember { mutableStateOf("") }
|
||||||
|
var useApiKey by remember { mutableStateOf(false) }
|
||||||
var passwordVisible by remember { mutableStateOf(false) }
|
var passwordVisible by remember { mutableStateOf(false) }
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -165,70 +175,107 @@ fun LoginScreenContent(
|
|||||||
onNext = { focusManager.moveFocus(FocusDirection.Down) }
|
onNext = { focusManager.moveFocus(FocusDirection.Down) }
|
||||||
),
|
),
|
||||||
value = serverUrl,
|
value = serverUrl,
|
||||||
onValueChange = { viewModel.onChangeServerUrl(it) },
|
onValueChange = { onServerUrlChange(it) },
|
||||||
label = { Text(stringResource(Res.string.lbl_server_url)) },
|
label = { Text(stringResource(Res.string.lbl_server_url)) },
|
||||||
placeholder = { Text(stringResource(Res.string.server_url_placeholder)) }
|
placeholder = { Text(stringResource(Res.string.server_url_placeholder)) }
|
||||||
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
OutlinedTextField(
|
if (!useApiKey) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
/* Use username and password */
|
||||||
singleLine = true,
|
OutlinedTextField(
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardActions = KeyboardActions(
|
singleLine = true,
|
||||||
onNext = { focusManager.moveFocus(FocusDirection.Down) }
|
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next),
|
||||||
),
|
keyboardActions = KeyboardActions(
|
||||||
value = username,
|
onNext = { focusManager.moveFocus(FocusDirection.Down) }
|
||||||
onValueChange = { username = it },
|
),
|
||||||
label = { Text(stringResource(Res.string.lbl_username)) }
|
value = username,
|
||||||
)
|
onValueChange = { username = it },
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
label = { Text(stringResource(Res.string.lbl_username)) }
|
||||||
OutlinedTextField(
|
)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
singleLine = true,
|
OutlinedTextField(
|
||||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
trailingIcon = {
|
singleLine = true,
|
||||||
val image =
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
if (passwordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff
|
trailingIcon = {
|
||||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
val image =
|
||||||
Icon(
|
if (passwordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff
|
||||||
imageVector = image,
|
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||||
contentDescription = if (passwordVisible) "Hide password" else "Show password"
|
Icon(
|
||||||
)
|
imageVector = image,
|
||||||
}
|
contentDescription = if (passwordVisible) "Hide password" else "Show password"
|
||||||
},
|
)
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
}
|
||||||
keyboardActions = KeyboardActions(
|
},
|
||||||
onDone = {
|
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
||||||
if (username.isBlank() || password.isBlank()) {
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
if (username.isBlank() || password.isBlank()) {
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar("Username and Password is required!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onLogin(username, password)
|
||||||
|
keyboardController?.hide()
|
||||||
|
focusManager.clearFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
value = password,
|
||||||
|
onValueChange = { password = it },
|
||||||
|
label = { Text(stringResource(Res.string.lbl_password)) }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
/* Use api key */
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
if (apiKey.isBlank()) {
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar("API Key is required!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onLoginWithApiKey(apiKey)
|
||||||
|
keyboardController?.hide()
|
||||||
|
focusManager.clearFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
value = apiKey,
|
||||||
|
onValueChange = { apiKey = it },
|
||||||
|
label = { Text(stringResource(Res.string.api_key)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(45.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
onClick = {
|
||||||
|
if (useApiKey) {
|
||||||
|
if (apiKey.isBlank()) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar("Username and Password is required!")
|
snackbarHostState.showSnackbar("API Key is required!")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onLogin(username, password)
|
onLoginWithApiKey(apiKey.trim())
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
),
|
|
||||||
value = password,
|
|
||||||
onValueChange = { password = it },
|
|
||||||
label = { Text(stringResource(Res.string.lbl_password)) }
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Button(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(150.dp)
|
|
||||||
.height(45.dp),
|
|
||||||
onClick = {
|
|
||||||
if (username.isBlank() || password.isBlank()) {
|
|
||||||
scope.launch {
|
|
||||||
snackbarHostState.showSnackbar("Username and password is required!")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
onLogin(username.trim(), password.trim())
|
if (username.isBlank() || password.isBlank()) {
|
||||||
keyboardController?.hide()
|
scope.launch {
|
||||||
focusManager.clearFocus()
|
snackbarHostState.showSnackbar("Username and password is required!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onLogin(username.trim(), password.trim())
|
||||||
|
keyboardController?.hide()
|
||||||
|
focusManager.clearFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = loginState !is LoginUiState.Loading
|
enabled = loginState !is LoginUiState.Loading
|
||||||
@@ -243,6 +290,15 @@ fun LoginScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
TextButton(
|
||||||
|
onClick = { useApiKey = !useApiKey },
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(stringResource(if (useApiKey) Res.string.use_password_instead else Res.string.use_api_key_instead))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,10 +307,11 @@ fun LoginScreenContent(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun LoginScreenPreview() {
|
private fun LoginScreenPreview() {
|
||||||
LoginScreenContent(
|
LoginScreenContent(
|
||||||
viewModel = viewModel(),
|
|
||||||
loginState = LoginUiState.Idle,
|
loginState = LoginUiState.Idle,
|
||||||
serverUrl = "http://localhost:3000",
|
serverUrl = "http://localhost:3000",
|
||||||
|
onServerUrlChange = {},
|
||||||
onLogin = {_, _ -> },
|
onLogin = {_, _ -> },
|
||||||
|
onLoginWithApiKey = {},
|
||||||
onLoginSuccess = {}
|
onLoginSuccess = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -1,14 +1,12 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp
|
package com.digitoolsolutions.app.torquevaultkmp
|
||||||
|
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.AuthApi
|
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.AuthApi
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.ReferKeys
|
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.Endpoints
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
|
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.darwin.Darwin
|
import io.ktor.client.engine.darwin.Darwin
|
||||||
import io.ktor.client.plugins.DefaultRequest
|
|
||||||
import io.ktor.client.plugins.HttpSend
|
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
import io.ktor.client.plugins.auth.Auth
|
import io.ktor.client.plugins.auth.Auth
|
||||||
import io.ktor.client.plugins.auth.providers.BearerTokens
|
import io.ktor.client.plugins.auth.providers.BearerTokens
|
||||||
@@ -17,11 +15,7 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
|||||||
import io.ktor.client.plugins.logging.LogLevel
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
import io.ktor.client.plugins.logging.Logging
|
||||||
import io.ktor.client.plugins.plugin
|
|
||||||
import io.ktor.client.request.header
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
actual fun createPlatformHttpClient(
|
actual fun createPlatformHttpClient(
|
||||||
@@ -45,6 +39,9 @@ actual fun createPlatformHttpClient(
|
|||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
refreshTokens {
|
refreshTokens {
|
||||||
|
if (response.call.request.url.encodedPath.contains(Endpoints.AUTH_TOKEN)) {
|
||||||
|
return@refreshTokens null
|
||||||
|
}
|
||||||
Napier.d(">>>>> [NetworkEngine.ios.kt] Access token expired, auto request refresh token...")
|
Napier.d(">>>>> [NetworkEngine.ios.kt] Access token expired, auto request refresh token...")
|
||||||
val newAccess = authApi?.refreshToken()
|
val newAccess = authApi?.refreshToken()
|
||||||
if (newAccess != null) {
|
if (newAccess != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user