From a67472135398000ef4b9ae8311c538cb87518fc6 Mon Sep 17 00:00:00 2001 From: b-dev-mobile Date: Thu, 9 Jul 2026 14:08:36 +0000 Subject: [PATCH] =?UTF-8?q?ux:=20=D0=B5=D0=B4=D0=B8=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20F7ErrorState=20+=20retry=20(=D0=AD=D1=82=D0=B0=D0=BF?= =?UTF-8?q?=202=20=D0=BF.12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новый F7ErrorState: центрированная иконка + сообщение + «Повторить» вместо россыпи красного текста. F7ModuleScreen при ошибке без данных показывает его (+ новый onErrorRetry — retry без хедер-кнопки ↻). Подключено: Контакты/Конференции/Карточки/Поддержка (через F7ModuleScreen), Файлы (onErrorRetry=refreshCurrent), Почта (пустой список + ошибка → F7ErrorState, убран верхний баннер-текст). Retry везде дергает load/refresh. --- .../f7cloud/core/designsystem/F7Components.kt | 71 +++++++++++++++++-- .../feature/contacts/ContactsScreen.kt | 1 + .../f7cloud/feature/files/FilesScreen.kt | 1 + .../f7cloud/feature/mail/MailScreen.kt | 12 ++-- .../f7cloud/feature/talk/TalkScreen.kt | 1 + 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7Components.kt b/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7Components.kt index 421439b..370c8ea 100644 --- a/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7Components.kt +++ b/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7Components.kt @@ -16,8 +16,11 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -43,11 +46,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog /** @@ -69,6 +75,46 @@ fun F7ScreenBackground(modifier: Modifier = Modifier, content: @Composable () -> } } +/** + * Единый экран ошибки (по центру): иконка + сообщение + кнопка «Повторить». + * Используется везде, где загрузка упала и данных нет — вместо россыпи красного текста. + */ +@Composable +fun F7ErrorState( + message: String, + modifier: Modifier = Modifier, + onRetry: (() -> Unit)? = null, +) { + Column( + modifier = modifier + .fillMaxSize() + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Box( + modifier = Modifier + .size(56.dp) + .clip(CircleShape) + .background(F7Colors.Error.copy(alpha = 0.12f)), + contentAlignment = Alignment.Center, + ) { + Text("!", color = F7Colors.Error, fontSize = 30.sp, fontWeight = FontWeight.Bold) + } + Spacer(Modifier.height(12.dp)) + Text( + text = message, + style = MaterialTheme.typography.bodyMedium, + color = F7Colors.TextSecondary, + textAlign = TextAlign.Center, + ) + if (onRetry != null) { + Spacer(Modifier.height(16.dp)) + F7SecondaryButton(text = "Повторить", onClick = onRetry) + } + } +} + @Composable fun F7ModuleScreen( title: String? = null, @@ -76,6 +122,9 @@ fun F7ModuleScreen( loading: Boolean = false, error: String? = null, onRefresh: (() -> Unit)? = null, + // Действие «Повторить» в экране ошибки; по умолчанию — как onRefresh. + // Позволяет дать retry, не показывая хедер-кнопку ↻ (когда onRefresh не задан). + onErrorRetry: (() -> Unit)? = onRefresh, headerActions: @Composable RowScope.() -> Unit = {}, content: @Composable ColumnScope.() -> Unit, ) { @@ -126,14 +175,22 @@ fun F7ModuleScreen( CircularProgressIndicator(color = F7Colors.Primary) } if (!error.isNullOrBlank()) { - Text(text = error, color = F7Colors.Error, style = MaterialTheme.typography.bodyMedium) + // Ошибка без данных → центрированный экран с «Повторить» + F7ErrorState( + message = error, + onRetry = onErrorRetry, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + ) + } else { + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + content = content, + ) } - Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - content = content, - ) } } diff --git a/feature/contacts/src/main/java/ru/forbion/f7cloud/feature/contacts/ContactsScreen.kt b/feature/contacts/src/main/java/ru/forbion/f7cloud/feature/contacts/ContactsScreen.kt index 4852e87..01246fb 100644 --- a/feature/contacts/src/main/java/ru/forbion/f7cloud/feature/contacts/ContactsScreen.kt +++ b/feature/contacts/src/main/java/ru/forbion/f7cloud/feature/contacts/ContactsScreen.kt @@ -79,6 +79,7 @@ fun ContactsScreen( modifier = modifier, loading = state.loading && state.contacts.isEmpty(), error = state.error, + onErrorRetry = { vm.refresh(session, force = true) }, ) { // Поиск + кнопка создания контакта (создание раньше вызывалось из нижней // панели, теперь — из шапки, панель стала навигационной) diff --git a/feature/files/src/main/java/ru/forbion/f7cloud/feature/files/FilesScreen.kt b/feature/files/src/main/java/ru/forbion/f7cloud/feature/files/FilesScreen.kt index 6e88dda..72ea273 100644 --- a/feature/files/src/main/java/ru/forbion/f7cloud/feature/files/FilesScreen.kt +++ b/feature/files/src/main/java/ru/forbion/f7cloud/feature/files/FilesScreen.kt @@ -172,6 +172,7 @@ fun FilesScreen( modifier = Modifier.fillMaxSize(), loading = state.loading && displayItems.isEmpty(), error = state.error, + onErrorRetry = { vm.refreshCurrent(session) }, headerActions = {}, ) { Column(modifier = Modifier.fillMaxSize()) { diff --git a/feature/mail/src/main/java/ru/forbion/f7cloud/feature/mail/MailScreen.kt b/feature/mail/src/main/java/ru/forbion/f7cloud/feature/mail/MailScreen.kt index 24cb42b..65e6ff3 100644 --- a/feature/mail/src/main/java/ru/forbion/f7cloud/feature/mail/MailScreen.kt +++ b/feature/mail/src/main/java/ru/forbion/f7cloud/feature/mail/MailScreen.kt @@ -48,6 +48,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import ru.forbion.f7cloud.core.auth.AuthSession import androidx.activity.compose.BackHandler import ru.forbion.f7cloud.core.designsystem.F7Colors +import ru.forbion.f7cloud.core.designsystem.F7ErrorState import ru.forbion.f7cloud.core.designsystem.F7OverlayDismissHandler import ru.forbion.f7cloud.core.designsystem.f7SwipeFromRightToDismiss import androidx.activity.compose.rememberLauncherForActivityResult @@ -599,9 +600,6 @@ private fun MailInboxScreen( color = F7Colors.TextPrimary, ) } - if (!error.isNullOrBlank()) { - Text(text = error, color = F7Colors.Error, style = MaterialTheme.typography.bodyMedium) - } PullToRefreshBox( isRefreshing = refreshing && !loading, onRefresh = onRefresh, @@ -643,7 +641,13 @@ private fun MailInboxScreen( } return@Box } - if (messages.isEmpty()) { + if (messages.isEmpty() && !error.isNullOrBlank()) { + F7ErrorState( + message = error, + onRetry = onRefresh, + modifier = Modifier.fillMaxSize(), + ) + } else if (messages.isEmpty()) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center, diff --git a/feature/talk/src/main/java/ru/forbion/f7cloud/feature/talk/TalkScreen.kt b/feature/talk/src/main/java/ru/forbion/f7cloud/feature/talk/TalkScreen.kt index 33ae3fd..f7b43b1 100644 --- a/feature/talk/src/main/java/ru/forbion/f7cloud/feature/talk/TalkScreen.kt +++ b/feature/talk/src/main/java/ru/forbion/f7cloud/feature/talk/TalkScreen.kt @@ -306,6 +306,7 @@ fun TalkScreen( modifier = modifier, loading = state.loading && state.rooms.isEmpty(), error = state.error, + onErrorRetry = { vm.load(session) }, ) { RoomListContent( session = session,