Skip to content

Commit

Permalink
feat(ui): enhance verbose error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
rhenwinch committed Oct 1, 2024
1 parent dc59832 commit bf8bb8c
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 19 deletions.
4 changes: 4 additions & 0 deletions core/locale/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

<!-- Errors -->
<string name="already_added_repo_error">Repository has already been added!</string>
<string name="an_error_occurred">An error occurred</string>
<string name="blank_media_id_error_message">Could not find media id from the provider</string>
<string name="connection_failed">Connection failure</string>
<string name="connection_timeout">Connection timeout</string>
Expand Down Expand Up @@ -153,6 +154,7 @@
<string name="github_repository">Github Repository</string>
<string name="heads_up">Heads up!</string>
<string name="help">Help</string>
<string name="hide_error_logs">Hide error logs</string>
<string name="ignore_warning_message">Don\'t warn me anymore.</string>
<string name="import_label">Import</string>
<string name="install">Install</string>
Expand Down Expand Up @@ -219,6 +221,7 @@
<string name="servers">Servers</string>
<string name="share_button">Share button</string>
<string name="share_the_app">Share the App!</string>
<string name="show_error_logs">Show error logs</string>
<string name="sign_up_prerelease">Use pre-release updates</string>
<string name="skip">Skip</string>
<string name="sort_date">Sort by date</string>
Expand Down Expand Up @@ -351,6 +354,7 @@
<string name="clear_text_button">Clear text button</string>
<string name="copy_button">Copy to clipboard button</string>
<string name="copy_full_logs_button">Copy full logs to clipboard</string>
<string name="copy_stack_trace_content_desc">Copy button for error stack trace</string>
<string name="decoder_priority_description">Preferred decoder for improved playback compatibility. May fix issues with high-quality streams and no-audio streams on devices with limited hardware support.</string>
<string name="default_user_agent_description">Your user agent is like a digital ID for your browser or app, telling websites and servers who\'s asking for information.</string>
<string name="delete_search_history_item_message">Are you sure you want to delete this search history item?</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ sealed class Resource<out T>(
constructor(error: Throwable?) : this(
when (error) {
null -> null
else -> UiText.StringValue(error.localizedMessage ?: error.message ?: error.stackTraceToString())
else -> UiText.StringValue(error.stackTraceToString())
}
)
constructor(error: String?) : this(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,61 @@
package com.flixclusive.core.ui.mobile.component

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.clickable
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ShapeDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastAny
import com.flixclusive.core.theme.FlixclusiveTheme
import com.flixclusive.core.ui.common.util.CustomClipboardManager.Companion.rememberClipboardManager
import com.flixclusive.core.ui.common.util.onMediumEmphasis
import com.flixclusive.core.locale.R as LocaleR
import com.flixclusive.core.ui.common.R as UiCommonR

val LARGE_ERROR = 480.dp
val SMALL_ERROR = 110.dp

private const val MAX_STACK_TRACE_COMPONENT_HEIGHT = 200

@Composable
fun RetryButton(
modifier: Modifier = Modifier,
Expand All @@ -31,28 +65,97 @@ fun RetryButton(
) {
AnimatedVisibility(
visible = shouldShowError,
enter = fadeIn(),
exit = fadeOut()
enter = scaleIn(),
exit = scaleOut()
) {
val defaultLabel = stringResource(LocaleR.string.something_went_wrong)

var isErrorLogsShown by remember { mutableStateOf(false) }
val isStackTrace = remember(error) {
error != null && isPossibleStackTrace(error)
}

Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = error ?: stringResource(id = LocaleR.string.pagination_error_message),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = 20.dp)
EmptyDataMessage(
modifier = Modifier.padding(horizontal = 15.dp),
title = stringResource(LocaleR.string.an_error_occurred),
description = if (isStackTrace) defaultLabel else error ?: defaultLabel,
icon = {
Icon(
painter = painterResource(UiCommonR.drawable.round_error_outline_24),
contentDescription = stringResource(LocaleR.string.error_icon_content_desc),
modifier = Modifier.size(60.dp),
tint = MaterialTheme.colorScheme.error.onMediumEmphasis()
)
}
)

if (isStackTrace) {
val contentColor = MaterialTheme.colorScheme.onSurface.onMediumEmphasis(0.8F)

Column(
verticalArrangement = Arrangement.spacedBy(5.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedContent(
targetState = isErrorLogsShown,
label = ""
) {
Box(
modifier = Modifier
.clip(MaterialTheme.shapes.extraSmall)
.clickable { isErrorLogsShown = !isErrorLogsShown },
) {
Row(
modifier = Modifier
.padding(3.dp)
.padding(horizontal = 5.dp),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (it) stringResource(LocaleR.string.hide_error_logs) else stringResource(LocaleR.string.show_error_logs),
style = LocalTextStyle.current.copy(
color = contentColor,
fontSize = 12.sp
)
)

Icon(
painter = if (it) painterResource(UiCommonR.drawable.up_arrow) else painterResource(UiCommonR.drawable.down_arrow),
contentDescription = stringResource(
if (isErrorLogsShown) LocaleR.string.overview_collapse else LocaleR.string.overview_expand,
),
tint = contentColor,
modifier = Modifier
.size(10.dp)
)
}
}
}

TextFieldStackTrace(
stackTrace = error!!,
modifier = Modifier
.animateContentSize(tween())
.fillMaxWidth(0.95F)
.height(if (isErrorLogsShown) MAX_STACK_TRACE_COMPONENT_HEIGHT.dp else 0.dp)
)
}
}

Spacer(modifier = Modifier.height(10.dp))

Button(
onClick = onRetry,
shape = ShapeDefaults.Medium
shape = ShapeDefaults.ExtraSmall
) {
Text(
text = stringResource(LocaleR.string.retry),
Expand All @@ -62,5 +165,94 @@ fun RetryButton(
}
}
}
}

@Composable
private fun TextFieldStackTrace(
modifier: Modifier = Modifier,
stackTrace: String,
) {
val clipboardManager = rememberClipboardManager()

val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)

Box(
modifier = modifier
) {
TextField(
value = stackTrace,
onValueChange = {},
modifier = Modifier
.fillMaxSize(),
textStyle = MaterialTheme.typography.bodyLarge.copy(
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Normal
),
shape = MaterialTheme.shapes.extraSmall,
readOnly = true,
colors = TextFieldDefaults.colors(
focusedContainerColor = containerColor,
unfocusedContainerColor = containerColor,
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
)
)

Surface(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp),
shape = MaterialTheme.shapes.extraSmall,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(5.dp)
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(MaterialTheme.shapes.extraSmall)
.clickable {
clipboardManager.setText(stackTrace)
},
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(UiCommonR.drawable.round_content_copy_24),
contentDescription = stringResource(LocaleR.string.copy_stack_trace_content_desc),
tint = LocalContentColor.current.onMediumEmphasis()
)
}
}
}
}

