forked from melod1n/fast-messenger
improvements for previewing validation & captcha screens
This commit is contained in:
+19
-34
@@ -7,7 +7,6 @@ import com.meloda.app.fast.auth.validation.model.ValidationScreenState
|
||||
import com.meloda.app.fast.auth.validation.model.ValidationType
|
||||
import com.meloda.app.fast.auth.validation.navigation.Validation
|
||||
import com.meloda.app.fast.auth.validation.validation.ValidationValidator
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.common.extensions.createTimerFlow
|
||||
import com.meloda.app.fast.common.extensions.listenValue
|
||||
import com.meloda.app.fast.common.extensions.setValue
|
||||
@@ -26,6 +25,8 @@ interface ValidationViewModel {
|
||||
|
||||
val screenState: StateFlow<ValidationScreenState>
|
||||
|
||||
val validationType: StateFlow<ValidationType?>
|
||||
|
||||
val isNeedToOpenLogin: StateFlow<Boolean>
|
||||
|
||||
fun onCodeInputChanged(newCode: String)
|
||||
@@ -47,10 +48,11 @@ class ValidationViewModelImpl(
|
||||
|
||||
override val screenState = MutableStateFlow(ValidationScreenState.EMPTY)
|
||||
|
||||
override val validationType = MutableStateFlow<ValidationType?>(null)
|
||||
|
||||
override val isNeedToOpenLogin = MutableStateFlow(false)
|
||||
|
||||
private var validationSid: String? = null
|
||||
|
||||
private var delayJob: Job? = null
|
||||
|
||||
init {
|
||||
@@ -61,11 +63,13 @@ class ValidationViewModelImpl(
|
||||
|
||||
validationSid = arguments.validationSid
|
||||
|
||||
validationType.setValue {
|
||||
ValidationType.parse(arguments.validationType)
|
||||
}
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
isSmsButtonVisible = arguments.canResendSms,
|
||||
codeError = arguments.wrongCodeError,
|
||||
validationText = getValidationText(ValidationType.parse(arguments.validationType)),
|
||||
phoneMask = arguments.phoneMask
|
||||
)
|
||||
}
|
||||
@@ -75,7 +79,7 @@ class ValidationViewModelImpl(
|
||||
screenState.updateValue(
|
||||
screenState.value.copy(
|
||||
code = newCode.trim(),
|
||||
codeError = null
|
||||
codeError = false
|
||||
)
|
||||
)
|
||||
|
||||
@@ -118,34 +122,29 @@ class ValidationViewModelImpl(
|
||||
private fun processValidation(): Boolean {
|
||||
val isValid = validator.validate(screenState.value).isValid()
|
||||
|
||||
screenState.updateValue(
|
||||
screenState.value.copy(
|
||||
codeError = if (isValid) null
|
||||
else "Field must not be empty"
|
||||
)
|
||||
)
|
||||
screenState.setValue { old -> old.copy(codeError = !isValid) }
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
private fun sendValidationCode() {
|
||||
authUseCase.validatePhone(validationSid.orEmpty())
|
||||
val sid = validationSid ?: return
|
||||
|
||||
authUseCase.validatePhone(sid)
|
||||
.listenValue { state ->
|
||||
state.processState(
|
||||
error = { error ->
|
||||
|
||||
},
|
||||
success = { response ->
|
||||
val newValidationType = response.validationType
|
||||
response.validationType?.let { newValidationType ->
|
||||
validationType.setValue { ValidationType.parse(newValidationType) }
|
||||
}
|
||||
|
||||
val newCanResendSms = response.validationResend == "sms"
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
isSmsButtonVisible = newCanResendSms,
|
||||
validationText = getValidationText(
|
||||
ValidationType.parse(newValidationType.orEmpty())
|
||||
)
|
||||
)
|
||||
old.copy(isSmsButtonVisible = newCanResendSms)
|
||||
}
|
||||
|
||||
startTickTimer(response.delay)
|
||||
@@ -158,7 +157,7 @@ class ValidationViewModelImpl(
|
||||
}
|
||||
}
|
||||
|
||||
fun startTickTimer(delay: Int?) {
|
||||
private fun startTickTimer(delay: Int?) {
|
||||
if (delay == null || delayJob?.isActive == true) return
|
||||
|
||||
delayJob = createTimerFlow(
|
||||
@@ -182,18 +181,4 @@ class ValidationViewModelImpl(
|
||||
},
|
||||
).launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun getValidationText(validationType: ValidationType): UiText {
|
||||
return when (validationType) {
|
||||
ValidationType.Sms -> {
|
||||
UiText.Simple("SMS with the code is sent to ${screenState.value.phoneMask}")
|
||||
}
|
||||
|
||||
ValidationType.App -> {
|
||||
UiText.Simple("Enter the code from the code generator application")
|
||||
}
|
||||
|
||||
is ValidationType.Other -> UiText.Simple(validationType.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -11,6 +11,5 @@ data class ValidationArguments(
|
||||
val redirectUri: String,
|
||||
val phoneMask: String,
|
||||
val validationType: String,
|
||||
val canResendSms: Boolean,
|
||||
val wrongCodeError: String?,
|
||||
val canResendSms: Boolean
|
||||
) : Parcelable
|
||||
|
||||
+4
-11
@@ -1,27 +1,20 @@
|
||||
package com.meloda.app.fast.auth.validation.model
|
||||
|
||||
import com.meloda.app.fast.common.UiText
|
||||
|
||||
data class ValidationScreenState(
|
||||
val code: String?,
|
||||
val codeError: String?,
|
||||
val codeError: Boolean,
|
||||
val isSmsButtonVisible: Boolean,
|
||||
val delayTime: Int,
|
||||
val phoneMask: String,
|
||||
|
||||
// TODO: 13/07/2024, Danil Nikolaev: check wtf is this
|
||||
val validationText: UiText,
|
||||
val phoneMask: String
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val EMPTY = ValidationScreenState(
|
||||
code = null,
|
||||
codeError = null,
|
||||
codeError = false,
|
||||
isSmsButtonVisible = false,
|
||||
delayTime = 0,
|
||||
phoneMask = "",
|
||||
|
||||
validationText = UiText.Simple("")
|
||||
phoneMask = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-17
@@ -1,23 +1,10 @@
|
||||
package com.meloda.app.fast.auth.validation.model
|
||||
|
||||
sealed class ValidationType(val value: String) {
|
||||
|
||||
data object Sms : ValidationType(TYPE_SMS)
|
||||
|
||||
data object App : ValidationType(TYPE_TWO_FA_APP)
|
||||
|
||||
data class Other(val type: String) : ValidationType(type)
|
||||
enum class ValidationType(val value: String) {
|
||||
SMS("sms"), APP("2fa_app");
|
||||
|
||||
companion object {
|
||||
private const val TYPE_SMS = "sms"
|
||||
private const val TYPE_TWO_FA_APP = "2fa_app"
|
||||
|
||||
fun parse(validationType: String): ValidationType {
|
||||
return when (validationType) {
|
||||
TYPE_SMS -> Sms
|
||||
TYPE_TWO_FA_APP -> App
|
||||
else -> Other(validationType)
|
||||
}
|
||||
}
|
||||
fun parse(value: String): ValidationType = entries.firstOrNull { it.value == value }
|
||||
?: throw IllegalArgumentException("Unknown validation type with value: $value")
|
||||
}
|
||||
}
|
||||
|
||||
+33
-7
@@ -42,15 +42,16 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.meloda.app.fast.auth.validation.ValidationViewModel
|
||||
import com.meloda.app.fast.auth.validation.ValidationViewModelImpl
|
||||
import com.meloda.app.fast.auth.validation.model.ValidationScreenState
|
||||
import com.meloda.app.fast.auth.validation.model.ValidationType
|
||||
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
|
||||
import com.meloda.app.fast.ui.components.MaterialDialog
|
||||
import com.meloda.app.fast.ui.components.TextFieldErrorText
|
||||
import com.meloda.app.fast.ui.util.getString
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import com.meloda.app.fast.ui.R as UiR
|
||||
|
||||
@@ -62,6 +63,7 @@ fun ValidationRoute(
|
||||
) {
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val isNeedToOpenLogin by viewModel.isNeedToOpenLogin.collectAsStateWithLifecycle()
|
||||
val validationType by viewModel.validationType.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(isNeedToOpenLogin) {
|
||||
if (isNeedToOpenLogin) {
|
||||
@@ -78,6 +80,7 @@ fun ValidationRoute(
|
||||
|
||||
ValidationScreen(
|
||||
screenState = screenState,
|
||||
validationType = validationType,
|
||||
onBack = onBack,
|
||||
onCodeInputChanged = viewModel::onCodeInputChanged,
|
||||
onTextFieldDoneAction = viewModel::onTextFieldDoneAction,
|
||||
@@ -89,6 +92,7 @@ fun ValidationRoute(
|
||||
@Composable
|
||||
fun ValidationScreen(
|
||||
screenState: ValidationScreenState = ValidationScreenState.EMPTY,
|
||||
validationType: ValidationType? = null,
|
||||
onBack: () -> Unit = {},
|
||||
onCodeInputChanged: (String) -> Unit = {},
|
||||
onTextFieldDoneAction: () -> Unit = {},
|
||||
@@ -105,6 +109,17 @@ fun ValidationScreen(
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val validationText by remember(validationType) {
|
||||
mutableStateOf(
|
||||
when (validationType) {
|
||||
ValidationType.SMS -> "SMS with the code is sent to ${screenState.phoneMask}"
|
||||
ValidationType.APP -> "Enter the code from the code generator application"
|
||||
|
||||
null -> ""
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(confirmedExit) {
|
||||
if (confirmedExit) {
|
||||
onBack()
|
||||
@@ -130,7 +145,6 @@ fun ValidationScreen(
|
||||
}
|
||||
|
||||
var code by remember { mutableStateOf(TextFieldValue(screenState.code.orEmpty())) }
|
||||
val codeError = screenState.codeError
|
||||
|
||||
Scaffold { padding ->
|
||||
Column(
|
||||
@@ -167,7 +181,7 @@ fun ValidationScreen(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(38.dp))
|
||||
Text(
|
||||
text = screenState.validationText.getString().orEmpty(),
|
||||
text = validationText.orEmpty(),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
@@ -201,7 +215,7 @@ fun ValidationScreen(
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.round_qr_code_24),
|
||||
contentDescription = "QR Code icon",
|
||||
tint = if (codeError != null) {
|
||||
tint = if (screenState.codeError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
@@ -219,11 +233,11 @@ fun ValidationScreen(
|
||||
onTextFieldDoneAction()
|
||||
}
|
||||
),
|
||||
isError = codeError != null
|
||||
isError = screenState.codeError
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = codeError != null) {
|
||||
TextFieldErrorText(text = codeError.orEmpty())
|
||||
AnimatedVisibility(screenState.codeError) {
|
||||
TextFieldErrorText(text = "Field must not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,3 +293,15 @@ fun ValidationScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ValidationScreenPreview() {
|
||||
ValidationScreen(
|
||||
screenState = ValidationScreenState.EMPTY.copy(
|
||||
phoneMask = "+7 (***) ***-**-21",
|
||||
code = "222222"
|
||||
),
|
||||
validationType = ValidationType.SMS
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user