Загрузка данных


Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SignUpLoginInv1">
        <activity
            android:name=".LoginIn"
            android:exported="false"
            android:label="@string/app_name"
            android:theme="@style/Theme.SignUpLoginInv1" />
        <activity
            android:name=".SignUp"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.SignUpLoginInv1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
Login:
package com.example.signuplogininv1

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class LoginIn : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        val presetEmail = intent.getStringExtra(EXTRA_EMAIL).orEmpty()
        val presetPassword = intent.getStringExtra(EXTRA_PASSWORD).orEmpty()

        setContent {
            LoginInScreen(
                presetEmail = presetEmail,
                presetPassword = presetPassword,
                onOpenSignUp = {
                    startActivity(Intent(this, SignUp::class.java))
                }
            )
        }
    }
}

@Preview(showSystemUi = true)
@Composable
fun LoginInScreen(
    presetEmail: String = "",
    presetPassword: String = "",
    onOpenSignUp: () -> Unit = {}
) {
    var email by remember { mutableStateOf(presetEmail) }
    var password by remember { mutableStateOf(presetPassword) }
    var passwordVisible by remember { mutableStateOf(false) }

    var emailError by remember { mutableStateOf<String?>(null) }
    var passwordError by remember { mutableStateOf<String?>(null) }

    fun validate(): Boolean {
        emailError = when {
            email.isBlank() -> "Введите email."
            !email.contains("@") -> "Введите корректный email с символом @."
            else -> null
        }

        passwordError = when {
            password.isBlank() -> "Введите пароль."
            else -> null
        }

        return emailError == null && passwordError == null
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .statusBarsPadding()
                .verticalScroll(rememberScrollState())
                .padding(horizontal = 24.dp, vertical = 24.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Spacer(modifier = Modifier.height(96.dp))

            Text(
                text = "Добро пожаловать!",
                fontSize = 24.sp,
                fontWeight = FontWeight.Bold,
                color = Color(0xFF3A3A3A)
            )

            Spacer(modifier = Modifier.height(8.dp))

            Text(
                text = "Введите свой адрес электронной почты\nи пароль, чтобы продолжить",
                fontSize = 14.sp,
                color = Color(0xFFA7A7A7),
                textAlign = TextAlign.Center
            )

            Spacer(modifier = Modifier.height(72.dp))

            AuthTextField(
                label = "Email",
                value = email,
                onValueChange = {
                    email = it
                    emailError = null
                },
                placeholder = "example@gmail.com",
                keyboardType = KeyboardType.Email,
                error = emailError
            )

            Spacer(modifier = Modifier.height(20.dp))

            AuthTextField(
                label = "Пароль",
                value = password,
                onValueChange = {
                    password = it
                    passwordError = null
                },
                placeholder = "********",
                keyboardType = KeyboardType.Password,
                error = passwordError,
                isPassword = true,
                isPasswordVisible = passwordVisible,
                onPasswordVisibilityToggle = { passwordVisible = !passwordVisible }
            )

            Spacer(modifier = Modifier.height(160.dp))

            Button(
                onClick = { validate() },
                modifier = Modifier
                    .fillMaxWidth()
                    .height(46.dp),
                shape = RoundedCornerShape(8.dp),
                colors = ButtonDefaults.buttonColors(
                    containerColor = Color(0xFFA7A7A7)
                )
            ) {
                Text(
                    text = "Войти",
                    fontSize = 16.sp,
                    fontWeight = FontWeight.Bold
                )
            }

            Spacer(modifier = Modifier.height(24.dp))

            Row(
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "Нет аккаунта? ",
                    fontSize = 14.sp,
                    color = Color(0xFFA7A7A7)
                )
                Text(
                    text = "Регистрация",
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Bold,
                    color = Color(0xFF0560FA),
                    modifier = Modifier.clickable(onClick = onOpenSignUp)
                )
            }
        }
    }
}
Sign:
package com.example.signuplogininv1

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

const val EXTRA_EMAIL = "extra_email"
const val EXTRA_PASSWORD = "extra_password"

class SignUp : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            RegistrationScreen(
                onOpenLogin = { email, password ->
                    startActivity(
                        Intent(this, LoginIn::class.java).apply {
                            putExtra(EXTRA_EMAIL, email)
                            putExtra(EXTRA_PASSWORD, password)
                        }
                    )
                }
            )
        }
    }
}