private fun isPossibleStackTrace(input: String): Boolean {
val stackTracePatterns = listOf(
"at ", // Common line starter
"Caused by: ", // Exception cause indicator
"java.", // Java packages
"kotlin.", // Kotlin packages
"android.", // Android packages
)

return stackTracePatterns.fastAny { input.contains(it) }
}

@Preview
@Composable
private fun RetryButtonPreview() {
FlixclusiveTheme {
Surface(
modifier = Modifier
.fillMaxSize()
) {
RetryButton(
shouldShowError = true,
error = """
lang.NullPointerExceptione
""".trimIndent()
) {

}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.flixclusive.core.ui.common.util.PagingState
import com.flixclusive.core.ui.mobile.R
import com.flixclusive.core.ui.mobile.component.LARGE_ERROR
import com.flixclusive.core.ui.mobile.component.RetryButton
import com.flixclusive.core.ui.mobile.component.SMALL_ERROR
import com.flixclusive.core.ui.mobile.util.isAtTop
import com.flixclusive.core.ui.mobile.util.isScrollingUp
import com.flixclusive.core.ui.common.util.PagingState
import com.flixclusive.core.util.exception.safeCall
import com.flixclusive.model.film.util.FilmType
import com.flixclusive.model.film.Film
import com.flixclusive.model.film.util.FilmType
import kotlinx.coroutines.launch
import com.flixclusive.core.ui.common.R as UiCommonR
import com.flixclusive.core.locale.R as LocaleR
import com.flixclusive.core.ui.common.R as UiCommonR

@Composable
fun FilmsGridScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ import androidx.compose.ui.unit.sp
import com.flixclusive.core.theme.FlixclusiveTheme
import com.flixclusive.core.ui.common.util.onMediumEmphasis
import com.flixclusive.core.ui.common.util.showToast
import com.flixclusive.core.ui.common.R as UiCommonR
import com.flixclusive.core.locale.R as LocaleR
import com.flixclusive.core.ui.common.R as UiCommonR

// TODO: Make this internal once TV UI gets its own CrashScreen
@Composable
Expand Down Expand Up @@ -142,7 +142,7 @@ fun CrashMobileScreen(
) {
Icon(
painter = painterResource(UiCommonR.drawable.round_content_copy_24),
contentDescription = "Copy button for error stack trace",
contentDescription = stringResource(LocaleR.string.copy_stack_trace_content_desc),
)
}
}
Expand Down

0 comments on commit bf8bb8c

Please sign in to comment.