diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b86273d..b589d56 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..0c0c338
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..bb44937
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b2c751a..0ad17cb 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,7 @@
+
-
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4b04a59..0547ef6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -97,8 +97,8 @@ dependencies {
implementation(libs.lottie.compose)
// Coroutines
- implementation(libs.kotlinx.coroutines.core)
- implementation(libs.kotlinx.coroutines.android)
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
// Testing
testImplementation(libs.junit)
diff --git a/app/src/main/java/com/novayaplaneta/MainActivity.kt b/app/src/main/java/com/novayaplaneta/MainActivity.kt
index d381f52..9581495 100644
--- a/app/src/main/java/com/novayaplaneta/MainActivity.kt
+++ b/app/src/main/java/com/novayaplaneta/MainActivity.kt
@@ -27,18 +27,20 @@ class MainActivity : ComponentActivity() {
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
- BottomNavigationBar(
- currentRoute = currentRoute,
- onNavigate = { route ->
- navController.navigate(route) {
- popUpTo(navController.graph.startDestinationId) {
- saveState = true
+ if (currentRoute != "login") {
+ BottomNavigationBar(
+ currentRoute = currentRoute,
+ onNavigate = { route ->
+ navController.navigate(route) {
+ popUpTo(navController.graph.startDestinationId) {
+ saveState = true
+ }
+ launchSingleTop = true
+ restoreState = true
}
- launchSingleTop = true
- restoreState = true
}
- }
- )
+ )
+ }
}
) { innerPadding ->
NewPlanetNavigation(
diff --git a/app/src/main/java/com/novayaplaneta/ui/components/Logo.kt b/app/src/main/java/com/novayaplaneta/ui/components/Logo.kt
new file mode 100644
index 0000000..606b73b
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/components/Logo.kt
@@ -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
+ )
+ }
+}
diff --git a/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt b/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt
index 5ab0fcd..b381408 100644
--- a/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt
+++ b/app/src/main/java/com/novayaplaneta/ui/navigation/NewPlanetNavigation.kt
@@ -6,6 +6,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
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.schedule.ScheduleScreen
import com.novayaplaneta.ui.screens.settings.SettingsScreen
@@ -16,13 +17,16 @@ import com.novayaplaneta.ui.screens.timer.TimerScreen
fun NewPlanetNavigation(
navController: NavHostController,
modifier: Modifier = Modifier,
- startDestination: String = "schedule"
+ startDestination: String = "login"
) {
NavHost(
navController = navController,
startDestination = startDestination,
modifier = modifier
) {
+ composable("login") {
+ LoginScreen(navController = navController)
+ }
composable("schedule") {
ScheduleScreen()
}
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/auth/LoginScreen.kt b/app/src/main/java/com/novayaplaneta/ui/screens/auth/LoginScreen.kt
new file mode 100644
index 0000000..46e09e5
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/auth/LoginScreen.kt
@@ -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))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/novayaplaneta/ui/screens/auth/LoginViewModel.kt b/app/src/main/java/com/novayaplaneta/ui/screens/auth/LoginViewModel.kt
new file mode 100644
index 0000000..03ac640
--- /dev/null
+++ b/app/src/main/java/com/novayaplaneta/ui/screens/auth/LoginViewModel.kt
@@ -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 = _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
+)
diff --git a/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt b/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
index 8077fd8..867dd1a 100644
--- a/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
+++ b/app/src/main/java/com/novayaplaneta/ui/theme/Color.kt
@@ -24,3 +24,12 @@ val SuccessColor = Color(0xFF4CAF50)
val WarningColor = Color(0xFFFF6B35)
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) // Пастельно-зелёный темнее
+
diff --git a/app/src/main/res/drawable/logo_earth.png b/app/src/main/res/drawable/logo_earth.png
new file mode 100644
index 0000000..c1d2e98
Binary files /dev/null and b/app/src/main/res/drawable/logo_earth.png differ
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2a9ff7d..232f61d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,14 +1,14 @@
[versions]
-agp = "8.12.3"
-kotlin = "2.0.21"
-coreKtx = "1.17.0"
+agp = "8.3.1"
+kotlin = "2.1.0"
+coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
-lifecycleRuntimeKtx = "2.9.4"
-lifecycleViewmodel = "2.9.4"
-activityCompose = "1.11.0"
-composeBom = "2024.09.00"
+lifecycleRuntimeKtx = "2.8.7"
+lifecycleViewmodel = "2.8.7"
+activityCompose = "1.9.2"
+composeBom = "2024.06.00"
hilt = "2.53"
hiltNavigationCompose = "1.2.0"
room = "2.6.1"
@@ -19,8 +19,8 @@ kotlinxSerializationConverter = "1.0.0"
navigation = "2.8.4"
coil = "2.7.0"
lottie = "6.1.0"
-coroutines = "1.10.0"
-ksp = "2.0.21-1.0.26"
+coroutines = "1.9.0"
+ksp = "2.1.0-1.0.28"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 86468ce..d29f63c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Dec 05 21:32:23 MSK 2025
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists