twoFa -> validation naming; fixes for preview for screens (separating view model from ui); some improvements & fixes

This commit is contained in:
2024-07-13 22:45:49 +03:00
parent dfdc48b682
commit 733627f935
98 changed files with 1611 additions and 1637 deletions
@@ -18,7 +18,7 @@ interface CaptchaViewModel {
fun onCodeInputChanged(newCode: String)
fun onTextFieldDoneClicked()
fun onTextFieldDoneAction()
fun onDoneButtonClicked()
fun onNavigatedToLogin()
@@ -32,24 +32,22 @@ class CaptchaViewModelImpl(
override val screenState = MutableStateFlow(CaptchaScreenState.EMPTY)
override val isNeedToOpenLogin = MutableStateFlow(false)
init {
val arguments = Captcha.from(savedStateHandle).arguments
val captchaImage = Captcha.from(savedStateHandle).captchaImageUrl
screenState.setValue { old ->
old.copy(
captchaSid = arguments.captchaSid,
captchaImage = URLDecoder.decode(arguments.captchaImage, "utf-8")
)
old.copy(captchaImageUrl = URLDecoder.decode(captchaImage, "utf-8"))
}
}
override fun onCodeInputChanged(newCode: String) {
val newState = screenState.value.copy(captchaCode = newCode.trim())
val newState = screenState.value.copy(code = newCode.trim())
screenState.update { newState }
processValidation()
}
override fun onTextFieldDoneClicked() {
override fun onTextFieldDoneAction() {
onDoneButtonClicked()
}
@@ -1,12 +0,0 @@
package com.meloda.app.fast.auth.captcha.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Serializable
@Parcelize
data class CaptchaArguments(
val captchaSid: String,
val captchaImage: String
) : Parcelable
@@ -1,17 +1,15 @@
package com.meloda.app.fast.auth.captcha.model
data class CaptchaScreenState(
val captchaSid: String,
val captchaImage: String,
val captchaCode: String,
val captchaImageUrl: String,
val code: String,
val codeError: Boolean
) {
companion object {
val EMPTY = CaptchaScreenState(
captchaSid = "",
captchaImage = "",
captchaCode = "",
captchaImageUrl = "",
code = "",
codeError = false
)
}
@@ -0,0 +1,40 @@
package com.meloda.app.fast.auth.captcha.navigation
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.meloda.app.fast.auth.captcha.presentation.CaptchaRoute
import kotlinx.serialization.Serializable
@Serializable
data class Captcha(val captchaImageUrl: String) {
companion object {
fun from(savedStateHandle: SavedStateHandle) = savedStateHandle.toRoute<Captcha>()
}
}
fun NavGraphBuilder.captchaScreen(
onBack: () -> Unit,
onResult: (String) -> Unit
) {
composable<Captcha> {
CaptchaRoute(
onBack = onBack,
onResult = onResult
)
}
}
fun NavController.navigateToCaptcha(captchaImageUrl: String) {
this.navigate(Captcha(captchaImageUrl))
}
fun NavController.setCaptchaResult(code: String?) {
this.currentBackStackEntry
?.savedStateHandle
?.set("captcha_code", code)
}
@@ -1,48 +0,0 @@
package com.meloda.app.fast.auth.captcha.navigation
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.meloda.app.fast.auth.captcha.model.CaptchaArguments
import com.meloda.app.fast.auth.captcha.presentation.CaptchaScreen
import com.meloda.app.fast.common.customNavType
import kotlinx.serialization.Serializable
import kotlin.reflect.typeOf
@Serializable
data class Captcha(val arguments: CaptchaArguments) {
companion object {
val typeMap = mapOf(typeOf<CaptchaArguments>() to customNavType<CaptchaArguments>())
fun from(savedStateHandle: SavedStateHandle) =
savedStateHandle.toRoute<Captcha>(typeMap)
}
}
fun NavGraphBuilder.captchaRoute(
onBack: () -> Unit,
onResult: (String) -> Unit
) {
composable<Captcha>(
typeMap = Captcha.typeMap
) {
CaptchaScreen(
onBack = onBack,
onResult = onResult
)
}
}
fun NavController.navigateToCaptcha(arguments: CaptchaArguments) {
this.navigate(Captcha(arguments))
}
fun NavController.setCaptchaResult(code: String?) {
this.currentBackStackEntry
?.savedStateHandle
?.set("captchacode", code)
}
@@ -49,6 +49,7 @@ import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.meloda.app.fast.auth.captcha.CaptchaViewModel
import com.meloda.app.fast.auth.captcha.CaptchaViewModelImpl
import com.meloda.app.fast.auth.captcha.model.CaptchaScreenState
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.designsystem.MaterialDialog
import com.meloda.app.fast.designsystem.TextFieldErrorText
@@ -56,7 +57,7 @@ import org.koin.androidx.compose.koinViewModel
import com.meloda.app.fast.designsystem.R as UiR
@Composable
fun CaptchaScreen(
fun CaptchaRoute(
onBack: () -> Unit,
onResult: (String) -> Unit,
viewModel: CaptchaViewModel = koinViewModel<CaptchaViewModelImpl>()
@@ -64,6 +65,30 @@ fun CaptchaScreen(
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val isNeedToOpenLogin by viewModel.isNeedToOpenLogin.collectAsStateWithLifecycle()
LaunchedEffect(isNeedToOpenLogin) {
if (isNeedToOpenLogin) {
viewModel.onNavigatedToLogin()
onResult(screenState.code)
}
}
CaptchaScreen(
screenState = screenState,
onBack = onBack,
onCodeInputChanged = viewModel::onCodeInputChanged,
onTextFieldDoneAction = viewModel::onTextFieldDoneAction,
onDoneButtonClicked = viewModel::onDoneButtonClicked
)
}
@Composable
fun CaptchaScreen(
screenState: CaptchaScreenState = CaptchaScreenState.EMPTY,
onBack: () -> Unit = {},
onCodeInputChanged: (String) -> Unit = {},
onTextFieldDoneAction: () -> Unit = {},
onDoneButtonClicked: () -> Unit = {}
) {
var confirmedExit by rememberSaveable {
mutableStateOf(false)
}
@@ -97,13 +122,6 @@ fun CaptchaScreen(
)
}
LaunchedEffect(isNeedToOpenLogin) {
if (isNeedToOpenLogin) {
viewModel.onNavigatedToLogin()
onResult(screenState.captchaCode)
}
}
val focusManager = LocalFocusManager.current
Scaffold { padding ->
@@ -171,7 +189,7 @@ fun CaptchaScreen(
} else {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(screenState.captchaImage)
.data(screenState.captchaImageUrl)
.crossfade(true)
.build(),
contentDescription = "Captcha image",
@@ -183,14 +201,14 @@ fun CaptchaScreen(
Spacer(modifier = Modifier.height(30.dp))
var code by remember { mutableStateOf(TextFieldValue(screenState.captchaCode)) }
var code by remember { mutableStateOf(TextFieldValue(screenState.code)) }
val showError = screenState.codeError
TextField(
value = code,
onValueChange = { newText ->
code = newText
viewModel.onCodeInputChanged(newText.text)
onCodeInputChanged(newText.text)
},
label = { Text(text = "Code") },
placeholder = { Text(text = "Code") },
@@ -213,7 +231,7 @@ fun CaptchaScreen(
keyboardActions = KeyboardActions(
onDone = {
focusManager.clearFocus()
viewModel.onTextFieldDoneClicked()
onTextFieldDoneAction()
}
),
isError = showError
@@ -225,7 +243,7 @@ fun CaptchaScreen(
}
FloatingActionButton(
onClick = viewModel::onDoneButtonClicked,
onClick = onDoneButtonClicked,
containerColor = MaterialTheme.colorScheme.secondaryContainer,
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
@@ -7,7 +7,7 @@ class CaptchaValidator {
fun validate(screenState: CaptchaScreenState): CaptchaValidationResult {
return when {
screenState.captchaCode.isEmpty() -> CaptchaValidationResult.Empty
screenState.code.trim().isEmpty() -> CaptchaValidationResult.Empty
else -> CaptchaValidationResult.Valid
}
}