diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index d6d139c0..7a53b8e1 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -21,6 +21,9 @@
+
+
+
@@ -42,12 +45,12 @@
-
-
-
+
+
+
diff --git a/feature/authentication/api/.gitignore b/feature/authentication/api/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/authentication/api/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/authentication/api/build.gradle.kts b/feature/authentication/api/build.gradle.kts
new file mode 100644
index 00000000..156a49db
--- /dev/null
+++ b/feature/authentication/api/build.gradle.kts
@@ -0,0 +1,40 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "ru.yeahub.authentication.api"
+ compileSdk = 35
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/feature/authentication/api/consumer-rules.pro b/feature/authentication/api/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/authentication/api/proguard-rules.pro b/feature/authentication/api/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/authentication/api/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/authentication/api/src/main/AndroidManifest.xml b/feature/authentication/api/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/feature/authentication/api/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/authentication/impl/.gitignore b/feature/authentication/impl/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/authentication/impl/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/authentication/impl/build.gradle.kts b/feature/authentication/impl/build.gradle.kts
new file mode 100644
index 00000000..2759ccdb
--- /dev/null
+++ b/feature/authentication/impl/build.gradle.kts
@@ -0,0 +1,71 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+}
+
+android {
+ namespace = "ru.yeahub.authentication.impl"
+ compileSdk = 35
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ buildFeatures {
+ compose = true
+ }
+ testOptions {
+ unitTests.all {
+ it.useJUnitPlatform()
+ it.jvmArgs("-XX:+EnableDynamicAgentLoading")
+ }
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.compose.material.icons.extended)
+ implementation(project(":core:ui"))
+ implementation(project(":core:utils"))
+ implementation(project(":core:network-api"))
+ implementation(project(":core:navigation-api"))
+ implementation(libs.timber)
+ implementation(libs.androidx.core.ktx)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.lifecycle.viewmodel.ktx)
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.material3)
+ implementation(libs.androidx.runtime.android)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.koin.core)
+ implementation(libs.koin.android)
+ implementation(libs.koin.compose)
+ implementation(libs.compose.shimmer)
+ implementation(libs.androidx.navigation.compose)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+ testImplementation(libs.junit.jupiter)
+ testImplementation(platform(libs.junit.bom))
+ testRuntimeOnly(libs.junit.platform.launcher)
+ testImplementation(libs.mockk)
+}
\ No newline at end of file
diff --git a/feature/authentication/impl/consumer-rules.pro b/feature/authentication/impl/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/authentication/impl/proguard-rules.pro b/feature/authentication/impl/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/authentication/impl/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/authentication/impl/src/main/AndroidManifest.xml b/feature/authentication/impl/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/feature/authentication/impl/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/PasswordValidation.kt b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/PasswordValidation.kt
new file mode 100644
index 00000000..2109da95
--- /dev/null
+++ b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/PasswordValidation.kt
@@ -0,0 +1,10 @@
+package ru.yeahub.authentication.impl.registration.presentation
+
+private const val MIN_PASSWORD_LENGTH = 8
+
+internal fun isPasswordValid(password: String): Boolean {
+ val isValid = password.matches(
+ Regex("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^A-Za-z\\d]).{$MIN_PASSWORD_LENGTH,}$")
+ )
+ return isValid
+}
\ No newline at end of file
diff --git a/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationScreen.kt b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationScreen.kt
new file mode 100644
index 00000000..ad3652b9
--- /dev/null
+++ b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationScreen.kt
@@ -0,0 +1,320 @@
+package ru.yeahub.authentication.impl.registration.presentation
+
+import androidx.compose.foundation.layout.Arrangement
+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.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.ClickableText
+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.Checkbox
+import androidx.compose.material3.CheckboxDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import ru.yeahub.authentication.impl.R
+import ru.yeahub.core_ui.component.PrimaryButton
+import ru.yeahub.core_ui.theme.Theme
+
+@Composable
+fun RegistrationScreen(
+ state: RegistrationUiState,
+ onAction: (RegistrationAction) -> Unit,
+ onOpenPdPolicy: () -> Unit,
+ onOpenOffer: () -> Unit
+) {
+ val linkColor = MaterialTheme.colorScheme.primary
+
+ Scaffold { paddings ->
+ Column(
+ modifier = Modifier
+ .padding(paddings)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 16.dp, vertical = 24.dp),
+ verticalArrangement = Arrangement.spacedBy(14.dp)
+ ) {
+ Text(
+ text = stringResource(R.string.registration_title),
+ style = MaterialTheme.typography.headlineSmall,
+ fontWeight = FontWeight.SemiBold
+ )
+
+ FormTextField(
+ title = stringResource(R.string.nickname_title),
+ placeholder = stringResource(R.string.nickname_placeholder),
+ value = state.nickname,
+ onValueChange = { onAction(RegistrationAction.NicknameChanged(it)) },
+ keyboardType = KeyboardType.Ascii
+ )
+
+ FormTextField(
+ title = stringResource(R.string.email_title),
+ placeholder = stringResource(R.string.email_placeholder),
+ value = state.email,
+ onValueChange = { onAction(RegistrationAction.EmailChanged(it)) },
+ keyboardType = KeyboardType.Email
+ )
+
+ FormPasswordField(
+ title = stringResource(R.string.password_title),
+ placeholder = stringResource(R.string.password_placeholder),
+ value = state.password,
+ isVisible = state.isPasswordVisible,
+ onValueChange = { onAction(RegistrationAction.PasswordChanged(it)) },
+ onToggleVisibility = { onAction(RegistrationAction.TogglePasswordVisible) }
+ )
+
+ FormPasswordField(
+ title = stringResource(R.string.confirm_password_title),
+ placeholder = stringResource(R.string.password_placeholder),
+ value = state.confirmPassword,
+ isVisible = state.isConfirmPasswordVisible,
+ onValueChange = { onAction(RegistrationAction.ConfirmPasswordChanged(it)) },
+ onToggleVisibility = { onAction(RegistrationAction.ToggleConfirmPasswordVisible) }
+ )
+
+ ConsentRow(
+ checked = state.isPdAccepted,
+ onCheckedChange = { onAction(RegistrationAction.PdAcceptedChanged(it)) },
+ text = pdConsentText(linkColor),
+ onLinkClicked = { tag ->
+ if (tag == "pd") onOpenPdPolicy()
+ }
+ )
+
+ ConsentRow(
+ checked = state.isOfferAccepted,
+ onCheckedChange = { onAction(RegistrationAction.OfferAcceptedChanged(it)) },
+ text = offerConsentText(linkColor),
+ onLinkClicked = { tag ->
+ if (tag == "offer") onOpenOffer()
+ }
+ )
+
+ ConsentRow(
+ checked = state.isMailingAccepted,
+ onCheckedChange = { onAction(RegistrationAction.MailingAcceptedChanged(it)) },
+ text = AnnotatedString(stringResource(R.string.marketing_opt_in_text)),
+ onLinkClicked = { }
+ )
+
+ Spacer(Modifier.height(8.dp))
+
+ PrimaryButton(
+ onClick = { onAction(RegistrationAction.SubmitClicked) },
+ enabled = state.isSubmitEnabled,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Text(stringResource(R.string.registration_button))
+ }
+ }
+ }
+}
+
+@Composable
+private fun FormTextField(
+ title: String,
+ placeholder: String,
+ value: String,
+ onValueChange: (String) -> Unit,
+ keyboardType: KeyboardType,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(text = title)
+
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = value,
+ onValueChange = onValueChange,
+ placeholder = {
+ Text(
+ text = placeholder,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ },
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ )
+ }
+}
+
+@Composable
+private fun FormPasswordField(
+ title: String,
+ placeholder: String,
+ value: String,
+ isVisible: Boolean,
+ onValueChange: (String) -> Unit,
+ onToggleVisibility: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(text = title)
+
+ PasswordField(
+ placeholder = placeholder,
+ value = value,
+ isVisible = isVisible,
+ onValueChange = onValueChange,
+ onToggleVisibility = onToggleVisibility
+ )
+ }
+}
+
+@Composable
+private fun PasswordField(
+ placeholder: String,
+ value: String,
+ isVisible: Boolean,
+ onValueChange: (String) -> Unit,
+ onToggleVisibility: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ OutlinedTextField(
+ modifier = modifier.fillMaxWidth(),
+ value = value,
+ onValueChange = onValueChange,
+ placeholder = {
+ Text(
+ text = placeholder,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ },
+ singleLine = true,
+ visualTransformation = if (isVisible) VisualTransformation.None else PasswordVisualTransformation(),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+ trailingIcon = {
+ IconButton(onClick = onToggleVisibility) {
+ Icon(
+ imageVector = if (isVisible) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
+ contentDescription = null
+ )
+ }
+ },
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ )
+}
+
+@Composable
+private fun pdConsentText(linkColor: Color): AnnotatedString = buildAnnotatedString {
+ append(stringResource(R.string.pd_consent_prefix))
+ pushStringAnnotation(tag = "pd", annotation = "pd")
+ pushStyle(SpanStyle(color = linkColor))
+ append(stringResource(R.string.pd_consent_link))
+ pop()
+ pop()
+ append(stringResource(R.string.pd_consent_suffix))
+}
+
+@Composable
+private fun offerConsentText(linkColor: Color): AnnotatedString = buildAnnotatedString {
+ append(stringResource(R.string.offer_consent_prefix))
+ pushStringAnnotation(tag = "offer", annotation = "offer")
+ pushStyle(SpanStyle(color = linkColor))
+ append(stringResource(R.string.offer_consent_link))
+ pop()
+ pop()
+}
+
+@Composable
+private fun ConsentRow(
+ checked: Boolean,
+ onCheckedChange: (Boolean) -> Unit,
+ text: AnnotatedString,
+ onLinkClicked: (tag: String) -> Unit
+) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.Top
+ ) {
+ Checkbox(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ colors = CheckboxDefaults.colors(
+ checkedColor = Theme.colors.purple700,
+ uncheckedColor = Theme.colors.purple200,
+ checkmarkColor = Theme.colors.white900
+ )
+ )
+ Spacer(Modifier.width(8.dp))
+ ClickableText(
+ text = text,
+ style = MaterialTheme.typography.bodySmall.copy(
+ color = MaterialTheme.colorScheme.onSurface,
+ textDecoration = null
+ ),
+ onClick = { offset ->
+ text.getStringAnnotations(start = offset, end = offset)
+ .firstOrNull()
+ ?.let { onLinkClicked(it.tag) }
+ ?: onCheckedChange(!checked)
+ }
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun RegistrationScreenPreview() {
+ MaterialTheme {
+ RegistrationScreen(
+ state = RegistrationUiState(
+ nickname = "admin",
+ email = "admin@mail.ru",
+ password = "1234",
+ confirmPassword = "1234",
+ isPasswordVisible = true,
+ isConfirmPasswordVisible = true,
+ isPdAccepted = true,
+ isOfferAccepted = true,
+ isMailingAccepted = false,
+ isSubmitEnabled = true
+ ),
+ onAction = {},
+ onOpenPdPolicy = {},
+ onOpenOffer = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationUiState.kt b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationUiState.kt
new file mode 100644
index 00000000..15e96754
--- /dev/null
+++ b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationUiState.kt
@@ -0,0 +1,27 @@
+package ru.yeahub.authentication.impl.registration.presentation
+
+data class RegistrationUiState(
+ val nickname: String,
+ val email: String,
+ val password: String,
+ val confirmPassword: String,
+ val isPdAccepted: Boolean,
+ val isOfferAccepted: Boolean,
+ val isMailingAccepted: Boolean,
+ val isPasswordVisible: Boolean,
+ val isConfirmPasswordVisible: Boolean,
+ val isSubmitEnabled: Boolean
+)
+
+sealed class RegistrationAction {
+ data class NicknameChanged(val value: String) : RegistrationAction()
+ data class EmailChanged(val value: String) : RegistrationAction()
+ data class PasswordChanged(val value: String) : RegistrationAction()
+ data class ConfirmPasswordChanged(val value: String) : RegistrationAction()
+ data class PdAcceptedChanged(val value: Boolean) : RegistrationAction()
+ data class OfferAcceptedChanged(val value: Boolean) : RegistrationAction()
+ data class MailingAcceptedChanged(val value: Boolean) : RegistrationAction()
+ data object TogglePasswordVisible : RegistrationAction()
+ data object ToggleConfirmPasswordVisible : RegistrationAction()
+ data object SubmitClicked : RegistrationAction()
+}
\ No newline at end of file
diff --git a/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationViewModel.kt b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationViewModel.kt
new file mode 100644
index 00000000..0af91624
--- /dev/null
+++ b/feature/authentication/impl/src/main/java/ru/yeahub/authentication/impl/registration/presentation/RegistrationViewModel.kt
@@ -0,0 +1,80 @@
+package ru.yeahub.authentication.impl.registration.presentation
+
+import android.util.Patterns
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+
+class RegistrationViewModel : ViewModel() {
+
+ private val _state = MutableStateFlow(
+ RegistrationUiState(
+ nickname = "",
+ email = "",
+ password = "",
+ confirmPassword = "",
+ isPdAccepted = false,
+ isOfferAccepted = false,
+ isMailingAccepted = false,
+ isPasswordVisible = false,
+ isConfirmPasswordVisible = false,
+ isSubmitEnabled = false
+ )
+ )
+ val state: StateFlow = _state
+
+ fun onAction(action: RegistrationAction) {
+ when (action) {
+ is RegistrationAction.ConfirmPasswordChanged -> {
+ _state.update { it.copy(confirmPassword = action.value).revalidate() }
+ }
+
+ is RegistrationAction.EmailChanged -> {
+ _state.update { it.copy(email = action.value).revalidate() }
+ }
+
+ is RegistrationAction.MailingAcceptedChanged -> {
+ _state.update { it.copy(isMailingAccepted = action.value).revalidate() }
+ }
+
+ is RegistrationAction.NicknameChanged -> {
+ _state.update { it.copy(nickname = action.value).revalidate() }
+ }
+
+ is RegistrationAction.OfferAcceptedChanged -> {
+ _state.update { it.copy(isOfferAccepted = action.value).revalidate() }
+ }
+
+ is RegistrationAction.PasswordChanged -> {
+ _state.update { it.copy(password = action.value).revalidate() }
+ }
+
+ is RegistrationAction.PdAcceptedChanged -> {
+ _state.update { it.copy(isPdAccepted = action.value).revalidate() }
+ }
+
+ RegistrationAction.SubmitClicked -> {}
+
+ RegistrationAction.ToggleConfirmPasswordVisible -> {
+ _state.update { it.copy(isConfirmPasswordVisible = !it.isConfirmPasswordVisible) }
+ }
+
+ RegistrationAction.TogglePasswordVisible -> {
+ _state.update { it.copy(isPasswordVisible = !it.isPasswordVisible) }
+ }
+ }
+ }
+
+ private fun RegistrationUiState.revalidate(): RegistrationUiState {
+ val nicknameOk = nickname.trim().isNotEmpty()
+ val emailOk = Patterns.EMAIL_ADDRESS.matcher(email.trim()).matches()
+ val passOk = isPasswordValid(password)
+ val confirmOk = password == confirmPassword && confirmPassword.isNotEmpty()
+ val requiredConsentsOk = isPdAccepted && isOfferAccepted
+
+ return copy(
+ isSubmitEnabled = nicknameOk && emailOk && passOk && confirmOk && requiredConsentsOk
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/authentication/impl/src/main/res/values/strings.xml b/feature/authentication/impl/src/main/res/values/strings.xml
new file mode 100644
index 00000000..85249f30
--- /dev/null
+++ b/feature/authentication/impl/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+
+
+ Регистрация
+ Никнейм
+ Введите никнейм
+ Электронная почта
+ Введите электронную почту
+ Пароль
+ Введите пароль
+ Подтвердить пароль
+ Зарегистрироваться
+ Даю согласие на получение рекламных и информационных рассылок
+ "Даю "
+ Согласие на обработку персональных данных
+ , в соответствии с Политикой в отношении ПД
+ "Подтверждаю что ознакомился(-ась) с "
+ Договором-офертой
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 00f8973c..25098835 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -33,6 +33,7 @@ runtime = "1.9.0"
immutableCollections = "0.4.0"
[libraries]
+androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 616cc767..261ce0c2 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -57,4 +57,5 @@ include(":feature:public-collections:api")
include(":feature:questions-or-collections")
include(":feature:questions-or-collections:api")
include(":feature:questions-or-collections:impl")
-
+include(":feature:authentication:api")
+include(":feature:authentication:impl")