Skip to content

Commit

Permalink
feat: survey content (#177)
Browse files Browse the repository at this point in the history
* feat: enabled the survey content type and moved all js injections to the asset folder

* refactor: added comments in js files

* refactor: added a new line in the AssetExt file

* refactor: added ViewModel for the HtmlUnitFragment class

* fix: bug when unable to see Survey content, minor fixes

* refactor: renamed and moved the onWebPageStartLoad function call to the onPageStarted function
  • Loading branch information
dixidroid authored Jan 19, 2024
1 parent 5f06478 commit d1fc2c7
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 41 deletions.
9 changes: 9 additions & 0 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.openedx.course.presentation.info.CourseInfoViewModel
import org.openedx.course.presentation.outline.CourseOutlineViewModel
import org.openedx.course.presentation.section.CourseSectionViewModel
import org.openedx.course.presentation.unit.container.CourseUnitContainerViewModel
import org.openedx.course.presentation.unit.html.HtmlUnitViewModel
import org.openedx.course.presentation.unit.video.EncodedVideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoUnitViewModel
import org.openedx.course.presentation.unit.video.VideoViewModel
Expand Down Expand Up @@ -256,4 +257,12 @@ val screenModule = module {
}

viewModel { (courseId: String) -> WhatsNewViewModel(courseId, get()) }
viewModel {
HtmlUnitViewModel(
get(),
get(),
get(),
get()
)
}
}
3 changes: 2 additions & 1 deletion core/src/main/java/org/openedx/core/BlockType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ enum class BlockType {
SEQUENTIAL{ override fun isContainer() = true },
VERTICAL{ override fun isContainer() = true },
VIDEO{ override fun isContainer() = false },
WORD_CLOUD{ override fun isContainer() = false };
WORD_CLOUD{ override fun isContainer() = false },
SURVEY{ override fun isContainer() = false };

abstract fun isContainer() : Boolean

Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/openedx/core/domain/model/Block.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ data class Block(
val isDragAndDropBlock get() = type == BlockType.DRAG_AND_DROP_V2
val isWordCloudBlock get() = type == BlockType.WORD_CLOUD
val isLTIConsumerBlock get() = type == BlockType.LTI_CONSUMER
val isSurveyBlock get() = type == BlockType.SURVEY
}

data class StudentViewData(
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/org/openedx/core/extension/AssetExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.openedx.core.extension

import android.content.res.AssetManager
import android.util.Log
import java.io.BufferedReader

fun AssetManager.readAsText(fileName: String): String? {
return try {
open(fileName).bufferedReader().use(BufferedReader::readText)
} catch (e: Exception) {
Log.e("AssetExt", "Unable to load file $fileName from assets")
e.printStackTrace()
null
}
}
8 changes: 8 additions & 0 deletions course/src/main/assets/js_injection/completions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//Injection to intercept completion state for xBlocks
$(document).on("ajaxSuccess", function(event, request, settings) {
console.log("loaded url is = " + settings.url);
if (settings.url.includes("publish_completion") &&
request.responseText.includes("ok")) {
javascript:window.callback.completionSet();
}
});
28 changes: 28 additions & 0 deletions course/src/main/assets/js_injection/survey_css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//Injection to fix CSS issues for Survey xBlock
var css = `
.survey-table:not(.poll-results) .survey-option label {
margin-bottom: 0px !important;
}
.survey-table:not(.poll-results) .survey-option .visible-mobile-only {
width: calc(100% - 21px) !important;
}
.survey-table:not(.poll-results) .survey-option input {
width: 13px !important;
height: 13px !important;
}
.survey-percentage .percentage {
width: 54px !important;
}`;
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');

head.appendChild(style);
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class CourseUnitContainerAdapter(
block.isOpenAssessmentBlock ||
block.isDragAndDropBlock ||
block.isWordCloudBlock ||
block.isLTIConsumerBlock -> {
block.isLTIConsumerBlock ||
block.isSurveyBlock -> {
HtmlUnitFragment.newInstance(block.id, block.studentViewUrl)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.openedx.course.presentation.unit.html
import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.util.Log
Expand All @@ -29,30 +30,18 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.zIndex
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.openedx.core.config.Config
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.openedx.core.extension.isEmailValid
import org.openedx.core.system.AppCookieManager
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.CourseCompletionSet
import org.openedx.core.system.notifier.CourseNotifier
import org.openedx.core.ui.ConnectionErrorView
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.rememberWindowSize
import org.openedx.core.ui.roundBorderWithoutBottom
import org.openedx.core.ui.*
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.windowSizeValue
import org.openedx.core.utils.EmailUtil

class HtmlUnitFragment : Fragment() {

private val config by inject<Config>()
private val edxCookieManager by inject<AppCookieManager>()
private val networkConnection by inject<NetworkConnection>()
private val notifier by inject<CourseNotifier>()
private val viewModel by viewModel<HtmlUnitViewModel>()
private var blockId: String = ""
private var blockUrl: String = ""

Expand All @@ -77,18 +66,21 @@ class HtmlUnitFragment : Fragment() {
}

var hasInternetConnection by remember {
mutableStateOf(networkConnection.isOnline())
mutableStateOf(viewModel.isOnline)
}

val injectJSList by viewModel.injectJSList.collectAsState()

val configuration = LocalConfiguration.current

val bottomPadding = if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
72.dp
} else {
0.dp
}
val bottomPadding =
if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
72.dp
} else {
0.dp
}

val border = if (!isSystemInDarkTheme() && !config.isCourseUnitProgressEnabled()) {
val border = if (!isSystemInDarkTheme() && !viewModel.isCourseUnitProgressEnabled) {
Modifier.roundBorderWithoutBottom(
borderWidth = 2.dp,
cornerRadius = 30.dp
Expand All @@ -114,14 +106,19 @@ class HtmlUnitFragment : Fragment() {
HTMLContentView(
windowSize = windowSize,
url = blockUrl,
cookieManager = edxCookieManager,
cookieManager = viewModel.cookieManager,
apiHostURL = viewModel.apiHostURL,
isLoading = isLoading,
injectJSList = injectJSList,
onCompletionSet = {
lifecycleScope.launch {
notifier.send(CourseCompletionSet())
}
viewModel.notifyCompletionSet()
},
onWebPageLoading = {
isLoading = true
},
onWebPageLoaded = {
isLoading = false
viewModel.setWebPageLoaded(requireContext().assets)
}
)
} else {
Expand All @@ -131,7 +128,7 @@ class HtmlUnitFragment : Fragment() {
.fillMaxHeight()
.background(MaterialTheme.appColors.background)
) {
hasInternetConnection = networkConnection.isOnline()
hasInternetConnection = viewModel.isOnline
}
}
if (isLoading && hasInternetConnection) {
Expand Down Expand Up @@ -174,7 +171,11 @@ private fun HTMLContentView(
windowSize: WindowSize,
url: String,
cookieManager: AppCookieManager,
apiHostURL: String,
isLoading: Boolean,
injectJSList: List<String>,
onCompletionSet: () -> Unit,
onWebPageLoading: () -> Unit,
onWebPageLoaded: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
Expand Down Expand Up @@ -204,21 +205,15 @@ private fun HTMLContentView(
}, "callback")
webViewClient = object : WebViewClient() {

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
onWebPageLoading()
}

override fun onPageCommitVisible(view: WebView?, url: String?) {
super.onPageCommitVisible(view, url)
Log.d("HTML", "onPageCommitVisible")
onWebPageLoaded()

evaluateJavascript(
"""
${'$'}(document).ajaxSuccess(function(event, request, settings) {
if (settings.url.includes("publish_completion") &&
request.responseText.includes("ok")) {
javascript:window.callback.completionSet();
}
});
""".trimIndent(), null
)
}

override fun shouldOverrideUrlLoading(
Expand Down Expand Up @@ -250,7 +245,7 @@ private fun HTMLContentView(
request: WebResourceRequest,
errorResponse: WebResourceResponse,
) {
if (request.url.toString() == view.url) {
if (request.url.toString().startsWith(apiHostURL)) {
when (errorResponse.statusCode) {
403, 401, 404 -> {
coroutineScope.launch {
Expand All @@ -275,6 +270,11 @@ private fun HTMLContentView(
isHorizontalScrollBarEnabled = false
loadUrl(url)
}
},
update = { webView ->
if (!isLoading && injectJSList.isNotEmpty()) {
injectJSList.forEach { webView.evaluateJavascript(it, null) }
}
})
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.openedx.course.presentation.unit.html

import android.content.res.AssetManager
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
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.system.AppCookieManager
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.CourseCompletionSet
import org.openedx.core.system.notifier.CourseNotifier

class HtmlUnitViewModel(
private val config: Config,
private val edxCookieManager: AppCookieManager,
private val networkConnection: NetworkConnection,
private val notifier: CourseNotifier
) : BaseViewModel() {

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

val isOnline get() = networkConnection.isOnline()
val isCourseUnitProgressEnabled get() = config.isCourseUnitProgressEnabled()
val apiHostURL get() = config.getApiHostURL()
val cookieManager get() = edxCookieManager

fun setWebPageLoaded(assets: AssetManager) {
if (_injectJSList.value.isNotEmpty()) return

val jsList = mutableListOf<String>()

//Injection to intercept completion state for xBlocks
assets.readAsText("js_injection/completions.js")?.let { jsList.add(it) }
//Injection to fix CSS issues for Survey xBlock
assets.readAsText("js_injection/survey_css.js")?.let { jsList.add(it) }

_injectJSList.value = jsList
}

fun notifyCompletionSet() {
viewModelScope.launch {
notifier.send(CourseCompletionSet())
}
}
}

0 comments on commit d1fc2c7

Please sign in to comment.