Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: App Level WebView/No Internet Error Handling #10

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions core/src/main/java/org/openedx/core/extension/StringExt.kt
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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,
),
}
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 18 additions & 13 deletions core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1136,33 +1137,41 @@ fun BackBtn(
}

@Composable
fun ConnectionErrorView(
modifier: Modifier,
onReloadClick: () -> Unit,
fun ConnectionErrorView(onReloadClick: () -> Unit) {
FullScreenErrorView(errorType = ErrorType.CONNECTION_ERROR, onReloadClick = onReloadClick)
}

@Composable
fun FullScreenErrorView(
k1rill marked this conversation as resolved.
Show resolved Hide resolved
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
)
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
Expand All @@ -1171,7 +1180,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,
Expand Down Expand Up @@ -1364,11 +1373,7 @@ private fun IconTextPreview() {
@Composable
private fun ConnectionErrorViewPreview() {
OpenEdXTheme(darkTheme = true) {
ConnectionErrorView(
modifier = Modifier
.fillMaxSize(),
onReloadClick = {}
)
ConnectionErrorView(onReloadClick = {})
}
}

Expand Down
23 changes: 23 additions & 0 deletions core/src/main/res/drawable/core_ic_unknown_error.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="86dp"
android:height="86dp"
android:viewportWidth="100"
android:viewportHeight="100">

<path
android:fillColor="#FFFFFF"
android:pathData="M85,61.35V20.67C85,18.09 82.91,16 80.33,16H19.67C17.09,16 15,18.09 15,20.67V61.35C15,62 15.52,62.52 16.17,62.52H83.83C84.48,62.52 85,62 85,61.35ZM55.74,34.47C55.28,34.01 55.29,33.25 55.77,32.8C56.23,32.36 56.97,32.4 57.43,32.86L58.44,33.87C58.67,34.1 59.04,34.1 59.26,33.87L60.28,32.86C60.73,32.4 61.47,32.36 61.93,32.8C62.42,33.25 62.42,34.01 61.96,34.47L60.91,35.52C60.69,35.75 60.69,36.11 60.91,36.34L61.93,37.36C62.68,38.11 62.19,39.38 61.14,39.38C60.84,39.38 60.54,39.27 60.31,39.04L59.26,37.99C59.04,37.76 58.67,37.76 58.44,37.99L57.39,39.04C56.92,39.52 56.13,39.49 55.68,38.98C55.28,38.51 55.33,37.8 55.77,37.36L56.79,36.34C57.02,36.11 57.02,35.75 56.79,35.52L55.74,34.47ZM59.67,50.9C60.41,51.63 59.89,52.9 58.85,52.9C58.56,52.9 58.26,52.78 58.03,52.56C54.93,49.52 51.1,49.28 50,49.28C45.65,49.28 42.75,51.79 41.97,52.56C41.51,53.01 40.77,53.01 40.32,52.55C39.86,52.09 39.87,51.35 40.33,50.9C45.81,45.52 54.43,45.75 59.67,50.9ZM38.04,34.47C37.58,34.01 37.58,33.25 38.07,32.8C38.53,32.36 39.27,32.4 39.72,32.86L40.74,33.87C40.96,34.1 41.33,34.1 41.56,33.87L42.57,32.86C43.02,32.4 43.77,32.36 44.23,32.8C44.71,33.25 44.72,34.01 44.26,34.47L43.21,35.52C42.98,35.75 42.98,36.11 43.21,36.34L44.23,37.36C44.98,38.11 44.49,39.38 43.43,39.38C43.13,39.38 42.84,39.27 42.61,39.04L41.56,37.99C41.33,37.76 40.96,37.76 40.74,37.99L39.69,39.04C39.21,39.52 38.43,39.49 37.98,38.98C37.58,38.51 37.63,37.8 38.07,37.36L39.09,36.34C39.31,36.11 39.31,35.75 39.09,35.52L38.04,34.47Z" />

<path
android:fillColor="#FFFFFF"
android:pathData="M15,66.02V67.48C15,70.06 17.09,72.15 19.67,72.15H80.33C82.88,72.15 84.96,70.09 85,67.55V66.02C85,65.38 84.48,64.85 83.83,64.85H16.17C15.52,64.85 15,65.38 15,66.02Z" />

<path
android:fillColor="#FFFFFF"
android:pathData="M32.55,83.28C32.36,82.67 32.71,82.01 33.32,81.82C43.55,78.64 56.47,78.65 66.68,81.82C67.3,82.01 67.64,82.67 67.45,83.28C67.26,83.9 66.6,84.24 65.99,84.05C56.28,81.03 43.72,81.03 34.01,84.05C33.4,84.24 32.74,83.9 32.55,83.28Z" />

<path
android:fillColor="#FFFFFF"
android:pathData="M56.92,77.47C52.43,76.98 47.67,76.97 43.08,77.47C42.26,77.56 41.61,76.79 41.83,75.99L42.02,75.33C42.16,74.83 42.62,74.48 43.14,74.48H56.86C57.38,74.48 57.84,74.83 57.98,75.33L58.17,75.99C58.39,76.79 57.74,77.56 56.92,77.47Z" />

</vector>
2 changes: 2 additions & 0 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
<string name="core_thank_you_dialog_negative_description">We received your feedback and will use it to help improve your learning experience going forward. Thank you for sharing!</string>
<string name="core_no_internet_connection">No internet connection</string>
<string name="core_no_internet_connection_description">Please connect to the internet to view this content.</string>
<string name="core_try_again">Try Again</string>
<string name="core_something_went_wrong_description">Something went wrong</string>
<string name="core_ok" tools:ignore="MissingTranslation">OK</string>
<string name="core_continue" tools:ignore="MissingTranslation">Continue</string>
<string name="core_leaving_the_app" tools:ignore="MissingTranslation">Leaving the app</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ 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
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
Expand Down Expand Up @@ -50,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.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
Expand Down Expand Up @@ -83,10 +84,7 @@ class HtmlUnitFragment : Fragment() {
setContent {
OpenEdXTheme {
val windowSize = rememberWindowSize()

var isLoading by remember {
mutableStateOf(true)
}
val uiState by viewModel.uiState.collectAsState()

var hasInternetConnection by remember {
mutableStateOf(viewModel.isOnline)
Expand Down Expand Up @@ -125,36 +123,43 @@ class HtmlUnitFragment : Fragment() {
.then(border),
contentAlignment = Alignment.TopCenter
) {
if (hasInternetConnection) {
HTMLContentView(
windowSize = windowSize,
url = blockUrl,
cookieManager = viewModel.cookieManager,
apiHostURL = viewModel.apiHostURL,
isLoading = isLoading,
injectJSList = injectJSList,
onCompletionSet = {
viewModel.notifyCompletionSet()
},
onWebPageLoading = {
isLoading = true
},
onWebPageLoaded = {
isLoading = false
if (isAdded) viewModel.setWebPageLoaded(requireContext().assets)
}
)
} else {
ConnectionErrorView(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(MaterialTheme.appColors.background)
) {
if ((uiState is HtmlUnitUIState.Error).not()) {
if (hasInternetConnection) {
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 = {
if ((uiState is HtmlUnitUIState.Error).not()) {
viewModel.onWebPageLoaded()
}
if (isAdded) viewModel.setWebPageLoaded(requireContext().assets)
},
onWebPageLoadError = {
viewModel.onWebPageLoadError()
}
)
} else {
viewModel.onWebPageLoadError()
}
}
if (uiState is HtmlUnitUIState.Error) {
val errorType = (uiState as HtmlUnitUIState.Error).errorType
FullScreenErrorView(errorType = errorType) {
hasInternetConnection = viewModel.isOnline
viewModel.onWebPageLoading()
}
}
if (isLoading && hasInternetConnection) {
if (uiState is HtmlUnitUIState.Loading && hasInternetConnection) {
Box(
modifier = Modifier
.fillMaxSize()
Expand Down Expand Up @@ -200,6 +205,7 @@ private fun HTMLContentView(
onCompletionSet: () -> Unit,
onWebPageLoading: () -> Unit,
onWebPageLoaded: () -> Unit,
onWebPageLoadError: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
Expand Down Expand Up @@ -282,6 +288,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
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +21,9 @@ class HtmlUnitViewModel(
private val notifier: CourseNotifier
) : BaseViewModel() {

private val _uiState = MutableStateFlow<HtmlUnitUIState>(HtmlUnitUIState.Loading)
val uiState = _uiState.asStateFlow()

private val _injectJSList = MutableStateFlow<List<String>>(listOf())
val injectJSList = _injectJSList.asStateFlow()

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
Loading
Loading