@Preview(showSystemUi = true)
@Composable
fun RegistrationScreen(
    onOpenLogin: (String, String) -> Unit = { _, _ -> }
) {
    var login by remember { mutableStateOf("") }
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var confirmPassword by remember { mutableStateOf("") }
    var isPrivacyPolicyChecked by remember { mutableStateOf(false) }

    var passwordVisible by remember { mutableStateOf(false) }
    var confirmPasswordVisible by remember { mutableStateOf(false) }

    var loginError by remember { mutableStateOf<String?>(null) }
    var emailError by remember { mutableStateOf<String?>(null) }
    var passwordError by remember { mutableStateOf<String?>(null) }
    var confirmPasswordError by remember { mutableStateOf<String?>(null) }
    var privacyPolicyError by remember { mutableStateOf<String?>(null) }

    fun validate(): Boolean {
        loginError = when {
            login.isBlank() -> "Введите логин."
            login.length < 8 -> "Логин должен содержать минимум 8 символов."
            else -> null
        }

        emailError = when {
            email.isBlank() -> "Введите email."
            email.length < 12 -> "Email должен содержать минимум 12 символов."
            !email.contains("@") -> "Email должен содержать символ @."
            else -> null
        }

        passwordError = when {
            password.isBlank() -> "Введите пароль."
            password.length < 8 -> "Пароль должен содержать минимум 8 цифр."
            !password.all { it.isDigit() } -> "Пароль должен состоять только из цифр."
            else -> null
        }

        confirmPasswordError = when {
            confirmPassword.isBlank() -> "Повторите пароль."
            confirmPassword != password -> "Пароли должны совпадать."
            else -> null
        }

        privacyPolicyError = if (!isPrivacyPolicyChecked) {
            "Подтвердите согласие с политикой конфиденциальности."
        } else {
            null
        }

        return listOf(
            loginError,
            emailError,
            passwordError,
            confirmPasswordError,
            privacyPolicyError
        ).all { it == null }
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .statusBarsPadding()
                .verticalScroll(rememberScrollState())
                .padding(horizontal = 24.dp, vertical = 24.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Spacer(modifier = Modifier.height(48.dp))

            Text(
                text = "Создайте аккаунт",
                fontSize = 24.sp,
                fontWeight = FontWeight.Bold,
                color = Color(0xFF3A3A3A)
            )

            Spacer(modifier = Modifier.height(8.dp))

            Text(
                text = "Завершите процесс регистрации,\nчтобы приступить к работе",
                fontSize = 14.sp,
                color = Color(0xFFA7A7A7),
                textAlign = TextAlign.Center
            )

            Spacer(modifier = Modifier.height(32.dp))

            AuthTextField(
                label = "Логин",
                value = login,
                onValueChange = {
                    login = it
                    loginError = null
                },
                placeholder = "Иванов Иван",
                error = loginError
            )

            Spacer(modifier = Modifier.height(20.dp))

            AuthTextField(
                label = "Email",
                value = email,
                onValueChange = {
                    email = it
                    emailError = null
                },
                placeholder = "example@gmail.com",
                keyboardType = KeyboardType.Email,
                error = emailError
            )

            Spacer(modifier = Modifier.height(20.dp))

            AuthTextField(
                label = "Пароль",
                value = password,
                onValueChange = {
                    val updatedPassword = it.filter(Char::isDigit)
                    password = updatedPassword
                    passwordError = null
                    if (confirmPassword.isNotEmpty() && confirmPassword != updatedPassword) {
                        confirmPasswordError = "Пароли должны совпадать."
                    } else {
                        confirmPasswordError = null
                    }
                },
                placeholder = "********",
                keyboardType = KeyboardType.NumberPassword,
                error = passwordError,
                isPassword = true,
                isPasswordVisible = passwordVisible,
                onPasswordVisibilityToggle = { passwordVisible = !passwordVisible }
            )

            Spacer(modifier = Modifier.height(20.dp))

            AuthTextField(
                label = "Повторите пароль",
                value = confirmPassword,
                onValueChange = {
                    confirmPassword = it.filter(Char::isDigit)
                    confirmPasswordError = null
                },
                placeholder = "********",
                keyboardType = KeyboardType.NumberPassword,
                error = confirmPasswordError,
                isPassword = true,
                isPasswordVisible = confirmPasswordVisible,
                onPasswordVisibilityToggle = {
                    confirmPasswordVisible = !confirmPasswordVisible
                }
            )

            Spacer(modifier = Modifier.height(28.dp))

            Row(
                modifier = Modifier.fillMaxWidth(),
                verticalAlignment = Alignment.Top
            ) {
                Checkbox(
                    checked = isPrivacyPolicyChecked,
                    onCheckedChange = {
                        isPrivacyPolicyChecked = it
                        privacyPolicyError = null
                    },
                    colors = CheckboxDefaults.colors(
                        checkedColor = Color(0xFF0560FA),
                        uncheckedColor = Color(0xFFA7A7A7)
                    )
                )

                Column(modifier = Modifier.padding(top = 11.dp, end = 4.dp)) {
                    Text(
                        text = "Отметьте это поле, если вы соглашаетесь с нашей Политикой конфиденциальности",
                        fontSize = 12.sp,
                        color = Color(0xFF8D8A8A)
                    )
                    privacyPolicyError?.let {
                        Spacer(modifier = Modifier.height(4.dp))
                        Text(
                            text = it,
                            fontSize = 12.sp,
                            color = Color(0xFFB3261E)
                        )
                    }
                }
            }

            Spacer(modifier = Modifier.height(72.dp))

            Button(
                onClick = {
                    if (validate()) {
                        onOpenLogin(email, password)
                    }
                },
                modifier = Modifier
                    .fillMaxWidth()
                    .height(46.dp),
                shape = RoundedCornerShape(8.dp),
                colors = ButtonDefaults.buttonColors(
                    containerColor = Color(0xFFA7A7A7)
                )
            ) {
                Text(
                    text = "Зарегистрироваться",
                    fontSize = 16.sp,
                    fontWeight = FontWeight.Bold
                )
            }

            Spacer(modifier = Modifier.height(24.dp))

            Row(
                modifier = Modifier.padding(bottom = 24.dp),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "Уже есть профиль? ",
                    fontSize = 14.sp,
                    color = Color(0xFFA7A7A7)
                )
                Text(
                    text = "Вход",
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Bold,
                    color = Color(0xFF0560FA),
                    modifier = Modifier.clickable { onOpenLogin(email, password) }
                )
            }
        }
    }
}

