From 936445e335ea3bf350c24841efc3c285494e2983 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sat, 14 Feb 2026 10:43:06 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=201:=20=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20XML=20=D0=BD=D0=B0=20Compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composehomework/ui/task1/Task1Screen.kt | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/otus/composehomework/ui/task1/Task1Screen.kt b/app/src/main/java/ru/otus/composehomework/ui/task1/Task1Screen.kt index baea737..f8d0062 100644 --- a/app/src/main/java/ru/otus/composehomework/ui/task1/Task1Screen.kt +++ b/app/src/main/java/ru/otus/composehomework/ui/task1/Task1Screen.kt @@ -1,6 +1,15 @@ package ru.otus.composehomework.ui.task1 -import androidx.compose.runtime.Composable +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp /** * Задание 1: Переписывание XML на Compose @@ -22,5 +31,31 @@ import androidx.compose.runtime.Composable */ @Composable fun Task1Screen() { - // TODO: Реализуйте экран на Compose + var welcomeText by remember { mutableStateOf("Добро пожаловать!") } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(32.dp) + ) { + Text( + text = welcomeText, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + + Image( + painter = painterResource(id = android.R.drawable.ic_dialog_info), + contentDescription = "Welcome", + modifier = Modifier.size(120.dp) + ) + + Button( + onClick = { welcomeText = "Кнопка нажата!" } + ) { + Text("Нажми меня") + } + } } From adfbdbf49ca34ab51cfa7b4e4c0865c0c8038675 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sat, 14 Feb 2026 10:51:20 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=202:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composehomework/ui/task2/Task2Screen.kt | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt b/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt index 9b625d4..d96216f 100644 --- a/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt +++ b/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt @@ -1,6 +1,15 @@ package ru.otus.composehomework.ui.task2 -import androidx.compose.runtime.Composable +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp /** * Задание 2: Добавление состояния @@ -20,5 +29,34 @@ import androidx.compose.runtime.Composable */ @Composable fun Task2Screen() { - // TODO: Реализуйте экран с состоянием счетчика + var welcomeText by remember { mutableStateOf("Добро пожаловать!") } + var count by remember { mutableStateOf(0) } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(32.dp) + ) { + Text( + text = welcomeText, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + + Image( + painter = painterResource(id = android.R.drawable.ic_dialog_info), + contentDescription = "Welcome", + modifier = Modifier.size(120.dp) + ) + + Text(text = "Счетчик: $count") + + Button( + onClick = { count++ } + ) { + Text("Нажми меня") + } + } } From 105af69e0daa7c00ee64fe3cf0c9133f9ca01834 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sat, 14 Feb 2026 11:38:01 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=203:=20ViewModel=20=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BB?= =?UTF-8?q?=D0=B5=D0=BA=D1=81=D0=BD=D0=BE=D0=B5=20=D1=81=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=20(MVI=20=D0=BF=D0=B0=D1=82?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D0=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composehomework/ui/task3/Task3Screen.kt | 187 +++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 186 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt b/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt index 8cffe89..cd1ea7b 100644 --- a/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt +++ b/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt @@ -1,6 +1,17 @@ package ru.otus.composehomework.ui.task3 -import androidx.compose.runtime.Composable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import ru.otus.composehomework.R /** * Задание 3: Подключение ViewModel и комплексное состояние (MVI паттерн) @@ -44,5 +55,177 @@ import androidx.compose.runtime.Composable */ @Composable fun Task3Screen() { - // TODO: Реализуйте экран с ViewModel и формой + val viewModel: Task3ViewModel = viewModel() + val state by viewModel.state.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Форма обратной связи", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 8.dp) + ) + + TextField( + value = state.name, + onValueChange = { + viewModel.handleIntent(Task3Intent.NameChanged(it)) + }, + label = { Text("Имя") }, + placeholder = { Text(stringResource(R.string.name_hint)) }, + modifier = Modifier.fillMaxWidth(), + isError = state.validationErrors?.errors?.get(FieldType.NAME) != null, + supportingText = state.validationErrors?.errors?.get(FieldType.NAME)?.let { + { Text(it) } + }, + enabled = state.uiState !is UiState.Loading + ) + + TextField( + value = state.email, + onValueChange = { + viewModel.handleIntent(Task3Intent.EmailChanged(it)) + }, + label = { Text("Email") }, + placeholder = { Text(stringResource(R.string.email_hint)) }, + modifier = Modifier.fillMaxWidth(), + isError = state.validationErrors?.errors?.get(FieldType.EMAIL) != null, + supportingText = state.validationErrors?.errors?.get(FieldType.EMAIL)?.let { + { Text(it) } + }, + enabled = state.uiState !is UiState.Loading + ) + + TextField( + value = state.message, + onValueChange = { + viewModel.handleIntent(Task3Intent.MessageChanged(it)) + }, + label = { Text("Сообщение") }, + placeholder = { Text(stringResource(R.string.message_hint)) }, + modifier = Modifier.fillMaxWidth(), + minLines = 3, + isError = state.validationErrors?.errors?.get(FieldType.MESSAGE) != null, + supportingText = state.validationErrors?.errors?.get(FieldType.MESSAGE)?.let { + { Text(it) } + }, + enabled = state.uiState !is UiState.Loading + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = { + viewModel.handleIntent(Task3Intent.SubmitClicked) + }, + enabled = state.uiState !is UiState.Loading, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.submit)) + } + + OutlinedButton( + onClick = { + viewModel.handleIntent(Task3Intent.ClearClicked) + }, + enabled = state.uiState !is UiState.Loading, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.clear)) + } + } + + when (state.uiState) { + is UiState.Loading -> { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + is UiState.Success -> { + val successState = state.uiState as UiState.Success + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Успешно!", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = "Форма успешно отправлена!", + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = "ID: ${successState.result.id}", + style = MaterialTheme.typography.bodySmall + ) + } + } + } + + is UiState.Error -> { + val errorState = state.uiState as UiState.Error + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.3f) + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Ошибка", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.error + ) + Text( + text = errorState.message, + style = MaterialTheme.typography.bodyMedium + ) + Button( + onClick = { + viewModel.handleIntent(Task3Intent.RetryClicked) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.retry)) + } + } + } + } + + is UiState.ValidationError -> { + // Ошибки валидации уже обработаны + } + + is UiState.Idle -> { + // Форма готова к заполнению + } + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26e74fc..c9469be 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,4 +13,5 @@ Отправить Повторить Загрузка... + Очистить From 539faa52f4ce28f5a88eed9ee26fd7c763ebe328 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sat, 14 Feb 2026 11:39:29 +0300 Subject: [PATCH 4/5] =?UTF-8?q?Task2-fix:=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20welcomeText?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt b/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt index d96216f..c95c5eb 100644 --- a/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt +++ b/app/src/main/java/ru/otus/composehomework/ui/task2/Task2Screen.kt @@ -29,8 +29,8 @@ import androidx.compose.ui.unit.sp */ @Composable fun Task2Screen() { - var welcomeText by remember { mutableStateOf("Добро пожаловать!") } var count by remember { mutableStateOf(0) } + val welcomeText = "Кнопка нажата $count раз!" Column( modifier = Modifier From 91c69794cbe2285d4373c041ff7cca07b3ebe1cb Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Thu, 26 Feb 2026 12:07:15 +0300 Subject: [PATCH 5/5] Fix: putted the details in separate composable --- .../composehomework/ui/task3/Task3Screen.kt | 384 +++++++++++------- 1 file changed, 235 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt b/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt index cd1ea7b..1a63a09 100644 --- a/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt +++ b/app/src/main/java/ru/otus/composehomework/ui/task3/Task3Screen.kt @@ -1,9 +1,25 @@ package ru.otus.composehomework.ui.task3 -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -15,16 +31,16 @@ import ru.otus.composehomework.R /** * Задание 3: Подключение ViewModel и комплексное состояние (MVI паттерн) - * + * * Развивайте Task2Screen, подключив ViewModel для управления состоянием. - * + * * MVI (Model-View-Intent) паттерн: * - State - единое состояние UI (Task3State) - определено в Task3Contracts.kt * - Intent - действия пользователя (Task3Intent) - определено в Task3Contracts.kt * - ViewModel обрабатывает Intent и обновляет State - * + * * Все контракты MVI (State, Intent, UiState) находятся в файле Task3Contracts.kt - * + * * Требования: * 1. Скопируйте код из Task2Screen.kt (или начните с Task1Screen, если нужно) * 2. Подключите ViewModel через: val viewModel: Task3ViewModel = viewModel() @@ -44,7 +60,7 @@ import ru.otus.composehomework.R * - UiState.Success -> показать результат * - UiState.Error -> показать сообщение об ошибке * - UiState.ValidationError -> показать ошибки валидации для каждого поля - * + * * Подсказки: * - Используйте TextField с параметрами value и onValueChange * - Для ошибок валидации используйте isError и supportingText @@ -58,174 +74,244 @@ fun Task3Screen() { val viewModel: Task3ViewModel = viewModel() val state by viewModel.state.collectAsState() - Column( + FeedbackFormLayout( modifier = Modifier .fillMaxSize() .padding(16.dp), + header = { + FeedbackHeader() + }, + fields = { + FeedbackFields( + state = state, + onNameChange = { viewModel.handleIntent(Task3Intent.NameChanged(it)) }, + onEmailChange = { viewModel.handleIntent(Task3Intent.EmailChanged(it)) }, + onMessageChange = { viewModel.handleIntent(Task3Intent.MessageChanged(it)) } + ) + }, + actions = { + FeedbackActions( + uiState = state.uiState, + onSubmit = { viewModel.handleIntent(Task3Intent.SubmitClicked) }, + onClear = { viewModel.handleIntent(Task3Intent.ClearClicked) } + ) + }, + status = { + FeedbackStatus( + uiState = state.uiState, + onRetry = { viewModel.handleIntent(Task3Intent.RetryClicked) } + ) + } + ) +} + +/** + * Fixed: layout slot формы, который раскладывает элементы экрана по зонам. + * Рекомендация: такой подход позволяет вынести детали в отдельные composable + * и избегать "простыней" в коде. + */ +@Composable +private fun FeedbackFormLayout( + modifier: Modifier = Modifier, + header: @Composable () -> Unit, + fields: @Composable ColumnScope.() -> Unit, + actions: @Composable ColumnScope.() -> Unit, + status: @Composable ColumnScope.() -> Unit +) { + Column( + modifier = modifier, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( - text = "Форма обратной связи", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(bottom = 8.dp) - ) + header() + fields() + actions() + status() + } +} + +/** + * Заголовок формы. + */ +@Composable +private fun FeedbackHeader() { + Text( + text = "Форма обратной связи", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 8.dp) + ) +} - TextField( - value = state.name, - onValueChange = { - viewModel.handleIntent(Task3Intent.NameChanged(it)) - }, - label = { Text("Имя") }, - placeholder = { Text(stringResource(R.string.name_hint)) }, - modifier = Modifier.fillMaxWidth(), - isError = state.validationErrors?.errors?.get(FieldType.NAME) != null, - supportingText = state.validationErrors?.errors?.get(FieldType.NAME)?.let { - { Text(it) } - }, - enabled = state.uiState !is UiState.Loading - ) +/** + * Блок полей формы. Работа с вводом и ошибками валидации. + */ +@Composable +private fun ColumnScope.FeedbackFields( + state: Task3State, + onNameChange: (String) -> Unit, + onEmailChange: (String) -> Unit, + onMessageChange: (String) -> Unit +) { + TextField( + value = state.name, + onValueChange = onNameChange, + label = { Text("Имя") }, + placeholder = { Text(stringResource(R.string.name_hint)) }, + modifier = Modifier.fillMaxWidth(), + isError = state.validationErrors?.errors?.get(FieldType.NAME) != null, + supportingText = state.validationErrors?.errors?.get(FieldType.NAME)?.let { + { Text(it) } + }, + enabled = state.uiState !is UiState.Loading + ) - TextField( - value = state.email, - onValueChange = { - viewModel.handleIntent(Task3Intent.EmailChanged(it)) - }, - label = { Text("Email") }, - placeholder = { Text(stringResource(R.string.email_hint)) }, - modifier = Modifier.fillMaxWidth(), - isError = state.validationErrors?.errors?.get(FieldType.EMAIL) != null, - supportingText = state.validationErrors?.errors?.get(FieldType.EMAIL)?.let { - { Text(it) } - }, - enabled = state.uiState !is UiState.Loading - ) + TextField( + value = state.email, + onValueChange = onEmailChange, + label = { Text("Email") }, + placeholder = { Text(stringResource(R.string.email_hint)) }, + modifier = Modifier.fillMaxWidth(), + isError = state.validationErrors?.errors?.get(FieldType.EMAIL) != null, + supportingText = state.validationErrors?.errors?.get(FieldType.EMAIL)?.let { + { Text(it) } + }, + enabled = state.uiState !is UiState.Loading + ) - TextField( - value = state.message, - onValueChange = { - viewModel.handleIntent(Task3Intent.MessageChanged(it)) - }, - label = { Text("Сообщение") }, - placeholder = { Text(stringResource(R.string.message_hint)) }, - modifier = Modifier.fillMaxWidth(), - minLines = 3, - isError = state.validationErrors?.errors?.get(FieldType.MESSAGE) != null, - supportingText = state.validationErrors?.errors?.get(FieldType.MESSAGE)?.let { - { Text(it) } - }, - enabled = state.uiState !is UiState.Loading - ) + TextField( + value = state.message, + onValueChange = onMessageChange, + label = { Text("Сообщение") }, + placeholder = { Text(stringResource(R.string.message_hint)) }, + modifier = Modifier.fillMaxWidth(), + minLines = 3, + isError = state.validationErrors?.errors?.get(FieldType.MESSAGE) != null, + supportingText = state.validationErrors?.errors?.get(FieldType.MESSAGE)?.let { + { Text(it) } + }, + enabled = state.uiState !is UiState.Loading + ) +} - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) +/** + * Блок с действиями - Отправка, очистка. + */ +@Composable +private fun ColumnScope.FeedbackActions( + uiState: UiState, + onSubmit: () -> Unit, + onClear: () -> Unit +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = onSubmit, + enabled = uiState !is UiState.Loading, + modifier = Modifier.weight(1f) ) { - Button( - onClick = { - viewModel.handleIntent(Task3Intent.SubmitClicked) - }, - enabled = state.uiState !is UiState.Loading, - modifier = Modifier.weight(1f) - ) { - Text(stringResource(R.string.submit)) - } + Text(stringResource(R.string.submit)) + } - OutlinedButton( - onClick = { - viewModel.handleIntent(Task3Intent.ClearClicked) - }, - enabled = state.uiState !is UiState.Loading, - modifier = Modifier.weight(1f) + OutlinedButton( + onClick = onClear, + enabled = uiState !is UiState.Loading, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.clear)) + } + } +} + +/** + * Визуализация состояния отправки формы (загрузка, успех, ошибка). + * Вся работа с UiState сконцентрирована в одном месте. + */ +@Composable +private fun ColumnScope.FeedbackStatus( + uiState: UiState, + onRetry: () -> Unit +) { + when (uiState) { + is UiState.Loading -> { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center ) { - Text(stringResource(R.string.clear)) + CircularProgressIndicator() } } - when (state.uiState) { - is UiState.Loading -> { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - contentAlignment = Alignment.Center + is UiState.Success -> { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - CircularProgressIndicator() - } - } - - is UiState.Success -> { - val successState = state.uiState as UiState.Success - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + Text( + text = "Успешно!", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = "Форма успешно отправлена!", + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = "ID: ${uiState.result.id}", + style = MaterialTheme.typography.bodySmall ) - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = "Успешно!", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) - Text( - text = "Форма успешно отправлена!", - style = MaterialTheme.typography.bodyMedium - ) - Text( - text = "ID: ${successState.result.id}", - style = MaterialTheme.typography.bodySmall - ) - } } } + } - is UiState.Error -> { - val errorState = state.uiState as UiState.Error - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.3f) - ) + is UiState.Error -> { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.3f) + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) + Text( + text = "Ошибка", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.error + ) + Text( + text = uiState.message, + style = MaterialTheme.typography.bodyMedium + ) + Button( + onClick = onRetry, + modifier = Modifier.fillMaxWidth() ) { - Text( - text = "Ошибка", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.error - ) - Text( - text = errorState.message, - style = MaterialTheme.typography.bodyMedium - ) - Button( - onClick = { - viewModel.handleIntent(Task3Intent.RetryClicked) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.retry)) - } + Text(stringResource(R.string.retry)) } } } + } - is UiState.ValidationError -> { - // Ошибки валидации уже обработаны - } + is UiState.ValidationError -> { + // Ошибки валидации уже подсвечены возле полей ввода + } - is UiState.Idle -> { - // Форма готова к заполнению - } + is UiState.Idle -> { + // Форма готова к заполнению } } }