update error content

This commit is contained in:
2026-08-19 19:37:13 +03:00
parent 1d609f88d2
commit a14a659876
11 changed files with 71 additions and 50 deletions
@@ -3,7 +3,6 @@ package ru.shadowsparky.vbox.shared.presentation.list
import androiddev.apps.vbox.client.shared.generated.resources.Res import androiddev.apps.vbox.client.shared.generated.resources.Res
import androiddev.apps.vbox.client.shared.generated.resources.not_found import androiddev.apps.vbox.client.shared.generated.resources.not_found
import androiddev.apps.vbox.client.shared.generated.resources.recommendations import androiddev.apps.vbox.client.shared.generated.resources.recommendations
import androiddev.apps.vbox.client.shared.generated.resources.retry
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -102,8 +101,7 @@ fun VideoScreenContent(
if (error != null) { if (error != null) {
ErrorContent( ErrorContent(
error, error,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize()
buttonText = stringResource(Res.string.retry)
) { onRefresh() } ) { onRefresh() }
} else if (items != null) { } else if (items != null) {
AnimatedContent( AnimatedContent(
@@ -8,7 +8,6 @@ import androiddev.apps.vbox.client.shared.generated.resources.details_serial
import androiddev.apps.vbox.client.shared.generated.resources.more import androiddev.apps.vbox.client.shared.generated.resources.more
import androiddev.apps.vbox.client.shared.generated.resources.offline_remove import androiddev.apps.vbox.client.shared.generated.resources.offline_remove
import androiddev.apps.vbox.client.shared.generated.resources.offline_save import androiddev.apps.vbox.client.shared.generated.resources.offline_save
import androiddev.apps.vbox.client.shared.generated.resources.retry
import androiddev.apps.vbox.client.shared.generated.resources.share import androiddev.apps.vbox.client.shared.generated.resources.share
import androiddev.apps.vbox.client.shared.generated.resources.star import androiddev.apps.vbox.client.shared.generated.resources.star
import androiddev.apps.vbox.client.shared.generated.resources.star_unchecked import androiddev.apps.vbox.client.shared.generated.resources.star_unchecked
@@ -98,8 +97,7 @@ fun OverviewScreen(component: OverviewComponent) {
errorState != null -> ErrorContent( errorState != null -> ErrorContent(
errorState!!, errorState!!,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize()
buttonText = stringResource(Res.string.retry)
) { component.loadDetailsIfNeeded() } ) { component.loadDetailsIfNeeded() }
details != null -> DetailsInfo( details != null -> DetailsInfo(
@@ -7,7 +7,6 @@ import androiddev.apps.vbox.client.shared.generated.resources.episodes_overview
import androiddev.apps.vbox.client.shared.generated.resources.eye import androiddev.apps.vbox.client.shared.generated.resources.eye
import androiddev.apps.vbox.client.shared.generated.resources.folder_check import androiddev.apps.vbox.client.shared.generated.resources.folder_check
import androiddev.apps.vbox.client.shared.generated.resources.pending import androiddev.apps.vbox.client.shared.generated.resources.pending
import androiddev.apps.vbox.client.shared.generated.resources.retry
import androiddev.apps.vbox.client.shared.generated.resources.save_as import androiddev.apps.vbox.client.shared.generated.resources.save_as
import androiddev.apps.vbox.client.shared.generated.resources.select_all import androiddev.apps.vbox.client.shared.generated.resources.select_all
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
@@ -174,8 +173,7 @@ fun EpisodesOverviewList(
if (errorCode != null) { if (errorCode != null) {
ErrorContent( ErrorContent(
errorCode, errorCode,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize()
buttonText = stringResource(Res.string.retry)
) { loadLinks() } ) { loadLinks() }
} else if (episodes != null) { } else if (episodes != null) {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
@@ -1,7 +1,6 @@
package ru.shadowsparky.vbox.shared.presentation.overview.season package ru.shadowsparky.vbox.shared.presentation.overview.season
import androiddev.apps.vbox.client.shared.generated.resources.Res import androiddev.apps.vbox.client.shared.generated.resources.Res
import androiddev.apps.vbox.client.shared.generated.resources.retry
import androiddev.apps.vbox.client.shared.generated.resources.seasons_overview import androiddev.apps.vbox.client.shared.generated.resources.seasons_overview
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -38,8 +37,7 @@ fun SeasonOverviewScreen(component: SeasonOverviewComponent) {
if (err.value != null) { if (err.value != null) {
ErrorContent( ErrorContent(
err.value!!, err.value!!,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize()
buttonText = stringResource(Res.string.retry)
) { component.load() } ) { component.load() }
} else if (seasons.value != null) { } else if (seasons.value != null) {
SeasonsInfo( SeasonsInfo(
@@ -2,13 +2,13 @@ package ru.shadowsparky.vbox.shared.presentation.player
import androiddev.apps.vbox.client.shared.generated.resources.Res import androiddev.apps.vbox.client.shared.generated.resources.Res
import androiddev.apps.vbox.client.shared.generated.resources.next_episode import androiddev.apps.vbox.client.shared.generated.resources.next_episode
import androiddev.apps.vbox.client.shared.generated.resources.retry
import androiddev.apps.vbox.client.shared.generated.resources.start_from import androiddev.apps.vbox.client.shared.generated.resources.start_from
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -24,6 +24,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@@ -49,14 +50,13 @@ fun PlayerScreen(component: PlayerComponent) {
is PlayerEvent.Error -> { is PlayerEvent.Error -> {
ErrorContent( ErrorContent(
state.message, state.message,
modifier = Modifier.fillMaxSize().padding(it), modifier = Modifier.fillMaxSize().padding(it)
buttonText = stringResource(Res.string.retry),
) { component.load() } ) { component.load() }
} }
is PlayerEvent.Ended -> component.requestNextEpisode() is PlayerEvent.Ended -> component.requestNextEpisode()
is PlayerEvent.Playing -> { is PlayerEvent.Playing -> {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize().background(Color.Black)) {
VideoPlayer( VideoPlayer(
component.playerController, component.playerController,
Modifier.fillMaxSize(), Modifier.fillMaxSize(),
@@ -282,15 +282,15 @@ fun AppBarWithSearch(
navigationIcon = {}, navigationIcon = {},
inputField = { inputField = {
Surface( Surface(
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth()
.clickable(enabled = !isDefault) { onEdit() },
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
color = Color.Transparent color = Color.Transparent
) { ) {
Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) { Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) {
BackButton { onBack() } BackButton { onBack() }
Text(text = title, maxLines = 1) Box(modifier = Modifier.weight(1f).clickable(enabled = !isDefault) { onEdit() }) {
Text(text = title, maxLines = 1)
}
} }
} }
} }
@@ -2,9 +2,7 @@ package ru.shadowsparky.chat.presentation
import androiddev.feature.chat.chat_client.generated.resources.Res import androiddev.feature.chat.chat_client.generated.resources.Res
import androiddev.feature.chat.chat_client.generated.resources.chat_input_placeholder import androiddev.feature.chat.chat_client.generated.resources.chat_input_placeholder
import androiddev.feature.chat.chat_client.generated.resources.chat_load_error
import androiddev.feature.chat.chat_client.generated.resources.chat_ok import androiddev.feature.chat.chat_client.generated.resources.chat_ok
import androiddev.feature.chat.chat_client.generated.resources.chat_retry_button
import androiddev.feature.chat.chat_client.generated.resources.send import androiddev.feature.chat.chat_client.generated.resources.send
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.animateScrollBy import androidx.compose.foundation.gestures.animateScrollBy
@@ -79,6 +77,7 @@ import ru.shadowsparky.chat.domain.ChatRoles
import ru.shadowsparky.chat.domain.Message import ru.shadowsparky.chat.domain.Message
import ru.shadowsparky.chat.domain.TextToken import ru.shadowsparky.chat.domain.TextToken
import ru.shadowsparky.ui.components.AlertContent import ru.shadowsparky.ui.components.AlertContent
import ru.shadowsparky.ui.components.ErrorContent
import ru.shadowsparky.ui.components.IconButton import ru.shadowsparky.ui.components.IconButton
import ru.shadowsparky.ui.components.LazyColumn import ru.shadowsparky.ui.components.LazyColumn
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
@@ -107,16 +106,7 @@ fun ChatContent(component: ChatComponent, paddingValues: PaddingValues, state: C
} }
is ChatState.Error -> { is ChatState.Error -> {
Column(horizontalAlignment = Alignment.CenterHorizontally) { ErrorContent(message = state.message) { component.clearSendError() }
Text(
text = stringResource(Res.string.chat_load_error),
color = MaterialTheme.colorScheme.error
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { component.loadInitialMessages() }) {
Text(stringResource(Res.string.chat_retry_button))
}
}
} }
is ChatState.Data -> { is ChatState.Data -> {
ChatContent( ChatContent(
@@ -29,10 +29,14 @@ import androidx.compose.material3.LinearWavyProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.extensions.compose.subscribeAsState
@@ -58,16 +62,19 @@ fun UpdateDialog(
onDismissRequest = { component.reset() }, onDismissRequest = { component.reset() },
canDismiss = canDismiss, canDismiss = canDismiss,
content = { content = {
val focusRequester = remember { FocusRequester() }
when (val currentState = state) { when (val currentState = state) {
is UpdateState.Checking -> UpdateCheckingContent() is UpdateState.Checking -> UpdateCheckingContent()
is UpdateState.UpdateAvailable -> UpdateAvailableContent( is UpdateState.UpdateAvailable -> UpdateAvailableContent(
versionName = currentState.versionName, versionName = currentState.versionName,
changeLog = currentState.changeLog, changeLog = currentState.changeLog,
onDownloadClick = { component.downloadUpdate(currentState.url) } onDownloadClick = { component.downloadUpdate(currentState.url) },
focusRequester = focusRequester
) )
is UpdateState.InstallPermissionRequired -> UpdatePermissionContent( is UpdateState.InstallPermissionRequired -> UpdatePermissionContent(
onGrantClick = { component.requestPermission() } onGrantClick = { component.requestPermission() },
focusRequester = focusRequester
) )
is UpdateState.Downloading -> UpdateDownloadingContent( is UpdateState.Downloading -> UpdateDownloadingContent(
@@ -77,12 +84,14 @@ fun UpdateDialog(
) )
is UpdateState.ReadyToInstall -> UpdateReadyContent( is UpdateState.ReadyToInstall -> UpdateReadyContent(
onInstallClick = { component.installApk(currentState.filePath) } onInstallClick = { component.installApk(currentState.filePath) },
focusRequester = focusRequester
) )
is UpdateState.Error -> UpdateErrorContent( is UpdateState.Error -> UpdateErrorContent(
message = currentState.message, message = currentState.message,
onRetryClick = { component.checkUpdates() } onRetryClick = { component.checkUpdates() },
focusRequester = focusRequester
) )
is UpdateState.UpToDate -> { is UpdateState.UpToDate -> {
@@ -98,6 +107,10 @@ fun UpdateDialog(
else -> Unit else -> Unit
} }
LaunchedEffect(state) {
withFrameNanos {}
focusRequester.requestFocus()
}
} }
) )
} }
@@ -113,6 +126,7 @@ private fun UpdateAvailableContent(
versionName: String, versionName: String,
changeLog: String?, changeLog: String?,
onDownloadClick: () -> Unit, onDownloadClick: () -> Unit,
focusRequester: FocusRequester,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column( Column(
@@ -141,7 +155,10 @@ private fun UpdateAvailableContent(
) )
} }
item { item {
Button(onClick = onDownloadClick, modifier = Modifier.fillMaxWidth()) { Button(
onClick = onDownloadClick,
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth()
) {
Text( Text(
stringResource(Res.string.updater_btn_download), stringResource(Res.string.updater_btn_download),
style = MaterialTheme.typography.labelLarge style = MaterialTheme.typography.labelLarge
@@ -155,6 +172,7 @@ private fun UpdateAvailableContent(
@Composable @Composable
private fun UpdatePermissionContent( private fun UpdatePermissionContent(
onGrantClick: () -> Unit, onGrantClick: () -> Unit,
focusRequester: FocusRequester,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column( Column(
@@ -170,7 +188,10 @@ private fun UpdatePermissionContent(
text = stringResource(Res.string.updater_desc_permission), text = stringResource(Res.string.updater_desc_permission),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
Button(onClick = onGrantClick, modifier = Modifier.fillMaxWidth()) { Button(
onClick = onGrantClick,
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth()
) {
Text( Text(
stringResource(Res.string.updater_btn_grant), stringResource(Res.string.updater_btn_grant),
style = MaterialTheme.typography.labelLarge style = MaterialTheme.typography.labelLarge
@@ -215,6 +236,7 @@ private fun UpdateDownloadingContent(
@Composable @Composable
private fun UpdateReadyContent( private fun UpdateReadyContent(
onInstallClick: () -> Unit, onInstallClick: () -> Unit,
focusRequester: FocusRequester,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column( Column(
@@ -226,7 +248,10 @@ private fun UpdateReadyContent(
text = stringResource(Res.string.updater_title_ready), text = stringResource(Res.string.updater_title_ready),
style = MaterialTheme.typography.titleLarge style = MaterialTheme.typography.titleLarge
) )
Button(onClick = onInstallClick, modifier = Modifier.fillMaxWidth()) { Button(
onClick = onInstallClick,
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth()
) {
Text( Text(
stringResource(Res.string.updater_btn_install), stringResource(Res.string.updater_btn_install),
style = MaterialTheme.typography.labelLarge style = MaterialTheme.typography.labelLarge
@@ -260,6 +285,7 @@ private fun UpdateFinishedContent(
private fun UpdateErrorContent( private fun UpdateErrorContent(
message: String, message: String,
onRetryClick: () -> Unit, onRetryClick: () -> Unit,
focusRequester: FocusRequester,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column( Column(
@@ -273,7 +299,10 @@ private fun UpdateErrorContent(
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.error
) )
Text(text = message, style = MaterialTheme.typography.bodyMedium) Text(text = message, style = MaterialTheme.typography.bodyMedium)
Button(onClick = onRetryClick, modifier = Modifier.fillMaxWidth()) { Button(
onClick = onRetryClick,
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth()
) {
Text( Text(
stringResource(Res.string.updater_btn_retry), stringResource(Res.string.updater_btn_retry),
style = MaterialTheme.typography.labelLarge style = MaterialTheme.typography.labelLarge
@@ -289,7 +318,8 @@ private fun UpdateAvailablePreview() {
UpdateAvailableContent( UpdateAvailableContent(
versionName = "v1.4.0", versionName = "v1.4.0",
changeLog = "- Добавили темную тему\n- Исправили падение на главном экране\n- Повысили стабильность", changeLog = "- Добавили темную тему\n- Исправили падение на главном экране\n- Повысили стабильность",
onDownloadClick = {} onDownloadClick = {},
focusRequester = FocusRequester()
) )
} }
} }
@@ -310,7 +340,10 @@ private fun UpdateDownloadingPreview() {
@Composable @Composable
private fun UpdatePermissionPreview() { private fun UpdatePermissionPreview() {
MaterialTheme { MaterialTheme {
UpdatePermissionContent(onGrantClick = {}) UpdatePermissionContent(
onGrantClick = {},
focusRequester = FocusRequester()
)
} }
} }
@@ -318,6 +351,10 @@ private fun UpdatePermissionPreview() {
@Composable @Composable
private fun UpdateErrorPreview() { private fun UpdateErrorPreview() {
MaterialTheme { MaterialTheme {
UpdateErrorContent(message = "Http status 503: Service Unavailable", onRetryClick = {}) UpdateErrorContent(
message = "Http status 503: Service Unavailable",
onRetryClick = {},
focusRequester = FocusRequester()
)
} }
} }
@@ -114,7 +114,7 @@ fun VideoPlayer(
) )
} }
Box(modifier = rootModifier.fillMaxSize().background(Color.Black)) { Box(modifier = rootModifier.fillMaxSize()) {
VideoCanvas( VideoCanvas(
controller = controller, controller = controller,
modifier = rootModifier.fillMaxSize(), modifier = rootModifier.fillMaxSize(),
@@ -1,4 +1,5 @@
<resources> <resources>
<string name="error_title">Произошла ошибка</string> <string name="error_title">Произошла ошибка</string>
<string name="ok">Понятно</string> <string name="ok">Понятно</string>
<string name="retry">Повторить</string>
</resources> </resources>
@@ -3,6 +3,7 @@ package ru.shadowsparky.ui.components
import androiddev.libs.ui.generated.resources.Res import androiddev.libs.ui.generated.resources.Res
import androiddev.libs.ui.generated.resources.error_title import androiddev.libs.ui.generated.resources.error_title
import androiddev.libs.ui.generated.resources.ok import androiddev.libs.ui.generated.resources.ok
import androiddev.libs.ui.generated.resources.retry
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -25,9 +26,10 @@ fun ErrorContent(
message: String, message: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
buttonModifier: Modifier = Modifier, buttonModifier: Modifier = Modifier,
buttonText: String = stringResource(Res.string.ok), retry: Boolean = true,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val buttonTextRes = if (retry) Res.string.retry else Res.string.ok
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -35,10 +37,10 @@ fun ErrorContent(
) { ) {
Text( Text(
text = stringResource(Res.string.error_title), text = stringResource(Res.string.error_title),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Spacer(Modifier.height(2.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = message, text = message,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
@@ -47,7 +49,7 @@ fun ErrorContent(
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Button(modifier = buttonModifier, onClick = { onClick() }) { Button(modifier = buttonModifier, onClick = { onClick() }) {
Text( Text(
text = buttonText, text = stringResource(buttonTextRes),
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
} }
@@ -57,7 +59,6 @@ fun ErrorContent(
@Composable @Composable
fun ErrorAlert( fun ErrorAlert(
message: String, message: String,
buttonText: String = stringResource(Res.string.ok),
onClick: () -> Unit, onClick: () -> Unit,
onSkip: () -> Unit = onClick onSkip: () -> Unit = onClick
) { ) {
@@ -66,7 +67,7 @@ fun ErrorAlert(
message, message,
Modifier, Modifier,
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
buttonText, false,
onClick onClick
) )
} }