forked from melod1n/fast-messenger
twoFa -> validation naming; fixes for preview for screens (separating view model from ui); some improvements & fixes
This commit is contained in:
+6
-8
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
-12
@@ -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
|
||||
+4
-6
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
+40
@@ -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)
|
||||
}
|
||||
-48
@@ -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)
|
||||
}
|
||||
+31
-13
@@ -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)
|
||||
) {
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user