attempt to fix ANR when handling markdown

This commit is contained in:
Phillip Thelen 2023-02-01 09:38:02 +01:00
parent 8518ec5802
commit 91e16d3208
2 changed files with 54 additions and 59 deletions

View file

@ -53,11 +53,13 @@ abstract class BaseTaskViewHolder constructor(
private val iconViewChallenge: ImageView? = itemView.findViewById(R.id.iconviewChallenge) private val iconViewChallenge: ImageView? = itemView.findViewById(R.id.iconviewChallenge)
private val iconViewReminder: ImageView? = itemView.findViewById(R.id.iconviewReminder) private val iconViewReminder: ImageView? = itemView.findViewById(R.id.iconviewReminder)
private val taskIconWrapper: LinearLayout? = itemView.findViewById(R.id.taskIconWrapper) private val taskIconWrapper: LinearLayout? = itemView.findViewById(R.id.taskIconWrapper)
private val approvalRequiredTextView: TextView = itemView.findViewById(R.id.approvalRequiredTextField) private val approvalRequiredTextView: TextView =
itemView.findViewById(R.id.approvalRequiredTextField)
private val expandNotesButton: Button? = itemView.findViewById(R.id.expand_notes_button) private val expandNotesButton: Button? = itemView.findViewById(R.id.expand_notes_button)
private val syncingView: ProgressBar? = itemView.findViewById(R.id.syncing_view) private val syncingView: ProgressBar? = itemView.findViewById(R.id.syncing_view)
private val errorIconView: ImageButton? = itemView.findViewById(R.id.error_icon) private val errorIconView: ImageButton? = itemView.findViewById(R.id.error_icon)
protected val taskGray: Int = ContextCompat.getColor(itemView.context, R.color.offset_background) protected val taskGray: Int =
ContextCompat.getColor(itemView.context, R.color.offset_background)
protected val streakIconView: ImageView = itemView.findViewById(R.id.iconViewStreak) protected val streakIconView: ImageView = itemView.findViewById(R.id.iconViewStreak)
protected val streakTextView: TextView = itemView.findViewById(R.id.streakTextView) protected val streakTextView: TextView = itemView.findViewById(R.id.streakTextView)
protected val reminderTextView: TextView = itemView.findViewById(R.id.reminder_textview) protected val reminderTextView: TextView = itemView.findViewById(R.id.reminder_textview)
@ -115,7 +117,8 @@ abstract class BaseTaskViewHolder constructor(
if (ellipses && notesTextView.maxLines != 3) { if (ellipses && notesTextView.maxLines != 3) {
notesTextView.maxLines = 3 notesTextView.maxLines = 3
} }
expandNotesButton?.visibility = if (ellipses || notesExpanded) View.VISIBLE else View.GONE expandNotesButton?.visibility =
if (ellipses || notesExpanded) View.VISIBLE else View.GONE
} }
} }
}) })
@ -158,78 +161,58 @@ abstract class BaseTaskViewHolder constructor(
notesTextView?.visibility = View.GONE notesTextView?.visibility = View.GONE
} }
if (canContainMarkdown()) { titleTextView.text = data.text
if (data.parsedText != null) { scope.launch(Dispatchers.IO) {
titleTextView.setParsedMarkdown(data.parsedText) if (data.text.isNotEmpty() && MarkdownParser.containsMarkdown(data.text)) {
} else if (MarkdownParser.hasCached(data.text)) { val parsedText = MarkdownParser.parseMarkdown(data.text)
titleTextView.setParsedMarkdown(MarkdownParser.parseMarkdown(data.text)) withContext(Dispatchers.Main) {
} else { data.parsedText = parsedText
titleTextView.text = data.text titleTextView.setParsedMarkdown(parsedText)
if (data.text.isNotEmpty()) {
scope.launch(Dispatchers.IO) {
val parsedText = MarkdownParser.parseMarkdown(data.text)
withContext(Dispatchers.Main) {
data.parsedText = parsedText
titleTextView.setParsedMarkdown(parsedText)
}
}
} }
} }
if (displayMode != "minimal") { }
when { if (displayMode != "minimal") {
data.parsedNotes != null -> { notesTextView?.text = data.notes
notesTextView?.setParsedMarkdown(data.parsedNotes) data.notes?.let { notes ->
scope.launch(Dispatchers.IO) {
if (notes.isEmpty() || !MarkdownParser.containsMarkdown(notes)) {
return@launch
} }
MarkdownParser.hasCached(data.notes) -> { val parsedNotes = MarkdownParser.parseMarkdown(notes)
notesTextView?.setParsedMarkdown(MarkdownParser.parseMarkdown(data.notes)) withContext(Dispatchers.Main) {
} data.parsedNotes = parsedNotes
else -> { notesTextView?.setParsedMarkdown(parsedNotes)
notesTextView?.text = data.notes
data.notes?.let { notes ->
if (notes.isEmpty()) {
return@let
}
scope.launch(Dispatchers.IO) {
val parsedNotes = MarkdownParser.parseMarkdown(notes)
withContext(Dispatchers.Main) {
data.parsedNotes = parsedNotes
notesTextView?.setParsedMarkdown(parsedNotes)
}
}
}
} }
} }
} else {
notesTextView?.visibility = View.GONE
} }
} else { } else {
titleTextView.text = data.text notesTextView?.visibility = View.GONE
if (displayMode != "minimal") {
notesTextView?.text = data.notes
} else {
notesTextView?.visibility = View.GONE
}
} }
titleTextView.setTextColor(ContextCompat.getColor(context, R.color.text_primary)) titleTextView.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
if (displayMode == "standard") { if (displayMode == "standard") {
iconViewReminder?.visibility = if ((data.reminders?.size ?: 0) > 0) View.VISIBLE else View.GONE iconViewReminder?.visibility =
if ((data.reminders?.size ?: 0) > 0) View.VISIBLE else View.GONE
iconViewChallenge?.visibility = if (task?.challengeID != null) View.VISIBLE else View.GONE iconViewChallenge?.visibility =
if (task?.challengeID != null) View.VISIBLE else View.GONE
if (task?.challengeID != null) { if (task?.challengeID != null) {
if (task?.challengeBroken?.isNotBlank() == true) { if (task?.challengeBroken?.isNotBlank() == true) {
iconViewChallenge?.alpha = 1.0f iconViewChallenge?.alpha = 1.0f
iconViewChallenge?.imageTintList = ContextCompat.getColorStateList(context, R.color.white) iconViewChallenge?.imageTintList =
ContextCompat.getColorStateList(context, R.color.white)
iconViewChallenge?.setImageResource(R.drawable.task_broken_megaphone) iconViewChallenge?.setImageResource(R.drawable.task_broken_megaphone)
} else { } else {
iconViewChallenge?.alpha = 0.3f iconViewChallenge?.alpha = 0.3f
iconViewChallenge?.imageTintList = ContextCompat.getColorStateList(context, R.color.text_ternary) iconViewChallenge?.imageTintList =
ContextCompat.getColorStateList(context, R.color.text_ternary)
iconViewChallenge?.setImageResource(R.drawable.task_megaphone) iconViewChallenge?.setImageResource(R.drawable.task_megaphone)
} }
} }
configureSpecialTaskTextView(data) configureSpecialTaskTextView(data)
iconViewTeam?.visibility = if (data.isGroupTask && ownerID != data.group?.groupID) View.VISIBLE else View.GONE iconViewTeam?.visibility =
if (data.isGroupTask && ownerID != data.group?.groupID) View.VISIBLE else View.GONE
taskIconWrapper?.visibility = if (taskIconWrapperIsVisible) View.VISIBLE else View.GONE taskIconWrapper?.visibility = if (taskIconWrapperIsVisible) View.VISIBLE else View.GONE
} else { } else {
@ -244,7 +227,10 @@ abstract class BaseTaskViewHolder constructor(
} }
if (data.group?.assignedUsers?.isNotEmpty() == true) { if (data.group?.assignedUsers?.isNotEmpty() == true) {
assignedTextView.text = assignedTextProvider?.assignedTextForTask(context.resources, data.group?.assignedUsers ?: emptyList()) assignedTextView.text = assignedTextProvider?.assignedTextForTask(
context.resources,
data.group?.assignedUsers ?: emptyList()
)
assignedTextView.visibility = View.VISIBLE assignedTextView.visibility = View.VISIBLE
} else { } else {
assignedTextView.visibility = View.GONE assignedTextView.visibility = View.GONE
@ -252,7 +238,8 @@ abstract class BaseTaskViewHolder constructor(
val completedCount = data.group?.assignedUsersDetail?.filter { it.completed }?.size ?: 0 val completedCount = data.group?.assignedUsersDetail?.filter { it.completed }?.size ?: 0
if (completedCount > 0) { if (completedCount > 0) {
completedCountTextView.text = "$completedCount/${data?.group?.assignedUsersDetail?.size}" completedCountTextView.text =
"$completedCount/${data?.group?.assignedUsersDetail?.size}"
completedCountTextView.visibility = View.VISIBLE completedCountTextView.visibility = View.VISIBLE
} else { } else {
completedCountTextView.visibility = View.GONE completedCountTextView.visibility = View.GONE
@ -310,10 +297,6 @@ abstract class BaseTaskViewHolder constructor(
return true return true
} }
open fun canContainMarkdown(): Boolean {
return true
}
open fun setDisabled(openTaskDisabled: Boolean, taskActionsDisabled: Boolean) { open fun setDisabled(openTaskDisabled: Boolean, taskActionsDisabled: Boolean) {
this.openTaskDisabled = openTaskDisabled this.openTaskDisabled = openTaskDisabled
this.taskActionsDisabled = taskActionsDisabled this.taskActionsDisabled = taskActionsDisabled

View file

@ -9,6 +9,7 @@ import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.text.util.Linkify import android.text.util.Linkify
import android.util.Log
import android.widget.TextView import android.widget.TextView
import com.habitrpg.common.habitica.R import com.habitrpg.common.habitica.R
import com.habitrpg.common.habitica.extensions.handleUrlClicks import com.habitrpg.common.habitica.extensions.handleUrlClicks
@ -28,7 +29,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.lang.Integer.min
import java.lang.NullPointerException import java.lang.NullPointerException
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis
object MarkdownParser { object MarkdownParser {
private val cache = sortedMapOf<Int, Spanned>() private val cache = sortedMapOf<Int, Spanned>()
@ -124,8 +128,11 @@ object MarkdownParser {
} }
fun hasCached(input: String?): Boolean { fun hasCached(input: String?): Boolean {
if (input == null) {
return false
}
return try { return try {
cache.containsKey(input?.hashCode()) cache.containsKey(input.hashCode())
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
false false
} }
@ -140,6 +147,11 @@ object MarkdownParser {
fun parseCompiled(input: CharSequence): String? { fun parseCompiled(input: CharSequence): String? {
return EmojiParser.convertToCheatCode(input.toString()) return EmojiParser.convertToCheatCode(input.toString())
} }
private val markdownRegex = ".*[\\*#_\\[].*".toRegex()
fun containsMarkdown(text: String): Boolean {
return text.matches(markdownRegex)
}
} }
fun TextView.setMarkdown(input: String?) { fun TextView.setMarkdown(input: String?) {