From 8292f729ea792284a77bb9420a5b48dde700f0cc Mon Sep 17 00:00:00 2001 From: Su TT Date: Wed, 30 Jul 2025 17:45:56 -0400 Subject: [PATCH] Updated ERefactor ErrorPanel to use ErrorInfo directly and remove UI models --- .../CommentSectionErrorIntegrationTest.kt | 119 +++++++++++++++++ .../video/comments/CommentSectionErrorTest.kt | 65 --------- .../org/schabi/newpipe/error/ErrorUIMapper.kt | 14 -- .../schabi/newpipe/paging/CommentsSource.kt | 2 +- .../schabi/newpipe/ui/UiModel/ErrorUiModel.kt | 41 ------ .../ui/components/common/ErrorPanel.kt | 123 ++++++++++-------- .../video/comment/CommentErrorHandler.kt | 64 --------- .../video/comment/CommentSection.kt | 53 ++++++-- .../newpipe/viewmodels/CommentsViewModel.kt | 1 - .../main/res/layout/fragment_video_detail.xml | 8 ++ .../schabi/newpipe/error/ErrorUiModelTest.kt | 63 --------- 11 files changed, 237 insertions(+), 316 deletions(-) create mode 100644 app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionErrorIntegrationTest.kt delete mode 100644 app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comments/CommentSectionErrorTest.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/error/ErrorUIMapper.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/ui/UiModel/ErrorUiModel.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentErrorHandler.kt delete mode 100644 app/src/test/java/org/schabi/newpipe/error/ErrorUiModelTest.kt diff --git a/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionErrorIntegrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionErrorIntegrationTest.kt new file mode 100644 index 000000000..12bcd85ac --- /dev/null +++ b/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionErrorIntegrationTest.kt @@ -0,0 +1,119 @@ +package org.schabi.newpipe.ui.components.video.comment + +import android.content.Context +import android.content.Intent +import androidx.paging.LoadState +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.UserAction +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException +import org.schabi.newpipe.ui.components.common.ErrorAction +import org.schabi.newpipe.ui.components.common.determineErrorAction +import org.schabi.newpipe.viewmodels.util.Resource +import java.io.IOException +import java.net.SocketTimeoutException + +@RunWith(AndroidJUnit4::class) +class CommentSectionErrorIntegrationTest { + + private lateinit var context: Context + + @Before + fun setUp() { + context = ApplicationProvider.getApplicationContext() + } + + // Test 1: Network error on initial load (Resource.Error) + @Test + fun testInitialCommentNetworkError() { + val expectedMessage = "Connection timeout" + val networkError = SocketTimeoutException(expectedMessage) + val resourceError = Resource.Error(networkError) + + val errorInfo = ErrorInfo( + throwable = resourceError.throwable, + userAction = UserAction.REQUESTED_COMMENTS, + request = "comments" + ) + assertEquals(networkError, errorInfo.throwable) + assertEquals(ErrorAction.REPORT, determineErrorAction(errorInfo)) + assertEquals(expectedMessage, errorInfo.getExplanation()) + } + + // Test 2: Network error on paging (LoadState.Error) + @Test + fun testPagingNetworkError() { + val expectedMessage = "Paging failed" + val pagingError = IOException(expectedMessage) + val loadStateError = LoadState.Error(pagingError) + + val errorInfo = ErrorInfo( + throwable = loadStateError.error, + userAction = UserAction.REQUESTED_COMMENTS, + request = "comments" + ) + assertEquals(pagingError, errorInfo.throwable) + assertEquals(ErrorAction.REPORT, determineErrorAction(errorInfo)) + assertEquals(expectedMessage, errorInfo.getExplanation()) + } + + // Test 3: ReCaptcha during comments load + @Test + fun testReCaptchaDuringComments() { + val url = "https://www.google.com/recaptcha/api/fallback?k=test" + val expectedMessage = "ReCaptcha needed" + val captcha = ReCaptchaException(expectedMessage, url) + val errorInfo = ErrorInfo( + throwable = captcha, + userAction = UserAction.REQUESTED_COMMENTS, + request = "comments" + ) + assertEquals(ErrorAction.SOLVE_CAPTCHA, determineErrorAction(errorInfo)) + assertEquals(expectedMessage, errorInfo.getExplanation()) + + val intent = Intent(context, org.schabi.newpipe.error.ReCaptchaActivity::class.java).apply { + putExtra(org.schabi.newpipe.error.ReCaptchaActivity.RECAPTCHA_URL_EXTRA, url) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + assertEquals(url, intent.getStringExtra(org.schabi.newpipe.error.ReCaptchaActivity.RECAPTCHA_URL_EXTRA)) + } + + // Test 4: Retry functionality integration with ErrorPanel + @Test + fun testRetryIntegrationWithErrorPanel() { + val expectedMessage = "Network request failed" + val networkError = IOException(expectedMessage) + val loadStateError = LoadState.Error(networkError) + + val errorInfo = ErrorInfo( + throwable = loadStateError.error, + userAction = UserAction.REQUESTED_COMMENTS, + request = "comments" + ) + + val errorAction = determineErrorAction(errorInfo) + assertEquals("Network errors should get REPORT action", ErrorAction.REPORT, errorAction) + var retryCallbackInvoked = false + val mockCommentRetry = { + retryCallbackInvoked = true + } + + mockCommentRetry() + assertTrue("Retry callback should be invoked when user clicks retry", retryCallbackInvoked) + + assertEquals( + "Error explanation should be available for retry scenarios", + expectedMessage, errorInfo.getExplanation() + ) + assertEquals( + "Error should maintain comment context for retry", + UserAction.REQUESTED_COMMENTS, errorInfo.userAction + ) + } +} diff --git a/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comments/CommentSectionErrorTest.kt b/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comments/CommentSectionErrorTest.kt deleted file mode 100644 index d4cb2ab54..000000000 --- a/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comments/CommentSectionErrorTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.schabi.newpipe.ui.components.video.comments - -import android.net.http.NetworkException -import androidx.paging.LoadState -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.R -import org.schabi.newpipe.ui.UiModel.UnableToLoadCommentsUiModel -import org.schabi.newpipe.viewmodels.util.Resource - -@RunWith(AndroidJUnit4::class) -class CommentSectionErrorTest { - - @get:Rule - val activityRule = ActivityScenarioRule(MainActivity::class.java) - - /** - * Test Resource.Error state - when initial comment info loading fails - */ - class TestNetworkException : NetworkException("Connection attempt timed out", null) { - override fun getErrorCode(): Int = NetworkException.ERROR_CONNECTION_TIMED_OUT - override fun isImmediatelyRetryable() = true - } - @Test - fun testResourceErrorState_ShowsUnableToLoadCommentsUiModel() { - - val networkException = TestNetworkException() - val errorResource = Resource.Error(networkException) - assertEquals("Should contain the network exception", networkException, errorResource.throwable) - - val errorModel = UnableToLoadCommentsUiModel(networkException) - val spec = errorModel.spec - - assertEquals("Should have correct message resource", R.string.error_unable_to_load_comments, spec.messageRes) - assertTrue("Should show retry button", spec.showRetry) - assertTrue("Should show report button", spec.showReport) - assertFalse("Should NOT show open in browser button", spec.showOpenInBrowser) - } - - /** - * Test LoadState.Error state - when paging data loading fails - */ - @Test - fun testLoadStateErrorState_ShowsUnableToLoadCommentsUiModel() { - val pagingException = RuntimeException("Paging data loading failed") - val loadStateError = LoadState.Error(pagingException) - - assertEquals("Should contain the paging exception", pagingException, loadStateError.error) - - val errorModel = UnableToLoadCommentsUiModel(pagingException) - val spec = errorModel.spec - - assertEquals("Should have correct message resource", R.string.error_unable_to_load_comments, spec.messageRes) - assertTrue("Should show retry button", spec.showRetry) - assertTrue("Should show report button", spec.showReport) - assertFalse("Should NOT show open in browser button", spec.showOpenInBrowser) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUIMapper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUIMapper.kt deleted file mode 100644 index 5df92ddfc..000000000 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUIMapper.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.schabi.newpipe.error - -import org.schabi.newpipe.ui.UiModel.ErrorUiModel -import org.schabi.newpipe.ui.UiModel.GenericErrorUiModel -import org.schabi.newpipe.ui.UiModel.UnableToLoadCommentsUiModel - -fun mapThrowableToErrorUiModel(throwable: Throwable, userAction: UserAction? = null): ErrorUiModel { - if (userAction == UserAction.REQUESTED_COMMENTS) { - - return UnableToLoadCommentsUiModel(rawError = throwable) - } - // Other ErrorInfo logic and throwable + user actions - return GenericErrorUiModel(rawError = throwable) -} diff --git a/app/src/main/java/org/schabi/newpipe/paging/CommentsSource.kt b/app/src/main/java/org/schabi/newpipe/paging/CommentsSource.kt index bec4911df..21963640d 100644 --- a/app/src/main/java/org/schabi/newpipe/paging/CommentsSource.kt +++ b/app/src/main/java/org/schabi/newpipe/paging/CommentsSource.kt @@ -17,7 +17,7 @@ class CommentsSource(private val commentInfo: CommentInfo) : PagingSource): LoadResult { // params.key is null the first time the load() function is called, so we need to return the // first batch of already-loaded comments - return LoadResult.Error(IOException("💥 forced test error")) + if (params.key == null) { return LoadResult.Page(commentInfo.comments, null, commentInfo.nextPage) } else { diff --git a/app/src/main/java/org/schabi/newpipe/ui/UiModel/ErrorUiModel.kt b/app/src/main/java/org/schabi/newpipe/ui/UiModel/ErrorUiModel.kt deleted file mode 100644 index 6d91c78cd..000000000 --- a/app/src/main/java/org/schabi/newpipe/ui/UiModel/ErrorUiModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.schabi.newpipe.ui.UiModel - -import androidx.compose.runtime.Immutable -import org.schabi.newpipe.R -import org.schabi.newpipe.ui.components.common.ErrorPanelSpec - -/** - * Each concrete case from this hierarchy represents a different failure state that the UI can render with ErrorPanel - */ -@Immutable -sealed interface ErrorUiModel { - val spec: ErrorPanelSpec - val rawError: Throwable? get() = null -} - -/** - * Concrete cases - Comments unable to load, Comments disabled, No connectivity, DNS failure, timeout etc - */ -@Immutable -data class UnableToLoadCommentsUiModel(override val rawError: Throwable?) : ErrorUiModel { - override val spec: ErrorPanelSpec = - ErrorPanelSpec( - messageRes = R.string.error_unable_to_load_comments, - showRetry = true, - showReport = true, - showOpenInBrowser = false - ) -} - -/** - * A generic ErrorUiModel for unhandled cases - */ -@Immutable -data class GenericErrorUiModel(override val rawError: Throwable?) : ErrorUiModel { - override val spec: ErrorPanelSpec = - ErrorPanelSpec( - messageRes = R.string.general_error, - showRetry = true, - showReport = true, - ) -} diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt index 5f16941d9..93486f546 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt @@ -1,117 +1,132 @@ package org.schabi.newpipe.ui.components.common +import android.content.Intent import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.ErrorUtil +import org.schabi.newpipe.error.ReCaptchaActivity +import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException +import org.schabi.newpipe.extractor.timeago.patterns.it import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.ui.theme.SizeTokens.SpacingExtraLarge -import org.schabi.newpipe.ui.theme.SizeTokens.SpacingLarge import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium import org.schabi.newpipe.ui.theme.SizeTokens.SpacingSmall +import org.schabi.newpipe.util.external_communication.ShareUtils + +enum class ErrorAction(@StringRes val actionStringId: Int) { + REPORT(R.string.error_snackbar_action), + SOLVE_CAPTCHA(R.string.recaptcha_solve) +} + +/** + * Determines the error action type based on the throwable in ErrorInfo + * + */ +fun determineErrorAction(errorInfo: ErrorInfo): ErrorAction { + return when (errorInfo.throwable) { + is ReCaptchaException -> ErrorAction.SOLVE_CAPTCHA + is AccountTerminatedException -> ErrorAction.REPORT + else -> ErrorAction.REPORT + } +} @Composable fun ErrorPanel( - spec: ErrorPanelSpec, - onRetry: () -> Unit, - onReport: () -> Unit, - onOpenInBrowser: () -> Unit, + errorInfo: ErrorInfo, + onRetry: (() -> Unit)? = null, modifier: Modifier = Modifier ) { + val explanation = errorInfo.getExplanation() + val canOpenInBrowser = errorInfo.openInBrowserUrl != null + val errorActionType = determineErrorAction(errorInfo) + + val context = LocalContext.current + Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .wrapContentWidth() - .padding(horizontal = SpacingLarge, vertical = SpacingMedium) ) { - val message = stringResource(spec.messageRes) - Text( - text = message, + text = stringResource(errorInfo.messageStringId), style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), textAlign = TextAlign.Center ) - spec.serviceInfoRes?.let { infoRes -> + if (explanation.isNotBlank()) { Spacer(Modifier.height(SpacingSmall)) - val serviceInfo = stringResource(infoRes) Text( - text = serviceInfo, + text = explanation, style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center ) } - spec.serviceExplanationRes?.let { explanationRes -> - Spacer(Modifier.height(SpacingSmall)) - val serviceExplanation = stringResource(explanationRes) - Text( - text = serviceExplanation, - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center - ) - } Spacer(Modifier.height(SpacingMedium)) - if (spec.showReport) { - ServiceColoredButton(onClick = onReport) { - Text(stringResource(R.string.error_snackbar_action).uppercase()) + when (errorActionType) { + ErrorAction.REPORT -> { + ServiceColoredButton(onClick = { + ErrorUtil.openActivity(context, errorInfo) + }) { + Text(stringResource(errorActionType.actionStringId).uppercase()) + } + } + ErrorAction.SOLVE_CAPTCHA -> { + ServiceColoredButton(onClick = { + // Starting ReCaptcha Challenge Activity + val intent = Intent(context, ReCaptchaActivity::class.java).apply { + putExtra( + ReCaptchaActivity.RECAPTCHA_URL_EXTRA, + (errorInfo.throwable as ReCaptchaException).url + ) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + }) { + Text(stringResource(errorActionType.actionStringId).uppercase()) + } } } - if (spec.showRetry) { - ServiceColoredButton(onClick = onRetry) { + + onRetry?.let { + ServiceColoredButton(onClick = it) { Text(stringResource(R.string.retry).uppercase()) } } - if (spec.showOpenInBrowser) { - ServiceColoredButton(onClick = onOpenInBrowser) { + if (canOpenInBrowser) { + ServiceColoredButton(onClick = { + errorInfo.openInBrowserUrl?.let { url -> + ShareUtils.openUrlInBrowser(context, url) + } + }) { Text(stringResource(R.string.open_in_browser).uppercase()) } } + Spacer(Modifier.height(SpacingExtraLarge)) } } -data class ErrorPanelSpec( - @StringRes val messageRes: Int, - @StringRes val serviceInfoRes: Int? = null, - val serviceExplanation: String? = null, - @StringRes val serviceExplanationRes: Int? = null, - val showRetry: Boolean = false, - val showReport: Boolean = false, - val showOpenInBrowser: Boolean = false -) - @Preview(showBackground = true, widthDp = 360, heightDp = 640) @Composable fun ErrorPanelPreview() { AppTheme { - ErrorPanel( - spec = ErrorPanelSpec( - messageRes = android.R.string.httpErrorBadUrl, - showRetry = true, - showReport = false, - showOpenInBrowser = false - ), - onRetry = {}, - onReport = {}, - onOpenInBrowser = {}, - modifier = Modifier - ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentErrorHandler.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentErrorHandler.kt deleted file mode 100644 index 152c032fd..000000000 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentErrorHandler.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.schabi.newpipe.ui.components.video.comment - -import android.util.Log -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.SideEffect -import androidx.compose.ui.tooling.preview.Preview -import org.schabi.newpipe.error.ErrorInfo -import org.schabi.newpipe.error.UserAction -import org.schabi.newpipe.error.mapThrowableToErrorUiModel -import org.schabi.newpipe.ui.UiModel.ErrorUiModel -import org.schabi.newpipe.ui.components.common.ErrorPanel -import java.io.IOException - -@Composable -fun CommentErrorHandler( - throwable: Throwable, - userAction: UserAction, - onRetry: () -> Unit, - onReport: (ErrorInfo) -> Unit -) { - SideEffect { - Log.d("CommentErrorHandler", "⛔️ rendered for error: ${throwable.message}") - } - - val uiModel: ErrorUiModel = mapThrowableToErrorUiModel(throwable, userAction) - val errorInfo = ErrorInfo( - throwable = throwable, - userAction = userAction, - request = "" - ) - - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - ErrorPanel( - spec = uiModel.spec, - onRetry = onRetry, - onReport = { onReport(errorInfo) }, - onOpenInBrowser = {}, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - ) - } -} - -@Preview(showBackground = true) -@Composable -fun PreviewCommentErrorHandler() { - CommentErrorHandler( - throwable = IOException("No network"), - userAction = UserAction.REQUESTED_COMMENTS, - onRetry = {}, - onReport = {} - ) -} diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt index 327d30f72..9a570c82c 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt @@ -1,6 +1,8 @@ package org.schabi.newpipe.ui.components.video.comment import android.content.res.Configuration +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -11,6 +13,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -26,11 +29,12 @@ import androidx.paging.compose.collectAsLazyPagingItems import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import org.schabi.newpipe.R -import org.schabi.newpipe.error.ErrorUtil +import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.extractor.Page import org.schabi.newpipe.extractor.comments.CommentsInfoItem import org.schabi.newpipe.extractor.stream.Description +import org.schabi.newpipe.ui.components.common.ErrorPanel import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar import org.schabi.newpipe.ui.components.common.LoadingIndicator import org.schabi.newpipe.ui.emptystate.EmptyStateComposable @@ -78,6 +82,7 @@ private fun CommentSection( modifier = Modifier .fillMaxWidth() .heightIn(min = 128.dp) + ) } } else if (count == 0) { @@ -109,13 +114,25 @@ private fun CommentSection( } } is LoadState.Error -> { + val error = (comments.loadState.refresh as LoadState.Error).error + val errorInfo = ErrorInfo( + throwable = error, + userAction = UserAction.REQUESTED_COMMENTS, + request = "comments" + ) + item { - CommentErrorHandler( - throwable = (comments.loadState.refresh as LoadState.Error).error, - userAction = UserAction.REQUESTED_COMMENTS, - onRetry = { comments.retry() }, - onReport = { info -> ErrorUtil.openActivity(context, info) }, - ) + Box( + modifier = Modifier + .fillMaxWidth() + + ) { + ErrorPanel( + errorInfo = errorInfo, + onRetry = { comments.retry() }, + modifier = Modifier.align(Alignment.Center) + ) + } } } else -> { @@ -127,13 +144,23 @@ private fun CommentSection( } } is Resource.Error -> { + val errorInfo = ErrorInfo( + throwable = uiState.throwable, + userAction = UserAction.REQUESTED_COMMENTS, + request = "comments" + ) item { - CommentErrorHandler( - throwable = uiState.throwable, - userAction = UserAction.REQUESTED_COMMENTS, - onRetry = { comments.retry() }, - onReport = { info -> ErrorUtil.openActivity(context, info) }, - ) + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + ErrorPanel( + errorInfo = errorInfo, + onRetry = { comments.retry() }, + modifier = Modifier.align(Alignment.Center) + ) + } } } } diff --git a/app/src/main/java/org/schabi/newpipe/viewmodels/CommentsViewModel.kt b/app/src/main/java/org/schabi/newpipe/viewmodels/CommentsViewModel.kt index 58886fc68..007292498 100644 --- a/app/src/main/java/org/schabi/newpipe/viewmodels/CommentsViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/viewmodels/CommentsViewModel.kt @@ -23,7 +23,6 @@ import org.schabi.newpipe.viewmodels.util.Resource class CommentsViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val uiState = savedStateHandle.getStateFlow(KEY_URL, "") .map { - // Resource.Error(RuntimeException("Forced error for testing")) try { Resource.Success(CommentInfo(CommentsInfo.getInfo(it))) } catch (e: Exception) { diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 1a4711581..d7e2e5a3c 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -214,6 +214,14 @@ android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" android:visibility="gone" tools:visibility="gone" /> +