From 3d3a732c45aea6366207a6077d136f093c236a25 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Wed, 19 Mar 2025 15:00:22 +0200 Subject: [PATCH 1/5] feat: replaced text with WebView to support rich formatting --- .../java/org/openedx/core/ui/ComposeCommon.kt | 16 ++++++++++++++++ .../discussion/presentation/ui/DiscussionUI.kt | 7 ++----- 2 files changed, 18 insertions(+), 5 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 aaaa0711d..09874dd28 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -2,6 +2,7 @@ package org.openedx.core.ui import android.os.Build import android.os.Build.VERSION.SDK_INT +import android.webkit.WebView import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -105,6 +106,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex import coil.ImageLoader import coil.compose.AsyncImage @@ -1404,6 +1406,20 @@ private fun RoundTab( } } +@Composable +fun HtmlTextView(htmlContent: String, modifier: Modifier = Modifier) { + AndroidView( + modifier = modifier, + factory = { context -> + WebView(context).apply { + settings.javaScriptEnabled = false + settings.loadsImagesAutomatically = true + loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null) + } + }, + ) +} + @Preview @Composable private fun StaticSearchBarPreview() { diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt index 64dd4dcd0..94c795bc6 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt @@ -50,6 +50,7 @@ import coil.request.ImageRequest import org.openedx.core.domain.model.ProfileImage import org.openedx.core.extension.TextConverter import org.openedx.core.ui.AutoSizeText +import org.openedx.core.ui.HtmlTextView import org.openedx.core.ui.HyperlinkImageText import org.openedx.core.ui.IconText import org.openedx.core.ui.theme.OpenEdXTheme @@ -162,11 +163,7 @@ fun ThreadMainItem( ) } Spacer(modifier = Modifier.height(24.dp)) - HyperlinkImageText( - title = thread.title, - imageText = thread.parsedRenderedBody, - linkTextColor = MaterialTheme.appColors.primary - ) + HtmlTextView(thread.rawBody) Spacer(modifier = Modifier.height(24.dp)) Row( Modifier From 8b028e5d2241924474251ffeabdf01d40d91380f Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Wed, 19 Mar 2025 19:30:44 +0200 Subject: [PATCH 2/5] feat: html renderer --- .../java/org/openedx/core/ui/ComposeCommon.kt | 150 --------- .../java/org/openedx/core/ui/HTMLRenderer.kt | 291 ++++++++++++++++++ .../responses/DiscussionResponsesFragment.kt | 25 +- .../presentation/ui/DiscussionUI.kt | 20 +- 4 files changed, 318 insertions(+), 168 deletions(-) create mode 100644 core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt 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 09874dd28..286d1bdb0 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -1,8 +1,5 @@ package org.openedx.core.ui -import android.os.Build -import android.os.Build.VERSION.SDK_INT -import android.webkit.WebView import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -20,7 +17,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -82,7 +78,6 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -106,17 +101,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex -import coil.ImageLoader -import coil.compose.AsyncImage -import coil.decode.GifDecoder -import coil.decode.ImageDecoderDecoder import kotlinx.coroutines.launch import org.openedx.core.NoContentScreenType import org.openedx.core.R import org.openedx.core.domain.model.RegistrationField -import org.openedx.core.extension.LinkedImageText import org.openedx.core.presentation.global.ErrorType import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors @@ -510,131 +499,6 @@ fun HyperlinkText( ) } -@Composable -fun HyperlinkImageText( - modifier: Modifier = Modifier, - title: String = "", - imageText: LinkedImageText, - textStyle: TextStyle = TextStyle.Default, - linkTextColor: Color = MaterialTheme.appColors.primary, - linkTextFontWeight: FontWeight = FontWeight.Normal, - linkTextDecoration: TextDecoration = TextDecoration.None, - fontSize: TextUnit = TextUnit.Unspecified, -) { - val fullText = imageText.text - val hyperLinks = imageText.links - val annotatedString = buildAnnotatedString { - if (title.isNotEmpty()) { - append(title) - append("\n\n") - } - append(fullText) - addStyle( - style = SpanStyle( - color = MaterialTheme.appColors.textPrimary, - fontSize = fontSize - ), - start = 0, - end = this.length - ) - - for ((key, value) in hyperLinks) { - val startIndex = this.toString().indexOf(key) - if (startIndex == -1) continue - val endIndex = startIndex + key.length - addStyle( - style = SpanStyle( - color = linkTextColor, - fontSize = fontSize, - fontWeight = linkTextFontWeight, - textDecoration = linkTextDecoration - ), - start = startIndex, - end = endIndex - ) - addStringAnnotation( - tag = "URL", - annotation = value, - start = startIndex, - end = endIndex - ) - } - if (title.isNotEmpty()) { - addStyle( - style = SpanStyle( - color = MaterialTheme.appColors.textPrimary, - fontSize = MaterialTheme.appTypography.titleLarge.fontSize, - fontWeight = MaterialTheme.appTypography.titleLarge.fontWeight - ), - start = 0, - end = title.length - ) - } - for (item in imageText.headers) { - val startIndex = this.toString().indexOf(item) - if (startIndex == -1) continue - val endIndex = startIndex + item.length - addStyle( - style = SpanStyle( - color = MaterialTheme.appColors.textPrimary, - fontSize = MaterialTheme.appTypography.titleLarge.fontSize, - fontWeight = MaterialTheme.appTypography.titleLarge.fontWeight - ), - start = startIndex, - end = endIndex - ) - } - addStyle( - style = SpanStyle( - fontSize = fontSize - ), - start = 0, - end = this.length - ) - } - - val uriHandler = LocalUriHandler.current - val context = LocalContext.current - val imageLoader = ImageLoader.Builder(context) - .components { - if (SDK_INT >= Build.VERSION_CODES.P) { - add(ImageDecoderDecoder.Factory()) - } else { - add(GifDecoder.Factory()) - } - } - .build() - - Column(Modifier.fillMaxWidth()) { - BasicText( - text = annotatedString, - modifier = modifier.pointerInput(Unit) { - detectTapGestures { offset -> - val position = offset.x.toInt() - annotatedString.getStringAnnotations("URL", position, position) - .firstOrNull()?.let { stringAnnotation -> - uriHandler.openUri(stringAnnotation.item) - } - } - }, - style = textStyle - ) - imageText.imageLinks.values.forEach { - Spacer(Modifier.height(8.dp)) - AsyncImage( - modifier = Modifier - .fillMaxWidth() - .heightIn(0.dp, 360.dp), - contentScale = ContentScale.Fit, - model = it, - contentDescription = null, - imageLoader = imageLoader - ) - } - Spacer(Modifier.height(16.dp)) - } -} - @Composable fun SheetContent( searchValue: TextFieldValue, @@ -1406,20 +1270,6 @@ private fun RoundTab( } } -@Composable -fun HtmlTextView(htmlContent: String, modifier: Modifier = Modifier) { - AndroidView( - modifier = modifier, - factory = { context -> - WebView(context).apply { - settings.javaScriptEnabled = false - settings.loadsImagesAutomatically = true - loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null) - } - }, - ) -} - @Preview @Composable private fun StaticSearchBarPreview() { diff --git a/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt b/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt new file mode 100644 index 000000000..7f4992ea0 --- /dev/null +++ b/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt @@ -0,0 +1,291 @@ +package org.openedx.core.ui + +import android.content.Intent +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times +import androidx.core.net.toUri +import coil.compose.AsyncImage +import coil.request.ImageRequest +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode +import org.openedx.core.R +import org.openedx.core.ui.theme.appColors + +@Composable +fun RenderHtmlContent(html: String) { + val document = remember(html) { Jsoup.parse(html) } + val bodyElements = document.body().children() + Column { + bodyElements.forEach { element -> + RenderBlockElement(element) + } + } +} + +@Composable +private fun RenderClickableText(annotated: AnnotatedString) { + val context = LocalContext.current + val hasLink = annotated.getStringAnnotations("URL", 0, annotated.length).isNotEmpty() + var textLayoutResult by remember { mutableStateOf(null) } + val modifier = if (hasLink) { + Modifier.pointerInput(annotated) { + detectTapGestures { offset -> + textLayoutResult?.let { layoutResult -> + val position = layoutResult.getOffsetForPosition(offset) + annotated.getStringAnnotations("URL", position, position) + .firstOrNull()?.let { annotation -> + val intent = Intent(Intent.ACTION_VIEW, annotation.item.toUri()) + context.startActivity(intent) + } + } + } + } + } else { + Modifier + } + Text( + text = annotated, + modifier = modifier, + color = MaterialTheme.appColors.textPrimary, + onTextLayout = { textLayoutResult = it } + ) +} + +@Composable +private fun RenderParagraph(element: Element) { + val segments = extractSegmentsFromNodes(element.childNodes()) + Column(modifier = Modifier.padding(vertical = 4.dp)) { + segments.forEach { segment -> + when (segment) { + is List<*> -> { + @Suppress("UNCHECKED_CAST") + val nodes = segment as List + val annotated = buildAnnotatedStringFromNodes(nodes) + RenderClickableText(annotated) + } + + is Element -> { + RenderBlockElement(segment) + } + } + } + } +} + +private fun extractSegmentsFromNodes(nodes: List): List { + val segments = mutableListOf() + val currentSegment = mutableListOf() + + for (node in nodes) { + if (node is Element) { + val tagName = node.tagName() + if (tagName == "img" || tagName == "ul" || tagName == "ol" || tagName == "blockquote") { + flush(currentSegment, segments) + segments.add(node) + } else if (node.select("img").isNotEmpty()) { + flush(currentSegment, segments) + segments.addAll(extractSegmentsFromNodes(node.childNodes())) + } else { + currentSegment.add(node) + } + } else { + currentSegment.add(node) + } + } + flush(currentSegment, segments) + return segments +} + +@Composable +private fun RenderBlockElement(element: Element, indent: Int = 0) { + when (element.tagName()) { + "p" -> { + RenderParagraph(element) + } + + "ul" -> { + Column(modifier = Modifier.padding(start = (indent + 1) * 16.dp)) { + element.children().forEach { child -> + if (child.tagName() == "li") { + Row( + modifier = Modifier.padding(vertical = 2.dp), + ) { + Text( + modifier = Modifier.padding(top = 4.dp), + text = AnnotatedString("• "), + style = TextStyle(fontWeight = FontWeight.Bold), + color = MaterialTheme.appColors.textPrimary + ) + RenderBlockElement(child, indent + 1) + } + } + } + } + } + + "ol" -> { + Column(modifier = Modifier.padding(start = (indent + 1) * 16.dp)) { + element.children().forEachIndexed { index, child -> + if (child.tagName() == "li") { + Row( + modifier = Modifier.padding(vertical = 2.dp), + ) { + Text( + modifier = Modifier.padding(top = 4.dp), + text = AnnotatedString("${index + 1}. "), + color = MaterialTheme.appColors.textPrimary + ) + RenderBlockElement(child, indent + 1) + } + } + } + } + } + + "li" -> { + RenderParagraph(element) + } + + "blockquote" -> { + Row( + modifier = Modifier.height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box( + modifier = Modifier + .width(2.dp) + .fillMaxHeight() + .background(MaterialTheme.appColors.cardViewBorder) + ) + Column { + element.children().forEach { child -> + RenderBlockElement(child) + } + } + } + } + + "img" -> { + val src = element.attr("src") + AsyncImage( + modifier = Modifier.fillMaxWidth(), + model = ImageRequest.Builder(LocalContext.current) + .data(src) + .error(R.drawable.core_no_image_course) + .placeholder(R.drawable.core_no_image_course) + .build(), + contentDescription = null, + contentScale = ContentScale.FillWidth + ) + } + + else -> { + RenderParagraph(element) + } + } +} + +@Composable +private fun AnnotatedString.Builder.AppendNodes(nodes: List) { + nodes.forEach { node -> + when (node) { + is TextNode -> append(node.text()) + is Element -> AppendElement(node) + } + } +} + +@Composable +private fun AnnotatedString.Builder.AppendElement(element: Element) { + when (element.tagName()) { + "br" -> append("\n") + "strong" -> withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + AppendNodes(element.childNodes()) + } + + "em" -> withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { + AppendNodes(element.childNodes()) + } + + "code" -> withStyle(SpanStyle(fontFamily = FontFamily.Monospace)) { + AppendNodes(element.childNodes()) + } + + "span" -> { + val styleAttr = element.attr("style") + if (styleAttr.contains("text-decoration: underline", ignoreCase = true)) { + withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) { + AppendNodes(element.childNodes()) + } + } else { + AppendNodes(element.childNodes()) + } + } + + "a" -> { + val href = element.attr("href") + val start = this.length + AppendNodes(element.childNodes()) + val end = this.length + addStyle( + SpanStyle( + color = MaterialTheme.appColors.primary, + textDecoration = TextDecoration.Underline + ), + start, + end + ) + addStringAnnotation(tag = "URL", annotation = href, start = start, end = end) + } + + else -> AppendNodes(element.childNodes()) + } +} + +@Composable +private fun buildAnnotatedStringFromNodes(nodes: List): AnnotatedString { + return AnnotatedString.Builder().apply { + AppendNodes(nodes) + }.toAnnotatedString() +} + +private fun flush(currentSegment: MutableList, segments: MutableList) { + if (currentSegment.isNotEmpty()) { + segments.add(currentSegment.toList()) + currentSegment.clear() + } +} diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt index 736455a7e..b3a28fe85 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt @@ -12,11 +12,9 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -47,6 +45,7 @@ import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -56,7 +55,9 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.ViewCompositionStrategy @@ -374,23 +375,31 @@ private fun DiscussionResponsesScreen( } items(uiState.childComments) { comment -> + var itemHeight by remember { mutableIntStateOf(0) } + val boxHeight = if (itemHeight > 0) { + Modifier.height(with(LocalDensity.current) { itemHeight.toDp() }) + } else { + Modifier + } Row( Modifier .fillMaxWidth() - .height(IntrinsicSize.Min) .padding(start = paddingContent), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier - .fillMaxHeight() .width(1.dp) + .then(boxHeight) .background(MaterialTheme.appColors.cardViewBorder) ) CommentMainItem( modifier = Modifier .padding(4.dp) - .fillMaxWidth(), + .fillMaxWidth() + .onGloballyPositioned { coordinates -> + itemHeight = coordinates.size.height + }, comment = comment, onClick = { action, commentId, bool -> onItemClick(action, commentId, bool) @@ -412,7 +421,11 @@ private fun DiscussionResponsesScreen( } } } - if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) { + if (scrollState.shouldLoadMore( + firstVisibleIndex, + LOAD_MORE_THRESHOLD + ) + ) { paginationCallBack() } } diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt index 94c795bc6..68dc09017 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt @@ -1,5 +1,3 @@ -@file:OptIn(ExperimentalComposeUiApi::class) - package org.openedx.discussion.presentation.ui import android.content.res.Configuration.UI_MODE_NIGHT_NO @@ -31,7 +29,6 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape @@ -50,9 +47,8 @@ import coil.request.ImageRequest import org.openedx.core.domain.model.ProfileImage import org.openedx.core.extension.TextConverter import org.openedx.core.ui.AutoSizeText -import org.openedx.core.ui.HtmlTextView -import org.openedx.core.ui.HyperlinkImageText import org.openedx.core.ui.IconText +import org.openedx.core.ui.RenderHtmlContent import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appShapes @@ -163,7 +159,9 @@ fun ThreadMainItem( ) } Spacer(modifier = Modifier.height(24.dp)) - HtmlTextView(thread.rawBody) + RenderHtmlContent( + html = thread.rawBody, + ) Spacer(modifier = Modifier.height(24.dp)) Row( Modifier @@ -313,9 +311,8 @@ fun CommentItem( ) } Spacer(modifier = Modifier.height(14.dp)) - HyperlinkImageText( - imageText = comment.parsedRenderedBody, - linkTextColor = MaterialTheme.appColors.primary + RenderHtmlContent( + html = comment.rawBody, ) Spacer(modifier = Modifier.height(16.dp)) Row( @@ -452,9 +449,8 @@ fun CommentMainItem( } } Spacer(modifier = Modifier.height(14.dp)) - HyperlinkImageText( - imageText = comment.parsedRenderedBody, - linkTextColor = MaterialTheme.appColors.primary + RenderHtmlContent( + html = comment.rawBody, ) Spacer(modifier = Modifier.height(16.dp)) Row( From 1295b5b84caf479e87ed3f411c3991e6e33847e7 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Tue, 1 Apr 2025 15:28:09 +0300 Subject: [PATCH 3/5] fix: mapping error --- .../openedx/core/extension/TextConverter.kt | 76 ------------------- .../data/model/response/CommentsResponse.kt | 2 - .../data/model/response/ThreadsResponse.kt | 2 - .../domain/model/DiscussionComment.kt | 2 - .../openedx/discussion/domain/model/Thread.kt | 2 - .../comments/DiscussionCommentsFragment.kt | 3 - .../responses/DiscussionResponsesFragment.kt | 11 ++- .../search/DiscussionSearchThreadFragment.kt | 2 - .../threads/DiscussionThreadsFragment.kt | 2 - .../presentation/ui/DiscussionUI.kt | 3 - .../DiscussionCommentsViewModelTest.kt | 3 - .../DiscussionResponsesViewModelTest.kt | 42 ---------- .../DiscussionSearchThreadViewModelTest.kt | 2 - .../DiscussionAddThreadViewModelTest.kt | 2 - .../threads/DiscussionThreadsViewModelTest.kt | 2 - 15 files changed, 5 insertions(+), 151 deletions(-) diff --git a/core/src/main/java/org/openedx/core/extension/TextConverter.kt b/core/src/main/java/org/openedx/core/extension/TextConverter.kt index 22879220e..f01d33aa3 100644 --- a/core/src/main/java/org/openedx/core/extension/TextConverter.kt +++ b/core/src/main/java/org/openedx/core/extension/TextConverter.kt @@ -1,8 +1,6 @@ package org.openedx.core.extension -import android.os.Parcelable import android.util.Patterns -import kotlinx.parcelize.Parcelize import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.select.Elements @@ -36,84 +34,10 @@ object TextConverter : KoinComponent { return LinkedText(text, linksMap.toMap()) } - fun textToLinkedImageText(html: String): LinkedImageText { - val doc: Document = - Jsoup.parse(html) - val links: Elements = doc.select("a[href]") - var text = doc.text() - val headers = getHeaders(doc) - val linksMap = mutableMapOf() - for (link in links) { - if (isLinkValid(link.attr("href"))) { - val linkText = if (link.hasText()) link.text() else link.attr("href") - linksMap[linkText] = link.attr("href") - } else { - val resultLink = - if (link.attr("href").isNotEmpty() && link.attr("href")[0] == '/') { - link.attr("href").substring(1) - } else { - link.attr("href") - } - if (resultLink.isNotEmpty() && isLinkValid(config.getApiHostURL() + resultLink)) { - linksMap[link.text()] = config.getApiHostURL() + resultLink - } - } - } - text = setSpacesForHeaders(text, headers) - return LinkedImageText( - text, - linksMap.toMap(), - getImageLinks(doc), - headers - ) - } - fun isLinkValid(link: String) = Patterns.WEB_URL.matcher(link.lowercase()).matches() - - @Suppress("MagicNumber") - private fun getHeaders(document: Document): List { - val headersList = mutableListOf() - for (index in 1..6) { - if (document.select("h$index").hasText()) { - headersList.add(document.select("h$index").text()) - } - } - return headersList.toList() - } - - private fun setSpacesForHeaders(text: String, headers: List): String { - var result = text - headers.forEach { - val startIndex = text.indexOf(it) - val endIndex = startIndex + it.length + 1 - result = text.replaceRange(startIndex, endIndex, it + "\n") - } - return result - } - - private fun getImageLinks(document: Document): Map { - val imageLinks = mutableMapOf() - val elements = document.getElementsByTag("img") - for (element in elements) { - if (element.hasAttr("alt")) { - imageLinks[element.attr("alt")] = element.attr("src") - } else { - imageLinks[element.attr("src")] = element.attr("src") - } - } - return imageLinks.toMap() - } } data class LinkedText( val text: String, val links: Map ) - -@Parcelize -data class LinkedImageText( - val text: String, - val links: Map, - val imageLinks: Map, - val headers: List -) : Parcelable diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt b/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt index 77b50e504..08bf03a0f 100644 --- a/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt +++ b/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt @@ -3,7 +3,6 @@ package org.openedx.discussion.data.model.response import com.google.gson.annotations.SerializedName import org.openedx.core.data.model.Pagination import org.openedx.core.data.model.ProfileImage -import org.openedx.core.extension.TextConverter import org.openedx.discussion.domain.model.CommentsData import org.openedx.discussion.domain.model.DiscussionComment @@ -78,7 +77,6 @@ data class CommentResult( updatedAt, rawBody, renderedBody, - TextConverter.textToLinkedImageText(renderedBody), abuseFlagged, voted, voteCount, diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt b/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt index b34005c04..c8f56ff8e 100644 --- a/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt +++ b/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt @@ -3,7 +3,6 @@ package org.openedx.discussion.data.model.response import com.google.gson.annotations.SerializedName import org.openedx.core.data.model.Pagination import org.openedx.core.data.model.ProfileImage -import org.openedx.core.extension.TextConverter import org.openedx.discussion.domain.model.DiscussionType import org.openedx.discussion.domain.model.ThreadsData @@ -104,7 +103,6 @@ data class ThreadsResponse( updatedAt, rawBody, renderedBody, - TextConverter.textToLinkedImageText(renderedBody), abuseFlagged, voted, voteCount, diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt index 13a2fba9c..6ffcc3d64 100644 --- a/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt +++ b/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt @@ -3,7 +3,6 @@ package org.openedx.discussion.domain.model import android.os.Parcelable import kotlinx.parcelize.Parcelize import org.openedx.core.domain.model.ProfileImage -import org.openedx.core.extension.LinkedImageText @Parcelize data class DiscussionComment( @@ -14,7 +13,6 @@ data class DiscussionComment( val updatedAt: String, val rawBody: String, val renderedBody: String, - val parsedRenderedBody: LinkedImageText, val abuseFlagged: Boolean, val voted: Boolean, val voteCount: Int, diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt index 9b7f2498c..c87cbc368 100644 --- a/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt +++ b/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt @@ -3,7 +3,6 @@ package org.openedx.discussion.domain.model import android.os.Parcelable import kotlinx.parcelize.Parcelize import org.openedx.core.domain.model.ProfileImage -import org.openedx.core.extension.LinkedImageText import org.openedx.discussion.R @Parcelize @@ -15,7 +14,6 @@ data class Thread( val updatedAt: String, val rawBody: String, val renderedBody: String, - val parsedRenderedBody: LinkedImageText, val abuseFlagged: Boolean, val voted: Boolean, val voteCount: Int, diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt index b33646b9a..5bbee6ff9 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt @@ -72,7 +72,6 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.domain.model.ProfileImage -import org.openedx.core.extension.TextConverter import org.openedx.core.ui.BackBtn import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.displayCutoutForLandscape @@ -550,7 +549,6 @@ private val mockThread = org.openedx.discussion.domain.model.Thread( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, @@ -585,7 +583,6 @@ private val mockComment = DiscussionComment( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt index b3a28fe85..47711c8c6 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt @@ -76,7 +76,6 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.domain.model.ProfileImage -import org.openedx.core.extension.TextConverter import org.openedx.core.ui.BackBtn import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.displayCutoutForLandscape @@ -86,6 +85,7 @@ import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appShapes import org.openedx.core.ui.theme.appTypography +import org.openedx.discussion.R import org.openedx.discussion.domain.model.DiscussionComment import org.openedx.discussion.presentation.DiscussionRouter import org.openedx.discussion.presentation.comments.DiscussionCommentsFragment @@ -218,7 +218,7 @@ private fun DiscussionResponsesScreen( val focusManager = LocalFocusManager.current val firstVisibleIndex = remember { - mutableStateOf(scrollState.firstVisibleItemIndex) + mutableIntStateOf(scrollState.firstVisibleItemIndex) } val pullRefreshState = rememberPullRefreshState(refreshing = refreshing, onRefresh = { onSwipeRefresh() }) @@ -363,7 +363,7 @@ private fun DiscussionResponsesScreen( .padding(horizontal = paddingContent) .padding(top = 24.dp, bottom = 8.dp), text = pluralStringResource( - id = org.openedx.discussion.R.plurals.discussion_comments, + id = R.plurals.discussion_comments, uiState.mainComment.childCount, uiState.mainComment.childCount ), @@ -462,7 +462,7 @@ private fun DiscussionResponsesScreen( placeholder = { Text( text = stringResource( - id = org.openedx.discussion.R.string.discussion_add_comment + id = R.string.discussion_add_comment ), color = MaterialTheme.appColors.textFieldHint, style = MaterialTheme.appTypography.labelLarge, @@ -493,7 +493,7 @@ private fun DiscussionResponsesScreen( Icon( modifier = Modifier.padding(7.dp), painter = painterResource( - id = org.openedx.discussion.R.drawable.discussion_ic_send + id = R.drawable.discussion_ic_send ), contentDescription = null, tint = iconButtonColor @@ -591,7 +591,6 @@ private val mockComment = DiscussionComment( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt index a8a835603..e67fe40b3 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt @@ -60,7 +60,6 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.R -import org.openedx.core.extension.TextConverter import org.openedx.core.ui.BackBtn import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.SearchBar @@ -414,7 +413,6 @@ private val mockThread = org.openedx.discussion.domain.model.Thread( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt index b68379afe..f610dfa9d 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt @@ -74,7 +74,6 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.FragmentViewType -import org.openedx.core.extension.TextConverter import org.openedx.core.ui.BackBtn import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.IconText @@ -742,7 +741,6 @@ private val mockThread = org.openedx.discussion.domain.model.Thread( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt index 68dc09017..1a544e40a 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt @@ -45,7 +45,6 @@ import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest import org.openedx.core.domain.model.ProfileImage -import org.openedx.core.extension.TextConverter import org.openedx.core.ui.AutoSizeText import org.openedx.core.ui.IconText import org.openedx.core.ui.RenderHtmlContent @@ -716,7 +715,6 @@ private val mockComment = DiscussionComment( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, @@ -742,7 +740,6 @@ private val mockThread = org.openedx.discussion.domain.model.Thread( "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt index e9323270e..f3a9704f5 100644 --- a/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt +++ b/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt @@ -27,7 +27,6 @@ import org.junit.rules.TestRule import org.openedx.core.R import org.openedx.core.data.storage.CorePreferences import org.openedx.core.domain.model.Pagination -import org.openedx.core.extension.TextConverter import org.openedx.discussion.domain.interactor.DiscussionInteractor import org.openedx.discussion.domain.model.CommentsData import org.openedx.discussion.domain.model.DiscussionComment @@ -68,7 +67,6 @@ class DiscussionCommentsViewModelTest { "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, @@ -107,7 +105,6 @@ class DiscussionCommentsViewModelTest { "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt index ac57556bc..bb3579eda 100644 --- a/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt +++ b/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt @@ -22,11 +22,9 @@ import org.junit.rules.TestRule import org.openedx.core.R import org.openedx.core.data.storage.CorePreferences import org.openedx.core.domain.model.Pagination -import org.openedx.core.extension.LinkedImageText import org.openedx.discussion.domain.interactor.DiscussionInteractor import org.openedx.discussion.domain.model.CommentsData import org.openedx.discussion.domain.model.DiscussionComment -import org.openedx.discussion.domain.model.DiscussionType import org.openedx.discussion.system.notifier.DiscussionNotifier import org.openedx.foundation.presentation.UIMessage import org.openedx.foundation.system.ResourceManager @@ -49,45 +47,6 @@ class DiscussionResponsesViewModelTest { private val somethingWrong = "Something went wrong" private val commentAddedSuccessfully = "Comment Successfully added" - //region mockThread - - val mockThread = org.openedx.discussion.domain.model.Thread( - "", - "", - "", - "", - "", - "", - "", - LinkedImageText("", emptyMap(), emptyMap(), emptyList()), - false, - true, - 20, - emptyList(), - false, - "", - "", - "", - "", - DiscussionType.DISCUSSION, - "", - "", - "Discussion title long Discussion title long good item", - true, - false, - true, - 21, - 4, - false, - false, - mapOf(), - 0, - false, - false - ) - - //endregion - //region mockComment private val mockComment = DiscussionComment( @@ -98,7 +57,6 @@ class DiscussionResponsesViewModelTest { "", "", "", - LinkedImageText("", emptyMap(), emptyMap(), emptyList()), false, true, 20, diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt index 39e01c194..14eb3f062 100644 --- a/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt +++ b/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt @@ -24,7 +24,6 @@ import org.junit.Test import org.junit.rules.TestRule import org.openedx.core.R import org.openedx.core.domain.model.Pagination -import org.openedx.core.extension.TextConverter import org.openedx.discussion.domain.interactor.DiscussionInteractor import org.openedx.discussion.domain.model.DiscussionType import org.openedx.discussion.domain.model.ThreadsData @@ -59,7 +58,6 @@ class DiscussionSearchThreadViewModelTest { "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt index 9dc8ba339..65b4a1ae8 100644 --- a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt +++ b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt @@ -19,7 +19,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.openedx.core.R -import org.openedx.core.extension.TextConverter import org.openedx.discussion.domain.interactor.DiscussionInteractor import org.openedx.discussion.domain.model.DiscussionType import org.openedx.discussion.domain.model.Topic @@ -53,7 +52,6 @@ class DiscussionAddThreadViewModelTest { "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt index ae4f966ba..15e49570d 100644 --- a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt +++ b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt @@ -26,7 +26,6 @@ import org.junit.Test import org.junit.rules.TestRule import org.openedx.core.R import org.openedx.core.domain.model.Pagination -import org.openedx.core.extension.TextConverter import org.openedx.discussion.domain.interactor.DiscussionInteractor import org.openedx.discussion.domain.model.DiscussionType import org.openedx.discussion.domain.model.ThreadsData @@ -63,7 +62,6 @@ class DiscussionThreadsViewModelTest { "", "", "", - TextConverter.textToLinkedImageText(""), false, true, 20, From 2edd22a08d60e3d8c1ba14805b21f0325a000a71 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Tue, 1 Apr 2025 16:30:35 +0300 Subject: [PATCH 4/5] fix: changes according code review --- .../main/java/org/openedx/core/ui/HTMLRenderer.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt b/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt index 7f4992ea0..0105e2cff 100644 --- a/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt +++ b/core/src/main/java/org/openedx/core/ui/HTMLRenderer.kt @@ -1,5 +1,6 @@ package org.openedx.core.ui +import android.content.ActivityNotFoundException import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures @@ -68,8 +69,12 @@ private fun RenderClickableText(annotated: AnnotatedString) { val position = layoutResult.getOffsetForPosition(offset) annotated.getStringAnnotations("URL", position, position) .firstOrNull()?.let { annotation -> - val intent = Intent(Intent.ACTION_VIEW, annotation.item.toUri()) - context.startActivity(intent) + try { + val intent = Intent(Intent.ACTION_VIEW, annotation.item.toUri()) + context.startActivity(intent) + } catch (e: ActivityNotFoundException) { + e.printStackTrace() + } } } } @@ -92,8 +97,7 @@ private fun RenderParagraph(element: Element) { segments.forEach { segment -> when (segment) { is List<*> -> { - @Suppress("UNCHECKED_CAST") - val nodes = segment as List + val nodes = segment.filterIsInstance() val annotated = buildAnnotatedStringFromNodes(nodes) RenderClickableText(annotated) } From 6025448ca2e5c16d5f2ba3fce4dc8f2505d2d5b9 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Thu, 3 Apr 2025 13:09:29 +0300 Subject: [PATCH 5/5] fix: changes according code review --- .../presentation/responses/DiscussionResponsesFragment.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt index 47711c8c6..863cc89ef 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt @@ -220,6 +220,7 @@ private fun DiscussionResponsesScreen( val firstVisibleIndex = remember { mutableIntStateOf(scrollState.firstVisibleItemIndex) } + val isShouldLoadMore = scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD) val pullRefreshState = rememberPullRefreshState(refreshing = refreshing, onRefresh = { onSwipeRefresh() }) @@ -421,11 +422,7 @@ private fun DiscussionResponsesScreen( } } } - if (scrollState.shouldLoadMore( - firstVisibleIndex, - LOAD_MORE_THRESHOLD - ) - ) { + if (isShouldLoadMore) { paginationCallBack() } }