diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..4c55468 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt b/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt
index d176516..9e5ea3c 100644
--- a/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt
@@ -5,7 +5,7 @@ import org.koin.core.annotation.Single
enum class AuthField {
FirstName,
- SecondName,
+ LastName,
Email,
Password,
ConfirmPassword,
@@ -24,15 +24,13 @@ data class ValidationResult(
class ValidateAuthFieldsUseCase {
fun validateFillProfile(
- displayName: String,
firstName: String,
lastName: String,
phone: String
): ValidationResult {
val errors = buildMap {
- if (displayName.isBlank()) put(AuthField.FirstName, "Введите никнейм")
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
- if (lastName.isBlank()) put(AuthField.SecondName, "Введите фамилию")
+ if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
if (!isPhoneValid(phone)) put(AuthField.Phone, "Некорректный номер телефона")
}
return ValidationResult(errors)
@@ -89,7 +87,7 @@ class ValidateAuthFieldsUseCase {
): ValidationResult {
val errors = buildMap {
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
- if (secondName.isBlank()) put(AuthField.SecondName, "Введите фамилию")
+ if (secondName.isBlank()) put(AuthField.LastName, "Введите фамилию")
}
return ValidationResult(errors)
}
diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/AppDestination.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/AppDestination.kt
index cd12537..e9e9c4a 100644
--- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/AppDestination.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/AppDestination.kt
@@ -15,4 +15,5 @@ sealed class AppDestination(val route: String) {
data object Profile : AppDestination("app/profile")
+ data object FillProfile : AppDestination("app/fill_profile")
}
diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt
index 38b5a82..988e0c6 100644
--- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt
@@ -9,6 +9,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.prodhack.moscow2025.presentation.screens.main.MainScreen
import com.prodhack.moscow2025.domain.utils.NetworkError
+import com.prodhack.moscow2025.presentation.screens.fillProfile.FillProfileScreen
import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
@@ -64,7 +65,7 @@ fun TTasksNavHost(
navController.popBackStack()
},
onSuccess = {
- navController.navigate(AppDestination.Main.route) {
+ navController.navigate(AppDestination.FillProfile.route) {
popUpTo(AppDestination.Register.route) {
inclusive = true
}
@@ -73,6 +74,19 @@ fun TTasksNavHost(
)
}
+ composable(AppDestination.FillProfile.route) {
+ FillProfileScreen(
+ snackbarHostState = snackbarHostState,
+ onSuccess = {
+ navController.navigate(AppDestination.Main.route) {
+ popUpTo(AppDestination.FillProfile.route) {
+ inclusive = true
+ }
+ }
+ }
+ )
+ }
+
composable(AppDestination.Main.route) {
MainScreen()
}
diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileScreen.kt
index fee1d41..8bbd9fe 100644
--- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileScreen.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileScreen.kt
@@ -1,9 +1,163 @@
package com.prodhack.moscow2025.presentation.screens.fillProfile
+import androidx.compose.foundation.Image
+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.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SnackbarDuration
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+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.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.prodhack.moscow2025.R
+import com.prodhack.moscow2025.domain.usecase.auth.AuthField
+import com.prodhack.moscow2025.presentation.components.standart.BigButton
+import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
+import com.prodhack.moscow2025.presentation.components.standart.TTTextField
+import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
+import com.prodhack.moscow2025.presentation.utils.UIState
+import org.koin.androidx.compose.koinViewModel
@Composable
-fun FillProfileScreen() {
- Text("Fill profile will be here soon :)")
+fun ErrorCollectorScope.FillProfileScreen(
+ snackbarHostState: SnackbarHostState,
+ onSuccess: () -> Unit,
+ viewModel: FillProfileViewModel = koinViewModel()
+) {
+ val typography = MaterialTheme.typography
+ val colorScheme = MaterialTheme.colorScheme
+
+ val formState by viewModel.formStateFillProfile.collectAsState()
+
+ var errorText by remember { mutableStateOf("") }
+ val fillProfileState by viewModel.profileFillState.collectAsStateWithCallbacks(
+ onInputError = {
+ errorText = it.error
+ },
+ onConnectionError = {
+ errorText = "Нет подключения к сети"
+ },
+ onUnexpectedError = {
+ errorText = it.error
+ },
+ onLoading = {
+ errorText = ""
+ },
+ onSuccess = {
+ errorText = ""
+ }
+ )
+
+ LaunchedEffect(fillProfileState) {
+ if (fillProfileState is UIState.Success) {
+ onSuccess()
+ }
+ }
+
+ LaunchedEffect(errorText) {
+ if (errorText.isNotEmpty()) {
+ snackbarHostState.showSnackbar(
+ message = "Ошибка: $errorText",
+ duration = SnackbarDuration.Short
+ )
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .imePadding()
+ .systemBarsPadding(),
+ contentAlignment = Alignment.BottomStart
+ ) {
+ Image(
+ painter = painterResource(R.drawable.lottie),
+ contentDescription = null,
+ modifier = Modifier.width(130.dp),
+ contentScale = ContentScale.Crop
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(start = 30.dp, end = 30.dp)
+ .verticalScroll(rememberScrollState()),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "Давайте\nзнакомиться!",
+ style = typography.titleLarge,
+ fontSize = 31.sp
+ )
+ Image(
+ painter = painterResource(R.drawable.ic_launcher_foreground),
+ contentDescription = null,
+ modifier = Modifier.size(140.dp),
+ contentScale = ContentScale.Crop
+ )
+ }
+ Spacer(Modifier.height(20.dp))
+ TTTextField(
+ value = formState.firstName,
+ onValueChange = viewModel::onFirstNameChange,
+ label = "Ваше имя",
+ error = formState.errors[AuthField.FirstName]
+ )
+ Spacer(Modifier.height(12.dp))
+ TTTextField(
+ value = formState.lastName,
+ onValueChange = viewModel::onLastNameChange,
+ label = "Ваша фамилия",
+ error = formState.errors[AuthField.LastName]
+ )
+ Spacer(Modifier.height(12.dp))
+ TTTextField(
+ value = formState.phone,
+ onValueChange = viewModel::onPhoneChange,
+ label = "Ваш телефон",
+ error = formState.errors[AuthField.Phone]
+ )
+ Spacer(modifier = Modifier.height(20.dp))
+ BigButton(
+ onClick = viewModel::submit,
+ modifier = Modifier.fillMaxWidth(),
+ buttonText = "Сохранить данные",
+ isLoading = fillProfileState is UIState.Loading
+ )
+ Spacer(modifier = Modifier.height(80.dp))
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt
index df97251..aac1dc3 100644
--- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt
@@ -23,9 +23,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import org.koin.android.annotation.KoinViewModel
+import org.koin.core.annotation.Single
data class FillProfileFormState(
- val displayName: String = "",
val firstName: String = "",
val lastName: String = "",
val phone: String = "",
@@ -38,7 +39,6 @@ data class FillProfileFormState(
other as FillProfileFormState
- if (displayName != other.displayName) return false
if (firstName != other.firstName) return false
if (lastName != other.lastName) return false
if (phone != other.phone) return false
@@ -49,8 +49,7 @@ data class FillProfileFormState(
}
override fun hashCode(): Int {
- var result = displayName.hashCode()
- result = 31 * result + firstName.hashCode()
+ var result = firstName.hashCode()
result = 31 * result + lastName.hashCode()
result = 31 * result + phone.hashCode()
result = 31 * result + (avatar?.contentHashCode() ?: 0)
@@ -59,33 +58,23 @@ data class FillProfileFormState(
}
}
+@KoinViewModel
class FillProfileViewModel(
private val updateUserUseCase: UpdateUserUseCase,
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
private val galleryRepository: GalleryRepository
) : BaseViewModel() {
private val _formStateFillProfile = MutableStateFlow(FillProfileFormState())
- val formStateSignUp: StateFlow = _formStateFillProfile
-
+ val formStateFillProfile: StateFlow = _formStateFillProfile
private val _profileFillState = MutableUIStateFlow()
val profileFillState: StateFlow> = _profileFillState
-
- fun onDisplayNameChange(value: String) {
- _formStateFillProfile.update {
- it.copy(
- displayName = value,
- errors = it.errors - AuthField.Email
- )
- }
- }
-
fun onFirstNameChange(value: String) {
_formStateFillProfile.update {
it.copy(
firstName = value,
- errors = it.errors - AuthField.Email
+ errors = it.errors - AuthField.FirstName
)
}
}
@@ -94,7 +83,7 @@ class FillProfileViewModel(
_formStateFillProfile.update {
it.copy(
lastName = value,
- errors = it.errors - AuthField.Email
+ errors = it.errors - AuthField.LastName
)
}
}
@@ -103,7 +92,7 @@ class FillProfileViewModel(
_formStateFillProfile.update {
it.copy(
phone = value,
- errors = it.errors - AuthField.Email
+ errors = it.errors - AuthField.Phone
)
}
}
@@ -158,7 +147,6 @@ class FillProfileViewModel(
fun submit() {
viewModelScope.launch {
val validation = validateAuthFieldsUseCase.validateFillProfile(
- displayName = _formStateFillProfile.value.displayName,
firstName = _formStateFillProfile.value.firstName,
lastName = _formStateFillProfile.value.lastName,
phone = _formStateFillProfile.value.phone
@@ -173,7 +161,6 @@ class FillProfileViewModel(
val result = updateUserUseCase(
UpdateUserData(
- displayName = _formStateFillProfile.value.displayName,
firstName = _formStateFillProfile.value.firstName,
lastName = _formStateFillProfile.value.lastName,
phone = _formStateFillProfile.value.phone
diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/login/LoginScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/login/LoginScreen.kt
index e7f1dc1..281b02f 100644
--- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/login/LoginScreen.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/login/LoginScreen.kt
@@ -131,7 +131,7 @@ fun ErrorCollectorScope.LoginScreen(
painter = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier
- .size(250.dp)
+ .size(200.dp)
.noRippleClickable {
showDialog.value = true
}
diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/register/RegisterScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/register/RegisterScreen.kt
index 1bf160a..d321fda 100644
--- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/register/RegisterScreen.kt
+++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/register/RegisterScreen.kt
@@ -45,6 +45,7 @@ import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
import com.prodhack.moscow2025.presentation.utils.UIState
+import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
import org.koin.androidx.compose.koinViewModel
@Composable
@@ -114,24 +115,18 @@ fun ErrorCollectorScope.RegisterScreen(
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(
- text = "Давайте\nзнакомиться!",
- style = typography.titleLarge,
- fontSize = 31.sp
- )
- Image(
- painter = painterResource(R.drawable.ic_launcher_foreground),
- contentDescription = null,
- modifier = Modifier.size(140.dp),
- contentScale = ContentScale.Crop
- )
- }
- Spacer(Modifier.height(20.dp))
+ Image(
+ painter = painterResource(R.drawable.ic_launcher_foreground),
+ contentDescription = null,
+ modifier = Modifier
+ .size(200.dp)
+ )
+ Text(
+ text = "Регистрация",
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = 40.sp
+ )
+ Spacer(modifier = Modifier.height(10.dp))
TTTextField(
value = formState.email,
onValueChange = viewModel::onEmailChange,