Commit 29fc25ea authored by Leonardo Aramaki's avatar Leonardo Aramaki

Add some null checks to loading_views and remove regex parsing for usernames,...

Add some null checks to loading_views and remove regex parsing for usernames, now getting them from mentions
parent d5fde6a3
...@@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable ...@@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable
import android.support.design.widget.BaseTransientBottomBar import android.support.design.widget.BaseTransientBottomBar
import android.support.v4.view.ViewCompat import android.support.v4.view.ViewCompat
import android.text.Spannable import android.text.Spannable
import android.text.SpannableStringBuilder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -41,7 +42,7 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> { ...@@ -41,7 +42,7 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
var text: String = "" var text: String = ""
set(value) { set(value) {
val spannable = parser.renderMarkdown(value) as Spannable val spannable = SpannableStringBuilder.valueOf(value)
spannable.setSpan(MessageParser.QuoteMarginSpan(marginDrawable, 10), 0, spannable.length, 0) spannable.setSpan(MessageParser.QuoteMarginSpan(marginDrawable, 10), 0, spannable.length, 0)
messageTextView.content = spannable messageTextView.content = spannable
} }
......
...@@ -249,7 +249,8 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -249,7 +249,8 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val quoteMessage: Message = quote val quoteMessage: Message = quote
quoteViewModel = mapMessage(quoteMessage) quoteViewModel = mapMessage(quoteMessage)
} }
return parser.renderMarkdown(message.message, quoteViewModel, currentUsername)
return parser.renderMarkdown(message, quoteViewModel, currentUsername)
} }
private fun getSystemMessage(message: Message, context: Context): CharSequence { private fun getSystemMessage(message: Message, context: Context): CharSequence {
...@@ -270,7 +271,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -270,7 +271,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
setSpan(StyleSpan(Typeface.ITALIC), 0, length, 0) setSpan(StyleSpan(Typeface.ITALIC), 0, length, 0)
setSpan(ForegroundColorSpan(Color.GRAY), 0, length, 0) setSpan(ForegroundColorSpan(Color.GRAY), 0, length, 0)
} }
.append(quoteMessage(attachment.author!!, attachment.text!!, attachment.timestamp!!)) .append(quoteMessage(attachment.author!!, message, attachment.timestamp!!))
} }
return pinnedSystemMessage return pinnedSystemMessage
} }
...@@ -310,7 +311,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -310,7 +311,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return spannableMsg return spannableMsg
} }
private fun quoteMessage(author: String, text: String, timestamp: Long): CharSequence { private fun quoteMessage(author: String, message: Message, timestamp: Long): CharSequence {
return SpannableStringBuilder().apply { return SpannableStringBuilder().apply {
val header = "\n$author ${getTime(timestamp)}\n" val header = "\n$author ${getTime(timestamp)}\n"
...@@ -320,7 +321,7 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -320,7 +321,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
setSpan(AbsoluteSizeSpan(context.resources.getDimensionPixelSize(R.dimen.message_time_text_size)), setSpan(AbsoluteSizeSpan(context.resources.getDimensionPixelSize(R.dimen.message_time_text_size)),
author.length + 1, length, 0) author.length + 1, length, 0)
}) })
append(SpannableString(parser.renderMarkdown(text)).apply { append(SpannableString(parser.renderMarkdown(message)).apply {
setSpan(MessageParser.QuoteMarginSpan(context.getDrawable(R.drawable.quote), 10), 0, length, 0) setSpan(MessageParser.QuoteMarginSpan(context.getDrawable(R.drawable.quote), 10), 0, length, 0)
}) })
} }
......
...@@ -107,7 +107,11 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -107,7 +107,11 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun showLoading() = view_loading.setVisible(true) override fun showLoading() = view_loading.setVisible(true)
override fun hideLoading() = view_loading.setVisible(false) override fun hideLoading() {
if (view_loading != null) {
view_loading.setVisible(false)
}
}
override fun showMessage(resId: Int) = showToast(resId) override fun showMessage(resId: Int) = showToast(resId)
......
...@@ -13,7 +13,6 @@ import android.support.v4.content.res.ResourcesCompat ...@@ -13,7 +13,6 @@ import android.support.v4.content.res.ResourcesCompat
import android.text.Layout import android.text.Layout
import android.text.Spannable import android.text.Spannable
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils
import android.text.style.* import android.text.style.*
import android.util.Patterns import android.util.Patterns
import android.view.View import android.view.View
...@@ -22,6 +21,8 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel ...@@ -22,6 +21,8 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.widget.emoji.EmojiParser import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.android.widget.emoji.EmojiRepository import chat.rocket.android.widget.emoji.EmojiRepository
import chat.rocket.android.widget.emoji.EmojiTypefaceSpan import chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message
import org.commonmark.node.AbstractVisitor import org.commonmark.node.AbstractVisitor
import org.commonmark.node.BlockQuote import org.commonmark.node.BlockQuote
import org.commonmark.node.Text import org.commonmark.node.Text
...@@ -30,26 +31,30 @@ import ru.noties.markwon.SpannableBuilder ...@@ -30,26 +31,30 @@ import ru.noties.markwon.SpannableBuilder
import ru.noties.markwon.SpannableConfiguration import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.renderer.SpannableMarkdownVisitor import ru.noties.markwon.renderer.SpannableMarkdownVisitor
import timber.log.Timber import timber.log.Timber
import java.util.regex.Pattern
import javax.inject.Inject import javax.inject.Inject
class MessageParser @Inject constructor(val context: Application, private val configuration: SpannableConfiguration) { class MessageParser @Inject constructor(val context: Application, private val configuration: SpannableConfiguration) {
private val parser = Markwon.createParser() private val parser = Markwon.createParser()
private val regexUsername = Pattern.compile("([^\\S]|^)+(@[\\w.\\-]+)", private val othersTextColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme)
Pattern.MULTILINE or Pattern.CASE_INSENSITIVE) private val othersBackgroundColor = ResourcesCompat.getColor(context.resources, android.R.color.transparent, context.theme)
private val selfReferList = listOf("@all", "@here") private val myselfTextColor = ResourcesCompat.getColor(context.resources, R.color.white, context.theme)
private val myselfBackgroundColor = ResourcesCompat.getColor(context.resources, R.color.colorAccent, context.theme)
private val mentionPadding = context.resources.getDimensionPixelSize(R.dimen.padding_mention).toFloat()
private val mentionRadius = context.resources.getDimensionPixelSize(R.dimen.radius_mention).toFloat()
/** /**
* Render a markdown text message to Spannable. * Render a markdown text message to Spannable.
* *
* @param text The text message containing markdown syntax. * @param message The [Message] object we're interested on rendering.
* @param quote An optional message to be quoted either by a quote or reply action. * @param quote An optional [MessageViewModel] to be quoted.
* @param urls A list of urls to convert to markdown link syntax. * @param selfUsername This user username.
* *
* @return A Spannable with the parsed markdown. * @return A Spannable with the parsed markdown.
*/ */
fun renderMarkdown(text: String, quote: MessageViewModel? = null, selfUsername: String? = null): CharSequence { fun renderMarkdown(message: Message, quote: MessageViewModel? = null, selfUsername: String? = null): CharSequence {
val text = message.message
val builder = SpannableBuilder() val builder = SpannableBuilder()
val content = EmojiRepository.shortnameToUnicode(text, true) val content = EmojiRepository.shortnameToUnicode(text, true)
val parentNode = parser.parse(toLenientMarkdown(content)) val parentNode = parser.parse(toLenientMarkdown(content))
...@@ -65,54 +70,46 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -65,54 +70,46 @@ class MessageParser @Inject constructor(val context: Application, private val co
parentNode.accept(LinkVisitor(builder)) parentNode.accept(LinkVisitor(builder))
parentNode.accept(EmojiVisitor(builder)) parentNode.accept(EmojiVisitor(builder))
val result = builder.text() val result = builder.text()
applySpans(result, selfUsername) applySpans(result, message, selfUsername)
return result return result
} }
private fun applySpans(text: CharSequence, currentUser: String?) { private fun applySpans(text: CharSequence, message: Message, currentUser: String?) {
if (text !is Spannable) return if (text !is Spannable || !containsAnyMentions(text, message.mentions)) return
applyMentionSpans(text, currentUser) applyMentionSpans(text, message.mentions!!, currentUser)
} }
private fun applyMentionSpans(text: CharSequence, currentUser: String?) { private fun containsAnyMentions(text: CharSequence, mentions: List<SimpleUser>?): Boolean {
val matcher = regexUsername.matcher(text) return mentions != null && mentions.isNotEmpty() ||
val result = text as Spannable text.contains("@all", true) ||
while (matcher.find()) { text.contains("@here", true)
val user = matcher.group(2) }
val start = matcher.start(2)
//TODO: should check if username actually exists prior to applying.
with(context) {
val referSelf = when (user) {
in selfReferList -> true
"@$currentUser" -> true
else -> false
}
val mentionTextColor: Int
val mentionBgColor: Int
if (referSelf) {
mentionTextColor = ResourcesCompat.getColor(resources, R.color.white, theme)
mentionBgColor = ResourcesCompat.getColor(context.resources,
R.color.colorAccent, theme)
} else {
mentionTextColor = ResourcesCompat.getColor(resources, R.color.colorAccent,
theme)
mentionBgColor = ResourcesCompat.getColor(resources,
android.R.color.transparent, theme)
}
val padding = resources.getDimensionPixelSize(R.dimen.padding_mention).toFloat() private fun applyMentionSpans(text: Spannable, mentions: List<SimpleUser>, currentUser: String?) {
val radius = resources.getDimensionPixelSize(R.dimen.radius_mention).toFloat() val mentionsList = mentions.map { it.username }.toMutableList()
val usernameSpan = MentionSpan(mentionBgColor, mentionTextColor, radius, padding, mentionsList.add("all")
referSelf) mentionsList.add("here")
result.setSpan(usernameSpan, start, start + user.length, 0)
mentionsList.toList().forEach {
if (it != null) {
val mentionMe = it == currentUser || it == "all" || it == "here"
var offset = text.indexOf("@$it", 0, true)
while (offset > -1) {
val textColor = if (mentionMe) myselfTextColor else othersTextColor
val backgroundColor = if (mentionMe) myselfBackgroundColor else othersBackgroundColor
val usernameSpan = MentionSpan(backgroundColor, textColor, mentionRadius, mentionPadding,
mentionMe)
// Add 1 to end offset to include the @.
val end = offset + it.length + 1
text.setSpan(usernameSpan, offset, end, 0)
offset = text.indexOf("@$it", end, true)
}
} }
} }
} }
/** // Convert to a lenient markdown consistent with Rocket.Chat web markdown instead of the official specs.
* Convert to a lenient markdown consistent with Rocket.Chat web markdown instead of the official specs.
*/
private fun toLenientMarkdown(text: String): String { private fun toLenientMarkdown(text: String): String {
return text.trim().replace("\\*(.+)\\*".toRegex()) { "**${it.groupValues[1].trim()}**" } return text.trim().replace("\\*(.+)\\*".toRegex()) { "**${it.groupValues[1].trim()}**" }
.replace("\\~(.+)\\~".toRegex()) { "~~${it.groupValues[1].trim()}~~" } .replace("\\~(.+)\\~".toRegex()) { "~~${it.groupValues[1].trim()}~~" }
...@@ -228,12 +225,10 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -228,12 +225,10 @@ class MessageParser @Inject constructor(val context: Application, private val co
text: CharSequence, start: Int, end: Int, text: CharSequence, start: Int, end: Int,
first: Boolean, layout: Layout) { first: Boolean, layout: Layout) {
val st = (text as Spanned).getSpanStart(this) val st = (text as Spanned).getSpanStart(this)
val ix = x
val itop = layout.getLineTop(layout.getLineForOffset(st)) val itop = layout.getLineTop(layout.getLineForOffset(st))
val dw = drawable.intrinsicWidth val dw = drawable.intrinsicWidth
val dh = drawable.intrinsicHeight
// XXX What to do about Paint? // XXX What to do about Paint?
drawable.setBounds(ix, itop, ix + dw, itop + layout.height) drawable.setBounds(x, itop, x + dw, itop + layout.height)
drawable.draw(c) drawable.draw(c)
} }
...@@ -279,9 +274,9 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -279,9 +274,9 @@ class MessageParser @Inject constructor(val context: Application, private val co
val length = paint.measureText(text.subSequence(start, end).toString()) val length = paint.measureText(text.subSequence(start, end).toString())
val rect = RectF(x, top.toFloat(), x + length + padding * 2, val rect = RectF(x, top.toFloat(), x + length + padding * 2,
bottom.toFloat()) bottom.toFloat())
paint.setColor(backgroundColor) paint.color = backgroundColor
canvas.drawRoundRect(rect, radius, radius, paint) canvas.drawRoundRect(rect, radius, radius, paint)
paint.setColor(textColor) paint.color = textColor
canvas.drawText(text, start, end, x + padding, y.toFloat(), paint) canvas.drawText(text, start, end, x + padding, y.toFloat(), paint)
} }
......
...@@ -74,7 +74,9 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -74,7 +74,9 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
override fun hideLoading() { override fun hideLoading() {
view_loading.setVisible(false) if (view_loading != null) {
view_loading.setVisible(false)
}
enableUserInput(true) enableUserInput(true)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment