forked from github/szkolny
[UI/Messages] Improve HTML lists presentation.
This commit is contained in:
parent
60641742ed
commit
507657f273
@ -1,17 +1,21 @@
|
||||
package pl.szczodrzynski.edziennik.ui.modules.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.text.Spanned
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||
import pl.szczodrzynski.edziennik.fixName
|
||||
import pl.szczodrzynski.edziennik.getNameInitials
|
||||
import pl.szczodrzynski.edziennik.utils.Colors
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.navlib.blendColors
|
||||
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object MessagesUtils {
|
||||
@ -179,39 +183,6 @@ object MessagesUtils {
|
||||
|
||||
@JvmStatic
|
||||
fun htmlToSpannable(context: Context, html: String): Spanned {
|
||||
val hexPattern = "(#[a-fA-F0-9]{6})"
|
||||
val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})"
|
||||
.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
var text = html
|
||||
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
|
||||
.replace("background-color: ?$hexPattern;".toRegex(), "")
|
||||
|
||||
val colorBackground = android.R.attr.colorBackground.resolveAttr(context)
|
||||
val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context) and 0xffffff
|
||||
|
||||
colorRegex.findAll(text).forEach { result ->
|
||||
val group = result.groups.drop(1).firstOrNull { it != null } ?: return@forEach
|
||||
|
||||
val color = Color.parseColor(group.value)
|
||||
var newColor = 0xff000000.toInt() or color
|
||||
|
||||
var blendAmount = 1
|
||||
var numIterations = 0
|
||||
|
||||
while (numIterations < 100 && ColorUtils.calculateContrast(colorBackground, newColor) < 4.5f) {
|
||||
blendAmount += 2
|
||||
newColor = blendColors(color, blendAmount shl 24 or textColorPrimary)
|
||||
numIterations++
|
||||
}
|
||||
|
||||
text = text.replaceRange(group.range, "#" + (newColor and 0xffffff).toString(16))
|
||||
}
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)
|
||||
} else {
|
||||
Html.fromHtml(text)
|
||||
}
|
||||
return BetterHtml.fromHtml(context, html)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.utils.html
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.BulletSpan
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import pl.szczodrzynski.edziennik.dp
|
||||
import pl.szczodrzynski.edziennik.resolveAttr
|
||||
import pl.szczodrzynski.navlib.blendColors
|
||||
|
||||
object BetterHtml {
|
||||
|
||||
@JvmStatic
|
||||
fun fromHtml(context: Context, html: String): Spanned {
|
||||
val hexPattern = "(#[a-fA-F0-9]{6})"
|
||||
val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})"
|
||||
.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
var text = html
|
||||
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
|
||||
.replace("background-color: ?$hexPattern;".toRegex(), "")
|
||||
|
||||
val colorBackground = android.R.attr.colorBackground.resolveAttr(context)
|
||||
val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context) and 0xffffff
|
||||
|
||||
colorRegex.findAll(text).forEach { result ->
|
||||
val group = result.groups.drop(1).firstOrNull { it != null } ?: return@forEach
|
||||
|
||||
val color = Color.parseColor(group.value)
|
||||
var newColor = 0xff000000.toInt() or color
|
||||
|
||||
var blendAmount = 1
|
||||
var numIterations = 0
|
||||
|
||||
while (numIterations < 100 && ColorUtils.calculateContrast(colorBackground, newColor) < 4.5f) {
|
||||
blendAmount += 2
|
||||
newColor = blendColors(color, blendAmount shl 24 or textColorPrimary)
|
||||
numIterations++
|
||||
}
|
||||
|
||||
text = text.replaceRange(group.range, "#" + (newColor and 0xffffff).toString(16))
|
||||
}
|
||||
|
||||
/*val olRegex = """<ol>(.+?)</\s*?ol>"""
|
||||
.toRegex(setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
|
||||
olRegex.findAll(text).forEach {
|
||||
text.replaceRange(
|
||||
it.range,
|
||||
text.slice(it.range).replace("li>", "_li>")
|
||||
)
|
||||
}*/
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val htmlSpannable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Html.fromHtml(
|
||||
text,
|
||||
Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
|
||||
null,
|
||||
LiTagHandler()
|
||||
)
|
||||
} else {
|
||||
Html.fromHtml(text, null, 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(bulletRadius = 3.dp, startWidth = 24.dp, gapWidth = 8.dp),
|
||||
start,
|
||||
end,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
return spannableBuilder
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://github.com/davidbilik/bullet-span-sample/blob/master/app/src/main/java/cz/davidbilik/bulletsample/ImprovedBulletSpan.kt
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.utils.html
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Copy of [android.text.style.BulletSpan] from android SDK 28 with removed internal code
|
||||
*/
|
||||
class ImprovedBulletSpan(
|
||||
val bulletRadius: Int = STANDARD_BULLET_RADIUS,
|
||||
val startWidth: Int = STANDARD_GAP_WIDTH,
|
||||
val gapWidth: Int = STANDARD_GAP_WIDTH,
|
||||
val color: Int = STANDARD_COLOR
|
||||
) : LeadingMarginSpan {
|
||||
|
||||
companion object {
|
||||
// Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
|
||||
private const val STANDARD_BULLET_RADIUS = 4
|
||||
private const val STANDARD_GAP_WIDTH = 2
|
||||
private const val STANDARD_COLOR = 0
|
||||
}
|
||||
|
||||
private var mBulletPath: Path? = null
|
||||
|
||||
override fun getLeadingMargin(first: Boolean): Int {
|
||||
return startWidth + 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
|
||||
|
||||
val yPosition = if (layout != null) {
|
||||
val line = layout.getLineForOffset(start)
|
||||
layout.getLineBaseline(line).toFloat() - bulletRadius * 2f
|
||||
} else {
|
||||
(top + bottom) / 2f
|
||||
}
|
||||
|
||||
val xPosition = startWidth + (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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-3-17.
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://github.com/davidbilik/bullet-span-sample/blob/master/app/src/main/java/cz/davidbilik/bulletsample/LiTagHandler.kt
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.utils.html
|
||||
|
||||
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 <ul> and <li> tags and creates bullets.
|
||||
*
|
||||
* Note: This class is only applied on SDK < 25 and processes only one-level list, nested lists do not work.
|
||||
*/
|
||||
class LiTagHandler : Html.TagHandler {
|
||||
/**
|
||||
* 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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user