Konrad Pozniak 2023-11-01 09:22:23 +01:00 committed by GitHub
parent ede66c4eb8
commit 6773342b60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 36 deletions

View File

@ -5,10 +5,7 @@ import android.graphics.Typeface.DEFAULT_BOLD
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.Editable import android.text.Editable
import android.text.Html
import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextPaint import android.text.TextPaint
import android.text.style.CharacterStyle import android.text.style.CharacterStyle
import android.util.TypedValue import android.util.TypedValue
@ -29,6 +26,7 @@ import com.keylesspalace.tusky.entity.StatusEdit
import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.AbsoluteTimeFormatter import com.keylesspalace.tusky.util.AbsoluteTimeFormatter
import com.keylesspalace.tusky.util.BindingHolder import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.TuskyTagHandler
import com.keylesspalace.tusky.util.aspectRatios import com.keylesspalace.tusky.util.aspectRatios
import com.keylesspalace.tusky.util.decodeBlurHash import com.keylesspalace.tusky.util.decodeBlurHash
import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.emojify
@ -118,7 +116,7 @@ class ViewEditsAdapter(
val emojifiedText = edit val emojifiedText = edit
.content .content
.parseAsMastodonHtml(TuskyTagHandler(context)) .parseAsMastodonHtml(EditsTagHandler(context))
.emojify(edit.emojis, binding.statusEditContent, animateEmojis) .emojify(edit.emojis, binding.statusEditContent, animateEmojis)
setClickableText(binding.statusEditContent, emojifiedText, emptyList(), emptyList(), listener) setClickableText(binding.statusEditContent, emojifiedText, emptyList(), emptyList(), listener)
@ -224,7 +222,7 @@ class ViewEditsAdapter(
* Handle XML tags created by [ViewEditsViewModel] and create custom spans to display inserted or * Handle XML tags created by [ViewEditsViewModel] and create custom spans to display inserted or
* deleted text. * deleted text.
*/ */
class TuskyTagHandler(val context: Context) : Html.TagHandler { class EditsTagHandler(val context: Context) : TuskyTagHandler() {
/** Class to mark the start of a span of deleted text */ /** Class to mark the start of a span of deleted text */
class Del class Del
@ -255,34 +253,7 @@ class TuskyTagHandler(val context: Context) : Html.TagHandler {
) )
} }
} }
} else -> super.handleTag(opening, tag, output, xmlReader)
}
/** @return the last span in [text] of type [kind], or null if that kind is not in text */
private fun <T> getLast(text: Spanned, kind: Class<T>): Any? {
val spans = text.getSpans(0, text.length, kind)
return spans?.get(spans.size - 1)
}
/**
* Mark the start of a span of [text] with [mark] so it can be discovered later by [end].
*/
private fun start(text: SpannableStringBuilder, mark: Any) {
val len = text.length
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK)
}
/**
* Set a [span] over the [text] most from the point recently marked with [mark] to the end
* of the text.
*/
private fun <T> end(text: SpannableStringBuilder, mark: Class<T>, span: Any) {
val len = text.length
val obj = getLast(text, mark)
val where = text.getSpanStart(obj)
text.removeSpan(obj)
if (where != len) {
text.setSpan(span, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} }
} }

View File

@ -18,8 +18,8 @@ package com.keylesspalace.tusky.components.viewthread.edits
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import at.connyduck.calladapter.networkresult.getOrElse import at.connyduck.calladapter.networkresult.getOrElse
import com.keylesspalace.tusky.components.viewthread.edits.TuskyTagHandler.Companion.DELETED_TEXT_EL import com.keylesspalace.tusky.components.viewthread.edits.EditsTagHandler.Companion.DELETED_TEXT_EL
import com.keylesspalace.tusky.components.viewthread.edits.TuskyTagHandler.Companion.INSERTED_TEXT_EL import com.keylesspalace.tusky.components.viewthread.edits.EditsTagHandler.Companion.INSERTED_TEXT_EL
import com.keylesspalace.tusky.entity.StatusEdit import com.keylesspalace.tusky.entity.StatusEdit
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@ -17,21 +17,78 @@
package com.keylesspalace.tusky.util package com.keylesspalace.tusky.util
import android.text.Editable
import android.text.Html.TagHandler import android.text.Html.TagHandler
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.style.TypefaceSpan
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import org.xml.sax.XMLReader
/** /**
* parse a String containing html from the Mastodon api to Spanned * parse a String containing html from the Mastodon api to Spanned
*/ */
@JvmOverloads @JvmOverloads
fun String.parseAsMastodonHtml(tagHandler: TagHandler? = null): Spanned { fun String.parseAsMastodonHtml(tagHandler: TagHandler? = tuskyTagHandler): Spanned {
return this.replace("<br> ", "<br>&nbsp;") return this.replace("<br> ", "<br>&nbsp;")
.replace("<br /> ", "<br />&nbsp;") .replace("<br /> ", "<br />&nbsp;")
.replace("<br/> ", "<br/>&nbsp;") .replace("<br/> ", "<br/>&nbsp;")
.replace("\n", "<br/>")
.replace(" ", "&nbsp;&nbsp;") .replace(" ", "&nbsp;&nbsp;")
.parseAsHtml(tagHandler = tagHandler) .parseAsHtml(tagHandler = tagHandler)
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which /* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which
* most status contents do, so it should be trimmed. */ * most status contents do, so it should be trimmed. */
.trimTrailingWhitespace() .trimTrailingWhitespace()
} }
val tuskyTagHandler = TuskyTagHandler()
open class TuskyTagHandler : TagHandler {
class Code
override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader) {
when (tag) {
"code" -> {
if (opening) {
start(output as SpannableStringBuilder, Code())
} else {
end(
output as SpannableStringBuilder,
Code::class.java,
TypefaceSpan("monospace")
)
}
}
}
}
/** @return the last span in [text] of type [kind], or null if that kind is not in text */
protected fun <T> getLast(text: Spanned, kind: Class<T>): Any? {
val spans = text.getSpans(0, text.length, kind)
return spans?.get(spans.size - 1)
}
/**
* Mark the start of a span of [text] with [mark] so it can be discovered later by [end].
*/
protected fun start(text: SpannableStringBuilder, mark: Any) {
val len = text.length
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK)
}
/**
* Set a [span] over the [text] from the point recently marked with [mark] to the end
* of the text.
*/
protected fun <T> end(text: SpannableStringBuilder, mark: Class<T>, span: Any) {
val len = text.length
val obj = getLast(text, mark)
val where = text.getSpanStart(obj)
text.removeSpan(obj)
if (where != len) {
text.setSpan(span, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}