mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-13 17:51:57 +00:00
attempt to fix ANR when handling markdown
This commit is contained in:
parent
8518ec5802
commit
91e16d3208
2 changed files with 54 additions and 59 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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?) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue