-add a password and login table

-disallow screenshots
This commit is contained in:
m-kuchergin 2026-02-25 14:19:49 +03:00
parent 8540a15e54
commit aa78bfc14b
13 changed files with 106 additions and 50 deletions

View File

@ -4,7 +4,8 @@ object TestIds {
object Auth {
const val ERROR = "auth_error"
const val SIGN_BUTTON = "auth_sign_button"
const val CODE_INPUT = "auth_code_input"
const val LOGIN_INPUT = "auth_login_input"
const val PASSWORD_INPUT = "auth_password_input"
}
object Main {
const val ERROR = "main_error"

View File

@ -16,13 +16,13 @@ object AuthRepository {
private var codeCache: String? = null
suspend fun checkAndSave(text: String): Result<Boolean> {
return NetworkDataSource.checkAuth(text).onSuccess { success ->
suspend fun checkAndSave(textLogin: String, textPassword: String): Result<Boolean> {
return NetworkDataSource.checkAuth(textLogin, textPassword).onSuccess { success ->
if (success) {
codeCache = text
codeCache = textLogin // TODO(переделать под отправку и логина и пароля в api)
App.context.userDataStore.edit { preferences ->
val prefKey = stringPreferencesKey(CODE_KEY)
preferences[prefKey] = text
preferences[prefKey] = textLogin // TODO(переделать под отправку и логина и пароля в api)
}
}
}

View File

@ -36,9 +36,9 @@ object NetworkDataSource {
}
}
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
suspend fun checkAuth(login: String, password: String): Result<Boolean> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.AUTH_URL))
val response = client.post(getUrl(login, Constants.AUTH_URL))
when (response.status) {
HttpStatusCode.OK -> true
else -> false

View File

@ -1,15 +0,0 @@
package ru.myitschool.work.domain.auth
import ru.myitschool.work.data.repo.AuthRepository
class CheckAndSaveAuthCodeUseCase(
private val repository: AuthRepository
) {
suspend operator fun invoke(
text: String
): Result<Unit> {
return repository.checkAndSave(text).mapCatching { success ->
if (!success) error("Code is incorrect")
}
}
}

View File

@ -0,0 +1,16 @@
package ru.myitschool.work.domain.auth
import ru.myitschool.work.data.repo.AuthRepository
class CheckAndSaveAuthDataUseCase(
private val repository: AuthRepository
) {
suspend operator fun invoke(
textLogin: String,
textPassword: String
): Result<Unit> {
return repository.checkAndSave(textLogin, textPassword).mapCatching { success ->
if (!success) error("Login or password is incorrect")
}
}
}

View File

@ -1,12 +0,0 @@
package ru.myitschool.work.domain.auth
class CheckCodeFormatUseCase {
operator fun invoke(
text: String
): Boolean {
return text.length == 4 && text.all { char ->
char.isLetterOrDigit() &&
((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char.isDigit())
}
}
}

View File

@ -0,0 +1,14 @@
package ru.myitschool.work.domain.auth
class CheckLoginFormatUseCase {
operator fun invoke(
text: String
): Boolean {
return text.all { char ->
(char.isLetterOrDigit() &&
((char in ('A'..'Z')) || (char in ('a'..'z')) || char.isDigit()))
|| char == '.'
}
}
}

View File

@ -0,0 +1,24 @@
package ru.myitschool.work.domain.auth
import java.util.Locale
import java.util.Locale.getDefault
class CheckPasswordFormatUseCase {
operator fun invoke(
textLogin: String,
textPassword: String
): Boolean {
val lowerCasePassword = textPassword.lowercase(getDefault())
val lowerCaseLogin = textLogin.lowercase(getDefault())
val intersect = lowerCasePassword.toList().intersect(lowerCaseLogin.toList());
return !(textPassword.length < 8 || !textPassword.all { char ->
char.isLetterOrDigit()
&& ((char in 'A'..'Z')
|| (char in 'a'..'z')
|| char.isDigit())
&& textPassword.count { it == char } < 3
&& intersect.size < 3
})
}
}

View File

@ -1,6 +1,7 @@
package ru.myitschool.work.ui.root
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@ -15,6 +16,8 @@ class RootActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
setContent {
WorkTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

View File

@ -1,6 +1,6 @@
package ru.myitschool.work.ui.screen.auth
sealed interface AuthIntent {
data class Send(val text: String): AuthIntent
data class TextInput(val text: String): AuthIntent
data class Send(val textLogin: String, val textPassword: String): AuthIntent
data class TextInput(val textLogin: String, val textPassword: String): AuthIntent
}

View File

@ -77,22 +77,39 @@ private fun Content(
viewModel: AuthViewModel,
state: AuthState.Data
) {
var inputText by rememberSaveable { mutableStateOf("") }
var inputLogin by rememberSaveable { mutableStateOf("") }
Spacer(modifier = Modifier.size(16.dp))
TextField(
modifier = Modifier.testTag(TestIds.Auth.CODE_INPUT).fillMaxWidth(),
value = inputText,
modifier = Modifier.testTag(TestIds.Auth.LOGIN_INPUT).fillMaxWidth(),
value = inputLogin,
onValueChange = {
inputText = it
viewModel.onIntent(AuthIntent.TextInput(it))
inputLogin = it
viewModel.onIntent(AuthIntent.TextInput(
it,
textPassword = ""
))
},
label = { Text(stringResource(R.string.auth_label)) }
label = { Text(stringResource(R.string.auth_label_login)) }
)
var inputPassword by rememberSaveable { mutableStateOf("") }
Spacer(modifier = Modifier.size(16.dp))
TextField(
modifier = Modifier.testTag(TestIds.Auth.PASSWORD_INPUT).fillMaxWidth(),
value = inputPassword,
onValueChange = {
inputPassword = it
viewModel.onIntent(AuthIntent.TextInput(
inputLogin,
textPassword = it
))
},
label = { Text(stringResource(R.string.auth_label_password)) }
)
Spacer(modifier = Modifier.size(16.dp))
Button(
modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(),
onClick = {
viewModel.onIntent(AuthIntent.Send(inputText))
viewModel.onIntent(AuthIntent.Send(inputLogin, inputPassword))
},
enabled = state.isEnabledSend
) {

View File

@ -10,13 +10,17 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
import ru.myitschool.work.domain.auth.CheckCodeFormatUseCase
import ru.myitschool.work.domain.auth.CheckAndSaveAuthDataUseCase
import ru.myitschool.work.domain.auth.CheckLoginFormatUseCase
import ru.myitschool.work.domain.auth.CheckPasswordFormatUseCase
import ru.myitschool.work.ui.nav.MainScreenDestination
class AuthViewModel : ViewModel() {
private val checkCodeFormatUseCase by lazy { CheckCodeFormatUseCase() }
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
private val checkLoginFormatUseCase by lazy { CheckLoginFormatUseCase() }
private val checkAndSaveAuthDataUseCase by lazy { CheckAndSaveAuthDataUseCase(AuthRepository) }
private val checkPasswordFormatUseCase by lazy { CheckPasswordFormatUseCase() }
private val _uiState = MutableStateFlow<AuthState>(
AuthState.Data(
isEnabledSend = false,
@ -32,7 +36,7 @@ class AuthViewModel : ViewModel() {
when (intent) {
is AuthIntent.Send -> {
viewModelScope.launch {
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
checkAndSaveAuthDataUseCase.invoke(intent.textLogin, intent.textPassword).fold(
onSuccess = {
_actionFlow.emit(AuthAction.Open(MainScreenDestination))
},
@ -46,10 +50,13 @@ class AuthViewModel : ViewModel() {
)
}
}
is AuthIntent.TextInput -> {
updateStateIfData { oldState ->
oldState.copy(
isEnabledSend = checkCodeFormatUseCase.invoke(intent.text),
isEnabledSend = (checkLoginFormatUseCase.invoke(intent.textLogin) && checkPasswordFormatUseCase.invoke(
intent.textLogin, intent.textPassword
)),
error = null
)
}

View File

@ -2,7 +2,8 @@
<string name="app_name">Work</string>
<string name="title_activity_root">Приложение</string>
<string name="auth_title">Привет! Введи код для авторизации</string>
<string name="auth_label">Код</string>
<string name="auth_label_login">Логин</string>
<string name="auth_label_password">Пароль</string>
<string name="auth_sign_in">Войти</string>
<string name="main_refresh">Обновить</string>