Commit 2cd3446d authored by Leonardo Aramaki's avatar Leonardo Aramaki

Fix url markdown parsing and change style for usernames

parent 37795ffb
...@@ -13,6 +13,7 @@ import chat.rocket.common.RocketChatTwoFactorException ...@@ -13,6 +13,7 @@ import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import javax.inject.Inject import javax.inject.Inject
...@@ -93,7 +94,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -93,7 +94,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
try { try {
val token = client.login(usernameOrEmail, password) val token = client.login(usernameOrEmail, password)
val me = client.me()
multiServerRepository.save(server, TokenModel(token.userId, token.authToken)) multiServerRepository.save(server, TokenModel(token.userId, token.authToken))
localRepository.save(LocalRepository.USERNAME_KEY, me.username)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
......
...@@ -12,6 +12,7 @@ import chat.rocket.common.RocketChatException ...@@ -12,6 +12,7 @@ import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.internal.rest.signup import chat.rocket.core.internal.rest.signup
import javax.inject.Inject import javax.inject.Inject
...@@ -51,6 +52,8 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -51,6 +52,8 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
try { try {
client.signup(email, name, username, password) // TODO This function returns a user so should we save it? client.signup(email, name, username, password) // TODO This function returns a user so should we save it?
client.login(username, password) // TODO This function returns a user token so should we save it? client.login(username, password) // TODO This function returns a user token so should we save it?
val me = client.me()
localRepository.save(LocalRepository.USERNAME_KEY, me.username)
registerPushToken() registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
......
...@@ -12,6 +12,7 @@ import android.text.style.StyleSpan ...@@ -12,6 +12,7 @@ import android.text.style.StyleSpan
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.SITE_URL import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
...@@ -29,7 +30,8 @@ data class MessageViewModel(val context: Context, ...@@ -29,7 +30,8 @@ data class MessageViewModel(val context: Context,
private val message: Message, private val message: Message,
private val settings: Map<String, Value<Any>>, private val settings: Map<String, Value<Any>>,
private val parser: MessageParser, private val parser: MessageParser,
private val messagesRepository: MessagesRepository) { private val messagesRepository: MessagesRepository,
private val localRepository: LocalRepository) {
val id: String = message.id val id: String = message.id
val roomId: String = message.roomId val roomId: String = message.roomId
val time: CharSequence val time: CharSequence
...@@ -46,8 +48,10 @@ data class MessageViewModel(val context: Context, ...@@ -46,8 +48,10 @@ data class MessageViewModel(val context: Context,
var attachmentTimestamp: Long? = null var attachmentTimestamp: Long? = null
var isSystemMessage: Boolean = false var isSystemMessage: Boolean = false
var isPinned: Boolean = false var isPinned: Boolean = false
var currentUsername: String? = null
init { init {
currentUsername = localRepository.get(LocalRepository.USERNAME_KEY)
sender = getSenderName() sender = getSenderName()
time = getTime(message.timestamp) time = getTime(message.timestamp)
isPinned = message.pinned isPinned = message.pinned
...@@ -153,9 +157,9 @@ data class MessageViewModel(val context: Context, ...@@ -153,9 +157,9 @@ data class MessageViewModel(val context: Context,
var quoteViewModel: MessageViewModel? = null var quoteViewModel: MessageViewModel? = null
if (quote != null) { if (quote != null) {
val quoteMessage: Message = quote!! val quoteMessage: Message = quote!!
quoteViewModel = MessageViewModel(context, token, quoteMessage, settings, parser, messagesRepository) quoteViewModel = MessageViewModel(context, token, quoteMessage, settings, parser, messagesRepository, localRepository)
} }
return parser.renderMarkdown(message.message, quoteViewModel, urlsWithMeta) return parser.renderMarkdown(message.message, quoteViewModel, currentUsername)
} }
private fun getSystemMessage(content: String): CharSequence { private fun getSystemMessage(content: String): CharSequence {
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.viewmodel ...@@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.viewmodel
import android.content.Context import android.content.Context
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.TokenRepository import chat.rocket.core.TokenRepository
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
...@@ -13,7 +14,8 @@ import javax.inject.Inject ...@@ -13,7 +14,8 @@ import javax.inject.Inject
class MessageViewModelMapper @Inject constructor(private val context: Context, class MessageViewModelMapper @Inject constructor(private val context: Context,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val messageParser: MessageParser, private val messageParser: MessageParser,
private val messagesRepository: MessagesRepository) { private val messagesRepository: MessagesRepository,
private val localRepository: LocalRepository) {
suspend fun mapToViewModel(message: Message, settings: Map<String, Value<Any>>): MessageViewModel = withContext(CommonPool) { suspend fun mapToViewModel(message: Message, settings: Map<String, Value<Any>>): MessageViewModel = withContext(CommonPool) {
MessageViewModel( MessageViewModel(
...@@ -22,11 +24,18 @@ class MessageViewModelMapper @Inject constructor(private val context: Context, ...@@ -22,11 +24,18 @@ class MessageViewModelMapper @Inject constructor(private val context: Context,
message, message,
settings, settings,
messageParser, messageParser,
messagesRepository messagesRepository,
localRepository
) )
} }
suspend fun mapToViewModelList(messageList: List<Message>, settings: Map<String, Value<Any>>): List<MessageViewModel> { suspend fun mapToViewModelList(messageList: List<Message>, settings: Map<String, Value<Any>>): List<MessageViewModel> {
return messageList.map { MessageViewModel(context, tokenRepository.get(), it, settings, messageParser, messagesRepository) } return messageList.map { MessageViewModel(context,
tokenRepository.get(),
it,
settings,
messageParser,
messagesRepository,
localRepository) }
} }
} }
\ No newline at end of file
...@@ -15,7 +15,6 @@ import android.text.style.* ...@@ -15,7 +15,6 @@ import android.text.style.*
import android.view.View import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.core.model.url.Url
import org.commonmark.node.BlockQuote import org.commonmark.node.BlockQuote
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import ru.noties.markwon.SpannableBuilder import ru.noties.markwon.SpannableBuilder
...@@ -28,7 +27,9 @@ import javax.inject.Inject ...@@ -28,7 +27,9 @@ 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 usernameRegex = Pattern.compile("([^\\S]|^)+(@[\\w.]+)", private val regexUsername = Pattern.compile("([^\\S]|^)+(@[\\w.]+)",
Pattern.MULTILINE or Pattern.CASE_INSENSITIVE)
private val regexLink = Pattern.compile("(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&/=]*)",
Pattern.MULTILINE or Pattern.CASE_INSENSITIVE) Pattern.MULTILINE or Pattern.CASE_INSENSITIVE)
/** /**
...@@ -40,14 +41,19 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -40,14 +41,19 @@ class MessageParser @Inject constructor(val context: Application, private val co
* *
* @return A Spannable with the parsed markdown. * @return A Spannable with the parsed markdown.
*/ */
fun renderMarkdown(text: String, quote: MessageViewModel? = null, urls: List<Url>? = null): CharSequence { fun renderMarkdown(text: String, quote: MessageViewModel? = null, selfUsername: String? = null): CharSequence {
val builder = SpannableBuilder() val builder = SpannableBuilder()
var content: String = text var content: String = text
// Replace all url links to markdown url syntax. // Replace all url links to markdown url syntax.
if (urls != null) { val matcher = regexLink.matcher(content)
for (url in urls) { val consumed = mutableListOf<String>()
content = content.replace(url.url, "[${url.url}](${url.url})") while (matcher.find()) {
val link = matcher.group(0)
// skip usernames
if (!link.startsWith("@") && !consumed.contains(link)) {
content = content.replace(link, "[$link]($link)")
consumed.add(link)
} }
} }
...@@ -62,18 +68,22 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -62,18 +68,22 @@ class MessageParser @Inject constructor(val context: Application, private val co
} }
val result = builder.text() val result = builder.text()
applySpans(result) applySpans(result, selfUsername)
return result return result
} }
private fun applySpans(text: CharSequence) { private fun applySpans(text: CharSequence, currentUser: String?) {
val matcher = usernameRegex.matcher(text) val matcher = regexUsername.matcher(text)
val result = text as Spannable val result = text as Spannable
while (matcher.find()) { while (matcher.find()) {
val user = matcher.group(2) val user = matcher.group(2)
val start = matcher.start(2) val start = matcher.start(2)
//TODO: should check if username actually exists prior to applying. //TODO: should check if username actually exists prior to applying.
result.setSpan(UsernameClickableSpan(), start, start + user.length, 0) val linkColor = context.resources.getColor(R.color.linkTextColor)
val linkBackgroundColor = context.resources.getColor(R.color.linkBackgroundColor)
val referSelf = currentUser != null && "@$currentUser" == user
val usernameSpan = UsernameClickableSpan(linkBackgroundColor, linkColor, referSelf)
result.setSpan(usernameSpan, start, start + user.length, 0)
} }
} }
...@@ -164,13 +174,23 @@ class MessageParser @Inject constructor(val context: Application, private val co ...@@ -164,13 +174,23 @@ class MessageParser @Inject constructor(val context: Application, private val co
} }
} }
class UsernameClickableSpan : ClickableSpan() { class UsernameClickableSpan(private val linkBackgroundColor: Int,
private val linkTextColor: Int,
private val referSelf: Boolean) : ClickableSpan() {
override fun onClick(widget: View) { override fun onClick(widget: View) {
//TODO: Implement action when clicking on username, like showing user profile. //TODO: Implement action when clicking on username, like showing user profile.
} }
override fun updateDrawState(ds: TextPaint) { override fun updateDrawState(ds: TextPaint) {
ds.color = ds.linkColor if (referSelf) {
ds.color = Color.WHITE
ds.typeface = Typeface.DEFAULT_BOLD
ds.bgColor = linkTextColor
} else {
ds.color = linkTextColor
ds.bgColor = linkBackgroundColor
}
ds.isUnderlineText = false ds.isUnderlineText = false
} }
......
...@@ -6,6 +6,7 @@ interface LocalRepository { ...@@ -6,6 +6,7 @@ interface LocalRepository {
const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN" const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN"
const val TOKEN_KEY = "token_" const val TOKEN_KEY = "token_"
const val SETTINGS_KEY = "settings_" const val SETTINGS_KEY = "settings_"
const val USERNAME_KEY = "my_username"
} }
fun save(key: String, value: String?) fun save(key: String, value: String?)
......
...@@ -20,5 +20,6 @@ class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : L ...@@ -20,5 +20,6 @@ class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : L
clear(LocalRepository.KEY_PUSH_TOKEN) clear(LocalRepository.KEY_PUSH_TOKEN)
clear(LocalRepository.TOKEN_KEY + server) clear(LocalRepository.TOKEN_KEY + server)
clear(LocalRepository.SETTINGS_KEY + server) clear(LocalRepository.SETTINGS_KEY + server)
clear(LocalRepository.USERNAME_KEY + server)
} }
} }
\ No newline at end of file
...@@ -21,4 +21,7 @@ ...@@ -21,4 +21,7 @@
<color name="darkGray">#a0a0a0</color> <color name="darkGray">#a0a0a0</color>
<color name="actionMenuColor">#727272</color> <color name="actionMenuColor">#727272</color>
<color name="linkTextColor">#FF074481</color>
<color name="linkBackgroundColor">#30074481</color>
</resources> </resources>
\ No newline at end of file
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