oppia-android icon indicating copy to clipboard operation
oppia-android copied to clipboard

[BUG]: Multiplying by powers of 10: the content of the page is not showing completly in the text box

Open cam-pinheiro opened this issue 10 months ago • 4 comments

Describe the bug

In the Multiplying by powers of 10 lesson there is a page where its content is not being displayed completly in the text box The last letters do not appear in the text box.

This happens in English and Portuguese language.

Steps To Reproduce

Steps:

  1. Go to Multiplication
  2. Go to Multiplying by powers of 10
  3. Playthrough the lesson
  4. See a text box that the content is not shown completly

Expected Behavior

It expected to see the content fully

Screenshots/Videos

Image

What device/emulator are you using?

OPPPO A79 5G

Which Android version is your device/emulator running?

Android 14

Which version of the Oppia Android app are you using?

0.14-beta-17f2ef3044

Additional Context

No response

cam-pinheiro avatar Feb 26 '25 13:02 cam-pinheiro

Reproduced this for both English and Portuguese, and will likely be able to repro it in RTL as well.

Generally, this is occuring when we have text in numbered lists(also spotted in rounding numbers in Place values). It appears that there is no padding to the end of the content textview.

Image Image
Image Image

adhiamboperes avatar Apr 24 '25 04:04 adhiamboperes


Hi @adhiamboperes,

I checked this issue and found that the problem only occurs with ordered lists, not with unordered lists.

The class OlSpan in ListItemLeadingMarginSpan.kt overrides the following functions:

  1. getLeadingMargin(first: Boolean)
  2. drawLeadingMargin(...)

Typical Order of Function Calls

  1. getLeadingMargin(first: Boolean) is called first

    • This happens during the text layout phase, to determine how much space to leave at the start of each line of text (i.e., how much to indent).
    • It’s called once per line of the paragraph that has the span.
    • The first line gets first = true, and the remaining lines get first = false.
  2. Then, drawLeadingMargin(...) is called

    • This occurs during the drawing phase, when the layout is rendered onto the canvas.
    • It is typically called only once, for the first line of the paragraph.

The Issue

In our case, the getLeadingMargin() method returns a computedLeadingMargin, which depends on drawLeadingMargin() for accurate calculation (e.g., the width of prefix text like "1.", "2.", "3.", etc.). This is because we can access the Paint object only from inside the drawLeadingMargin() method.


Proposed Solution

We need to calculate the width of the text outside of drawLeadingMargin(), so that getLeadingMargin() can return the correct computedLeadingMargin independently.


✅ Suggested Fix

Pass a reference of the TextView so that we can use it to:

  • Construct a matching TextPaint object
  • Dynamically calculate the prefix width (e.g., "1.", "10.") using its font, text size, and other styling

This will allow us to decouple getLeadingMargin() from drawLeadingMargin() and compute the margin correctly during layout.


class OlSpan(
    override val parent: ListItemLeadingMarginSpan?,
    context: Context,
    private val numberedItemPrefix: String,
    private val longestNumberedItemPrefix: String,
    private val displayLocale: OppiaLocale.DisplayLocale,
    private val textView: TextView? = null //subha
  ) : ListItemLeadingMarginSpan() {


private val paint = textView?.paint
    private val textWidth = Rect().also {
      paint?.getTextBounds(
        numberedItemPrefix, /* start= */ 0, /* end= */ numberedItemPrefix.length, it
      )
    }.width()

    private val longestTextWidth = Rect().also {
      paint?.getTextBounds(
        longestNumberedItemPrefix,
        /* start= */ 0,
        /* end= */ longestNumberedItemPrefix.length,
        it
      )
    }.width()


  private var computedLeadingMargin =
    longestTextWidth + spacingBeforeText + spacingBeforeNumberPrefix

override fun getLeadingMargin(first: Boolean) :Int { return computedLeadingMargin }
override fun drawLeadingMargin(...) {...}

}

Output:

Before After
Before After

subhajitxyz avatar May 06 '25 10:05 subhajitxyz

@subhajitxyz, do you mind putting up a PR with the solution you proposed above? I have found it quite difficult to implement it myself, due to the fact that it is not clear where the textView will be passed in from, and the paint object created in private val paint = textView?.paint doesn't appear to be used.

adhiamboperes avatar May 29 '25 02:05 adhiamboperes

Ok.

subhajitxyz avatar Jun 03 '25 06:06 subhajitxyz