Попробовал сделать экран входа с помощью курсора
This commit is contained in:
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="21" />
|
<bytecodeTargetLevel target="17" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<value>
|
||||||
|
<entry key="app">
|
||||||
|
<State />
|
||||||
|
</entry>
|
||||||
|
</value>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="2.1.0" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ dependencies {
|
|||||||
implementation(libs.lottie.compose)
|
implementation(libs.lottie.compose)
|
||||||
|
|
||||||
// Coroutines
|
// Coroutines
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
||||||
implementation(libs.kotlinx.coroutines.android)
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
|||||||
@@ -27,18 +27,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
BottomNavigationBar(
|
if (currentRoute != "login") {
|
||||||
currentRoute = currentRoute,
|
BottomNavigationBar(
|
||||||
onNavigate = { route ->
|
currentRoute = currentRoute,
|
||||||
navController.navigate(route) {
|
onNavigate = { route ->
|
||||||
popUpTo(navController.graph.startDestinationId) {
|
navController.navigate(route) {
|
||||||
saveState = true
|
popUpTo(navController.graph.startDestinationId) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
NewPlanetNavigation(
|
NewPlanetNavigation(
|
||||||
|
|||||||
38
app/src/main/java/com/novayaplaneta/ui/components/Logo.kt
Normal file
38
app/src/main/java/com/novayaplaneta/ui/components/Logo.kt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.novayaplaneta.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.novayaplaneta.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NovayaPlanetaLogo(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: androidx.compose.ui.unit.Dp? = null
|
||||||
|
) {
|
||||||
|
// Адаптивный размер - используем размер экрана или переданный размер
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
|
val logoSize = size ?: kotlin.run {
|
||||||
|
// Базовый размер для планшета, адаптируется под экран
|
||||||
|
val minScreenSize = kotlin.math.min(configuration.screenWidthDp, configuration.screenHeightDp)
|
||||||
|
(minScreenSize / 3).dp
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier.size(logoSize),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.logo_earth),
|
||||||
|
contentDescription = "Логотип Новая Планета",
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import androidx.navigation.NavHostController
|
|||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import com.novayaplaneta.ui.screens.ai.AIScreen
|
import com.novayaplaneta.ui.screens.ai.AIScreen
|
||||||
|
import com.novayaplaneta.ui.screens.auth.LoginScreen
|
||||||
import com.novayaplaneta.ui.screens.rewards.RewardsScreen
|
import com.novayaplaneta.ui.screens.rewards.RewardsScreen
|
||||||
import com.novayaplaneta.ui.screens.schedule.ScheduleScreen
|
import com.novayaplaneta.ui.screens.schedule.ScheduleScreen
|
||||||
import com.novayaplaneta.ui.screens.settings.SettingsScreen
|
import com.novayaplaneta.ui.screens.settings.SettingsScreen
|
||||||
@@ -16,13 +17,16 @@ import com.novayaplaneta.ui.screens.timer.TimerScreen
|
|||||||
fun NewPlanetNavigation(
|
fun NewPlanetNavigation(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
startDestination: String = "schedule"
|
startDestination: String = "login"
|
||||||
) {
|
) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = startDestination,
|
startDestination = startDestination,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
|
composable("login") {
|
||||||
|
LoginScreen(navController = navController)
|
||||||
|
}
|
||||||
composable("schedule") {
|
composable("schedule") {
|
||||||
ScheduleScreen()
|
ScheduleScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
package com.novayaplaneta.ui.screens.auth
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.novayaplaneta.ui.components.NovayaPlanetaLogo
|
||||||
|
import com.novayaplaneta.ui.theme.*
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoginScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: LoginViewModel = hiltViewModel(),
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.errorMessage) {
|
||||||
|
val errorMsg = uiState.errorMessage
|
||||||
|
if (errorMsg != null) {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = errorMsg,
|
||||||
|
actionLabel = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(
|
||||||
|
hostState = snackbarHostState,
|
||||||
|
snackbar = { snackbarData ->
|
||||||
|
Snackbar(
|
||||||
|
snackbarData = snackbarData,
|
||||||
|
containerColor = MaterialTheme.colorScheme.error,
|
||||||
|
contentColor = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = LoginBackgroundTurquoise
|
||||||
|
) { paddingValues ->
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.background(color = LoginBackgroundTurquoise)
|
||||||
|
) {
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
|
val screenWidthDp: Int = configuration.screenWidthDp
|
||||||
|
val screenHeightDp: Int = configuration.screenHeightDp
|
||||||
|
val isLandscape = screenWidthDp > screenHeightDp
|
||||||
|
|
||||||
|
// Адаптивные отступы
|
||||||
|
val horizontalPadding = (screenWidthDp * 0.04f).toInt().coerceIn(24, 48).dp
|
||||||
|
val verticalPadding = (screenHeightDp * 0.03f).toInt().coerceIn(16, 32).dp
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// Логотип вверху слева - уменьшенный размер
|
||||||
|
val logoSizeByHeight = (screenHeightDp * 0.15f).toInt()
|
||||||
|
val logoSizeByWidth = screenWidthDp / 5
|
||||||
|
val logoSize = min(logoSizeByHeight, logoSizeByWidth).coerceIn(100, 160).dp
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
NovayaPlanetaLogo(
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp),
|
||||||
|
size = logoSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(0.05f))
|
||||||
|
|
||||||
|
// Центрированный контент - адаптивная ширина (50-70% экрана)
|
||||||
|
val contentWidthRatio = if (isLandscape) 0.5f else 0.7f
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(contentWidthRatio),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||||
|
) {
|
||||||
|
// Заголовок "Вход" - увеличен
|
||||||
|
val titleFontSize = (screenHeightDp * 0.06f).toInt().coerceIn(44, 76).sp
|
||||||
|
Text(
|
||||||
|
text = "Вход",
|
||||||
|
fontSize = titleFontSize,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = LoginGreenAccent,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
// Поля ввода - увеличены
|
||||||
|
val inputHeightValue = (screenHeightDp * 0.075f).toInt().coerceIn(64, 100)
|
||||||
|
val inputHeight: androidx.compose.ui.unit.Dp = inputHeightValue.dp
|
||||||
|
val inputTextSizeValue = (screenHeightDp * 0.026f).toInt().coerceIn(20, 28)
|
||||||
|
val inputTextSize: androidx.compose.ui.unit.TextUnit = inputTextSizeValue.sp
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
// Поле логина
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height = inputHeight)
|
||||||
|
.background(
|
||||||
|
color = LoginInputLightBlue,
|
||||||
|
shape = RoundedCornerShape(20.dp)
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = uiState.login,
|
||||||
|
onValueChange = { viewModel.onLoginChange(it) },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "Введи логин",
|
||||||
|
fontSize = inputTextSize,
|
||||||
|
color = Color.Gray.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedTextColor = Color.Black,
|
||||||
|
focusedTextColor = Color.Black
|
||||||
|
),
|
||||||
|
textStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
fontSize = inputTextSize
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поле пароля
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height = inputHeight)
|
||||||
|
.background(
|
||||||
|
color = LoginInputLightBlue,
|
||||||
|
shape = RoundedCornerShape(20.dp)
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = uiState.password,
|
||||||
|
onValueChange = { viewModel.onPasswordChange(it) },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "Введи пароль",
|
||||||
|
fontSize = inputTextSize,
|
||||||
|
color = Color.Gray.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedTextColor = Color.Black,
|
||||||
|
focusedTextColor = Color.Black
|
||||||
|
),
|
||||||
|
textStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
fontSize = inputTextSize
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Кнопка "Войти" - адаптивный размер
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.login {
|
||||||
|
navController.navigate("schedule") {
|
||||||
|
popUpTo(0) { inclusive = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height = inputHeight),
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
enabled = uiState.isFormValid && !uiState.isLoading,
|
||||||
|
colors = if (uiState.isFormValid) {
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor = LoginButtonBlue,
|
||||||
|
contentColor = Color.White
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor = LoginInputLightBlue,
|
||||||
|
contentColor = Color.Gray,
|
||||||
|
disabledContainerColor = LoginInputLightBlue,
|
||||||
|
disabledContentColor = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (uiState.isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val buttonTextSize = (screenHeightDp * 0.027f).toInt().coerceIn(22, 34).sp
|
||||||
|
Text(
|
||||||
|
text = "Войти",
|
||||||
|
fontSize = buttonTextSize,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ссылки - зелёные, около кнопки
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
val linkTextSize = (screenHeightDp * 0.024f).toInt().coerceIn(18, 26).sp
|
||||||
|
Text(
|
||||||
|
text = "Нет логина и пароля?",
|
||||||
|
fontSize = linkTextSize,
|
||||||
|
color = LoginGreenAccent,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
// TODO: Переход на регистрацию
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Не помнишь пароль?",
|
||||||
|
fontSize = linkTextSize,
|
||||||
|
color = LoginGreenAccent,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
// TODO: Переход на восстановление пароля
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(0.1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.novayaplaneta.ui.screens.auth
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.novayaplaneta.domain.repository.AuthRepository
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LoginViewModel @Inject constructor(
|
||||||
|
private val authRepository: AuthRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(LoginUiState())
|
||||||
|
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
fun onLoginChange(login: String) {
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
login = login,
|
||||||
|
isFormValid = isFormValid(login, _uiState.value.password)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPasswordChange(password: String) {
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
password = password,
|
||||||
|
isFormValid = isFormValid(_uiState.value.login, password)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun login(onSuccess: () -> Unit) {
|
||||||
|
if (!_uiState.value.isFormValid) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.value = _uiState.value.copy(isLoading = true, errorMessage = null)
|
||||||
|
|
||||||
|
val result = authRepository.login(_uiState.value.login, _uiState.value.password)
|
||||||
|
|
||||||
|
result.onSuccess { user ->
|
||||||
|
_uiState.value = _uiState.value.copy(isLoading = false, isLoggedIn = true)
|
||||||
|
onSuccess()
|
||||||
|
}.onFailure { exception ->
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
isLoading = false,
|
||||||
|
errorMessage = exception.message ?: "Ошибка входа"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isFormValid(login: String, password: String): Boolean {
|
||||||
|
return login.isNotBlank() && password.isNotBlank()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LoginUiState(
|
||||||
|
val login: String = "",
|
||||||
|
val password: String = "",
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val isFormValid: Boolean = false,
|
||||||
|
val isLoggedIn: Boolean = false,
|
||||||
|
val errorMessage: String? = null
|
||||||
|
)
|
||||||
@@ -24,3 +24,12 @@ val SuccessColor = Color(0xFF4CAF50)
|
|||||||
val WarningColor = Color(0xFFFF6B35)
|
val WarningColor = Color(0xFFFF6B35)
|
||||||
val ErrorColor = Color(0xFFE53935)
|
val ErrorColor = Color(0xFFE53935)
|
||||||
|
|
||||||
|
// Цвета для экрана авторизации (благоприятные для РАС из PDF)
|
||||||
|
val LoginBackgroundTurquoise = Color(0xFFDAE7E9) // Мягкий голубой фон
|
||||||
|
val LoginCardLightBlue = Color(0xFFBCDAEC) // Спокойный светло-голубой
|
||||||
|
val LoginInputLightBlue = Color(0xFFBCDAEC) // Для полей ввода
|
||||||
|
val LoginButtonBlue = Color(0xFFBCDAEC) // Для кнопки
|
||||||
|
val LoginGreenAccent = Color(0xFF80EF80) // Пастельно-зелёный акцент
|
||||||
|
val LoginGreenSoft = Color(0xFFC5E6C5) // Мягкий пастельно-зелёный
|
||||||
|
val LoginGreenDark = Color(0xFF80EF80) // Пастельно-зелёный темнее
|
||||||
|
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/logo_earth.png
Normal file
BIN
app/src/main/res/drawable/logo_earth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -1,14 +1,14 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.12.3"
|
agp = "8.3.1"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.1.0"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.13.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.3.0"
|
junitVersion = "1.3.0"
|
||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
lifecycleRuntimeKtx = "2.9.4"
|
lifecycleRuntimeKtx = "2.8.7"
|
||||||
lifecycleViewmodel = "2.9.4"
|
lifecycleViewmodel = "2.8.7"
|
||||||
activityCompose = "1.11.0"
|
activityCompose = "1.9.2"
|
||||||
composeBom = "2024.09.00"
|
composeBom = "2024.06.00"
|
||||||
hilt = "2.53"
|
hilt = "2.53"
|
||||||
hiltNavigationCompose = "1.2.0"
|
hiltNavigationCompose = "1.2.0"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
@@ -19,8 +19,8 @@ kotlinxSerializationConverter = "1.0.0"
|
|||||||
navigation = "2.8.4"
|
navigation = "2.8.4"
|
||||||
coil = "2.7.0"
|
coil = "2.7.0"
|
||||||
lottie = "6.1.0"
|
lottie = "6.1.0"
|
||||||
coroutines = "1.10.0"
|
coroutines = "1.9.0"
|
||||||
ksp = "2.0.21-1.0.26"
|
ksp = "2.1.0-1.0.28"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Fri Dec 05 21:32:23 MSK 2025
|
#Fri Dec 05 21:32:23 MSK 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Reference in New Issue
Block a user