From 9d597ef33b175ce442b54de0538fb489aa406cd9 Mon Sep 17 00:00:00 2001 From: Farhan Arshad Date: Thu, 4 Jul 2024 11:21:55 +0500 Subject: [PATCH 1/5] feat: App Level Webview/No Internet Error Handling - Not Internet Error Handling with reload CTA - Unknown error while load webpage with reload CTA. fixes: LEARNER-10046 --- .../java/org/openedx/core/ui/ComposeCommon.kt | 54 +++++++++++++-- core/src/main/res/drawable/core_unkown.xml | 23 +++++++ core/src/main/res/values/strings.xml | 2 + .../unit/html/HtmlUnitFragment.kt | 52 +++++++++++++-- .../presentation/WebViewDiscoveryFragment.kt | 43 +++++++++--- .../presentation/catalog/CatalogWebView.kt | 11 ++++ .../presentation/info/CourseInfoFragment.kt | 38 +++++++++-- .../presentation/program/ProgramFragment.kt | 66 ++++++++++++++----- .../presentation/program/ProgramUIState.kt | 8 +++ .../presentation/program/ProgramViewModel.kt | 6 ++ 10 files changed, 260 insertions(+), 43 deletions(-) create mode 100644 core/src/main/res/drawable/core_unkown.xml diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index 26806897f..ae8a65e9d 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -1139,6 +1139,40 @@ fun BackBtn( fun ConnectionErrorView( modifier: Modifier, onReloadClick: () -> Unit, +) { + ErrorScreen( + modifier = modifier, + title = stringResource(id = R.string.core_no_internet_connection), + description = stringResource(id = R.string.core_no_internet_connection_description), + buttonText = stringResource(id = R.string.core_reload), + icon = painterResource(id = R.drawable.core_no_internet_connection), + onReloadClick = onReloadClick + ) +} + +@Composable +fun SomethingWentWrongErrorView( + modifier: Modifier, + onReloadClick: () -> Unit, +) { + ErrorScreen( + modifier = modifier, + title = stringResource(id = R.string.core_try_again), + description = stringResource(id = R.string.core_something_went_wrong_description), + buttonText = stringResource(id = R.string.core_reload), + icon = painterResource(id = R.drawable.core_unkown), + onReloadClick = onReloadClick + ) +} + +@Composable +fun ErrorScreen( + modifier: Modifier, + title: String, + description: String, + buttonText: String, + icon: Painter, + onReloadClick: () -> Unit ) { Column( modifier = modifier, @@ -1147,14 +1181,14 @@ fun ConnectionErrorView( ) { Icon( modifier = Modifier.size(100.dp), - painter = painterResource(id = R.drawable.core_no_internet_connection), + painter = icon, contentDescription = null, tint = MaterialTheme.appColors.onSurface ) Spacer(Modifier.height(28.dp)) Text( modifier = Modifier.fillMaxWidth(0.8f), - text = stringResource(id = R.string.core_no_internet_connection), + text = title, color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.titleLarge, textAlign = TextAlign.Center @@ -1162,7 +1196,7 @@ fun ConnectionErrorView( Spacer(Modifier.height(16.dp)) Text( modifier = Modifier.fillMaxWidth(0.8f), - text = stringResource(id = R.string.core_no_internet_connection_description), + text = description, color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.bodyLarge, textAlign = TextAlign.Center @@ -1171,7 +1205,7 @@ fun ConnectionErrorView( OpenEdXButton( modifier = Modifier .widthIn(Dp.Unspecified, 162.dp), - text = stringResource(id = R.string.core_reload), + text = buttonText, textColor = MaterialTheme.appColors.primaryButtonText, backgroundColor = MaterialTheme.appColors.secondaryButtonBackground, onClick = onReloadClick, @@ -1372,6 +1406,18 @@ private fun ConnectionErrorViewPreview() { } } +@Preview +@Composable +private fun SomethingWentWrongErrorViewPreview() { + OpenEdXTheme(darkTheme = true) { + SomethingWentWrongErrorView( + modifier = Modifier + .fillMaxSize(), + onReloadClick = {} + ) + } +} + val mockTab = object : TabItem { override val labelResId: Int = R.string.app_name override val icon: ImageVector = Icons.Default.AccountCircle diff --git a/core/src/main/res/drawable/core_unkown.xml b/core/src/main/res/drawable/core_unkown.xml new file mode 100644 index 000000000..d7d2c0c02 --- /dev/null +++ b/core/src/main/res/drawable/core_unkown.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 931d2c6da..98d218899 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ We received your feedback and will use it to help improve your learning experience going forward. Thank you for sharing! No internet connection Please connect to the internet to view this content. + Try Again + Something went wrong OK Continue Leaving the app diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt index 392fa07fa..172972ebc 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt @@ -10,6 +10,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.webkit.JavascriptInterface +import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView @@ -54,6 +55,7 @@ import org.openedx.core.extension.isEmailValid import org.openedx.core.extension.loadUrl import org.openedx.core.system.AppCookieManager import org.openedx.core.ui.ConnectionErrorView +import org.openedx.core.ui.SomethingWentWrongErrorView import org.openedx.core.ui.WindowSize import org.openedx.core.ui.rememberWindowSize import org.openedx.core.ui.roundBorderWithoutBottom @@ -88,6 +90,10 @@ class HtmlUnitFragment : Fragment() { mutableStateOf(true) } + var isError by remember { + mutableStateOf(false) + } + var hasInternetConnection by remember { mutableStateOf(viewModel.isOnline) } @@ -125,7 +131,7 @@ class HtmlUnitFragment : Fragment() { .then(border), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { + if (hasInternetConnection && !isError) { HTMLContentView( windowSize = windowSize, url = blockUrl, @@ -138,21 +144,43 @@ class HtmlUnitFragment : Fragment() { }, onWebPageLoading = { isLoading = true + isError = false }, onWebPageLoaded = { isLoading = false + isError = false if (isAdded) viewModel.setWebPageLoaded(requireContext().assets) + }, + onWebPageLoadError = { + isLoading = false + isError = true } ) } else { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background) - ) { + isError = true + } + if (isError) { + val onReloadClick = { + isError = false hasInternetConnection = viewModel.isOnline } + if (!hasInternetConnection) { + ConnectionErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } else { + SomethingWentWrongErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } } if (isLoading && hasInternetConnection) { Box( @@ -200,6 +228,7 @@ private fun HTMLContentView( onCompletionSet: () -> Unit, onWebPageLoading: () -> Unit, onWebPageLoaded: () -> Unit, + onWebPageLoadError: () -> Unit, ) { val coroutineScope = rememberCoroutineScope() val context = LocalContext.current @@ -282,6 +311,15 @@ private fun HTMLContentView( } super.onReceivedHttpError(view, request, errorResponse) } + + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError? + ) { + onWebPageLoadError() + super.onReceivedError(view, request, error) + } } with(settings) { javaScriptEnabled = true diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt index 6696e765b..814f482a6 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt @@ -54,6 +54,7 @@ import org.koin.core.parameter.parametersOf import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.ui.AuthButtonsPanel import org.openedx.core.ui.ConnectionErrorView +import org.openedx.core.ui.SomethingWentWrongErrorView import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -185,6 +186,7 @@ private fun WebViewDiscoveryScreen( val scaffoldState = rememberScaffoldState() val configuration = LocalConfiguration.current var isLoading by remember { mutableStateOf(true) } + var isError by remember { mutableStateOf(false) } Scaffold( scaffoldState = scaffoldState, @@ -248,23 +250,46 @@ private fun WebViewDiscoveryScreen( .background(Color.White), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { + if (hasInternetConnection && !isError) { DiscoveryWebView( contentUrl = contentUrl, uriScheme = uriScheme, - onWebPageLoaded = { isLoading = false }, + onWebPageLoaded = { + isLoading = false + isError = false + }, onWebPageUpdated = onWebPageUpdated, onUriClick = onUriClick, + onWebPageLoadError = { + isLoading = false + isError = true + } ) } else { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background) - ) { + isError = true + } + if (isError) { + val onReloadClick = { + isError = false checkInternetConnection() } + if (!hasInternetConnection) { + ConnectionErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } else { + SomethingWentWrongErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } } if (isLoading && hasInternetConnection) { Box( @@ -290,6 +315,7 @@ private fun DiscoveryWebView( onWebPageLoaded: () -> Unit, onWebPageUpdated: (String) -> Unit, onUriClick: (String, WebViewLink.Authority) -> Unit, + onWebPageLoadError: () -> Unit ) { val webView = CatalogWebViewScreen( url = contentUrl, @@ -297,6 +323,7 @@ private fun DiscoveryWebView( onWebPageLoaded = onWebPageLoaded, onWebPageUpdated = onWebPageUpdated, onUriClick = onUriClick, + onWebPageLoadError = onWebPageLoadError ) AndroidView( diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt index 373516b0a..304b8360f 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt @@ -1,6 +1,7 @@ package org.openedx.discovery.presentation.catalog import android.annotation.SuppressLint +import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebView import androidx.compose.foundation.isSystemInDarkTheme @@ -20,6 +21,7 @@ fun CatalogWebViewScreen( refreshSessionCookie: () -> Unit = {}, onWebPageUpdated: (String) -> Unit = {}, onUriClick: (String, linkAuthority) -> Unit, + onWebPageLoadError: () -> Unit ): WebView { val context = LocalContext.current val isDarkTheme = isSystemInDarkTheme() @@ -81,6 +83,15 @@ fun CatalogWebViewScreen( else -> false } } + + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError? + ) { + onWebPageLoadError() + super.onReceivedError(view, request, error) + } } with(settings) { diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt index b3b3275eb..dcc512102 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt @@ -48,6 +48,7 @@ import org.openedx.core.presentation.dialog.alert.InfoDialogFragment import org.openedx.core.ui.AuthButtonsPanel import org.openedx.core.ui.ConnectionErrorView import org.openedx.core.ui.HandleUIMessage +import org.openedx.core.ui.SomethingWentWrongErrorView import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -232,6 +233,7 @@ private fun CourseInfoScreen( val scaffoldState = rememberScaffoldState() val configuration = LocalConfiguration.current var isLoading by remember { mutableStateOf(true) } + var isError by remember { mutableStateOf(false) } HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState) @@ -291,22 +293,42 @@ private fun CourseInfoScreen( .navigationBarsPadding(), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { + if (hasInternetConnection && !isError) { CourseInfoWebView( contentUrl = uiState.initialUrl, uriScheme = uriScheme, onWebPageLoaded = { isLoading = false }, onUriClick = onUriClick, + onWebPageLoadError = { + isLoading = false + isError = true + } ) } else { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background) - ) { + isError = true + } + if (isError) { + val onReloadClick = { + isError = false checkInternetConnection() } + if (!hasInternetConnection) { + ConnectionErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } else { + SomethingWentWrongErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } } if (isLoading && hasInternetConnection) { Box( @@ -331,6 +353,7 @@ private fun CourseInfoWebView( uriScheme: String, onWebPageLoaded: () -> Unit, onUriClick: (String, linkAuthority) -> Unit, + onWebPageLoadError: () -> Unit ) { val webView = CatalogWebViewScreen( @@ -339,6 +362,7 @@ private fun CourseInfoWebView( isAllLinksExternal = true, onWebPageLoaded = onWebPageLoaded, onUriClick = onUriClick, + onWebPageLoadError = onWebPageLoadError ) AndroidView( diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt index ef79e1f32..10cddcedf 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt @@ -53,6 +53,7 @@ import org.openedx.core.presentation.dialog.alert.InfoDialogFragment import org.openedx.core.system.AppCookieManager import org.openedx.core.ui.ConnectionErrorView import org.openedx.core.ui.HandleUIMessage +import org.openedx.core.ui.SomethingWentWrongErrorView import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -133,10 +134,25 @@ class ProgramFragment : Fragment() { isNestedFragment = isNestedFragment, uriScheme = viewModel.uriScheme, hasInternetConnection = hasInternetConnection, - checkInternetConnection = { - hasInternetConnection = viewModel.hasInternetConnection + onProgramAction = { action -> + when (action) { + ProgramUIAction.CHECK_INTERNET_CONNECTION -> { + hasInternetConnection = viewModel.hasInternetConnection + } + + ProgramUIAction.WEB_PAGE_LOADED -> { + viewModel.showLoading(false) + } + + ProgramUIAction.WEB_PAGE_ERROR -> { + viewModel.onPageLoadError() + } + + ProgramUIAction.ON_RELOAD -> { + viewModel.showLoading(true) + } + } }, - onWebPageLoaded = { viewModel.showLoading(false) }, onBackClick = { requireActivity().supportFragmentManager.popBackStackImmediate() }, @@ -192,7 +208,7 @@ class ProgramFragment : Fragment() { }, onSettingsClick = { viewModel.navigateToSettings(requireActivity().supportFragmentManager) - }, + } ) } } @@ -234,8 +250,7 @@ private fun ProgramInfoScreen( canShowBackBtn: Boolean, isNestedFragment: Boolean, hasInternetConnection: Boolean, - checkInternetConnection: () -> Unit, - onWebPageLoaded: () -> Unit, + onProgramAction: (ProgramUIAction) -> Unit, onSettingsClick: () -> Unit, onBackClick: () -> Unit, onUriClick: (String, WebViewLink.Authority) -> Unit, @@ -304,18 +319,19 @@ private fun ProgramInfoScreen( .background(Color.White), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { + if (hasInternetConnection && uiState != ProgramUIState.Error) { val webView = CatalogWebViewScreen( url = contentUrl, uriScheme = uriScheme, isAllLinksExternal = true, - onWebPageLoaded = onWebPageLoaded, + onWebPageLoaded = { onProgramAction(ProgramUIAction.WEB_PAGE_LOADED) }, refreshSessionCookie = { coroutineScope.launch { cookieManager.tryToRefreshSessionCookie() } }, onUriClick = onUriClick, + onWebPageLoadError = { onProgramAction(ProgramUIAction.WEB_PAGE_ERROR) } ) AndroidView( @@ -329,15 +345,32 @@ private fun ProgramInfoScreen( } ) } else { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background) - ) { - checkInternetConnection() + onProgramAction(ProgramUIAction.WEB_PAGE_LOADED) + } + + if (uiState == ProgramUIState.Error) { + val onReloadClick = { + onProgramAction(ProgramUIAction.ON_RELOAD) + } + if (!hasInternetConnection) { + ConnectionErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) + } else { + SomethingWentWrongErrorView( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(MaterialTheme.appColors.background), + onReloadClick = onReloadClick + ) } } + if (isLoading && hasInternetConnection) { Box( modifier = Modifier @@ -368,9 +401,8 @@ fun MyProgramsPreview() { canShowBackBtn = false, isNestedFragment = false, hasInternetConnection = false, - checkInternetConnection = {}, + onProgramAction = {}, onBackClick = {}, - onWebPageLoaded = {}, onSettingsClick = {}, onUriClick = { _, _ -> }, ) diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt index fa7f395d7..c368f7ca9 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt @@ -5,8 +5,16 @@ import org.openedx.core.UIMessage sealed class ProgramUIState { data object Loading : ProgramUIState() data object Loaded : ProgramUIState() + data object Error : ProgramUIState() class CourseEnrolled(val courseId: String, val isEnrolled: Boolean) : ProgramUIState() class UiMessage(val uiMessage: UIMessage) : ProgramUIState() } + +enum class ProgramUIAction { + CHECK_INTERNET_CONNECTION, + WEB_PAGE_LOADED, + WEB_PAGE_ERROR, + ON_RELOAD +} diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt index 1bed6d2cd..45ea51c89 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt @@ -106,4 +106,10 @@ class ProgramViewModel( fun navigateToSettings(fragmentManager: FragmentManager) { router.navigateToSettings(fragmentManager) } + + fun onPageLoadError() { + viewModelScope.launch { + _uiState.emit(ProgramUIState.Error) + } + } } From ca665827c14d5818ed3d0d70db231a0eda690aa0 Mon Sep 17 00:00:00 2001 From: Farhan Arshad Date: Tue, 9 Jul 2024 21:51:57 +0500 Subject: [PATCH 2/5] fix: address PR comments by Moin --- .../core/presentation/global/ErrorType.kt | 23 ++++ .../global/webview/WebViewState.kt | 9 ++ .../java/org/openedx/core/ui/ComposeCommon.kt | 44 +------- ...e_unkown.xml => core_ic_unknown_error.xml} | 0 .../unit/html/HtmlUnitFragment.kt | 101 +++++++---------- .../presentation/unit/html/HtmlUnitUIState.kt | 9 ++ .../unit/html/HtmlUnitViewModel.kt | 17 +++ .../unit/video/VideoUnitFragment.kt | 11 +- .../unit/video/YoutubeVideoUnitFragment.kt | 11 +- .../presentation/DiscoveryUIState.kt | 10 ++ .../presentation/NativeDiscoveryFragment.kt | 2 + .../presentation/WebViewDiscoveryFragment.kt | 102 +++++++++-------- .../presentation/WebViewDiscoveryViewModel.kt | 18 +++ .../presentation/catalog/CatalogWebView.kt | 10 +- .../presentation/info/CourseInfoFragment.kt | 104 +++++++++--------- .../presentation/info/CourseInfoUIState.kt | 18 ++- .../presentation/info/CourseInfoViewModel.kt | 22 +++- .../presentation/program/ProgramFragment.kt | 49 +++------ .../presentation/program/ProgramUIState.kt | 6 +- .../presentation/program/ProgramViewModel.kt | 18 ++- 20 files changed, 313 insertions(+), 271 deletions(-) create mode 100644 core/src/main/java/org/openedx/core/presentation/global/ErrorType.kt create mode 100644 core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt rename core/src/main/res/drawable/{core_unkown.xml => core_ic_unknown_error.xml} (100%) create mode 100644 course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt diff --git a/core/src/main/java/org/openedx/core/presentation/global/ErrorType.kt b/core/src/main/java/org/openedx/core/presentation/global/ErrorType.kt new file mode 100644 index 000000000..481758ecb --- /dev/null +++ b/core/src/main/java/org/openedx/core/presentation/global/ErrorType.kt @@ -0,0 +1,23 @@ +package org.openedx.core.presentation.global + +import org.openedx.core.R + +enum class ErrorType( + val iconResId: Int = 0, + val titleResId: Int = 0, + val descriptionResId: Int = 0, + val actionResId: Int = 0, +) { + CONNECTION_ERROR( + iconResId = R.drawable.core_no_internet_connection, + titleResId = R.string.core_no_internet_connection, + descriptionResId = R.string.core_no_internet_connection_description, + actionResId = R.string.core_reload, + ), + UNKNOWN_ERROR( + iconResId = R.drawable.core_ic_unknown_error, + titleResId = R.string.core_try_again, + descriptionResId = R.string.core_something_went_wrong_description, + actionResId = R.string.core_reload, + ), +} diff --git a/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt b/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt new file mode 100644 index 000000000..32b771a66 --- /dev/null +++ b/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt @@ -0,0 +1,9 @@ +package org.openedx.core.presentation.global.webview + +import org.openedx.core.presentation.global.ErrorType + +sealed class WebViewState { + data object Loading : WebViewState() + data object Loaded : WebViewState() + data class Error(val errorType: ErrorType) : WebViewState() +} diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index ae8a65e9d..267dfb474 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -1136,12 +1136,8 @@ fun BackBtn( } @Composable -fun ConnectionErrorView( - modifier: Modifier, - onReloadClick: () -> Unit, -) { +fun ConnectionErrorView(onReloadClick: () -> Unit) { ErrorScreen( - modifier = modifier, title = stringResource(id = R.string.core_no_internet_connection), description = stringResource(id = R.string.core_no_internet_connection_description), buttonText = stringResource(id = R.string.core_reload), @@ -1150,24 +1146,8 @@ fun ConnectionErrorView( ) } -@Composable -fun SomethingWentWrongErrorView( - modifier: Modifier, - onReloadClick: () -> Unit, -) { - ErrorScreen( - modifier = modifier, - title = stringResource(id = R.string.core_try_again), - description = stringResource(id = R.string.core_something_went_wrong_description), - buttonText = stringResource(id = R.string.core_reload), - icon = painterResource(id = R.drawable.core_unkown), - onReloadClick = onReloadClick - ) -} - @Composable fun ErrorScreen( - modifier: Modifier, title: String, description: String, buttonText: String, @@ -1175,7 +1155,9 @@ fun ErrorScreen( onReloadClick: () -> Unit ) { Column( - modifier = modifier, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.appColors.background), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { @@ -1398,23 +1380,7 @@ private fun IconTextPreview() { @Composable private fun ConnectionErrorViewPreview() { OpenEdXTheme(darkTheme = true) { - ConnectionErrorView( - modifier = Modifier - .fillMaxSize(), - onReloadClick = {} - ) - } -} - -@Preview -@Composable -private fun SomethingWentWrongErrorViewPreview() { - OpenEdXTheme(darkTheme = true) { - SomethingWentWrongErrorView( - modifier = Modifier - .fillMaxSize(), - onReloadClick = {} - ) + ConnectionErrorView(onReloadClick = {}) } } diff --git a/core/src/main/res/drawable/core_unkown.xml b/core/src/main/res/drawable/core_ic_unknown_error.xml similarity index 100% rename from core/src/main/res/drawable/core_unkown.xml rename to core/src/main/res/drawable/core_ic_unknown_error.xml diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt index 172972ebc..62318a4ab 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt @@ -18,7 +18,6 @@ import android.webkit.WebViewClient import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -42,6 +41,8 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -54,8 +55,7 @@ import org.openedx.core.extension.applyDarkModeIfEnabled import org.openedx.core.extension.isEmailValid import org.openedx.core.extension.loadUrl import org.openedx.core.system.AppCookieManager -import org.openedx.core.ui.ConnectionErrorView -import org.openedx.core.ui.SomethingWentWrongErrorView +import org.openedx.core.ui.ErrorScreen import org.openedx.core.ui.WindowSize import org.openedx.core.ui.rememberWindowSize import org.openedx.core.ui.roundBorderWithoutBottom @@ -85,14 +85,7 @@ class HtmlUnitFragment : Fragment() { setContent { OpenEdXTheme { val windowSize = rememberWindowSize() - - var isLoading by remember { - mutableStateOf(true) - } - - var isError by remember { - mutableStateOf(false) - } + val uiState by viewModel.uiState.collectAsState() var hasInternetConnection by remember { mutableStateOf(viewModel.isOnline) @@ -131,58 +124,46 @@ class HtmlUnitFragment : Fragment() { .then(border), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection && !isError) { - HTMLContentView( - windowSize = windowSize, - url = blockUrl, - cookieManager = viewModel.cookieManager, - apiHostURL = viewModel.apiHostURL, - isLoading = isLoading, - injectJSList = injectJSList, - onCompletionSet = { - viewModel.notifyCompletionSet() - }, - onWebPageLoading = { - isLoading = true - isError = false - }, - onWebPageLoaded = { - isLoading = false - isError = false - if (isAdded) viewModel.setWebPageLoaded(requireContext().assets) - }, - onWebPageLoadError = { - isLoading = false - isError = true - } - ) + if (hasInternetConnection) { + if ((uiState is HtmlUnitUIState.Error).not()) { + HTMLContentView( + windowSize = windowSize, + url = blockUrl, + cookieManager = viewModel.cookieManager, + apiHostURL = viewModel.apiHostURL, + isLoading = uiState is HtmlUnitUIState.Loading, + injectJSList = injectJSList, + onCompletionSet = { + viewModel.notifyCompletionSet() + }, + onWebPageLoading = { + viewModel.onWebPageLoading() + }, + onWebPageLoaded = { + viewModel.onWebPageLoaded() + if (isAdded) viewModel.setWebPageLoaded(requireContext().assets) + }, + onWebPageLoadError = { + viewModel.onWebPageLoadError() + } + ) + } } else { - isError = true + viewModel.onWebPageLoadError() } - if (isError) { - val onReloadClick = { - isError = false + if (uiState is HtmlUnitUIState.Error) { + val errorType = (uiState as HtmlUnitUIState.Error).errorType + ErrorScreen( + title = stringResource(id = errorType.titleResId), + description = stringResource(id = errorType.descriptionResId), + buttonText = stringResource(id = errorType.actionResId), + icon = painterResource(id = errorType.iconResId) + ) { hasInternetConnection = viewModel.isOnline - } - if (!hasInternetConnection) { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) - } else { - SomethingWentWrongErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) + viewModel.onWebPageLoading() } } - if (isLoading && hasInternetConnection) { + if (uiState is HtmlUnitUIState.Loading && hasInternetConnection) { Box( modifier = Modifier .fillMaxSize() @@ -317,7 +298,9 @@ private fun HTMLContentView( request: WebResourceRequest?, error: WebResourceError? ) { - onWebPageLoadError() + if (view?.url?.equals(request?.url) == true) { + onWebPageLoadError() + } super.onReceivedError(view, request, error) } } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt new file mode 100644 index 000000000..4050722ef --- /dev/null +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitUIState.kt @@ -0,0 +1,9 @@ +package org.openedx.course.presentation.unit.html + +import org.openedx.core.presentation.global.ErrorType + +sealed class HtmlUnitUIState { + data object Loading : HtmlUnitUIState() + data object Loaded : HtmlUnitUIState() + data class Error(val errorType: ErrorType) : HtmlUnitUIState() +} diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt index 9d52c979b..07ffe18c0 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.launch import org.openedx.core.BaseViewModel import org.openedx.core.config.Config import org.openedx.core.extension.readAsText +import org.openedx.core.presentation.global.ErrorType import org.openedx.core.system.AppCookieManager import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.system.notifier.CourseCompletionSet @@ -20,6 +21,9 @@ class HtmlUnitViewModel( private val notifier: CourseNotifier ) : BaseViewModel() { + private val _uiState = MutableStateFlow(HtmlUnitUIState.Loading) + val uiState = _uiState.asStateFlow() + private val _injectJSList = MutableStateFlow>(listOf()) val injectJSList = _injectJSList.asStateFlow() @@ -28,6 +32,19 @@ class HtmlUnitViewModel( val apiHostURL get() = config.getApiHostURL() val cookieManager get() = edxCookieManager + fun onWebPageLoading() { + _uiState.value = HtmlUnitUIState.Loading + } + + fun onWebPageLoaded() { + _uiState.value = HtmlUnitUIState.Loaded + } + + fun onWebPageLoadError() { + _uiState.value = + HtmlUnitUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) + } + fun setWebPageLoaded(assets: AssetManager) { if (_injectJSList.value.isNotEmpty()) return diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt index d92b3b067..694151d30 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt @@ -6,14 +6,10 @@ import android.os.Handler import android.os.Looper import android.view.View import android.widget.FrameLayout -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Modifier import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -37,7 +33,6 @@ import org.openedx.core.presentation.global.viewBinding import org.openedx.core.ui.ConnectionErrorView import org.openedx.core.ui.WindowSize import org.openedx.core.ui.theme.OpenEdXTheme -import org.openedx.core.ui.theme.appColors import org.openedx.core.utils.LocaleUtils import org.openedx.course.R import org.openedx.course.databinding.FragmentVideoUnitBinding @@ -104,11 +99,7 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { binding.connectionError.setContent { OpenEdXTheme { - ConnectionErrorView( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.appColors.background) - ) { + ConnectionErrorView { binding.connectionError.isVisible = !viewModel.hasInternetConnection && !viewModel.isDownloaded } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt index 8ee99b970..6e0fabfdd 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt @@ -4,14 +4,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Modifier import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -32,7 +28,6 @@ import org.openedx.core.presentation.dialog.selectorbottomsheet.SelectBottomDial import org.openedx.core.ui.ConnectionErrorView import org.openedx.core.ui.WindowSize import org.openedx.core.ui.theme.OpenEdXTheme -import org.openedx.core.ui.theme.appColors import org.openedx.core.utils.LocaleUtils import org.openedx.course.R import org.openedx.course.databinding.FragmentYoutubeVideoUnitBinding @@ -95,11 +90,7 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) binding.connectionError.setContent { OpenEdXTheme { - ConnectionErrorView( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.appColors.background) - ) { + ConnectionErrorView { binding.connectionError.isVisible = !viewModel.hasInternetConnection } } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt index 7001c26c6..e4ec24cdd 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt @@ -1,8 +1,18 @@ package org.openedx.discovery.presentation +import org.openedx.core.presentation.global.ErrorType import org.openedx.discovery.domain.model.Course sealed class DiscoveryUIState { data class Courses(val courses: List) : DiscoveryUIState() data object Loading : DiscoveryUIState() + data object Loaded : DiscoveryUIState() + data class Error(val errorType: ErrorType) : DiscoveryUIState() } + +enum class DiscoveryUIAction { + WEB_PAGE_LOADED, + WEB_PAGE_ERROR, + RELOAD_WEB_PAGE +} + diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt index 3a50ab707..bd3029aca 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt @@ -417,6 +417,8 @@ internal fun DiscoveryScreen( } } } + + else -> {} } PullRefreshIndicator( refreshing, diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt index 814f482a6..c10d62d9a 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt @@ -24,6 +24,7 @@ import androidx.compose.material.Surface import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -36,6 +37,7 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -52,9 +54,9 @@ import androidx.lifecycle.LifecycleOwner import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.presentation.dialog.alert.ActionDialogFragment +import org.openedx.core.presentation.global.ErrorType import org.openedx.core.ui.AuthButtonsPanel -import org.openedx.core.ui.ConnectionErrorView -import org.openedx.core.ui.SomethingWentWrongErrorView +import org.openedx.core.ui.ErrorScreen import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -84,17 +86,32 @@ class WebViewDiscoveryFragment : Fragment() { setContent { OpenEdXTheme { val windowSize = rememberWindowSize() + val uiState by viewModel.uiState.collectAsState() var hasInternetConnection by remember { mutableStateOf(viewModel.hasInternetConnection) } WebViewDiscoveryScreen( windowSize = windowSize, + uiState = uiState, isPreLogin = viewModel.isPreLogin, contentUrl = viewModel.discoveryUrl, uriScheme = viewModel.uriScheme, hasInternetConnection = hasInternetConnection, - checkInternetConnection = { - hasInternetConnection = viewModel.hasInternetConnection + onDiscoveryUIAction = { action -> + when (action) { + DiscoveryUIAction.WEB_PAGE_LOADED -> { + viewModel.onWebPageLoaded() + } + + DiscoveryUIAction.WEB_PAGE_ERROR -> { + viewModel.onWebPageLoadError() + } + + DiscoveryUIAction.RELOAD_WEB_PAGE -> { + hasInternetConnection = viewModel.hasInternetConnection + viewModel.onWebPageLoading() + } + } }, onWebPageUpdated = { url -> viewModel.updateDiscoveryUrl(url) @@ -171,11 +188,12 @@ class WebViewDiscoveryFragment : Fragment() { @SuppressLint("SetJavaScriptEnabled") private fun WebViewDiscoveryScreen( windowSize: WindowSize, + uiState: DiscoveryUIState, isPreLogin: Boolean, contentUrl: String, uriScheme: String, hasInternetConnection: Boolean, - checkInternetConnection: () -> Unit, + onDiscoveryUIAction: (DiscoveryUIAction) -> Unit, onWebPageUpdated: (String) -> Unit, onUriClick: (String, WebViewLink.Authority) -> Unit, onRegisterClick: () -> Unit, @@ -185,8 +203,6 @@ private fun WebViewDiscoveryScreen( ) { val scaffoldState = rememberScaffoldState() val configuration = LocalConfiguration.current - var isLoading by remember { mutableStateOf(true) } - var isError by remember { mutableStateOf(false) } Scaffold( scaffoldState = scaffoldState, @@ -250,48 +266,37 @@ private fun WebViewDiscoveryScreen( .background(Color.White), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection && !isError) { - DiscoveryWebView( - contentUrl = contentUrl, - uriScheme = uriScheme, - onWebPageLoaded = { - isLoading = false - isError = false - }, - onWebPageUpdated = onWebPageUpdated, - onUriClick = onUriClick, - onWebPageLoadError = { - isLoading = false - isError = true - } - ) + if (hasInternetConnection) { + if ((uiState is DiscoveryUIState.Error).not()) { + DiscoveryWebView( + contentUrl = contentUrl, + uriScheme = uriScheme, + onWebPageLoaded = { + if ((uiState is DiscoveryUIState.Error).not()) { + onDiscoveryUIAction(DiscoveryUIAction.WEB_PAGE_LOADED) + } + }, + onWebPageUpdated = onWebPageUpdated, + onUriClick = onUriClick, + onWebPageLoadError = { + onDiscoveryUIAction(DiscoveryUIAction.WEB_PAGE_ERROR) + } + ) + } } else { - isError = true + onDiscoveryUIAction(DiscoveryUIAction.WEB_PAGE_ERROR) } - if (isError) { - val onReloadClick = { - isError = false - checkInternetConnection() - } - if (!hasInternetConnection) { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) - } else { - SomethingWentWrongErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) + if (uiState is DiscoveryUIState.Error) { + ErrorScreen( + title = stringResource(id = uiState.errorType.titleResId), + description = stringResource(id = uiState.errorType.descriptionResId), + buttonText = stringResource(id = uiState.errorType.actionResId), + icon = painterResource(id = uiState.errorType.iconResId) + ) { + onDiscoveryUIAction(DiscoveryUIAction.RELOAD_WEB_PAGE) } } - if (isLoading && hasInternetConnection) { + if (uiState is DiscoveryUIState.Loading && hasInternetConnection) { Box( modifier = Modifier .fillMaxSize() @@ -387,17 +392,18 @@ private fun WebViewDiscoveryScreenPreview() { OpenEdXTheme { WebViewDiscoveryScreen( windowSize = WindowSize(WindowType.Compact, WindowType.Compact), - contentUrl = "https://www.example.com/", + uiState = DiscoveryUIState.Error(ErrorType.CONNECTION_ERROR), isPreLogin = false, + contentUrl = "https://www.example.com/", uriScheme = "", hasInternetConnection = false, - checkInternetConnection = {}, + onDiscoveryUIAction = {}, onWebPageUpdated = {}, onUriClick = { _, _ -> }, onRegisterClick = {}, onSignInClick = {}, onSettingsClick = {}, - onBackClick = {} + onBackClick = {}, ) } } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt index e786a3970..b1a38688b 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt @@ -1,9 +1,13 @@ package org.openedx.discovery.presentation import androidx.fragment.app.FragmentManager +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import org.openedx.core.BaseViewModel import org.openedx.core.config.Config import org.openedx.core.data.storage.CorePreferences +import org.openedx.core.presentation.global.ErrorType import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.utils.UrlUtils @@ -16,6 +20,8 @@ class WebViewDiscoveryViewModel( private val analytics: DiscoveryAnalytics, ) : BaseViewModel() { + private val _uiState = MutableStateFlow(DiscoveryUIState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() val uriScheme: String get() = config.getUriScheme() private val webViewConfig get() = config.getDiscoveryConfig().webViewConfig @@ -37,6 +43,18 @@ class WebViewDiscoveryViewModel( val hasInternetConnection: Boolean get() = networkConnection.isOnline() + fun onWebPageLoading() { + _uiState.value = DiscoveryUIState.Loaded + } + fun onWebPageLoaded() { + _uiState.value = DiscoveryUIState.Loaded + } + + fun onWebPageLoadError() { + _uiState.value = + DiscoveryUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) + } + fun updateDiscoveryUrl(url: String) { if (url.isNotEmpty()) { _discoveryUrl = url diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt index 304b8360f..3c31ed4f1 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt @@ -85,12 +85,14 @@ fun CatalogWebViewScreen( } override fun onReceivedError( - view: WebView?, - request: WebResourceRequest?, - error: WebResourceError? + view: WebView, + request: WebResourceRequest, + error: WebResourceError ) { - onWebPageLoadError() super.onReceivedError(view, request, error) + if (view.url?.equals(request.url) == true) { + onWebPageLoadError() + } } } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt index dcc512102..a52d0d92a 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -45,10 +46,10 @@ import org.koin.core.parameter.parametersOf import org.openedx.core.UIMessage import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.presentation.dialog.alert.InfoDialogFragment +import org.openedx.core.presentation.global.webview.WebViewState import org.openedx.core.ui.AuthButtonsPanel -import org.openedx.core.ui.ConnectionErrorView +import org.openedx.core.ui.ErrorScreen import org.openedx.core.ui.HandleUIMessage -import org.openedx.core.ui.SomethingWentWrongErrorView import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -86,6 +87,7 @@ class CourseInfoFragment : Fragment() { val uiMessage by viewModel.uiMessage.collectAsState(initial = null) val showAlert by viewModel.showAlert.collectAsState(initial = false) val uiState by viewModel.uiState.collectAsState() + val webViewState by viewModel.webViewState.collectAsState() val windowSize = rememberWindowSize() var hasInternetConnection by remember { mutableStateOf(viewModel.hasInternetConnection) @@ -106,25 +108,41 @@ class CourseInfoFragment : Fragment() { } } - LaunchedEffect(uiState.enrollmentSuccess.get()) { - if (uiState.enrollmentSuccess.get().isNotEmpty()) { + LaunchedEffect((uiState as CourseInfoUIState.CourseInfo).enrollmentSuccess.get()) { + if ((uiState as CourseInfoUIState.CourseInfo).enrollmentSuccess.get() + .isNotEmpty() + ) { viewModel.onSuccessfulCourseEnrollment( fragmentManager = requireActivity().supportFragmentManager, - courseId = uiState.enrollmentSuccess.get(), + courseId = (uiState as CourseInfoUIState.CourseInfo).enrollmentSuccess.get(), ) // Clear after navigation - uiState.enrollmentSuccess.set("") + (uiState as CourseInfoUIState.CourseInfo).enrollmentSuccess.set("") } } CourseInfoScreen( windowSize = windowSize, uiState = uiState, + webViewState = webViewState, uiMessage = uiMessage, uriScheme = viewModel.uriScheme, hasInternetConnection = hasInternetConnection, - checkInternetConnection = { - hasInternetConnection = viewModel.hasInternetConnection + onCourseInfoUIAction = { action -> + when (action) { + CourseInfoUIAction.WEB_PAGE_LOADED -> { + viewModel.onWebPageLoaded() + } + + CourseInfoUIAction.WEB_PAGE_ERROR -> { + viewModel.onWebPageError() + } + + CourseInfoUIAction.RELOAD_WEB_PAGE -> { + hasInternetConnection = viewModel.hasInternetConnection + viewModel.onWebPageLoading() + } + } }, onRegisterClick = { viewModel.navigateToSignUp( @@ -180,7 +198,7 @@ class CourseInfoFragment : Fragment() { linkAuthority.ENROLL -> { viewModel.courseEnrollClickedEvent(param) - if (uiState.isPreLogin) { + if ((uiState as CourseInfoUIState.CourseInfo).isPreLogin) { viewModel.navigateToSignUp( fragmentManager = requireActivity().supportFragmentManager, courseId = viewModel.pathId, @@ -221,10 +239,11 @@ class CourseInfoFragment : Fragment() { private fun CourseInfoScreen( windowSize: WindowSize, uiState: CourseInfoUIState, + webViewState: WebViewState, uiMessage: UIMessage?, uriScheme: String, hasInternetConnection: Boolean, - checkInternetConnection: () -> Unit, + onCourseInfoUIAction: (CourseInfoUIAction) -> Unit, onRegisterClick: () -> Unit, onSignInClick: () -> Unit, onBackClick: () -> Unit, @@ -232,8 +251,6 @@ private fun CourseInfoScreen( ) { val scaffoldState = rememberScaffoldState() val configuration = LocalConfiguration.current - var isLoading by remember { mutableStateOf(true) } - var isError by remember { mutableStateOf(false) } HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState) @@ -242,7 +259,7 @@ private fun CourseInfoScreen( modifier = Modifier.fillMaxSize(), backgroundColor = MaterialTheme.appColors.background, bottomBar = { - if (uiState.isPreLogin) { + if ((uiState as CourseInfoUIState.CourseInfo).isPreLogin) { Box( modifier = Modifier .padding( @@ -293,44 +310,32 @@ private fun CourseInfoScreen( .navigationBarsPadding(), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection && !isError) { - CourseInfoWebView( - contentUrl = uiState.initialUrl, - uriScheme = uriScheme, - onWebPageLoaded = { isLoading = false }, - onUriClick = onUriClick, - onWebPageLoadError = { - isLoading = false - isError = true - } - ) + if (hasInternetConnection) { + if ((webViewState is WebViewState.Error).not()) { + CourseInfoWebView( + contentUrl = (uiState as CourseInfoUIState.CourseInfo).initialUrl, + uriScheme = uriScheme, + onWebPageLoaded = { onCourseInfoUIAction(CourseInfoUIAction.WEB_PAGE_LOADED) }, + onUriClick = onUriClick, + onWebPageLoadError = { + onCourseInfoUIAction(CourseInfoUIAction.WEB_PAGE_ERROR) + } + ) + } } else { - isError = true + onCourseInfoUIAction(CourseInfoUIAction.WEB_PAGE_ERROR) } - if (isError) { - val onReloadClick = { - isError = false - checkInternetConnection() - } - if (!hasInternetConnection) { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) - } else { - SomethingWentWrongErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) + if (webViewState is WebViewState.Error) { + ErrorScreen( + title = stringResource(id = webViewState.errorType.titleResId), + description = stringResource(id = webViewState.errorType.descriptionResId), + buttonText = stringResource(id = webViewState.errorType.actionResId), + icon = painterResource(id = webViewState.errorType.iconResId) + ) { + onCourseInfoUIAction(CourseInfoUIAction.RELOAD_WEB_PAGE) } } - if (isLoading && hasInternetConnection) { + if (webViewState is WebViewState.Loading && hasInternetConnection) { Box( modifier = Modifier .fillMaxSize() @@ -384,7 +389,7 @@ fun CourseInfoScreenPreview() { OpenEdXTheme { CourseInfoScreen( windowSize = WindowSize(WindowType.Compact, WindowType.Compact), - uiState = CourseInfoUIState( + uiState = CourseInfoUIState.CourseInfo( initialUrl = "https://www.example.com/", isPreLogin = false, enrollmentSuccess = AtomicReference("") @@ -392,11 +397,12 @@ fun CourseInfoScreenPreview() { uiMessage = null, uriScheme = "", hasInternetConnection = false, - checkInternetConnection = {}, + onCourseInfoUIAction = {}, onRegisterClick = {}, onSignInClick = {}, onBackClick = {}, onUriClick = { _, _ -> }, + webViewState = WebViewState.Loading, ) } } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt index ffabf1daf..01b14960b 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt @@ -2,8 +2,16 @@ package org.openedx.discovery.presentation.info import java.util.concurrent.atomic.AtomicReference -internal data class CourseInfoUIState( - val initialUrl: String = "", - val isPreLogin: Boolean = false, - val enrollmentSuccess: AtomicReference = AtomicReference("") -) +sealed class CourseInfoUIState { + data class CourseInfo( + val initialUrl: String = "", + val isPreLogin: Boolean = false, + val enrollmentSuccess: AtomicReference = AtomicReference("") + ) : CourseInfoUIState() +} + +enum class CourseInfoUIAction { + WEB_PAGE_LOADED, + WEB_PAGE_ERROR, + RELOAD_WEB_PAGE +} diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt index 487027e8f..fff2ad40e 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -17,6 +18,8 @@ import org.openedx.core.config.Config import org.openedx.core.data.storage.CorePreferences import org.openedx.core.extension.isInternetError import org.openedx.core.presentation.CoreAnalyticsKey +import org.openedx.core.presentation.global.ErrorType +import org.openedx.core.presentation.global.webview.WebViewState import org.openedx.core.system.ResourceManager import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.system.notifier.CourseDashboardUpdate @@ -46,13 +49,17 @@ class CourseInfoViewModel( private val _uiState = MutableStateFlow( - CourseInfoUIState( + CourseInfoUIState.CourseInfo( initialUrl = getInitialUrl(), isPreLogin = config.isPreLoginExperienceEnabled() && corePreferences.user == null ) ) internal val uiState: StateFlow = _uiState + private val _webViewState = MutableStateFlow(WebViewState.Loading) + val webViewState + get() = _webViewState.asStateFlow() + private val _uiMessage = MutableSharedFlow() val uiMessage: SharedFlow get() = _uiMessage.asSharedFlow() @@ -187,6 +194,19 @@ class CourseInfoViewModel( } } + fun onWebPageLoaded() { + _webViewState.value = WebViewState.Loaded + } + + fun onWebPageError() { + _webViewState.value = + WebViewState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) + } + + fun onWebPageLoading() { + _webViewState.value = WebViewState.Loading + } + companion object { private const val ARG_PATH_ID = "path_id" } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt index 10cddcedf..82d019a07 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -51,9 +52,8 @@ import org.openedx.core.extension.toastMessage import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.presentation.dialog.alert.InfoDialogFragment import org.openedx.core.system.AppCookieManager -import org.openedx.core.ui.ConnectionErrorView +import org.openedx.core.ui.ErrorScreen import org.openedx.core.ui.HandleUIMessage -import org.openedx.core.ui.SomethingWentWrongErrorView import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -136,10 +136,6 @@ class ProgramFragment : Fragment() { hasInternetConnection = hasInternetConnection, onProgramAction = { action -> when (action) { - ProgramUIAction.CHECK_INTERNET_CONNECTION -> { - hasInternetConnection = viewModel.hasInternetConnection - } - ProgramUIAction.WEB_PAGE_LOADED -> { viewModel.showLoading(false) } @@ -148,7 +144,8 @@ class ProgramFragment : Fragment() { viewModel.onPageLoadError() } - ProgramUIAction.ON_RELOAD -> { + ProgramUIAction.RELOAD_WEB_PAGE -> { + hasInternetConnection = viewModel.hasInternetConnection viewModel.showLoading(true) } } @@ -258,7 +255,6 @@ private fun ProgramInfoScreen( val scaffoldState = rememberScaffoldState() val configuration = LocalConfiguration.current val coroutineScope = rememberCoroutineScope() - val isLoading = uiState is ProgramUIState.Loading when (uiState) { is ProgramUIState.UiMessage -> { @@ -319,7 +315,7 @@ private fun ProgramInfoScreen( .background(Color.White), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection && uiState != ProgramUIState.Error) { + if (hasInternetConnection && (uiState is ProgramUIState.Error).not()) { val webView = CatalogWebViewScreen( url = contentUrl, uriScheme = uriScheme, @@ -345,33 +341,22 @@ private fun ProgramInfoScreen( } ) } else { - onProgramAction(ProgramUIAction.WEB_PAGE_LOADED) + onProgramAction(ProgramUIAction.WEB_PAGE_ERROR) } - if (uiState == ProgramUIState.Error) { - val onReloadClick = { - onProgramAction(ProgramUIAction.ON_RELOAD) - } - if (!hasInternetConnection) { - ConnectionErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) - } else { - SomethingWentWrongErrorView( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(MaterialTheme.appColors.background), - onReloadClick = onReloadClick - ) - } + if (uiState is ProgramUIState.Error) { + ErrorScreen( + title = stringResource(id = uiState.errorType.titleResId), + description = stringResource(id = uiState.errorType.descriptionResId), + buttonText = stringResource(id = uiState.errorType.actionResId), + icon = painterResource(id = uiState.errorType.iconResId), + onReloadClick = { + onProgramAction(ProgramUIAction.RELOAD_WEB_PAGE) + } + ) } - if (isLoading && hasInternetConnection) { + if (uiState == ProgramUIState.Loading && hasInternetConnection) { Box( modifier = Modifier .fillMaxSize() diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt index c368f7ca9..ddc97d4aa 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt @@ -1,11 +1,12 @@ package org.openedx.discovery.presentation.program import org.openedx.core.UIMessage +import org.openedx.core.presentation.global.ErrorType sealed class ProgramUIState { data object Loading : ProgramUIState() data object Loaded : ProgramUIState() - data object Error : ProgramUIState() + data class Error(val errorType: ErrorType) : ProgramUIState() class CourseEnrolled(val courseId: String, val isEnrolled: Boolean) : ProgramUIState() @@ -13,8 +14,7 @@ sealed class ProgramUIState { } enum class ProgramUIAction { - CHECK_INTERNET_CONNECTION, WEB_PAGE_LOADED, WEB_PAGE_ERROR, - ON_RELOAD + RELOAD_WEB_PAGE } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt index 45ea51c89..cfc0ff285 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt @@ -2,16 +2,16 @@ package org.openedx.discovery.presentation.program import androidx.fragment.app.FragmentManager import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.openedx.core.BaseViewModel import org.openedx.core.R import org.openedx.core.UIMessage import org.openedx.core.config.Config import org.openedx.core.extension.isInternetError +import org.openedx.core.presentation.global.ErrorType import org.openedx.core.system.AppCookieManager import org.openedx.core.system.ResourceManager import org.openedx.core.system.connection.NetworkConnection @@ -38,12 +38,8 @@ class ProgramViewModel( val hasInternetConnection: Boolean get() = networkConnection.isOnline() - private val _uiState = MutableSharedFlow( - replay = 0, - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - val uiState: SharedFlow get() = _uiState.asSharedFlow() + private val _uiState = MutableStateFlow(ProgramUIState.Loading) + val uiState: StateFlow get() = _uiState.asStateFlow() fun showLoading(isLoading: Boolean) { viewModelScope.launch { @@ -109,7 +105,7 @@ class ProgramViewModel( fun onPageLoadError() { viewModelScope.launch { - _uiState.emit(ProgramUIState.Error) + _uiState.emit(ProgramUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR)) } } } From 1f106d5063aaa006b9cc8dc78e3d426f67307503 Mon Sep 17 00:00:00 2001 From: Farhan Arshad Date: Mon, 15 Jul 2024 20:53:58 +0500 Subject: [PATCH 3/5] fix: address PR comments by Moin-2 --- .../org/openedx/core/extension/StringExt.kt | 9 ++ .../global/webview/WebViewState.kt | 9 -- .../global/webview/WebViewUIState.kt | 15 ++++ .../java/org/openedx/core/ui/ComposeCommon.kt | 24 ++---- .../unit/html/HtmlUnitFragment.kt | 32 ++++---- .../presentation/DiscoveryUIState.kt | 9 -- .../presentation/WebViewDiscoveryFragment.kt | 48 +++++------ .../presentation/WebViewDiscoveryViewModel.kt | 12 +-- .../presentation/catalog/CatalogWebView.kt | 5 +- .../presentation/info/CourseInfoFragment.kt | 49 +++++------ .../presentation/info/CourseInfoUIState.kt | 6 -- .../presentation/info/CourseInfoViewModel.kt | 14 ++-- .../presentation/program/ProgramFragment.kt | 82 +++++++++---------- .../presentation/program/ProgramUIState.kt | 6 -- .../presentation/program/ProgramViewModel.kt | 3 + 15 files changed, 149 insertions(+), 174 deletions(-) delete mode 100644 core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt create mode 100644 core/src/main/java/org/openedx/core/presentation/global/webview/WebViewUIState.kt diff --git a/core/src/main/java/org/openedx/core/extension/StringExt.kt b/core/src/main/java/org/openedx/core/extension/StringExt.kt index 343398782..45032c809 100644 --- a/core/src/main/java/org/openedx/core/extension/StringExt.kt +++ b/core/src/main/java/org/openedx/core/extension/StringExt.kt @@ -1,6 +1,7 @@ package org.openedx.core.extension import android.util.Patterns +import java.net.URL import java.util.Locale import java.util.regex.Pattern @@ -37,3 +38,11 @@ fun String.tagId(): String = this.replaceSpace("_").lowercase(Locale.getDefault( fun String.takeIfNotEmpty(): String? { return if (this.isEmpty().not()) this else null } + +fun String?.equalsHost(host: String?): Boolean { + return try { + host?.startsWith(URL(this).host, ignoreCase = true) == true + } catch (e: Exception) { + false + } +} diff --git a/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt b/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt deleted file mode 100644 index 32b771a66..000000000 --- a/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.openedx.core.presentation.global.webview - -import org.openedx.core.presentation.global.ErrorType - -sealed class WebViewState { - data object Loading : WebViewState() - data object Loaded : WebViewState() - data class Error(val errorType: ErrorType) : WebViewState() -} diff --git a/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewUIState.kt b/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewUIState.kt new file mode 100644 index 000000000..3a99afaaf --- /dev/null +++ b/core/src/main/java/org/openedx/core/presentation/global/webview/WebViewUIState.kt @@ -0,0 +1,15 @@ +package org.openedx.core.presentation.global.webview + +import org.openedx.core.presentation.global.ErrorType + +sealed class WebViewUIState { + data object Loading : WebViewUIState() + data object Loaded : WebViewUIState() + data class Error(val errorType: ErrorType) : WebViewUIState() +} + +enum class WebViewUIAction { + WEB_PAGE_LOADED, + WEB_PAGE_ERROR, + RELOAD_WEB_PAGE +} diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index 267dfb474..1a11ac2a1 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -109,6 +109,7 @@ import org.openedx.core.domain.model.RegistrationField import org.openedx.core.extension.LinkedImageText import org.openedx.core.extension.tagId import org.openedx.core.extension.toastMessage +import org.openedx.core.presentation.global.ErrorType import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appShapes @@ -1137,21 +1138,12 @@ fun BackBtn( @Composable fun ConnectionErrorView(onReloadClick: () -> Unit) { - ErrorScreen( - title = stringResource(id = R.string.core_no_internet_connection), - description = stringResource(id = R.string.core_no_internet_connection_description), - buttonText = stringResource(id = R.string.core_reload), - icon = painterResource(id = R.drawable.core_no_internet_connection), - onReloadClick = onReloadClick - ) + FullScreenErrorView(errorType = ErrorType.CONNECTION_ERROR, onReloadClick = onReloadClick) } @Composable -fun ErrorScreen( - title: String, - description: String, - buttonText: String, - icon: Painter, +fun FullScreenErrorView( + errorType: ErrorType, onReloadClick: () -> Unit ) { Column( @@ -1163,14 +1155,14 @@ fun ErrorScreen( ) { Icon( modifier = Modifier.size(100.dp), - painter = icon, + painter = painterResource(id = errorType.iconResId), contentDescription = null, tint = MaterialTheme.appColors.onSurface ) Spacer(Modifier.height(28.dp)) Text( modifier = Modifier.fillMaxWidth(0.8f), - text = title, + text = stringResource(id = errorType.titleResId), color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.titleLarge, textAlign = TextAlign.Center @@ -1178,7 +1170,7 @@ fun ErrorScreen( Spacer(Modifier.height(16.dp)) Text( modifier = Modifier.fillMaxWidth(0.8f), - text = description, + text = stringResource(id = errorType.descriptionResId), color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.bodyLarge, textAlign = TextAlign.Center @@ -1187,7 +1179,7 @@ fun ErrorScreen( OpenEdXButton( modifier = Modifier .widthIn(Dp.Unspecified, 162.dp), - text = buttonText, + text = stringResource(id = errorType.actionResId), textColor = MaterialTheme.appColors.primaryButtonText, backgroundColor = MaterialTheme.appColors.secondaryButtonBackground, onClick = onReloadClick, diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt index 62318a4ab..57ea836e4 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt @@ -41,8 +41,6 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -52,10 +50,11 @@ import androidx.fragment.app.Fragment import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.viewModel import org.openedx.core.extension.applyDarkModeIfEnabled +import org.openedx.core.extension.equalsHost import org.openedx.core.extension.isEmailValid import org.openedx.core.extension.loadUrl import org.openedx.core.system.AppCookieManager -import org.openedx.core.ui.ErrorScreen +import org.openedx.core.ui.FullScreenErrorView import org.openedx.core.ui.WindowSize import org.openedx.core.ui.rememberWindowSize import org.openedx.core.ui.roundBorderWithoutBottom @@ -124,8 +123,8 @@ class HtmlUnitFragment : Fragment() { .then(border), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { - if ((uiState is HtmlUnitUIState.Error).not()) { + if ((uiState is HtmlUnitUIState.Error).not()) { + if (hasInternetConnection) { HTMLContentView( windowSize = windowSize, url = blockUrl, @@ -140,25 +139,22 @@ class HtmlUnitFragment : Fragment() { viewModel.onWebPageLoading() }, onWebPageLoaded = { - viewModel.onWebPageLoaded() + if ((uiState is HtmlUnitUIState.Error).not()) { + viewModel.onWebPageLoaded() + } if (isAdded) viewModel.setWebPageLoaded(requireContext().assets) }, onWebPageLoadError = { viewModel.onWebPageLoadError() } ) + } else { + viewModel.onWebPageLoadError() } - } else { - viewModel.onWebPageLoadError() } if (uiState is HtmlUnitUIState.Error) { val errorType = (uiState as HtmlUnitUIState.Error).errorType - ErrorScreen( - title = stringResource(id = errorType.titleResId), - description = stringResource(id = errorType.descriptionResId), - buttonText = stringResource(id = errorType.actionResId), - icon = painterResource(id = errorType.iconResId) - ) { + FullScreenErrorView(errorType = errorType) { hasInternetConnection = viewModel.isOnline viewModel.onWebPageLoading() } @@ -294,11 +290,11 @@ private fun HTMLContentView( } override fun onReceivedError( - view: WebView?, - request: WebResourceRequest?, - error: WebResourceError? + view: WebView, + request: WebResourceRequest, + error: WebResourceError ) { - if (view?.url?.equals(request?.url) == true) { + if (view.url.equalsHost(request.url.host)) { onWebPageLoadError() } super.onReceivedError(view, request, error) diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt index e4ec24cdd..ae91f8365 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt @@ -6,13 +6,4 @@ import org.openedx.discovery.domain.model.Course sealed class DiscoveryUIState { data class Courses(val courses: List) : DiscoveryUIState() data object Loading : DiscoveryUIState() - data object Loaded : DiscoveryUIState() - data class Error(val errorType: ErrorType) : DiscoveryUIState() } - -enum class DiscoveryUIAction { - WEB_PAGE_LOADED, - WEB_PAGE_ERROR, - RELOAD_WEB_PAGE -} - diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt index c10d62d9a..fa6d992d3 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -55,8 +54,10 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.presentation.global.ErrorType +import org.openedx.core.presentation.global.webview.WebViewUIAction +import org.openedx.core.presentation.global.webview.WebViewUIState import org.openedx.core.ui.AuthButtonsPanel -import org.openedx.core.ui.ErrorScreen +import org.openedx.core.ui.FullScreenErrorView import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType @@ -97,17 +98,17 @@ class WebViewDiscoveryFragment : Fragment() { contentUrl = viewModel.discoveryUrl, uriScheme = viewModel.uriScheme, hasInternetConnection = hasInternetConnection, - onDiscoveryUIAction = { action -> + onWebViewUIAction = { action -> when (action) { - DiscoveryUIAction.WEB_PAGE_LOADED -> { + WebViewUIAction.WEB_PAGE_LOADED -> { viewModel.onWebPageLoaded() } - DiscoveryUIAction.WEB_PAGE_ERROR -> { + WebViewUIAction.WEB_PAGE_ERROR -> { viewModel.onWebPageLoadError() } - DiscoveryUIAction.RELOAD_WEB_PAGE -> { + WebViewUIAction.RELOAD_WEB_PAGE -> { hasInternetConnection = viewModel.hasInternetConnection viewModel.onWebPageLoading() } @@ -188,12 +189,12 @@ class WebViewDiscoveryFragment : Fragment() { @SuppressLint("SetJavaScriptEnabled") private fun WebViewDiscoveryScreen( windowSize: WindowSize, - uiState: DiscoveryUIState, + uiState: WebViewUIState, isPreLogin: Boolean, contentUrl: String, uriScheme: String, hasInternetConnection: Boolean, - onDiscoveryUIAction: (DiscoveryUIAction) -> Unit, + onWebViewUIAction: (WebViewUIAction) -> Unit, onWebPageUpdated: (String) -> Unit, onUriClick: (String, WebViewLink.Authority) -> Unit, onRegisterClick: () -> Unit, @@ -266,37 +267,32 @@ private fun WebViewDiscoveryScreen( .background(Color.White), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { - if ((uiState is DiscoveryUIState.Error).not()) { + if ((uiState is WebViewUIState.Error).not()) { + if (hasInternetConnection) { DiscoveryWebView( contentUrl = contentUrl, uriScheme = uriScheme, onWebPageLoaded = { - if ((uiState is DiscoveryUIState.Error).not()) { - onDiscoveryUIAction(DiscoveryUIAction.WEB_PAGE_LOADED) + if ((uiState is WebViewUIState.Error).not()) { + onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) } }, onWebPageUpdated = onWebPageUpdated, onUriClick = onUriClick, onWebPageLoadError = { - onDiscoveryUIAction(DiscoveryUIAction.WEB_PAGE_ERROR) + onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR) } ) + } else { + onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR) } - } else { - onDiscoveryUIAction(DiscoveryUIAction.WEB_PAGE_ERROR) } - if (uiState is DiscoveryUIState.Error) { - ErrorScreen( - title = stringResource(id = uiState.errorType.titleResId), - description = stringResource(id = uiState.errorType.descriptionResId), - buttonText = stringResource(id = uiState.errorType.actionResId), - icon = painterResource(id = uiState.errorType.iconResId) - ) { - onDiscoveryUIAction(DiscoveryUIAction.RELOAD_WEB_PAGE) + if (uiState is WebViewUIState.Error) { + FullScreenErrorView(errorType = uiState.errorType) { + onWebViewUIAction(WebViewUIAction.RELOAD_WEB_PAGE) } } - if (uiState is DiscoveryUIState.Loading && hasInternetConnection) { + if (uiState is WebViewUIState.Loading && hasInternetConnection) { Box( modifier = Modifier .fillMaxSize() @@ -392,12 +388,12 @@ private fun WebViewDiscoveryScreenPreview() { OpenEdXTheme { WebViewDiscoveryScreen( windowSize = WindowSize(WindowType.Compact, WindowType.Compact), - uiState = DiscoveryUIState.Error(ErrorType.CONNECTION_ERROR), + uiState = WebViewUIState.Error(ErrorType.CONNECTION_ERROR), isPreLogin = false, contentUrl = "https://www.example.com/", uriScheme = "", hasInternetConnection = false, - onDiscoveryUIAction = {}, + onWebViewUIAction = {}, onWebPageUpdated = {}, onUriClick = { _, _ -> }, onRegisterClick = {}, diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt index b1a38688b..c5ca27598 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt @@ -8,6 +8,7 @@ import org.openedx.core.BaseViewModel import org.openedx.core.config.Config import org.openedx.core.data.storage.CorePreferences import org.openedx.core.presentation.global.ErrorType +import org.openedx.core.presentation.global.webview.WebViewUIState import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.utils.UrlUtils @@ -20,8 +21,8 @@ class WebViewDiscoveryViewModel( private val analytics: DiscoveryAnalytics, ) : BaseViewModel() { - private val _uiState = MutableStateFlow(DiscoveryUIState.Loading) - val uiState: StateFlow = _uiState.asStateFlow() + private val _uiState = MutableStateFlow(WebViewUIState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() val uriScheme: String get() = config.getUriScheme() private val webViewConfig get() = config.getDiscoveryConfig().webViewConfig @@ -44,15 +45,16 @@ class WebViewDiscoveryViewModel( get() = networkConnection.isOnline() fun onWebPageLoading() { - _uiState.value = DiscoveryUIState.Loaded + _uiState.value = WebViewUIState.Loaded } + fun onWebPageLoaded() { - _uiState.value = DiscoveryUIState.Loaded + _uiState.value = WebViewUIState.Loaded } fun onWebPageLoadError() { _uiState.value = - DiscoveryUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) + WebViewUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) } fun updateDiscoveryUrl(url: String) { diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt index 3c31ed4f1..785e77767 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import org.openedx.core.extension.applyDarkModeIfEnabled +import org.openedx.core.extension.equalsHost import org.openedx.discovery.presentation.catalog.WebViewLink.Authority as linkAuthority @SuppressLint("SetJavaScriptEnabled", "ComposableNaming") @@ -89,10 +90,10 @@ fun CatalogWebViewScreen( request: WebResourceRequest, error: WebResourceError ) { - super.onReceivedError(view, request, error) - if (view.url?.equals(request.url) == true) { + if (view.url.equalsHost(request.url.host)) { onWebPageLoadError() } + super.onReceivedError(view, request, error) } } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt index a52d0d92a..17a1dd48b 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -46,9 +45,10 @@ import org.koin.core.parameter.parametersOf import org.openedx.core.UIMessage import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.presentation.dialog.alert.InfoDialogFragment -import org.openedx.core.presentation.global.webview.WebViewState +import org.openedx.core.presentation.global.webview.WebViewUIAction +import org.openedx.core.presentation.global.webview.WebViewUIState import org.openedx.core.ui.AuthButtonsPanel -import org.openedx.core.ui.ErrorScreen +import org.openedx.core.ui.FullScreenErrorView import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize @@ -124,21 +124,21 @@ class CourseInfoFragment : Fragment() { CourseInfoScreen( windowSize = windowSize, uiState = uiState, - webViewState = webViewState, + webViewUIState = webViewState, uiMessage = uiMessage, uriScheme = viewModel.uriScheme, hasInternetConnection = hasInternetConnection, - onCourseInfoUIAction = { action -> + onWebViewUIAction = { action -> when (action) { - CourseInfoUIAction.WEB_PAGE_LOADED -> { + WebViewUIAction.WEB_PAGE_LOADED -> { viewModel.onWebPageLoaded() } - CourseInfoUIAction.WEB_PAGE_ERROR -> { + WebViewUIAction.WEB_PAGE_ERROR -> { viewModel.onWebPageError() } - CourseInfoUIAction.RELOAD_WEB_PAGE -> { + WebViewUIAction.RELOAD_WEB_PAGE -> { hasInternetConnection = viewModel.hasInternetConnection viewModel.onWebPageLoading() } @@ -239,11 +239,11 @@ class CourseInfoFragment : Fragment() { private fun CourseInfoScreen( windowSize: WindowSize, uiState: CourseInfoUIState, - webViewState: WebViewState, + webViewUIState: WebViewUIState, uiMessage: UIMessage?, uriScheme: String, hasInternetConnection: Boolean, - onCourseInfoUIAction: (CourseInfoUIAction) -> Unit, + onWebViewUIAction: (WebViewUIAction) -> Unit, onRegisterClick: () -> Unit, onSignInClick: () -> Unit, onBackClick: () -> Unit, @@ -310,32 +310,27 @@ private fun CourseInfoScreen( .navigationBarsPadding(), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection) { - if ((webViewState is WebViewState.Error).not()) { + if ((webViewUIState is WebViewUIState.Error).not()) { + if (hasInternetConnection) { CourseInfoWebView( contentUrl = (uiState as CourseInfoUIState.CourseInfo).initialUrl, uriScheme = uriScheme, - onWebPageLoaded = { onCourseInfoUIAction(CourseInfoUIAction.WEB_PAGE_LOADED) }, + onWebPageLoaded = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) }, onUriClick = onUriClick, onWebPageLoadError = { - onCourseInfoUIAction(CourseInfoUIAction.WEB_PAGE_ERROR) + onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR) } ) + } else { + onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR) } - } else { - onCourseInfoUIAction(CourseInfoUIAction.WEB_PAGE_ERROR) } - if (webViewState is WebViewState.Error) { - ErrorScreen( - title = stringResource(id = webViewState.errorType.titleResId), - description = stringResource(id = webViewState.errorType.descriptionResId), - buttonText = stringResource(id = webViewState.errorType.actionResId), - icon = painterResource(id = webViewState.errorType.iconResId) - ) { - onCourseInfoUIAction(CourseInfoUIAction.RELOAD_WEB_PAGE) + if (webViewUIState is WebViewUIState.Error) { + FullScreenErrorView(errorType = webViewUIState.errorType) { + onWebViewUIAction(WebViewUIAction.RELOAD_WEB_PAGE) } } - if (webViewState is WebViewState.Loading && hasInternetConnection) { + if (webViewUIState is WebViewUIState.Loading && hasInternetConnection) { Box( modifier = Modifier .fillMaxSize() @@ -397,12 +392,12 @@ fun CourseInfoScreenPreview() { uiMessage = null, uriScheme = "", hasInternetConnection = false, - onCourseInfoUIAction = {}, + onWebViewUIAction = {}, onRegisterClick = {}, onSignInClick = {}, onBackClick = {}, onUriClick = { _, _ -> }, - webViewState = WebViewState.Loading, + webViewUIState = WebViewUIState.Loading, ) } } diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt index 01b14960b..cd28abd2b 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoUIState.kt @@ -9,9 +9,3 @@ sealed class CourseInfoUIState { val enrollmentSuccess: AtomicReference = AtomicReference("") ) : CourseInfoUIState() } - -enum class CourseInfoUIAction { - WEB_PAGE_LOADED, - WEB_PAGE_ERROR, - RELOAD_WEB_PAGE -} diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt index fff2ad40e..83ed16a71 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt @@ -19,7 +19,7 @@ import org.openedx.core.data.storage.CorePreferences import org.openedx.core.extension.isInternetError import org.openedx.core.presentation.CoreAnalyticsKey import org.openedx.core.presentation.global.ErrorType -import org.openedx.core.presentation.global.webview.WebViewState +import org.openedx.core.presentation.global.webview.WebViewUIState import org.openedx.core.system.ResourceManager import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.system.notifier.CourseDashboardUpdate @@ -56,9 +56,9 @@ class CourseInfoViewModel( ) internal val uiState: StateFlow = _uiState - private val _webViewState = MutableStateFlow(WebViewState.Loading) + private val _webViewUIState = MutableStateFlow(WebViewUIState.Loading) val webViewState - get() = _webViewState.asStateFlow() + get() = _webViewUIState.asStateFlow() private val _uiMessage = MutableSharedFlow() val uiMessage: SharedFlow @@ -195,16 +195,16 @@ class CourseInfoViewModel( } fun onWebPageLoaded() { - _webViewState.value = WebViewState.Loaded + _webViewUIState.value = WebViewUIState.Loaded } fun onWebPageError() { - _webViewState.value = - WebViewState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) + _webViewUIState.value = + WebViewUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR) } fun onWebPageLoading() { - _webViewState.value = WebViewState.Loading + _webViewUIState.value = WebViewUIState.Loading } companion object { diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt index 82d019a07..85809a9fb 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -51,8 +50,9 @@ import org.openedx.core.extension.takeIfNotEmpty import org.openedx.core.extension.toastMessage import org.openedx.core.presentation.dialog.alert.ActionDialogFragment import org.openedx.core.presentation.dialog.alert.InfoDialogFragment +import org.openedx.core.presentation.global.webview.WebViewUIAction import org.openedx.core.system.AppCookieManager -import org.openedx.core.ui.ErrorScreen +import org.openedx.core.ui.FullScreenErrorView import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.Toolbar import org.openedx.core.ui.WindowSize @@ -134,17 +134,17 @@ class ProgramFragment : Fragment() { isNestedFragment = isNestedFragment, uriScheme = viewModel.uriScheme, hasInternetConnection = hasInternetConnection, - onProgramAction = { action -> + onWebViewUIAction = { action -> when (action) { - ProgramUIAction.WEB_PAGE_LOADED -> { + WebViewUIAction.WEB_PAGE_LOADED -> { viewModel.showLoading(false) } - ProgramUIAction.WEB_PAGE_ERROR -> { + WebViewUIAction.WEB_PAGE_ERROR -> { viewModel.onPageLoadError() } - ProgramUIAction.RELOAD_WEB_PAGE -> { + WebViewUIAction.RELOAD_WEB_PAGE -> { hasInternetConnection = viewModel.hasInternetConnection viewModel.showLoading(true) } @@ -247,7 +247,7 @@ private fun ProgramInfoScreen( canShowBackBtn: Boolean, isNestedFragment: Boolean, hasInternetConnection: Boolean, - onProgramAction: (ProgramUIAction) -> Unit, + onWebViewUIAction: (WebViewUIAction) -> Unit, onSettingsClick: () -> Unit, onBackClick: () -> Unit, onUriClick: (String, WebViewLink.Authority) -> Unit, @@ -315,45 +315,41 @@ private fun ProgramInfoScreen( .background(Color.White), contentAlignment = Alignment.TopCenter ) { - if (hasInternetConnection && (uiState is ProgramUIState.Error).not()) { - val webView = CatalogWebViewScreen( - url = contentUrl, - uriScheme = uriScheme, - isAllLinksExternal = true, - onWebPageLoaded = { onProgramAction(ProgramUIAction.WEB_PAGE_LOADED) }, - refreshSessionCookie = { - coroutineScope.launch { - cookieManager.tryToRefreshSessionCookie() - } - }, - onUriClick = onUriClick, - onWebPageLoadError = { onProgramAction(ProgramUIAction.WEB_PAGE_ERROR) } - ) + if ((uiState is ProgramUIState.Error).not()) { + if (hasInternetConnection) { + val webView = CatalogWebViewScreen( + url = contentUrl, + uriScheme = uriScheme, + isAllLinksExternal = true, + onWebPageLoaded = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) }, + refreshSessionCookie = { + coroutineScope.launch { + cookieManager.tryToRefreshSessionCookie() + } + }, + onUriClick = onUriClick, + onWebPageLoadError = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR) } + ) - AndroidView( - modifier = Modifier - .background(MaterialTheme.appColors.background), - factory = { - webView - }, - update = { - webView.loadUrl(contentUrl, coroutineScope, cookieManager) - } - ) - } else { - onProgramAction(ProgramUIAction.WEB_PAGE_ERROR) + AndroidView( + modifier = Modifier + .background(MaterialTheme.appColors.background), + factory = { + webView + }, + update = { + webView.loadUrl(contentUrl, coroutineScope, cookieManager) + } + ) + } else { + onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR) + } } if (uiState is ProgramUIState.Error) { - ErrorScreen( - title = stringResource(id = uiState.errorType.titleResId), - description = stringResource(id = uiState.errorType.descriptionResId), - buttonText = stringResource(id = uiState.errorType.actionResId), - icon = painterResource(id = uiState.errorType.iconResId), - onReloadClick = { - onProgramAction(ProgramUIAction.RELOAD_WEB_PAGE) - } - ) + FullScreenErrorView(errorType = uiState.errorType) { + onWebViewUIAction(WebViewUIAction.RELOAD_WEB_PAGE) + } } if (uiState == ProgramUIState.Loading && hasInternetConnection) { @@ -386,7 +382,7 @@ fun MyProgramsPreview() { canShowBackBtn = false, isNestedFragment = false, hasInternetConnection = false, - onProgramAction = {}, + onWebViewUIAction = {}, onBackClick = {}, onSettingsClick = {}, onUriClick = { _, _ -> }, diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt index ddc97d4aa..bed418100 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt @@ -12,9 +12,3 @@ sealed class ProgramUIState { class UiMessage(val uiMessage: UIMessage) : ProgramUIState() } - -enum class ProgramUIAction { - WEB_PAGE_LOADED, - WEB_PAGE_ERROR, - RELOAD_WEB_PAGE -} diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt index cfc0ff285..59a26cba5 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt @@ -93,6 +93,9 @@ class ProgramViewModel( enrollmentMode = "" ) } + viewModelScope.launch { + _uiState.emit(ProgramUIState.Loaded) + } } fun navigateToDiscovery() { From e8a866e0ac2af6ebceb383ee7598371bfbd5dc8a Mon Sep 17 00:00:00 2001 From: Farhan Arshad Date: Wed, 17 Jul 2024 15:01:51 +0500 Subject: [PATCH 4/5] fix: address PR comments by Moin-3 --- .../java/org/openedx/discovery/presentation/DiscoveryUIState.kt | 1 - .../openedx/discovery/presentation/NativeDiscoveryFragment.kt | 2 -- 2 files changed, 3 deletions(-) diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt index ae91f8365..7001c26c6 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryUIState.kt @@ -1,6 +1,5 @@ package org.openedx.discovery.presentation -import org.openedx.core.presentation.global.ErrorType import org.openedx.discovery.domain.model.Course sealed class DiscoveryUIState { diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt index bd3029aca..3a50ab707 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt @@ -417,8 +417,6 @@ internal fun DiscoveryScreen( } } } - - else -> {} } PullRefreshIndicator( refreshing, From 1affc0a2ddc6e3a88a4ce4c93a6e4afd2d280480 Mon Sep 17 00:00:00 2001 From: Farhan Arshad Date: Wed, 17 Jul 2024 19:59:56 +0500 Subject: [PATCH 5/5] fix: adress PR comments by Kirill-1 --- core/src/main/java/org/openedx/core/ui/ComposeCommon.kt | 3 ++- .../discovery/presentation/WebViewDiscoveryViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index 1a11ac2a1..50e5b0993 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -1143,11 +1143,12 @@ fun ConnectionErrorView(onReloadClick: () -> Unit) { @Composable fun FullScreenErrorView( + modifier: Modifier = Modifier, errorType: ErrorType, onReloadClick: () -> Unit ) { Column( - modifier = Modifier + modifier = modifier .fillMaxSize() .background(MaterialTheme.appColors.background), verticalArrangement = Arrangement.Center, diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt index c5ca27598..a41252ae7 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt @@ -45,7 +45,7 @@ class WebViewDiscoveryViewModel( get() = networkConnection.isOnline() fun onWebPageLoading() { - _uiState.value = WebViewUIState.Loaded + _uiState.value = WebViewUIState.Loading } fun onWebPageLoaded() {