compose: details: Expose actual error message when possible

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-08-31 12:38:03 +08:00
parent b5733d070e
commit eeff0a65aa
7 changed files with 44 additions and 29 deletions

View File

@@ -5,11 +5,11 @@
package com.aurora.store.compose.composables package com.aurora.store.compose.composables
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
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.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -17,9 +17,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R import com.aurora.store.R
@@ -35,13 +37,15 @@ import com.aurora.store.R
@Composable @Composable
fun ErrorComposable( fun ErrorComposable(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@DrawableRes icon: Int, icon: Painter,
@StringRes message: Int, message: String,
@StringRes actionMessage: Int? = null, actionMessage: String? = null,
onAction: () -> Unit = {} onAction: () -> Unit = {}
) { ) {
Column( Column(
modifier = modifier.fillMaxSize(), modifier = modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium)),
verticalArrangement = Arrangement.spacedBy( verticalArrangement = Arrangement.spacedBy(
dimensionResource(R.dimen.margin_small), dimensionResource(R.dimen.margin_small),
Alignment.CenterVertically Alignment.CenterVertically
@@ -49,16 +53,20 @@ fun ErrorComposable(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Icon( Icon(
painter = painterResource(id = icon), painter = icon,
contentDescription = null, contentDescription = null,
modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size)) modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size))
) )
Text(text = stringResource(message)) Text(
modifier = Modifier.fillMaxWidth(),
text = message,
textAlign = TextAlign.Center
)
if (actionMessage != null) { if (actionMessage != null) {
Button(onClick = onAction) { Button(onClick = onAction) {
Text( Text(
text = stringResource(actionMessage), text = actionMessage,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -71,8 +79,8 @@ fun ErrorComposable(
@Composable @Composable
private fun ErrorComposablePreview() { private fun ErrorComposablePreview() {
ErrorComposable( ErrorComposable(
icon = R.drawable.ic_updates, icon = painterResource(R.drawable.ic_updates),
message = R.string.details_no_updates, message = stringResource(R.string.details_no_updates),
actionMessage = R.string.check_updates actionMessage = stringResource(R.string.check_updates)
) )
} }

View File

@@ -111,7 +111,12 @@ fun AppDetailsScreen(
when (state) { when (state) {
is AppState.Loading -> ScreenContentLoading(onNavigateUp = onNavigateUp) is AppState.Loading -> ScreenContentLoading(onNavigateUp = onNavigateUp)
is AppState.Error -> ScreenContentError(onNavigateUp = onNavigateUp) is AppState.Error -> {
ScreenContentError(
onNavigateUp = onNavigateUp,
message = (state as AppState.Error).message
)
}
else -> { else -> {
ScreenContentApp( ScreenContentApp(
@@ -164,14 +169,14 @@ private fun ScreenContentLoading(onNavigateUp: () -> Unit = {}) {
* Composable to display errors related to fetching app details * Composable to display errors related to fetching app details
*/ */
@Composable @Composable
private fun ScreenContentError(onNavigateUp: () -> Unit = {}) { private fun ScreenContentError(onNavigateUp: () -> Unit = {}, message: String? = null) {
Scaffold( Scaffold(
topBar = { TopAppBarComposable(onNavigateUp = onNavigateUp) } topBar = { TopAppBarComposable(onNavigateUp = onNavigateUp) }
) { paddingValues -> ) { paddingValues ->
ErrorComposable( ErrorComposable(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_apps_outage, icon = painterResource(R.drawable.ic_apps_outage),
message = R.string.toast_app_unavailable message = message ?: stringResource(R.string.toast_app_unavailable)
) )
} }
} }

View File

@@ -114,8 +114,8 @@ private fun ScreenContent(
is LoadState.Error -> { is LoadState.Error -> {
ErrorComposable( ErrorComposable(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_disclaimer, icon = painterResource(R.drawable.ic_disclaimer),
message = R.string.error message = stringResource(R.string.error)
) )
} }

View File

@@ -16,6 +16,8 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@@ -99,8 +101,8 @@ private fun ScreenContent(
is LoadState.Error -> { is LoadState.Error -> {
ErrorComposable( ErrorComposable(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_disclaimer, icon = painterResource(R.drawable.ic_disclaimer),
message = R.string.error message = stringResource(R.string.error)
) )
} }

View File

@@ -212,8 +212,8 @@ private fun ScreenContent(
is LoadState.Error -> { is LoadState.Error -> {
ErrorComposable( ErrorComposable(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_disclaimer, icon = painterResource(R.drawable.ic_disclaimer),
message = R.string.error message = stringResource(R.string.error)
) )
} }
@@ -221,8 +221,8 @@ private fun ScreenContent(
if (isSearching && results.itemCount == 0) { if (isSearching && results.itemCount == 0) {
ErrorComposable( ErrorComposable(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_disclaimer, icon = painterResource(R.drawable.ic_disclaimer),
message = R.string.no_apps_available message = stringResource(R.string.no_apps_available)
) )
} else { } else {
LazyColumn { LazyColumn {
@@ -261,10 +261,10 @@ private fun ScreenContent(
} }
else -> { else -> {
if (textFieldState.text.isNotBlank() && results.itemCount > 0) { if (isSearching && results.itemCount > 0) {
ErrorComposable( ErrorComposable(
icon = R.drawable.ic_round_search, icon = painterResource(R.drawable.ic_round_search),
message = R.string.select_app_for_details message = stringResource(R.string.select_app_for_details)
) )
} }
} }

View File

@@ -56,12 +56,12 @@ sealed class AppState {
) : AppState() ) : AppState()
data class Installing(val progress: Float) : AppState() data class Installing(val progress: Float) : AppState()
data class Error(val message: String?) : AppState()
data object Installed : AppState() data object Installed : AppState()
data object Archived : AppState() data object Archived : AppState()
data object Updatable : AppState() data object Updatable : AppState()
data object Unavailable : AppState() data object Unavailable : AppState()
data object Loading : AppState() data object Loading : AppState()
data object Error : AppState()
/** /**
* Whether there is some sort of ongoing process related to the app * Whether there is some sort of ongoing process related to the app

View File

@@ -153,7 +153,7 @@ class AppDetailsViewModel @Inject constructor(
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Failed to fetch app details", exception) Log.e(TAG, "Failed to fetch app details", exception)
_app.value = null _app.value = null
_state.value = AppState.Error _state.value = AppState.Error(exception.message)
} }
}.invokeOnCompletion { throwable -> }.invokeOnCompletion { throwable ->
// Only proceed if there was no error while fetching the app details // Only proceed if there was no error while fetching the app details