Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #456: Improvised bullet code #490

Merged
merged 13 commits into from
Dec 18, 2019
39 changes: 27 additions & 12 deletions utility/src/main/java/org/oppia/util/parser/HtmlParser.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.oppia.util.parser

import android.os.Build
import android.text.Html
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.BulletSpan
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import javax.inject.Inject

private const val CUSTOM_IMG_TAG = "oppia-noninteractive-image"
Expand All @@ -14,6 +18,7 @@ private const val REPLACE_IMG_FILE_PATH_ATTRIBUTE = "src"
/** Html Parser to parse custom Oppia tags with Android-compatible versions. */
class HtmlParser private constructor(
private val urlImageParserFactory: UrlImageParser.Factory,
private val activity: AppCompatActivity,
private val entityType: String,
private val entityId: String,
private val imageCenterAlign: Boolean
Expand Down Expand Up @@ -44,18 +49,28 @@ class HtmlParser private constructor(
}

val imageGetter = urlImageParserFactory.create(htmlContentTextView, entityType, entityId, imageCenterAlign)
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
trimSpannable(
Html.fromHtml(
htmlContent,
Html.FROM_HTML_MODE_LEGACY,
imageGetter, /* tagHandler= */
null
) as SpannableStringBuilder
)

@Suppress("DEPRECATION")
val htmlSpannable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(htmlContent, Html.FROM_HTML_MODE_LEGACY, imageGetter, LiTagHandler())
} else {
trimSpannable(Html.fromHtml(htmlContent, imageGetter, /* tagHandler= */ null) as SpannableStringBuilder)
Html.fromHtml(htmlContent, imageGetter, LiTagHandler())
}

val spannableBuilder = SpannableStringBuilder(htmlSpannable)
val bulletSpans = spannableBuilder.getSpans(0, spannableBuilder.length, BulletSpan::class.java)
bulletSpans.forEach {
val start = spannableBuilder.getSpanStart(it)
val end = spannableBuilder.getSpanEnd(it)
spannableBuilder.removeSpan(it)
spannableBuilder.setSpan(
ImprovedBulletSpan(activity),
start,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
return trimSpannable(spannableBuilder)
}

private fun trimSpannable(spannable: SpannableStringBuilder): SpannableStringBuilder {
Expand All @@ -77,9 +92,9 @@ class HtmlParser private constructor(
return spannable.delete(0, trimStart).delete(spannable.length - trimEnd, spannable.length)
}

class Factory @Inject constructor(private val urlImageParserFactory: UrlImageParser.Factory) {
class Factory @Inject constructor(private val urlImageParserFactory: UrlImageParser.Factory, private val activity: AppCompatActivity) {
fun create(entityType: String, entityId: String, imageCenterAlign: Boolean): HtmlParser {
return HtmlParser(urlImageParserFactory, entityType, entityId, imageCenterAlign)
return HtmlParser(urlImageParserFactory, activity, entityType, entityId, imageCenterAlign)
}
}
}
75 changes: 75 additions & 0 deletions utility/src/main/java/org/oppia/util/parser/ImprovedBulletSpan.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.oppia.util.parser

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Path.Direction
import android.text.Layout
import android.text.Spanned
import android.text.style.LeadingMarginSpan
import androidx.appcompat.app.AppCompatActivity
import org.oppia.util.R

/**
* Copy of [android.text.style.BulletSpan] from android SDK 28 with removed internal code.
rt4914 marked this conversation as resolved.
Show resolved Hide resolved
*
* Reference: https://github.com/davidbilik/bullet-span-sample
*/
class ImprovedBulletSpan(activity: AppCompatActivity) : LeadingMarginSpan {

private var bulletRadius: Int = 0
private var gapWidth: Int = 0
private var yOffset: Int = 0

init {
bulletRadius = (activity as Context).resources.getDimensionPixelSize(R.dimen.bullet_radius)
rt4914 marked this conversation as resolved.
Show resolved Hide resolved
gapWidth = (activity as Context).resources.getDimensionPixelSize(R.dimen.bullet_gap_width)
yOffset = (activity as Context).resources.getDimensionPixelSize(R.dimen.bullet_y_offset)
}

private var mBulletPath: Path? = null

override fun getLeadingMargin(first: Boolean): Int {
return 2 * bulletRadius + gapWidth
}

override fun drawLeadingMargin(
canvas: Canvas, paint: Paint, x: Int, dir: Int,
top: Int, baseline: Int, bottom: Int,
text: CharSequence, start: Int, end: Int,
first: Boolean,
layout: Layout?
) {
if ((text as Spanned).getSpanStart(this) == start) {
val style = paint.style
paint.style = Paint.Style.FILL

var yPosition = if (layout != null) {
val line = layout.getLineForOffset(start)
layout.getLineBaseline(line).toFloat() - bulletRadius * 2f
} else {
(top + bottom) / 2f
}
yPosition += yOffset

val xPosition = (x + dir * bulletRadius).toFloat()

if (canvas.isHardwareAccelerated) {
if (mBulletPath == null) {
mBulletPath = Path()
mBulletPath!!.addCircle(0.0f, 0.0f, bulletRadius.toFloat(), Direction.CW)
}

canvas.save()
canvas.translate(xPosition, yPosition)
canvas.drawPath(mBulletPath!!, paint)
canvas.restore()
} else {
canvas.drawCircle(xPosition, yPosition, bulletRadius.toFloat(), paint)
}

paint.style = style
}
}
}
37 changes: 37 additions & 0 deletions utility/src/main/java/org/oppia/util/parser/LiTagHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.oppia.util.parser

import android.text.Editable
import android.text.Html
import android.text.Spannable
import android.text.Spanned
import android.text.style.BulletSpan
import org.xml.sax.XMLReader

/**
* [Html.TagHandler] implementation that processes <li> tags and creates bullets.
*
* Reference: https://github.com/davidbilik/bullet-span-sample
*/
class LiTagHandler : Html.TagHandler {
rt4914 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Helper marker class. Idea stolen from [Html.fromHtml] implementation
*/
class Bullet

override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader) {
if (tag == "li" && opening) {
output.setSpan(Bullet(), output.length, output.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
}
if (tag == "li" && !opening) {
output.append("\n\n")
rt4914 marked this conversation as resolved.
Show resolved Hide resolved
val lastMark = output.getSpans(0, output.length, Bullet::class.java).lastOrNull()
lastMark?.let {
val start = output.getSpanStart(it)
output.removeSpan(it)
if (start != output.length) {
output.setSpan(BulletSpan(), start, output.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
}
}
}
}
6 changes: 6 additions & 0 deletions utility/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="bullet_radius">4dp</dimen>
<dimen name="bullet_gap_width">16dp</dimen>
<dimen name="bullet_y_offset">2dp</dimen>
</resources>