From a8e76c473298423318c482f4de1d388d95d5a533 Mon Sep 17 00:00:00 2001 From: Yogesh Bhagat Date: Tue, 13 Jan 2026 20:50:35 +0530 Subject: [PATCH 1/2] add in-app certificate preview --- app/src/main/java/org/openedx/app/AppRouter.kt | 7 +++++++ core/src/main/res/values/strings.xml | 3 +++ .../java/org/openedx/course/presentation/CourseRouter.kt | 2 ++ .../course/presentation/outline/CourseOutlineScreen.kt | 5 +---- .../presentation/outline/CourseOutlineViewModel.kt | 9 +++++++++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/openedx/app/AppRouter.kt b/app/src/main/java/org/openedx/app/AppRouter.kt index 90f6625e8..a2957848c 100644 --- a/app/src/main/java/org/openedx/app/AppRouter.kt +++ b/app/src/main/java/org/openedx/app/AppRouter.kt @@ -320,6 +320,13 @@ class AppRouter : HandoutsWebViewFragment.newInstance(type.name, courseId) ) } + + override fun navigateToCertificate(fm: FragmentManager, title: String, url: String) { + replaceFragmentWithBackStack( + fm, + WebContentFragment.newInstance(title = title, url = url) + ) + } // endregion // region DiscussionRouter diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 4702b8198..008ae9caf 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -200,4 +200,7 @@ Language + + + Certificate diff --git a/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt b/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt index 1f874e055..2d833787f 100644 --- a/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt +++ b/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt @@ -66,4 +66,6 @@ interface CourseRouter { fun navigateToVideoQuality(fm: FragmentManager, videoQualityType: VideoQualityType) fun navigateToDiscover(fm: FragmentManager) + + fun navigateToCertificate(fm: FragmentManager, title: String, url: String) } diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt index 27c4594da..b02435cae 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt @@ -33,8 +33,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.AndroidUriHandler -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource @@ -87,7 +85,6 @@ fun CourseOutlineScreen( val uiState by viewModel.uiState.collectAsState() val uiMessage by viewModel.uiMessage.collectAsState(null) val resumeBlockId by viewModel.resumeBlockId.collectAsState("") - val context = LocalContext.current LaunchedEffect(resumeBlockId) { if (resumeBlockId.isNotEmpty()) { @@ -156,7 +153,7 @@ fun CourseOutlineScreen( onCertificateClick = { viewModel.viewCertificateTappedEvent() it.takeIfNotEmpty() - ?.let { url -> AndroidUriHandler(context).openUri(url) } + ?.let { url -> viewModel.navigateToCertificate(fragmentManager, url) } } ) } diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt index f28013272..44964e964 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt @@ -376,6 +376,15 @@ class CourseOutlineViewModel( ) } + fun navigateToCertificate(fm: FragmentManager, url: String) { + viewCertificateTappedEvent() + courseRouter.navigateToCertificate( + fm = fm, + title = resourceManager.getString(org.openedx.core.R.string.core_certificate), + url = url + ) + } + private fun resumeCourseTappedEvent(blockId: String) { val currentState = uiState.value if (currentState is CourseOutlineUIState.CourseData) { From fdf28d4f53d3b7a317e1aeec4cd0180c579f34da Mon Sep 17 00:00:00 2001 From: Yogesh Bhagat Date: Tue, 13 Jan 2026 20:52:19 +0530 Subject: [PATCH 2/2] hide the header-footer from webview using centralized css injection --- .../org/openedx/core/extension/WebViewExt.kt | 51 +++++++++++++++++++ .../org/openedx/core/ui/WebContentScreen.kt | 22 ++++---- 2 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/org/openedx/core/extension/WebViewExt.kt diff --git a/core/src/main/java/org/openedx/core/extension/WebViewExt.kt b/core/src/main/java/org/openedx/core/extension/WebViewExt.kt new file mode 100644 index 000000000..ca47b1eaf --- /dev/null +++ b/core/src/main/java/org/openedx/core/extension/WebViewExt.kt @@ -0,0 +1,51 @@ +package org.openedx.core.extension + +import android.webkit.WebView + +/** + * Injects CSS to hide common headers and footers in web pages. + * + * Also removes padding/margins from body to eliminate empty space. + */ +fun WebView.injectHeaderFooterHidingCss() { + val css = """ + /* Hide common site headers/footers */ + header, footer, + [role="banner"], [role="contentinfo"], + .site-header, .site-footer, + .global-header, .global-footer, + .header, .footer, + .navbar-fixed-top, .navbar, .topbar, + .bottom-bar, .cookie-banner, .gdpr-banner, + .certificate .wrapper-banner.wrapper-banner-user, + #header, #footer, #masthead, #site-footer, #site-header { + display: none !important; + visibility: hidden !important; + height: 0 !important; + min-height: 0 !important; + max-height: 0 !important; + margin: 0 !important; + padding: 0 !important; + border: 0 !important; + } + + /* Remove empty space created by fixed headers */ + body { + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + } + """ + + val js = """ + (function() { + var style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(`$css`)); + document.head.appendChild(style); + })(); + """.trimIndent() + + evaluateJavascript(js, null) +} diff --git a/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt b/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt index 296f9999e..48657ede0 100644 --- a/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt +++ b/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex +import org.openedx.core.extension.injectHeaderFooterHidingCss import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.getDarkThemeFromPreferences import org.openedx.core.utils.EmailUtil @@ -53,6 +54,7 @@ fun WebContentScreen( onBackClick: () -> Unit, htmlBody: String? = null, contentUrl: String? = null, + hideHeaderFooter: Boolean = true, ) { val scaffoldState = rememberScaffoldState() Scaffold( @@ -111,6 +113,7 @@ fun WebContentScreen( apiHostUrl = apiHostUrl, body = htmlBody, contentUrl = contentUrl, + hideHeaderFooter = hideHeaderFooter, onWebPageLoaded = { webViewAlpha = 1f } @@ -129,6 +132,7 @@ private fun WebViewContent( apiHostUrl: String? = null, body: String? = null, contentUrl: String? = null, + hideHeaderFooter: Boolean = true, onWebPageLoaded: () -> Unit ) { val context = LocalContext.current @@ -165,11 +169,12 @@ private fun WebViewContent( override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) - val css = when { - url?.contains("privacy", ignoreCase = true) == true -> PRIVACY_PAGE_CSS - else -> return + if (hideHeaderFooter) { + if (url?.contains("privacy", ignoreCase = true) == true) { + injectCss(view, PRIVACY_PAGE_ADDITIONAL_CSS) + } + view?.injectHeaderFooterHidingCss() } - injectCss(view, css) } } with(settings) { @@ -214,14 +219,7 @@ private fun WebViewContent( ) } -private const val PRIVACY_PAGE_CSS = """ - header, footer { - display: none !important; - } - body { - margin-top: 0 !important; - padding-top: 0 !important; - } +private const val PRIVACY_PAGE_ADDITIONAL_CSS = """ .content-wrapper { padding: 0 !important; margin: 0;