@Composable
fun AuthTextField(
    label: String,
    value: String,
    onValueChange: (String) -> Unit,
    placeholder: String,
    modifier: Modifier = Modifier,
    keyboardType: KeyboardType = KeyboardType.Text,
    error: String? = null,
    isPassword: Boolean = false,
    isPasswordVisible: Boolean = false,
    onPasswordVisibilityToggle: (() -> Unit)? = null
) {
    Text(
        text = label,
        fontSize = 14.sp,
        color = Color(0xFF595959),
        modifier = modifier
            .fillMaxWidth()
            .padding(start = 4.dp, bottom = 6.dp)
    )

    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        modifier = modifier.fillMaxWidth(),
        placeholder = { Text(placeholder, color = Color(0xFFCFCFCF)) },
        singleLine = true,
        shape = RoundedCornerShape(8.dp),
        keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
        visualTransformation = if (isPassword && !isPasswordVisible) {
            PasswordVisualTransformation()
        } else {
            VisualTransformation.None
        },
        trailingIcon = if (isPassword && onPasswordVisibilityToggle != null) {
            {
                IconButton(onClick = onPasswordVisibilityToggle) {
                    Icon(
                        imageVector = if (isPasswordVisible) {
                            Icons.Default.Visibility
                        } else {
                            Icons.Default.VisibilityOff
                        },
                        contentDescription = if (isPasswordVisible) {
                            "Скрыть пароль"
                        } else {
                            "Показать пароль"
                        },
                        tint = Color.Gray
                    )
                }
            }
        } else {
            null
        },
        isError = error != null,
        supportingText = {
            error?.let {
                Text(text = it, color = Color(0xFFB3261E))
            }
        },
        colors = OutlinedTextFieldDefaults.colors(
            focusedBorderColor = Color(0xFFA7A7A7),
            unfocusedBorderColor = Color(0xFFA7A7A7),
            errorBorderColor = Color(0xFFB3261E),
            errorSupportingTextColor = Color(0xFFB3261E)
        )
    )
}