add auth-code validation

This commit is contained in:
2025-11-27 21:40:27 +03:00
parent 7dc115052d
commit 0cc27350bc
5 changed files with 94 additions and 47 deletions

View File

@@ -1,6 +1,7 @@
package ru.myitschool.work.ui package ru.myitschool.work.ui
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -25,6 +26,8 @@ import androidx.compose.ui.unit.sp
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.ui.theme.Black import ru.myitschool.work.ui.theme.Black
import ru.myitschool.work.ui.theme.Gray import ru.myitschool.work.ui.theme.Gray
import ru.myitschool.work.ui.theme.LightBlue
import ru.myitschool.work.ui.theme.LightGray
import ru.myitschool.work.ui.theme.Typography import ru.myitschool.work.ui.theme.Typography
import ru.myitschool.work.ui.theme.White import ru.myitschool.work.ui.theme.White
@@ -120,16 +123,14 @@ fun BaseText14(
@Composable @Composable
fun BaseInputText( fun BaseInputText(
placeholder: String= "", placeholder: String= "",
modifier: Modifier = Modifier modifier: Modifier = Modifier,
valueChange: (String) -> Unit,
value: String
) { ) {
var textState by remember { mutableStateOf("") }
TextField( TextField(
value = textState, value = value,
onValueChange = { onValueChange = valueChange,
textState = it
},
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
placeholder = { placeholder = {
BaseText16( BaseText16(
@@ -139,6 +140,8 @@ fun BaseInputText(
}, },
textStyle = Typography.bodySmall.copy(fontSize = 16.sp), textStyle = Typography.bodySmall.copy(fontSize = 16.sp),
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = LightBlue,
unfocusedContainerColor = LightBlue,
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent,
@@ -169,6 +172,7 @@ fun BaseText20(
@Composable @Composable
fun BaseButton( fun BaseButton(
enable: Boolean = true,
text: String, text: String,
btnColor: Color, btnColor: Color,
btnContentColor: Color, btnContentColor: Color,
@@ -177,12 +181,13 @@ fun BaseButton(
modifier: Modifier = Modifier.fillMaxWidth() modifier: Modifier = Modifier.fillMaxWidth()
) { ) {
Button( Button(
enabled = enable,
onClick = onClick, onClick = onClick,
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = btnColor, containerColor = btnColor,
contentColor = btnContentColor, contentColor = btnContentColor,
disabledContainerColor = btnColor, disabledContainerColor = LightGray,
disabledContentColor = btnContentColor disabledContentColor = Gray
), ),
modifier = modifier, modifier = modifier,
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),

View File

@@ -26,6 +26,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import ru.myitschool.work.R import ru.myitschool.work.R
@@ -40,7 +41,6 @@ import ru.myitschool.work.ui.theme.Blue
import ru.myitschool.work.ui.theme.Red import ru.myitschool.work.ui.theme.Red
import ru.myitschool.work.ui.theme.White import ru.myitschool.work.ui.theme.White
@Composable @Composable
fun AuthScreen( fun AuthScreen(
viewModel: AuthViewModel = viewModel(), viewModel: AuthViewModel = viewModel(),
@@ -62,15 +62,25 @@ fun AuthScreen(
Column( Column(
modifier = Modifier.padding(vertical = 20.dp) modifier = Modifier.padding(vertical = 20.dp)
) { ) {
val textState by viewModel.textState.collectAsStateWithLifecycle()
val errorState by viewModel.errorState.collectAsStateWithLifecycle()
BaseInputText( BaseInputText(
value = textState,
placeholder = stringResource(R.string.auth_label), placeholder = stringResource(R.string.auth_label),
modifier = Modifier.fillMaxWidth() modifier = Modifier
.testTag(TestIds.Auth.CODE_INPUT)
.fillMaxWidth(),
valueChange = {viewModel.updateText(it)}
) )
if (errorState) {
BaseText12( BaseText12(
text = "Недействительный код сотрудника", text = "Недействительный код сотрудника",
color = Red, color = Red,
modifier = Modifier modifier = Modifier
.testTag(TestIds.Auth.ERROR)
.padding( .padding(
start = 10.dp, start = 10.dp,
top = 5.dp, top = 5.dp,
@@ -79,12 +89,19 @@ fun AuthScreen(
.fillMaxWidth() .fillMaxWidth()
) )
} }
}
val isButtonEnabled by viewModel.isButtonEnabled.collectAsStateWithLifecycle()
BaseButton( BaseButton(
text = stringResource(R.string.auth_sign_in), text = stringResource(R.string.auth_sign_in),
onClick = { navController.navigate(MainScreenDestination)}, onClick = { navController.navigate(MainScreenDestination)},
btnColor = Blue, btnColor = Blue,
btnContentColor = White enable = isButtonEnabled,
btnContentColor = White,
modifier = Modifier
.testTag(TestIds.Auth.SIGN_BUTTON)
.fillMaxWidth()
) )
} }
} }

View File

@@ -1,43 +1,66 @@
package ru.myitschool.work.ui.screen.auth package ru.myitschool.work.ui.screen.auth
import androidx.compose.runtime.mutableIntStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.myitschool.work.data.repo.AuthRepository import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
class AuthViewModel : ViewModel() { class AuthViewModel : ViewModel() {
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) } // private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data) // private val _uiState = MutableStateFlow<AuthState>(AuthState.Data)
val uiState: StateFlow<AuthState> = _uiState.asStateFlow() // val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
//
// private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
// val actionFlow: SharedFlow<Unit> = _actionFlow
//
// fun onIntent(intent: AuthIntent) {
// when (intent) {
// is AuthIntent.Send -> {
// viewModelScope.launch(Dispatchers.Default) {
// _uiState.update { AuthState.Loading }
// checkAndSaveAuthCodeUseCase.invoke("9999").fold(
// onSuccess = {
// _actionFlow.emit(Unit)
// },
// onFailure = { error ->
// error.printStackTrace()
// _actionFlow.emit(Unit)
// }
// )
// }
// }
// is AuthIntent.TextInput -> Unit
// }
// }
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow() private val _textState = MutableStateFlow("")
val actionFlow: SharedFlow<Unit> = _actionFlow val textState: StateFlow<String> = _textState.asStateFlow()
fun onIntent(intent: AuthIntent) { private val _errorState = MutableStateFlow(false)
when (intent) { val errorState: StateFlow<Boolean> = _errorState.asStateFlow()
is AuthIntent.Send -> {
viewModelScope.launch(Dispatchers.Default) { fun updateText(newText: String) {
_uiState.update { AuthState.Loading } _textState.value = newText
checkAndSaveAuthCodeUseCase.invoke("9999").fold( _errorState.value = false
onSuccess = {
_actionFlow.emit(Unit)
},
onFailure = { error ->
error.printStackTrace()
_actionFlow.emit(Unit)
} }
val isButtonEnabled: StateFlow<Boolean> =
_textState.map { it.length == 4 && it.matches(Regex("^[a-zA-Z0-9]*\$"))}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = false
) )
}
}
is AuthIntent.TextInput -> Unit
}
}
} }

View File

@@ -14,6 +14,8 @@ val Blue = Color(0xFF004BFF)
val Gray = Color(0xFF777777) val Gray = Color(0xFF777777)
val LightBlue = Color(0xFFF2EFFF)
val White = Color(0xFFFFFFFF) val White = Color(0xFFFFFFFF)
val Red = Color(0xFFFF4D4D) val Red = Color(0xFFFF4D4D)

View File

@@ -1,7 +1,7 @@
<resources> <resources>
<string name="app_name">Work</string> <string name="app_name">Work</string>
<string name="title_activity_root">RootActivity</string> <string name="title_activity_root">RootActivity</string>
<string name="auth_title">Привет! Введи код для авторизации</string> <string name="auth_title">Введите код для авторизации</string>
<string name="auth_label">Код</string> <string name="auth_label">Код</string>
<string name="auth_sign_in">Войти</string> <string name="auth_sign_in">Войти</string>
</resources> </resources>