From f8143d2fe9517108ea48fa5fb0ea0c96787969a6 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Thu, 7 Nov 2024 19:53:16 +0530 Subject: [PATCH 01/12] Align policy text and symbols to the left, partial fix for list items --- .../app/policies/PoliciesFragmentPresenter.kt | 34 +++++++++++++++- .../util/locale/LeftAlignedSymbolsSpan.kt | 40 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create 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 1b2bcb20a3d..3a10778267b 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,5 +1,9 @@ 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 @@ -13,6 +17,7 @@ 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 @@ -45,6 +50,7 @@ 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) @@ -53,7 +59,8 @@ class PoliciesFragmentPresenter @Inject constructor( policyWebLink = resourceHandler.getStringInLocale(R.string.terms_of_service_web_link) } - binding.policyDescriptionTextView.text = htmlParserFactory.create( + // Parse the policy description to handle HTML and links + val parsedHtmlDescription = htmlParserFactory.create( policyOppiaTagActionListener = this, displayLocale = resourceHandler.getDisplayLocale() ).parseOppiaHtml( @@ -63,6 +70,31 @@ 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.text = htmlParserFactory.create( gcsResourceName = "", entityType = "", 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 new file mode 100644 index 00000000000..32719cdd224 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt @@ -0,0 +1,40 @@ +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 From fdb0929cd45696df61580fe61801e0e6540968c6 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Wed, 13 Nov 2024 00:42:13 +0530 Subject: [PATCH 02/12] 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
    element. */ @@ -202,8 +215,15 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int - ) = ListItemLeadingMarginSpan.UlSpan(parentSpan, context, indentationLevel, displayLocale) + peerItemCount: Int, + supportLtr: Boolean + ) = ListItemLeadingMarginSpan.UlSpan( + parentSpan, + context, + indentationLevel, + displayLocale, + supportLtr + ) } /** Marks the opening tag location of a list item inside an
      element. */ @@ -215,14 +235,16 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int + peerItemCount: Int, + supportLtr: Boolean ): ListItemLeadingMarginSpan.OlSpan { return ListItemLeadingMarginSpan.OlSpan( parentSpan, context, numberedItemPrefix = "${displayLocale.toHumanReadableString(number)}.", longestNumberedItemPrefix = "${displayLocale.toHumanReadableString(peerItemCount)}.", - displayLocale + displayLocale, + supportLtr ) } } @@ -236,7 +258,8 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int + peerItemCount: Int, + supportLtr: Boolean ) = error("Ending marks cannot be converted to spans.") } } @@ -291,4 +314,4 @@ class LiTagHandler( private fun > Spannable.addMark(mark: T) = setSpan(mark, length, length, Spanned.SPAN_MARK_MARK) } -} +} \ No newline at end of file diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index de1c028aed2..f447e1b2ed5 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -36,6 +36,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { context: Context, private val indentationLevel: Int, private val displayLocale: OppiaLocale.DisplayLocale, + private val supportLtr: Boolean = false ) : ListItemLeadingMarginSpan() { private val resources = context.resources private val bulletRadius = resources.getDimensionPixelSize(R.dimen.bullet_radius) @@ -44,7 +45,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { private val bulletDiameter by lazy { bulletRadius * 2 } private val isRtl by lazy { - displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL + (displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) && !supportLtr } private val clipBounds by lazy { Rect() } @@ -120,7 +121,8 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { context: Context, private val numberedItemPrefix: String, private val longestNumberedItemPrefix: String, - private val displayLocale: OppiaLocale.DisplayLocale + private val displayLocale: OppiaLocale.DisplayLocale, + private val supportLtr: Boolean = false ) : ListItemLeadingMarginSpan() { private val resources = context.resources private val spacingBeforeText = resources.getDimensionPixelSize(R.dimen.spacing_before_text) @@ -132,7 +134,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { 2 * longestNumberedItemPrefix.length + spacingBeforeText private val isRtl by lazy { - displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL + (displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) && !supportLtr } override fun drawLeadingMargin( @@ -179,4 +181,4 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { override fun getLeadingMargin(first: Boolean) = computedLeadingMargin } -} +} \ No newline at end of file From b5148d486dfee6cf858ac001cc3c5bab82ce8e14 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Thu, 14 Nov 2024 23:42:54 +0530 Subject: [PATCH 03/12] Fix: Remove unnecessary formatting changes and maintain consistent structure --- .../android/util/parser/html/HtmlParser.kt | 33 +++++++++++-------- .../android/util/parser/html/LiTagHandler.kt | 2 +- .../parser/html/ListItemLeadingMarginSpan.kt | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) 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 745a453aeab..bf80fc45fbc 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 @@ -125,11 +125,17 @@ 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,17 +234,17 @@ class HtmlParser private constructor( supportLtr: Boolean = false ): HtmlParser { return HtmlParser( - context, - urlImageParserFactory, - gcsResourceName, - entityType, - entityId, - imageCenterAlign, - consoleLogger, + context = context, + urlImageParserFactory = urlImageParserFactory, + gcsResourceName = gcsResourceName, + entityType = entityType, + entityId = entityId, + imageCenterAlign = imageCenterAlign, + consoleLogger = consoleLogger, cacheLatexRendering = enableCacheLatexRendering.value, - customOppiaTagActionListener, - null, - displayLocale, + customOppiaTagActionListener = customOppiaTagActionListener, + policyOppiaTagActionListener = null, + displayLocale = displayLocale, supportLtr = supportLtr ) } @@ -253,7 +259,6 @@ class HtmlParser private constructor( policyOppiaTagActionListener: PolicyOppiaTagActionListener? = null, displayLocale: OppiaLocale.DisplayLocale, supportLtr: Boolean = false - ): HtmlParser { return HtmlParser( context = context, @@ -271,4 +276,4 @@ class HtmlParser private constructor( ) } } -} \ 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 cd8b04536b5..1f3a3a3a12f 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 @@ -314,4 +314,4 @@ class LiTagHandler( private fun > Spannable.addMark(mark: T) = setSpan(mark, length, length, Spanned.SPAN_MARK_MARK) } -} \ No newline at end of file +} diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index f447e1b2ed5..f349f1dad4e 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -181,4 +181,4 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { override fun getLeadingMargin(first: Boolean) = computedLeadingMargin } -} \ No newline at end of file +} From 220580f488e6bcfab2216c970d4f51cea5fdc23d Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Fri, 15 Nov 2024 16:16:54 +0530 Subject: [PATCH 04/12] Added Newline At EOF --- .../org/oppia/android/app/policies/PoliciesFragmentPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 57e4c63d477..32b726ec52f 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 @@ -90,4 +90,4 @@ class PoliciesFragmentPresenter @Inject constructor( (activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.TERMS_OF_SERVICE) } } -} \ No newline at end of file +} From eebbadf57f89f6d1820d4fc23033ec99068a45ab Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Tue, 10 Dec 2024 03:18:22 +0530 Subject: [PATCH 05/12] Fix left-align policy text --- .../app/policies/PoliciesFragmentPresenter.kt | 42 ++++++++--- .../util/locale/LeftAlignedSymbolsSpan.kt | 40 ++++++++++ .../android/util/parser/html/HtmlParser.kt | 43 +++++++---- .../android/util/parser/html/LiTagHandler.kt | 51 ++++--------- .../parser/html/ListItemLeadingMarginSpan.kt | 74 +++++++------------ 5 files changed, 142 insertions(+), 108 deletions(-) create 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 32b726ec52f..2c3e6418172 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,5 +1,9 @@ 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 @@ -13,6 +17,7 @@ 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 @@ -53,27 +58,46 @@ class PoliciesFragmentPresenter @Inject constructor( policyWebLink = resourceHandler.getStringInLocale(R.string.terms_of_service_web_link) } - binding.policyDescriptionTextView.textAlignment = View.TEXT_ALIGNMENT_TEXT_START - binding.policyDescriptionTextView.text = htmlParserFactory.create( + val parsedHtmlDescription = htmlParserFactory.create( policyOppiaTagActionListener = this, - displayLocale = resourceHandler.getDisplayLocale(), - supportLtr = true + displayLocale = resourceHandler.getDisplayLocale() ).parseOppiaHtml( - policyDescription, - binding.policyDescriptionTextView, + rawString = policyDescription, + htmlContentTextView = binding.policyDescriptionTextView, supportsLinks = true, supportsConceptCards = false ) - binding.policyWebLinkTextView.textAlignment = View.TEXT_ALIGNMENT_TEXT_START + 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.text = htmlParserFactory.create( gcsResourceName = "", entityType = "", entityId = "", imageCenterAlign = false, customOppiaTagActionListener = null, - resourceHandler.getDisplayLocale(), - supportLtr = true + resourceHandler.getDisplayLocale() ).parseOppiaHtml( policyWebLink, binding.policyWebLinkTextView, 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 new file mode 100644 index 00000000000..7384f11cc35 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt @@ -0,0 +1,40 @@ +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 + } +} 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 bf80fc45fbc..f08fcfe807a 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,8 +32,7 @@ class HtmlParser private constructor( private val cacheLatexRendering: Boolean, customOppiaTagActionListener: CustomOppiaTagActionListener?, policyOppiaTagActionListener: PolicyOppiaTagActionListener?, - displayLocale: OppiaLocale.DisplayLocale, - private val supportLtr: Boolean = false + displayLocale: OppiaLocale.DisplayLocale ) { private val conceptCardTagHandler by lazy { ConceptCardTagHandler( @@ -56,11 +55,11 @@ class HtmlParser private constructor( consoleLogger ) } - private val bulletTagHandler by lazy { LiTagHandler(context, displayLocale, supportLtr) } + private val bulletTagHandler by lazy { LiTagHandler(context, displayLocale) } private val imageTagHandler by lazy { ImageTagHandler(consoleLogger) } private val isRtl by lazy { - (displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) && !supportLtr + displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL } /** @@ -79,7 +78,6 @@ 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 @@ -230,8 +228,7 @@ class HtmlParser private constructor( entityId: String, imageCenterAlign: Boolean, customOppiaTagActionListener: CustomOppiaTagActionListener? = null, - displayLocale: OppiaLocale.DisplayLocale, - supportLtr: Boolean = false + displayLocale: OppiaLocale.DisplayLocale ): HtmlParser { return HtmlParser( context = context, @@ -244,8 +241,30 @@ class HtmlParser private constructor( cacheLatexRendering = enableCacheLatexRendering.value, customOppiaTagActionListener = customOppiaTagActionListener, policyOppiaTagActionListener = null, - displayLocale = displayLocale, - supportLtr = supportLtr + 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, + cacheLatexRendering = enableCacheLatexRendering.value, + customOppiaTagActionListener = null, + policyOppiaTagActionListener = null, + displayLocale = displayLocale ) } @@ -257,8 +276,7 @@ class HtmlParser private constructor( */ fun create( policyOppiaTagActionListener: PolicyOppiaTagActionListener? = null, - displayLocale: OppiaLocale.DisplayLocale, - supportLtr: Boolean = false + displayLocale: OppiaLocale.DisplayLocale ): HtmlParser { return HtmlParser( context = context, @@ -271,8 +289,7 @@ class HtmlParser private constructor( cacheLatexRendering = false, customOppiaTagActionListener = null, policyOppiaTagActionListener = policyOppiaTagActionListener, - displayLocale = displayLocale, - supportLtr = supportLtr + displayLocale = displayLocale ) } } 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 1f3a3a3a12f..d0562b474a1 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,8 +22,7 @@ const val CUSTOM_LIST_OL_TAG = "oppia-ol" */ class LiTagHandler( private val context: Context, - private val displayLocale: OppiaLocale.DisplayLocale, - private val supportLtr: Boolean = false + private val displayLocale: OppiaLocale.DisplayLocale ) : CustomHtmlContentHandler.CustomTagHandler { private val pendingLists = Stack>() private val latestPendingList: ListTag<*, *>? @@ -53,12 +52,7 @@ 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, - supportLtr - ) + if (pendingLists.isEmpty()) closingList.finishListTree(output, context, displayLocale) } CUSTOM_LIST_LI_TAG -> latestPendingList?.closeItem(output) } @@ -128,13 +122,8 @@ 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, - supportLtr: Boolean - ) = - finishListRecursively(parentSpan = null, text, context, displayLocale, supportLtr) + fun finishListTree(text: Editable, context: Context, displayLocale: OppiaLocale.DisplayLocale) = + finishListRecursively(parentSpan = null, text, context, displayLocale) /** * Returns a new mark of type [M] for this tag. @@ -147,23 +136,22 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, text: Editable, context: Context, - displayLocale: OppiaLocale.DisplayLocale, - supportLtr: Boolean = false + displayLocale: OppiaLocale.DisplayLocale ) { val childrenToProcess = childrenLists.toMutableMap() markRangesToReplace.forEach { (startMark, endMark) -> val styledSpan = startMark.toSpan( - parentSpan, context, displayLocale, peerItemCount = markRangesToReplace.size, supportLtr + parentSpan, context, displayLocale, peerItemCount = markRangesToReplace.size ) text.replaceMarksWithSpan(startMark, endMark, styledSpan) childrenToProcess.remove(startMark)?.finishListRecursively( - parentSpan = styledSpan, text, context, displayLocale, supportLtr + parentSpan = styledSpan, text, context, displayLocale ) } // Process the remaining children that are not lists themselves. childrenToProcess.values.forEach { - it.finishListRecursively(parentSpan = null, text, context, displayLocale, supportLtr) + it.finishListRecursively(parentSpan = null, text, context, displayLocale) } } @@ -200,8 +188,7 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int, - supportLtr: Boolean + peerItemCount: Int ): S /** Marks the opening tag location of a list item inside an
        element. */ @@ -215,15 +202,8 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int, - supportLtr: Boolean - ) = ListItemLeadingMarginSpan.UlSpan( - parentSpan, - context, - indentationLevel, - displayLocale, - supportLtr - ) + peerItemCount: Int + ) = ListItemLeadingMarginSpan.UlSpan(parentSpan, context, indentationLevel, displayLocale) } /** Marks the opening tag location of a list item inside an
          element. */ @@ -235,16 +215,14 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int, - supportLtr: Boolean + peerItemCount: Int ): ListItemLeadingMarginSpan.OlSpan { return ListItemLeadingMarginSpan.OlSpan( parentSpan, context, numberedItemPrefix = "${displayLocale.toHumanReadableString(number)}.", longestNumberedItemPrefix = "${displayLocale.toHumanReadableString(peerItemCount)}.", - displayLocale, - supportLtr + displayLocale ) } } @@ -258,8 +236,7 @@ class LiTagHandler( parentSpan: ListItemLeadingMarginSpan?, context: Context, displayLocale: OppiaLocale.DisplayLocale, - peerItemCount: Int, - supportLtr: Boolean + peerItemCount: Int ) = error("Ending marks cannot be converted to spans.") } } diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index f349f1dad4e..0969612450a 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -11,14 +11,7 @@ import android.text.style.LeadingMarginSpan import androidx.core.view.ViewCompat import org.oppia.android.util.R import org.oppia.android.util.locale.OppiaLocale -import kotlin.math.max -// TODO(#562): Add screenshot tests to check whether the drawing logic works correctly on all devices. - -/** - * A version of [LeadingMarginSpan] that shows text inside the margin. - * Reference: https://medium.com/swlh/making-nested-lists-with-android-spannables-in-kotlin-4ad00052912c - */ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { /** The parent list of this span, or null if it doesn't have one (that is, it's a root list). */ protected abstract val parent: ListItemLeadingMarginSpan? @@ -36,18 +29,16 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { context: Context, private val indentationLevel: Int, private val displayLocale: OppiaLocale.DisplayLocale, - private val supportLtr: Boolean = false ) : ListItemLeadingMarginSpan() { private val resources = context.resources private val bulletRadius = resources.getDimensionPixelSize(R.dimen.bullet_radius) - private val spacingBeforeText = resources.getDimensionPixelSize(R.dimen.spacing_before_text) - private val spacingBeforeBullet = resources.getDimensionPixelSize(R.dimen.spacing_before_bullet) private val bulletDiameter by lazy { bulletRadius * 2 } + private val baseMargin = (16f * context.resources.displayMetrics.density).toInt() + private val isRtl by lazy { - (displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) && !supportLtr + displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL } - private val clipBounds by lazy { Rect() } override fun drawLeadingMargin( canvas: Canvas, @@ -70,16 +61,14 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { val previousStyle = paint.style val bulletDrawRadius = bulletRadius.toFloat() - val indentedX = parentAbsoluteLeadingMargin + spacingBeforeBullet - val bulletCenterLtrX = indentedX + bulletDrawRadius - val bulletCenterX = if (isRtl) { - // See https://stackoverflow.com/a/21845993/3689782 for 'right' property exclusivity. - val maxDrawX = if (canvas.getClipBounds(clipBounds)) { - clipBounds.right - 1 - } else canvas.width - 1 - maxDrawX - bulletCenterLtrX - } else bulletCenterLtrX + // Force left alignment + paint.textAlign = Paint.Align.LEFT + + // Positioning calculation + val bulletCenterLtrX = x.toFloat() + baseMargin * (indentationLevel + 1) + val bulletCenterX = bulletCenterLtrX val bulletCenterY = (top + bottom) / 2f + when (indentationLevel) { 0 -> { // A solid circle is used for the outermost bullet. @@ -112,7 +101,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { } override fun getLeadingMargin(first: Boolean) = - bulletDiameter + spacingBeforeBullet + spacingBeforeText + baseMargin * (indentationLevel + 2) } /** A subclass of [LeadingMarginSpan] that shows nested list span for
            tags. */ @@ -121,21 +110,14 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { context: Context, private val numberedItemPrefix: String, private val longestNumberedItemPrefix: String, - private val displayLocale: OppiaLocale.DisplayLocale, - private val supportLtr: Boolean = false + private val displayLocale: OppiaLocale.DisplayLocale ) : ListItemLeadingMarginSpan() { private val resources = context.resources - private val spacingBeforeText = resources.getDimensionPixelSize(R.dimen.spacing_before_text) - private val spacingBeforeNumberPrefix = - resources.getDimensionPixelSize(R.dimen.spacing_before_number_prefix) + private val baseMargin = (16f * context.resources.displayMetrics.density).toInt() // Try to use a computed margin, but otherwise guess if there's no guaranteed spacing. private var computedLeadingMargin = - 2 * longestNumberedItemPrefix.length + spacingBeforeText - - private val isRtl by lazy { - (displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) && !supportLtr - } + 2 * longestNumberedItemPrefix.length + baseMargin override fun drawLeadingMargin( canvas: Canvas, @@ -155,30 +137,24 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { val isFirstCharacter = startCharOfSpan == start if (isFirstCharacter) { + // Force left alignment + paint.textAlign = Paint.Align.LEFT + val textWidth = Rect().also { paint.getTextBounds( numberedItemPrefix, /* start= */ 0, /* end= */ numberedItemPrefix.length, it ) }.width() - val longestTextWidth = Rect().also { - paint.getTextBounds( - longestNumberedItemPrefix, - /* start= */ 0, - /* end= */ longestNumberedItemPrefix.length, - it - ) - }.width() - computedLeadingMargin = longestTextWidth + spacingBeforeNumberPrefix + spacingBeforeText - - // Compute the prefix's start x value such that it is right-aligned with other numbers in - // the list. - val indentedX = parentAbsoluteLeadingMargin + spacingBeforeNumberPrefix - val endAlignedX = (max(textWidth, longestTextWidth) - textWidth) + indentedX - val prefixStartX = if (isRtl) canvas.width - endAlignedX - 1 else endAlignedX - canvas.drawText(numberedItemPrefix, prefixStartX.toFloat(), baseline.toFloat(), paint) + + // Positioning calculation + val prefixStartX = x.toFloat() + baseMargin + + // Draw the numbered prefix + canvas.drawText(numberedItemPrefix, prefixStartX, baseline.toFloat(), paint) } } - override fun getLeadingMargin(first: Boolean) = computedLeadingMargin + override fun getLeadingMargin(first: Boolean) = + baseMargin * 2 } } From f54c59ca4c0fd368ea124b6e4d610e145eb668aa Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Tue, 10 Dec 2024 14:25:48 +0530 Subject: [PATCH 06/12] Small Change --- .../oppia/android/app/policies/PoliciesFragmentPresenter.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 2c3e6418172..81da370e431 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,7 @@ 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 @@ -14,10 +12,10 @@ import org.oppia.android.app.model.PoliciesFragmentArguments import org.oppia.android.app.model.PolicyPage import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.databinding.PoliciesFragmentBinding +import org.oppia.android.util.locale.LeftAlignedSymbolsSpan 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 From 28c8ea5c312d0cbebd941671bfa6eaaa03d26a3c Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Tue, 10 Dec 2024 19:35:35 +0530 Subject: [PATCH 07/12] Add Old Comments For Future Reference --- .../android/util/parser/html/ListItemLeadingMarginSpan.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index 0969612450a..c9e4d94bc2f 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -12,6 +12,12 @@ import androidx.core.view.ViewCompat import org.oppia.android.util.R import org.oppia.android.util.locale.OppiaLocale +// TODO(#562): Add screenshot tests to check whether the drawing logic works correctly on all devices. + +/** + * A version of [LeadingMarginSpan] that shows text inside the margin. + * Reference: https://medium.com/swlh/making-nested-lists-with-android-spannables-in-kotlin-4ad00052912c + */ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { /** The parent list of this span, or null if it doesn't have one (that is, it's a root list). */ protected abstract val parent: ListItemLeadingMarginSpan? From 957b27a645ef8c02aa1abebe6f4317e894364b3e Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Mon, 16 Dec 2024 13:29:10 +0530 Subject: [PATCH 08/12] Remove Ununsed Variable --- .../oppia/android/util/locale/LeftAlignedSymbolsSpan.kt | 6 ++++-- .../util/parser/html/ListItemLeadingMarginSpan.kt | 9 --------- 2 files changed, 4 insertions(+), 11 deletions(-) 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 index 7384f11cc35..9610d916da1 100644 --- a/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt @@ -4,8 +4,10 @@ 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 +/** + * Custom span to force LTR (left-to-right) alignment for all text, + * including symbols, regardless of the system's text direction. + */ class LeftAlignedSymbolsSpan : ReplacementSpan() { override fun getSize( paint: Paint, diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index c9e4d94bc2f..d99d0c7ba5f 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -143,15 +143,6 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { val isFirstCharacter = startCharOfSpan == start if (isFirstCharacter) { - // Force left alignment - paint.textAlign = Paint.Align.LEFT - - val textWidth = Rect().also { - paint.getTextBounds( - numberedItemPrefix, /* start= */ 0, /* end= */ numberedItemPrefix.length, it - ) - }.width() - // Positioning calculation val prefixStartX = x.toFloat() + baseMargin From 061e0a7ade64b29a79905d65129e7c0784cd9ac1 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Mon, 16 Dec 2024 13:35:25 +0530 Subject: [PATCH 09/12] Remove Ununsed Variable --- .../oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index d99d0c7ba5f..539ae145e0b 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -3,7 +3,6 @@ package org.oppia.android.util.parser.html import android.content.Context import android.graphics.Canvas import android.graphics.Paint -import android.graphics.Rect import android.graphics.RectF import android.text.Layout import android.text.Spanned From a6624fa0378d99c954de759d9c9133f5c4647bb4 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Mon, 16 Dec 2024 17:45:43 +0530 Subject: [PATCH 10/12] Resolved All Issues --- .../oppia/android/app/policies/PoliciesFragmentPresenter.kt | 2 +- .../java/org/oppia/android/util/parser/html/BUILD.bazel | 1 + .../util/{locale => parser/html}/LeftAlignedSymbolsSpan.kt | 2 +- .../android/util/parser/html/ListItemLeadingMarginSpan.kt | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) rename utility/src/main/java/org/oppia/android/util/{locale => parser/html}/LeftAlignedSymbolsSpan.kt (95%) 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 81da370e431..aca4bb680a2 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 @@ -12,10 +12,10 @@ import org.oppia.android.app.model.PoliciesFragmentArguments import org.oppia.android.app.model.PolicyPage import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.databinding.PoliciesFragmentBinding -import org.oppia.android.util.locale.LeftAlignedSymbolsSpan 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.parser.html.LeftAlignedSymbolsSpan /** The presenter for [PoliciesFragment]. */ @FragmentScope diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/BUILD.bazel b/utility/src/main/java/org/oppia/android/util/parser/html/BUILD.bazel index 959a1c43f69..5177894544f 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/BUILD.bazel +++ b/utility/src/main/java/org/oppia/android/util/parser/html/BUILD.bazel @@ -46,6 +46,7 @@ kt_android_library( name = "list_item_leading_margin_span", srcs = [ "ListItemLeadingMarginSpan.kt", + "LeftAlignedSymbolsSpan.kt", ], visibility = [ "//app:__subpackages__", diff --git a/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/LeftAlignedSymbolsSpan.kt similarity index 95% rename from utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt rename to utility/src/main/java/org/oppia/android/util/parser/html/LeftAlignedSymbolsSpan.kt index 9610d916da1..3e9365c409f 100644 --- a/utility/src/main/java/org/oppia/android/util/locale/LeftAlignedSymbolsSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/LeftAlignedSymbolsSpan.kt @@ -1,4 +1,4 @@ -package org.oppia.android.util.locale +package org.oppia.android.util.parser.html import android.graphics.Canvas import android.graphics.Paint diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index 539ae145e0b..ef2a25a296f 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -9,6 +9,7 @@ import android.text.Spanned import android.text.style.LeadingMarginSpan import androidx.core.view.ViewCompat import org.oppia.android.util.R +import org.oppia.android.util.R.dimen.spacing_before_bullet import org.oppia.android.util.locale.OppiaLocale // TODO(#562): Add screenshot tests to check whether the drawing logic works correctly on all devices. @@ -39,7 +40,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { private val bulletRadius = resources.getDimensionPixelSize(R.dimen.bullet_radius) private val bulletDiameter by lazy { bulletRadius * 2 } - private val baseMargin = (16f * context.resources.displayMetrics.density).toInt() + private val baseMargin = context.resources.getDimensionPixelSize((spacing_before_bullet)) private val isRtl by lazy { displayLocale.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL @@ -118,8 +119,7 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { private val displayLocale: OppiaLocale.DisplayLocale ) : ListItemLeadingMarginSpan() { private val resources = context.resources - private val baseMargin = (16f * context.resources.displayMetrics.density).toInt() - + private val baseMargin = context.resources.getDimensionPixelSize((R.dimen.spacing_before_number_prefix)) // Try to use a computed margin, but otherwise guess if there's no guaranteed spacing. private var computedLeadingMargin = 2 * longestNumberedItemPrefix.length + baseMargin From 73e09dc701def5d3f05173075211583cc45787bf Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Mon, 16 Dec 2024 17:47:36 +0530 Subject: [PATCH 11/12] Resolved All Issues --- .../android/util/parser/html/ListItemLeadingMarginSpan.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt index ef2a25a296f..27fa8b5b8fb 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpan.kt @@ -119,7 +119,9 @@ sealed class ListItemLeadingMarginSpan : LeadingMarginSpan { private val displayLocale: OppiaLocale.DisplayLocale ) : ListItemLeadingMarginSpan() { private val resources = context.resources - private val baseMargin = context.resources.getDimensionPixelSize((R.dimen.spacing_before_number_prefix)) + private val baseMargin = + context.resources.getDimensionPixelSize((R.dimen.spacing_before_number_prefix)) + // Try to use a computed margin, but otherwise guess if there's no guaranteed spacing. private var computedLeadingMargin = 2 * longestNumberedItemPrefix.length + baseMargin From 642f106ad1f1fd4c9471922a50313853a4cba157 Mon Sep 17 00:00:00 2001 From: TanishMoral11 Date: Mon, 16 Dec 2024 17:49:05 +0530 Subject: [PATCH 12/12] Improving Formatting --- .../org/oppia/android/app/policies/PoliciesFragmentPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aca4bb680a2..59fe472a3c7 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 @@ -13,9 +13,9 @@ import org.oppia.android.app.model.PolicyPage import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.databinding.PoliciesFragmentBinding import org.oppia.android.util.parser.html.HtmlParser +import org.oppia.android.util.parser.html.LeftAlignedSymbolsSpan import org.oppia.android.util.parser.html.PolicyType import javax.inject.Inject -import org.oppia.android.util.parser.html.LeftAlignedSymbolsSpan /** The presenter for [PoliciesFragment]. */ @FragmentScope