From fdb0929cd45696df61580fe61801e0e6540968c6 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Wed, 13 Nov 2024 00:42:13 +0530 Subject: [PATCH] Fix text alignment and bullet point formatting in PoliciesFragment --- .../app/policies/PoliciesFragmentPresenter.kt | 44 ++--------- .../util/locale/LeftAlignedSymbolsSpan.kt | 40 ---------- .../android/util/parser/html/HtmlParser.kt | 74 +++++++------------ .../android/util/parser/html/LiTagHandler.kt | 53 +++++++++---- .../parser/html/ListItemLeadingMarginSpan.kt | 10 ++- 5 files changed, 78 insertions(+), 143 deletions(-) delete mode 100644 utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt diff --git a/app/src/main/java/org/oppia/android/app/policies/PoliciesFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/policies/PoliciesFragmentPresenter.kt index 3a10778267b..57e4c63d477 100644 --- a/app/src/main/java/org/oppia/android/app/policies/PoliciesFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/policies/PoliciesFragmentPresenter.kt @@ -1,9 +1,5 @@ package org.oppia.android.app.policies -import android.graphics.Paint -import android.text.SpannableString -import android.text.Spanned -import android.text.TextPaint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -17,7 +13,6 @@ import org.oppia.android.databinding.PoliciesFragmentBinding import org.oppia.android.util.parser.html.HtmlParser import org.oppia.android.util.parser.html.PolicyType import javax.inject.Inject -import org.oppia.android.util.locale.LeftAlignedSymbolsSpan /** The presenter for [PoliciesFragment]. */ @FragmentScope @@ -50,7 +45,6 @@ class PoliciesFragmentPresenter @Inject constructor( var policyDescription = "" var policyWebLink = "" - // Get policy content based on the selected policy page if (policyPage == PolicyPage.PRIVACY_POLICY) { policyDescription = resourceHandler.getStringInLocale(R.string.privacy_policy_content) policyWebLink = resourceHandler.getStringInLocale(R.string.privacy_policy_web_link) @@ -59,10 +53,11 @@ class PoliciesFragmentPresenter @Inject constructor( policyWebLink = resourceHandler.getStringInLocale(R.string.terms_of_service_web_link) } - // Parse the policy description to handle HTML and links - val parsedHtmlDescription = htmlParserFactory.create( + binding.policyDescriptionTextView.textAlignment = View.TEXT_ALIGNMENT_TEXT_START + binding.policyDescriptionTextView.text = htmlParserFactory.create( policyOppiaTagActionListener = this, - displayLocale = resourceHandler.getDisplayLocale() + displayLocale = resourceHandler.getDisplayLocale(), + supportLtr = true ).parseOppiaHtml( policyDescription, binding.policyDescriptionTextView, @@ -70,38 +65,15 @@ class PoliciesFragmentPresenter @Inject constructor( supportsConceptCards = false ) - binding.policyDescriptionTextView.apply { - layoutDirection = View.LAYOUT_DIRECTION_LTR - textAlignment = View.TEXT_ALIGNMENT_TEXT_START - textDirection = View.TEXT_DIRECTION_LTR - setSingleLine(false) - setMaxLines(Int.MAX_VALUE) - } - - val spannableString = SpannableString(parsedHtmlDescription) - - parsedHtmlDescription.split("\n").forEachIndexed { lineIndex, line -> - val lineStart = parsedHtmlDescription.indexOf(line) - if (line.trimStart().startsWith("•")) { - val bulletIndex = lineStart + line.indexOf("•") - spannableString.setSpan( - LeftAlignedSymbolsSpan(), - bulletIndex, - bulletIndex + 1, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - } - - binding.policyDescriptionTextView.text = spannableString - + binding.policyWebLinkTextView.textAlignment = View.TEXT_ALIGNMENT_TEXT_START binding.policyWebLinkTextView.text = htmlParserFactory.create( gcsResourceName = "", entityType = "", entityId = "", imageCenterAlign = false, customOppiaTagActionListener = null, - resourceHandler.getDisplayLocale() + resourceHandler.getDisplayLocale(), + supportLtr = true ).parseOppiaHtml( policyWebLink, binding.policyWebLinkTextView, @@ -118,4 +90,4 @@ class PoliciesFragmentPresenter @Inject constructor( (activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.TERMS_OF_SERVICE) } } -} +} \ No newline at end of file diff --git a/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt b/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt deleted file mode 100644 index 32719cdd224..00000000000 --- a/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.oppia.android.util.locale - -import android.graphics.Canvas -import android.graphics.Paint -import android.text.style.ReplacementSpan - -// Custom span to force LTR alignment for symbols as well -// Custom span to force LTR alignment for all text including symbols -class LeftAlignedSymbolsSpan : ReplacementSpan() { - override fun getSize( - paint: Paint, - text: CharSequence?, - start: Int, - end: Int, - fm: Paint.FontMetricsInt? - ): Int { - return paint.measureText(text, start, end).toInt() - } - - override fun draw( - canvas: Canvas, - text: CharSequence, - start: Int, - end: Int, - x: Float, - top: Int, - y: Int, - bottom: Int, - paint: Paint - ) { - val originalAlignment = paint.textAlign - paint.textAlign = Paint.Align.LEFT - - // Draw the bullet point at the exact x position - canvas.drawText(text, start, end, x, y.toFloat(), paint) - - // Restore original alignment - paint.textAlign = originalAlignment - } -} \ No newline at end of file diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt b/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt index f08fcfe807a..745a453aeab 100755 --- a/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/HtmlParser.kt @@ -32,7 +32,8 @@ class HtmlParser private constructor( private val cacheLatexRendering: Boolean, customOppiaTagActionListener: CustomOppiaTagActionListener?, policyOppiaTagActionListener: PolicyOppiaTagActionListener?, - displayLocale: OppiaLocale.DisplayLocale + displayLocale: OppiaLocale.DisplayLocale, + private val supportLtr: Boolean = false ) { private val conceptCardTagHandler by lazy { ConceptCardTagHandler( @@ -55,11 +56,11 @@ class HtmlParser private constructor( consoleLogger ) } - private val bulletTagHandler by lazy { LiTagHandler(context, displayLocale) } + private val bulletTagHandler by lazy { LiTagHandler(context, displayLocale, supportLtr) } private val imageTagHandler by lazy { ImageTagHandler(consoleLogger) } private val isRtl by lazy { - displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL + (displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) && !supportLtr } /** @@ -78,6 +79,7 @@ class HtmlParser private constructor( supportsLinks: Boolean = false, supportsConceptCards: Boolean = false ): Spannable { + var htmlContent = rawString // Canvas does not support RTL, it always starts from left to right in RTL due to which compound drawables are @@ -123,17 +125,11 @@ class HtmlParser private constructor( } val imageGetter = urlImageParserFactory?.create( - htmlContentTextView, - gcsResourceName, - entityType, - entityId, - imageCenterAlign + htmlContentTextView, gcsResourceName, entityType, entityId, imageCenterAlign ) val htmlSpannable = CustomHtmlContentHandler.fromHtml( - htmlContent, - imageGetter, - computeCustomTagHandlers(supportsConceptCards, htmlContentTextView) + htmlContent, imageGetter, computeCustomTagHandlers(supportsConceptCards, htmlContentTextView) ) val urlPattern = Patterns.WEB_URL @@ -228,43 +224,22 @@ class HtmlParser private constructor( entityId: String, imageCenterAlign: Boolean, customOppiaTagActionListener: CustomOppiaTagActionListener? = null, - displayLocale: OppiaLocale.DisplayLocale + displayLocale: OppiaLocale.DisplayLocale, + supportLtr: Boolean = false ): HtmlParser { return HtmlParser( - context = context, - urlImageParserFactory = urlImageParserFactory, - gcsResourceName = gcsResourceName, - entityType = entityType, - entityId = entityId, - imageCenterAlign = imageCenterAlign, - consoleLogger = consoleLogger, - cacheLatexRendering = enableCacheLatexRendering.value, - customOppiaTagActionListener = customOppiaTagActionListener, - policyOppiaTagActionListener = null, - displayLocale = displayLocale - ) - } - - /** - * Returns a new [HtmlParser] with the empty entity type and ID for loading images, - * doesn't require GCS properties and imageCenterAlign set to false - * optionally specified [CustomOppiaTagActionListener] for handling custom Oppia tag events. - */ - fun create( - displayLocale: OppiaLocale.DisplayLocale - ): HtmlParser { - return HtmlParser( - context = context, - urlImageParserFactory = urlImageParserFactory, - gcsResourceName = "", - entityType = "", - entityId = "", - imageCenterAlign = false, - consoleLogger = consoleLogger, + context, + urlImageParserFactory, + gcsResourceName, + entityType, + entityId, + imageCenterAlign, + consoleLogger, cacheLatexRendering = enableCacheLatexRendering.value, - customOppiaTagActionListener = null, - policyOppiaTagActionListener = null, - displayLocale = displayLocale + customOppiaTagActionListener, + null, + displayLocale, + supportLtr = supportLtr ) } @@ -276,7 +251,9 @@ class HtmlParser private constructor( */ fun create( policyOppiaTagActionListener: PolicyOppiaTagActionListener? = null, - displayLocale: OppiaLocale.DisplayLocale + displayLocale: OppiaLocale.DisplayLocale, + supportLtr: Boolean = false + ): HtmlParser { return HtmlParser( context = context, @@ -289,8 +266,9 @@ class HtmlParser private constructor( cacheLatexRendering = false, customOppiaTagActionListener = null, policyOppiaTagActionListener = policyOppiaTagActionListener, - displayLocale = displayLocale + displayLocale = displayLocale, + supportLtr = supportLtr ) } } -} +} \ No newline at end of file diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/LiTagHandler.kt b/utility/src/main/java/org/oppia/android/util/parser/html/LiTagHandler.kt index d0562b474a1..cd8b04536b5 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/LiTagHandler.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/LiTagHandler.kt @@ -22,7 +22,8 @@ const val CUSTOM_LIST_OL_TAG = "oppia-ol" */ class LiTagHandler( private val context: Context, - private val displayLocale: OppiaLocale.DisplayLocale + private val displayLocale: OppiaLocale.DisplayLocale, + private val supportLtr: Boolean = false ) : CustomHtmlContentHandler.CustomTagHandler { private val pendingLists = Stack>() private val latestPendingList: ListTag<*, *>? @@ -52,7 +53,12 @@ class LiTagHandler( // Actually place the spans only if the root tree has been finished (as the entirety of the // tree is needed for analysis). val closingList = pendingLists.pop().also { it.recordList() } - if (pendingLists.isEmpty()) closingList.finishListTree(output, context, displayLocale) + if (pendingLists.isEmpty()) closingList.finishListTree( + output, + context, + displayLocale, + supportLtr + ) } CUSTOM_LIST_LI_TAG -> latestPendingList?.closeItem(output) } @@ -122,8 +128,13 @@ class LiTagHandler( * Recursively replaces all marks for this root list (and all its children) with renderable * spans in the provided [text]. */ - fun finishListTree(text: Editable, context: Context, displayLocale: OppiaLocale.DisplayLocale) = - finishListRecursively(parentSpan = null, text, context, displayLocale) + fun finishListTree( + text: Editable, + context: Context, + displayLocale: OppiaLocale.DisplayLocale, + supportLtr: Boolean + ) = + finishListRecursively(parentSpan = null, text, context, displayLocale, supportLtr) /** * Returns a new mark of type [M] for this tag. @@ -136,22 +147,23 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, text: Editable, context: Context, - displayLocale: OppiaLocale.DisplayLocale + displayLocale: OppiaLocale.DisplayLocale, + supportLtr: Boolean = false ) { val childrenToProcess = childrenLists.toMutableMap() markRangesToReplace.forEach { (startMark, endMark) -> val styledSpan = startMark.toSpan( - parentSpan, context, displayLocale, peerItemCount = markRangesToReplace.size + parentSpan, context, displayLocale, peerItemCount = markRangesToReplace.size, supportLtr ) text.replaceMarksWithSpan(startMark, endMark, styledSpan) childrenToProcess.remove(startMark)?.finishListRecursively( - parentSpan = styledSpan, text, context, displayLocale + parentSpan = styledSpan, text, context, displayLocale, supportLtr ) } // Process the remaining children that are not lists themselves. childrenToProcess.values.forEach { - it.finishListRecursively(parentSpan = null, text, context, displayLocale) + it.finishListRecursively(parentSpan = null, text, context, displayLocale, supportLtr) } } @@ -188,7 +200,8 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int + peerItemCount: Int, + supportLtr: Boolean ): S /** Marks the opening tag location of a list item inside an