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 d383cf57f..0ecc86e1f 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
@@ -38,6 +39,14 @@ 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
+ }
+}
+
fun String.toImageLink(apiHostURL: String): String =
if (this.isLinkValid()) {
this
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/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 3eb16bd9b..d50b05cbe 100644
--- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
+++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
@@ -108,6 +108,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
@@ -1133,25 +1134,33 @@ fun BackBtn(
}
@Composable
-fun ConnectionErrorView(
- modifier: Modifier,
- onReloadClick: () -> Unit,
+fun ConnectionErrorView(onReloadClick: () -> Unit) {
+ FullScreenErrorView(errorType = ErrorType.CONNECTION_ERROR, onReloadClick = onReloadClick)
+}
+
+@Composable
+fun FullScreenErrorView(
+ modifier: Modifier = Modifier,
+ errorType: ErrorType,
+ onReloadClick: () -> Unit
) {
Column(
- modifier = modifier,
+ modifier = modifier
+ .fillMaxSize()
+ .background(MaterialTheme.appColors.background),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = Modifier.size(100.dp),
- painter = painterResource(id = R.drawable.core_no_internet_connection),
+ painter = painterResource(id = errorType.iconResId),
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 = stringResource(id = errorType.titleResId),
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.titleLarge,
textAlign = TextAlign.Center
@@ -1159,7 +1168,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 = stringResource(id = errorType.descriptionResId),
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.bodyLarge,
textAlign = TextAlign.Center
@@ -1168,7 +1177,7 @@ fun ConnectionErrorView(
OpenEdXButton(
modifier = Modifier
.widthIn(Dp.Unspecified, 162.dp),
- text = stringResource(id = R.string.core_reload),
+ text = stringResource(id = errorType.actionResId),
textColor = MaterialTheme.appColors.primaryButtonText,
backgroundColor = MaterialTheme.appColors.secondaryButtonBackground,
onClick = onReloadClick,
@@ -1369,11 +1378,7 @@ private fun IconTextPreview() {
@Composable
private fun ConnectionErrorViewPreview() {
OpenEdXTheme(darkTheme = true) {
- ConnectionErrorView(
- modifier = Modifier
- .fillMaxSize(),
- onReloadClick = {}
- )
+ ConnectionErrorView(onReloadClick = {})
}
}
diff --git a/core/src/main/res/drawable/core_ic_unknown_error.xml b/core/src/main/res/drawable/core_ic_unknown_error.xml
new file mode 100644
index 000000000..d7d2c0c02
--- /dev/null
+++ b/core/src/main/res/drawable/core_ic_unknown_error.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 78263a819..b023e8845 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -75,6 +75,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 db88ae6c8..b5747d4d0 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.WebSettings
@@ -18,7 +19,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
@@ -53,10 +53,11 @@ import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
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.ConnectionErrorView
+import org.openedx.core.ui.FullScreenErrorView
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.rememberWindowSize
import org.openedx.core.ui.roundBorderWithoutBottom
@@ -96,10 +97,6 @@ class HtmlUnitFragment : Fragment() {
OpenEdXTheme {
val windowSize = rememberWindowSize()
- var isLoading by remember {
- mutableStateOf(true)
- }
-
var hasInternetConnection by remember {
mutableStateOf(viewModel.isOnline)
}
@@ -148,7 +145,8 @@ class HtmlUnitFragment : Fragment() {
.then(border),
contentAlignment = Alignment.TopCenter
) {
- if (uiState.isLoadingEnabled) {
+ if (uiState is HtmlUnitUIState.Initialization) return@Box
+ if ((uiState is HtmlUnitUIState.Error).not()) {
if (hasInternetConnection || fromDownloadedContent) {
HTMLContentView(
uiState = uiState,
@@ -156,41 +154,45 @@ class HtmlUnitFragment : Fragment() {
url = url,
cookieManager = viewModel.cookieManager,
apiHostURL = viewModel.apiHostURL,
- isLoading = isLoading,
+ isLoading = uiState is HtmlUnitUIState.Loading,
injectJSList = injectJSList,
onCompletionSet = {
viewModel.notifyCompletionSet()
},
onWebPageLoading = {
- isLoading = true
+ viewModel.onWebPageLoading()
},
onWebPageLoaded = {
- isLoading = false
+ if ((uiState is HtmlUnitUIState.Error).not()) {
+ viewModel.onWebPageLoaded()
+ }
if (isAdded) viewModel.setWebPageLoaded(requireContext().assets)
},
+ onWebPageLoadError = {
+ viewModel.onWebPageLoadError()
+ },
saveXBlockProgress = { jsonProgress ->
viewModel.saveXBlockProgress(jsonProgress)
},
)
} else {
- ConnectionErrorView(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .background(MaterialTheme.appColors.background)
- ) {
- hasInternetConnection = viewModel.isOnline
- }
+ viewModel.onWebPageLoadError()
}
- if (isLoading && hasInternetConnection) {
- Box(
- modifier = Modifier
- .fillMaxSize()
- .zIndex(1f),
- contentAlignment = Alignment.Center
- ) {
- CircularProgressIndicator(color = MaterialTheme.appColors.primary)
- }
+ } else {
+ val errorType = (uiState as HtmlUnitUIState.Error).errorType
+ FullScreenErrorView(errorType = errorType) {
+ hasInternetConnection = viewModel.isOnline
+ viewModel.onWebPageLoading()
+ }
+ }
+ if (uiState is HtmlUnitUIState.Loading && hasInternetConnection) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .zIndex(1f),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
}
}
@@ -239,7 +241,8 @@ private fun HTMLContentView(
onCompletionSet: () -> Unit,
onWebPageLoading: () -> Unit,
onWebPageLoaded: () -> Unit,
- saveXBlockProgress: (String) -> Unit
+ onWebPageLoadError: () -> Unit,
+ saveXBlockProgress: (String) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
@@ -333,6 +336,17 @@ private fun HTMLContentView(
}
super.onReceivedHttpError(view, request, errorResponse)
}
+
+ override fun onReceivedError(
+ view: WebView,
+ request: WebResourceRequest,
+ error: WebResourceError
+ ) {
+ if (view.url.equalsHost(request.url.host)) {
+ onWebPageLoadError()
+ }
+ super.onReceivedError(view, request, error)
+ }
}
with(settings) {
javaScriptEnabled = true
@@ -356,7 +370,7 @@ private fun HTMLContentView(
update = { webView ->
if (!isLoading && injectJSList.isNotEmpty()) {
injectJSList.forEach { webView.evaluateJavascript(it, null) }
- val jsonProgress = uiState.jsonProgress
+ val jsonProgress = (uiState as? HtmlUnitUIState.Loaded)?.jsonProgress
if (!jsonProgress.isNullOrEmpty()) {
webView.setupOfflineProgress(jsonProgress)
}
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
index 2dc14424c..855a7a1e9 100644
--- 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
@@ -1,6 +1,10 @@
package org.openedx.course.presentation.unit.html
-data class HtmlUnitUIState(
- val jsonProgress: String?,
- val isLoadingEnabled: Boolean
-)
+import org.openedx.core.presentation.global.ErrorType
+
+sealed class HtmlUnitUIState {
+ data object Initialization : HtmlUnitUIState()
+ data object Loading : HtmlUnitUIState()
+ data class Loaded(val jsonProgress: String? = null) : 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 f852c1f2d..bccdcd0fd 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
@@ -11,6 +11,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
@@ -29,9 +30,8 @@ class HtmlUnitViewModel(
private val offlineProgressSyncScheduler: OfflineProgressSyncScheduler
) : BaseViewModel() {
- private val _uiState = MutableStateFlow(HtmlUnitUIState(null, false))
- val uiState: StateFlow
- get() = _uiState.asStateFlow()
+ private val _uiState = MutableStateFlow(HtmlUnitUIState.Initialization)
+ val uiState = _uiState.asStateFlow()
private val _injectJSList = MutableStateFlow>(listOf())
val injectJSList = _injectJSList.asStateFlow()
@@ -45,6 +45,19 @@ class HtmlUnitViewModel(
tryToSyncProgress()
}
+ 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
@@ -80,7 +93,7 @@ class HtmlUnitViewModel(
}
} catch (e: Exception) {
} finally {
- _uiState.update { it.copy(isLoadingEnabled = true) }
+ _uiState.value = HtmlUnitUIState.Loading
}
}
}
@@ -90,7 +103,7 @@ class HtmlUnitViewModel(
if (!isOnline) {
val xBlockProgress = courseInteractor.getXBlockProgress(blockId)
delay(500)
- _uiState.update { it.copy(jsonProgress = xBlockProgress?.jsonProgress?.toJson()) }
+ _uiState.value = HtmlUnitUIState.Loaded(jsonProgress = xBlockProgress?.jsonProgress?.toJson())
}
}
}
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 276f48574..49431ba46 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 e6b04687d..b163a7cde 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
@@ -102,11 +97,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/WebViewDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt
index fcd185efe..cf67d1a2c 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
@@ -52,8 +53,11 @@ 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.presentation.global.webview.WebViewUIAction
+import org.openedx.core.presentation.global.webview.WebViewUIState
import org.openedx.core.ui.AuthButtonsPanel
-import org.openedx.core.ui.ConnectionErrorView
+import org.openedx.core.ui.FullScreenErrorView
import org.openedx.core.ui.Toolbar
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.WindowType
@@ -83,18 +87,33 @@ 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,
isRegistrationEnabled = viewModel.isRegistrationEnabled,
hasInternetConnection = hasInternetConnection,
- checkInternetConnection = {
- hasInternetConnection = viewModel.hasInternetConnection
+ onWebViewUIAction = { action ->
+ when (action) {
+ WebViewUIAction.WEB_PAGE_LOADED -> {
+ viewModel.onWebPageLoaded()
+ }
+
+ WebViewUIAction.WEB_PAGE_ERROR -> {
+ viewModel.onWebPageLoadError()
+ }
+
+ WebViewUIAction.RELOAD_WEB_PAGE -> {
+ hasInternetConnection = viewModel.hasInternetConnection
+ viewModel.onWebPageLoading()
+ }
+ }
},
onWebPageUpdated = { url ->
viewModel.updateDiscoveryUrl(url)
@@ -171,12 +190,13 @@ class WebViewDiscoveryFragment : Fragment() {
@SuppressLint("SetJavaScriptEnabled")
private fun WebViewDiscoveryScreen(
windowSize: WindowSize,
+ uiState: WebViewUIState,
isPreLogin: Boolean,
contentUrl: String,
uriScheme: String,
isRegistrationEnabled: Boolean,
hasInternetConnection: Boolean,
- checkInternetConnection: () -> Unit,
+ onWebViewUIAction: (WebViewUIAction) -> Unit,
onWebPageUpdated: (String) -> Unit,
onUriClick: (String, WebViewLink.Authority) -> Unit,
onRegisterClick: () -> Unit,
@@ -186,7 +206,6 @@ private fun WebViewDiscoveryScreen(
) {
val scaffoldState = rememberScaffoldState()
val configuration = LocalConfiguration.current
- var isLoading by remember { mutableStateOf(true) }
Scaffold(
scaffoldState = scaffoldState,
@@ -251,25 +270,32 @@ private fun WebViewDiscoveryScreen(
.background(Color.White),
contentAlignment = Alignment.TopCenter
) {
- if (hasInternetConnection) {
- DiscoveryWebView(
- contentUrl = contentUrl,
- uriScheme = uriScheme,
- onWebPageLoaded = { isLoading = false },
- onWebPageUpdated = onWebPageUpdated,
- onUriClick = onUriClick,
- )
- } else {
- ConnectionErrorView(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .background(MaterialTheme.appColors.background)
- ) {
- checkInternetConnection()
+ if ((uiState is WebViewUIState.Error).not()) {
+ if (hasInternetConnection) {
+ DiscoveryWebView(
+ contentUrl = contentUrl,
+ uriScheme = uriScheme,
+ onWebPageLoaded = {
+ if ((uiState is WebViewUIState.Error).not()) {
+ onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED)
+ }
+ },
+ onWebPageUpdated = onWebPageUpdated,
+ onUriClick = onUriClick,
+ onWebPageLoadError = {
+ onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR)
+ }
+ )
+ } else {
+ onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR)
}
}
- if (isLoading && hasInternetConnection) {
+ if (uiState is WebViewUIState.Error) {
+ FullScreenErrorView(errorType = uiState.errorType) {
+ onWebViewUIAction(WebViewUIAction.RELOAD_WEB_PAGE)
+ }
+ }
+ if (uiState is WebViewUIState.Loading && hasInternetConnection) {
Box(
modifier = Modifier
.fillMaxSize()
@@ -293,6 +319,7 @@ private fun DiscoveryWebView(
onWebPageLoaded: () -> Unit,
onWebPageUpdated: (String) -> Unit,
onUriClick: (String, WebViewLink.Authority) -> Unit,
+ onWebPageLoadError: () -> Unit
) {
val webView = CatalogWebViewScreen(
url = contentUrl,
@@ -300,6 +327,7 @@ private fun DiscoveryWebView(
onWebPageLoaded = onWebPageLoaded,
onWebPageUpdated = onWebPageUpdated,
onUriClick = onUriClick,
+ onWebPageLoadError = onWebPageLoadError
)
AndroidView(
@@ -363,18 +391,19 @@ private fun WebViewDiscoveryScreenPreview() {
OpenEdXTheme {
WebViewDiscoveryScreen(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
- contentUrl = "https://www.example.com/",
+ uiState = WebViewUIState.Error(ErrorType.CONNECTION_ERROR),
isPreLogin = false,
+ contentUrl = "https://www.example.com/",
uriScheme = "",
isRegistrationEnabled = true,
hasInternetConnection = false,
- checkInternetConnection = {},
+ onWebViewUIAction = {},
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 94f62574d..a8f8cfc45 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,14 @@
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.presentation.global.webview.WebViewUIState
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.utils.UrlUtils
@@ -16,6 +21,8 @@ class WebViewDiscoveryViewModel(
private val analytics: DiscoveryAnalytics,
) : BaseViewModel() {
+ private val _uiState = MutableStateFlow(WebViewUIState.Loading)
+ val uiState: StateFlow = _uiState.asStateFlow()
val uriScheme: String get() = config.getUriScheme()
private val webViewConfig get() = config.getDiscoveryConfig().webViewConfig
@@ -38,6 +45,19 @@ class WebViewDiscoveryViewModel(
val hasInternetConnection: Boolean
get() = networkConnection.isOnline()
+ fun onWebPageLoading() {
+ _uiState.value = WebViewUIState.Loading
+ }
+
+ fun onWebPageLoaded() {
+ _uiState.value = WebViewUIState.Loaded
+ }
+
+ fun onWebPageLoadError() {
+ _uiState.value =
+ WebViewUIState.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 373516b0a..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
@@ -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
@@ -8,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")
@@ -20,6 +22,7 @@ fun CatalogWebViewScreen(
refreshSessionCookie: () -> Unit = {},
onWebPageUpdated: (String) -> Unit = {},
onUriClick: (String, linkAuthority) -> Unit,
+ onWebPageLoadError: () -> Unit
): WebView {
val context = LocalContext.current
val isDarkTheme = isSystemInDarkTheme()
@@ -81,6 +84,17 @@ fun CatalogWebViewScreen(
else -> false
}
}
+
+ override fun onReceivedError(
+ view: WebView,
+ request: WebResourceRequest,
+ error: WebResourceError
+ ) {
+ if (view.url.equalsHost(request.url.host)) {
+ 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 1345ae5c6..4906e91f8 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
@@ -45,8 +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.WebViewUIAction
+import org.openedx.core.presentation.global.webview.WebViewUIState
import org.openedx.core.ui.AuthButtonsPanel
-import org.openedx.core.ui.ConnectionErrorView
+import org.openedx.core.ui.FullScreenErrorView
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.Toolbar
import org.openedx.core.ui.WindowSize
@@ -85,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)
@@ -105,26 +108,42 @@ 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,
+ webViewUIState = webViewState,
uiMessage = uiMessage,
uriScheme = viewModel.uriScheme,
hasInternetConnection = hasInternetConnection,
isRegistrationEnabled = viewModel.isRegistrationEnabled,
- checkInternetConnection = {
- hasInternetConnection = viewModel.hasInternetConnection
+ onWebViewUIAction = { action ->
+ when (action) {
+ WebViewUIAction.WEB_PAGE_LOADED -> {
+ viewModel.onWebPageLoaded()
+ }
+
+ WebViewUIAction.WEB_PAGE_ERROR -> {
+ viewModel.onWebPageError()
+ }
+
+ WebViewUIAction.RELOAD_WEB_PAGE -> {
+ hasInternetConnection = viewModel.hasInternetConnection
+ viewModel.onWebPageLoading()
+ }
+ }
},
onRegisterClick = {
viewModel.navigateToSignUp(
@@ -180,7 +199,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,11 +240,12 @@ class CourseInfoFragment : Fragment() {
private fun CourseInfoScreen(
windowSize: WindowSize,
uiState: CourseInfoUIState,
+ webViewUIState: WebViewUIState,
uiMessage: UIMessage?,
uriScheme: String,
isRegistrationEnabled: Boolean,
hasInternetConnection: Boolean,
- checkInternetConnection: () -> Unit,
+ onWebViewUIAction: (WebViewUIAction) -> Unit,
onRegisterClick: () -> Unit,
onSignInClick: () -> Unit,
onBackClick: () -> Unit,
@@ -233,7 +253,6 @@ private fun CourseInfoScreen(
) {
val scaffoldState = rememberScaffoldState()
val configuration = LocalConfiguration.current
- var isLoading by remember { mutableStateOf(true) }
HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState)
@@ -242,7 +261,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(
@@ -294,24 +313,27 @@ private fun CourseInfoScreen(
.navigationBarsPadding(),
contentAlignment = Alignment.TopCenter
) {
- if (hasInternetConnection) {
- CourseInfoWebView(
- contentUrl = uiState.initialUrl,
- uriScheme = uriScheme,
- onWebPageLoaded = { isLoading = false },
- onUriClick = onUriClick,
- )
- } else {
- ConnectionErrorView(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .background(MaterialTheme.appColors.background)
- ) {
- checkInternetConnection()
+ if ((webViewUIState is WebViewUIState.Error).not()) {
+ if (hasInternetConnection) {
+ CourseInfoWebView(
+ contentUrl = (uiState as CourseInfoUIState.CourseInfo).initialUrl,
+ uriScheme = uriScheme,
+ onWebPageLoaded = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) },
+ onUriClick = onUriClick,
+ onWebPageLoadError = {
+ onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR)
+ }
+ )
+ } else {
+ onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR)
+ }
+ }
+ if (webViewUIState is WebViewUIState.Error) {
+ FullScreenErrorView(errorType = webViewUIState.errorType) {
+ onWebViewUIAction(WebViewUIAction.RELOAD_WEB_PAGE)
}
}
- if (isLoading && hasInternetConnection) {
+ if (webViewUIState is WebViewUIState.Loading && hasInternetConnection) {
Box(
modifier = Modifier
.fillMaxSize()
@@ -334,6 +356,7 @@ private fun CourseInfoWebView(
uriScheme: String,
onWebPageLoaded: () -> Unit,
onUriClick: (String, linkAuthority) -> Unit,
+ onWebPageLoadError: () -> Unit
) {
val webView = CatalogWebViewScreen(
@@ -342,6 +365,7 @@ private fun CourseInfoWebView(
isAllLinksExternal = true,
onWebPageLoaded = onWebPageLoaded,
onUriClick = onUriClick,
+ onWebPageLoadError = onWebPageLoadError
)
AndroidView(
@@ -360,7 +384,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("")
@@ -369,11 +393,12 @@ fun CourseInfoScreenPreview() {
uriScheme = "",
isRegistrationEnabled = true,
hasInternetConnection = false,
- checkInternetConnection = {},
+ onWebViewUIAction = {},
onRegisterClick = {},
onSignInClick = {},
onBackClick = {},
onUriClick = { _, _ -> },
+ 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 ffabf1daf..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
@@ -2,8 +2,10 @@ 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()
+}
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 87c64c770..65907f5cf 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.WebViewUIState
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 _webViewUIState = MutableStateFlow(WebViewUIState.Loading)
+ val webViewState
+ get() = _webViewUIState.asStateFlow()
+
private val _uiMessage = MutableSharedFlow()
val uiMessage: SharedFlow
get() = _uiMessage.asSharedFlow()
@@ -189,6 +196,19 @@ class CourseInfoViewModel(
}
}
+ fun onWebPageLoaded() {
+ _webViewUIState.value = WebViewUIState.Loaded
+ }
+
+ fun onWebPageError() {
+ _webViewUIState.value =
+ WebViewUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR)
+ }
+
+ fun onWebPageLoading() {
+ _webViewUIState.value = WebViewUIState.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 ef79e1f32..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
@@ -50,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.ConnectionErrorView
+import org.openedx.core.ui.FullScreenErrorView
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.Toolbar
import org.openedx.core.ui.WindowSize
@@ -133,10 +134,22 @@ class ProgramFragment : Fragment() {
isNestedFragment = isNestedFragment,
uriScheme = viewModel.uriScheme,
hasInternetConnection = hasInternetConnection,
- checkInternetConnection = {
- hasInternetConnection = viewModel.hasInternetConnection
+ onWebViewUIAction = { action ->
+ when (action) {
+ WebViewUIAction.WEB_PAGE_LOADED -> {
+ viewModel.showLoading(false)
+ }
+
+ WebViewUIAction.WEB_PAGE_ERROR -> {
+ viewModel.onPageLoadError()
+ }
+
+ WebViewUIAction.RELOAD_WEB_PAGE -> {
+ hasInternetConnection = viewModel.hasInternetConnection
+ viewModel.showLoading(true)
+ }
+ }
},
- onWebPageLoaded = { viewModel.showLoading(false) },
onBackClick = {
requireActivity().supportFragmentManager.popBackStackImmediate()
},
@@ -192,7 +205,7 @@ class ProgramFragment : Fragment() {
},
onSettingsClick = {
viewModel.navigateToSettings(requireActivity().supportFragmentManager)
- },
+ }
)
}
}
@@ -234,8 +247,7 @@ private fun ProgramInfoScreen(
canShowBackBtn: Boolean,
isNestedFragment: Boolean,
hasInternetConnection: Boolean,
- checkInternetConnection: () -> Unit,
- onWebPageLoaded: () -> Unit,
+ onWebViewUIAction: (WebViewUIAction) -> Unit,
onSettingsClick: () -> Unit,
onBackClick: () -> Unit,
onUriClick: (String, WebViewLink.Authority) -> Unit,
@@ -243,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 -> {
@@ -304,41 +315,44 @@ private fun ProgramInfoScreen(
.background(Color.White),
contentAlignment = Alignment.TopCenter
) {
- if (hasInternetConnection) {
- val webView = CatalogWebViewScreen(
- url = contentUrl,
- uriScheme = uriScheme,
- isAllLinksExternal = true,
- onWebPageLoaded = onWebPageLoaded,
- refreshSessionCookie = {
- coroutineScope.launch {
- cookieManager.tryToRefreshSessionCookie()
+ 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)
}
- },
- onUriClick = onUriClick,
- )
+ )
+ } else {
+ onWebViewUIAction(WebViewUIAction.WEB_PAGE_ERROR)
+ }
+ }
- AndroidView(
- modifier = Modifier
- .background(MaterialTheme.appColors.background),
- factory = {
- webView
- },
- update = {
- webView.loadUrl(contentUrl, coroutineScope, cookieManager)
- }
- )
- } else {
- ConnectionErrorView(
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .background(MaterialTheme.appColors.background)
- ) {
- checkInternetConnection()
+ if (uiState is ProgramUIState.Error) {
+ FullScreenErrorView(errorType = uiState.errorType) {
+ onWebViewUIAction(WebViewUIAction.RELOAD_WEB_PAGE)
}
}
- if (isLoading && hasInternetConnection) {
+
+ if (uiState == ProgramUIState.Loading && hasInternetConnection) {
Box(
modifier = Modifier
.fillMaxSize()
@@ -368,9 +382,8 @@ fun MyProgramsPreview() {
canShowBackBtn = false,
isNestedFragment = false,
hasInternetConnection = false,
- checkInternetConnection = {},
+ onWebViewUIAction = {},
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..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
@@ -1,10 +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 class Error(val errorType: ErrorType) : ProgramUIState()
class CourseEnrolled(val courseId: String, val isEnrolled: Boolean) : ProgramUIState()
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..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
@@ -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 {
@@ -97,6 +93,9 @@ class ProgramViewModel(
enrollmentMode = ""
)
}
+ viewModelScope.launch {
+ _uiState.emit(ProgramUIState.Loaded)
+ }
}
fun navigateToDiscovery() {
@@ -106,4 +105,10 @@ class ProgramViewModel(
fun navigateToSettings(fragmentManager: FragmentManager) {
router.navigateToSettings(fragmentManager)
}
+
+ fun onPageLoadError() {
+ viewModelScope.launch {
+ _uiState.emit(ProgramUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR))
+ }
+ }
}