Unverified Commit 7c8e577d authored by Leonardo Aramaki's avatar Leonardo Aramaki Committed by GitHub

Merge pull request #732 from RocketChat/markdown-support

[NEW] Markdown support
parents d31b9ea1 0749391e
......@@ -99,6 +99,10 @@ dependencies {
implementation libraries.textDrawable
implementation libraries.markwon
implementation libraries.markwonImageLoader
implementation libraries.moshiLazyAdapters
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
......
......@@ -4,6 +4,7 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI
import chat.rocket.common.model.roomTypeOf
......@@ -26,6 +27,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val strategy: CancelStrategy,
getSettingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor,
private val messagesRepository: MessagesRepository,
factory: RocketChatClientFactory,
private val mapper: MessageViewModelMapper) {
private val client = factory.create(serverInteractor.get()!!)
......@@ -46,6 +48,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
val messages = client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
synchronized(roomMessages) {
roomMessages.addAll(messages)
messagesRepository.saveAll(messages)
}
val messagesViewModels = mapper.mapToViewModelList(messages, settings)
......
package chat.rocket.android.chatroom.ui
import android.support.v7.widget.RecyclerView
import android.text.method.LinkMovementMethod
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
......@@ -9,8 +10,10 @@ import chat.rocket.android.R
import chat.rocket.android.chatroom.viewmodel.AttachmentType
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.content
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisible
import chat.rocket.android.util.textContent
import chat.rocket.common.util.ifNull
import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer
......@@ -63,9 +66,10 @@ class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<Chat
fun bind(message: MessageViewModel) = with(itemView) {
bindUserAvatar(message, image_avatar, image_unknown_avatar)
text_user_name.text = message.sender
text_message_time.text = message.time
text_content.text = message.content
text_user_name.content = message.sender
text_message_time.content = message.time
text_content.content = message.content
text_content.movementMethod = LinkMovementMethod()
bindAttachment(message, message_attachment, image_attachment, audio_video_attachment,
file_name)
......
......@@ -8,7 +8,9 @@ import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import chat.rocket.android.R
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.USE_REALNAME
import chat.rocket.common.model.Token
......@@ -19,29 +21,50 @@ import chat.rocket.core.model.attachment.AudioAttachment
import chat.rocket.core.model.attachment.FileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
import chat.rocket.core.model.attachment.VideoAttachment
import chat.rocket.core.model.url.Url
import okhttp3.HttpUrl
import timber.log.Timber
data class MessageViewModel(val context: Context,
private val token: Token?,
private val message: Message,
private val settings: Map<String, Value<Any>>?) {
private val settings: Map<String, Value<Any>>?,
private val parser: MessageParser,
private val messagesRepository: MessagesRepository) {
val id: String = message.id
val time: CharSequence
val sender: CharSequence
val content: CharSequence
var quote: Message? = null
var urlsWithMeta = arrayListOf<Url>()
var attachmentUrl: String? = null
var attachmentTitle: CharSequence? = null
var attachmentType: AttachmentType? = null
init {
sender = getSenderName()
content = getContent(context)
time = getTime()
val baseUrl = settings?.get(SITE_URL)
message.urls?.let {
if (it.isEmpty()) return@let
for (url in it) {
if (url.meta != null) {
urlsWithMeta.add(url)
}
baseUrl?.let {
val quoteUrl = HttpUrl.parse(url.url)
val serverUrl = HttpUrl.parse(baseUrl.value.toString())
if (quoteUrl != null && serverUrl != null) {
makeQuote(quoteUrl, serverUrl)
}
}
}
}
message.attachments?.let {
if (it.isEmpty() || it[0] == null) return@let
val attachment = it[0] as FileAttachment
val baseUrl = settings?.get(SITE_URL)
baseUrl?.let {
attachmentUrl = attachmentUrl("${baseUrl.value}${attachment.url}")
attachmentTitle = attachment.title
......@@ -54,6 +77,18 @@ data class MessageViewModel(val context: Context,
}
}
}
content = getContent(context)
}
private fun makeQuote(quoteUrl: HttpUrl, serverUrl: HttpUrl) {
if (quoteUrl.host() == serverUrl.host()) {
val msgIdToQuote = quoteUrl.queryParameter("msg")
Timber.d("Will quote message Id: $msgIdToQuote")
if (msgIdToQuote != null) {
quote = messagesRepository.getById(msgIdToQuote)
}
}
}
fun getAvatarUrl(serverUrl: String): String? {
......@@ -62,9 +97,11 @@ data class MessageViewModel(val context: Context,
}
}
fun getTime() = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(message.timestamp))
fun getOriginalMessage() = message.message
fun getSenderName(): CharSequence {
private fun getTime() = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(message.timestamp))
private fun getSenderName(): CharSequence {
val useRealName = settings?.get(USE_REALNAME)?.value as Boolean
val username = message.sender?.username
val realName = message.sender?.name
......@@ -72,7 +109,7 @@ data class MessageViewModel(val context: Context,
return senderName ?: username.toString()
}
fun getContent(context: Context): CharSequence {
private fun getContent(context: Context): CharSequence {
val contentMessage: CharSequence
when (message.type) {
//TODO: Add implementation for Welcome type.
......@@ -90,7 +127,14 @@ data class MessageViewModel(val context: Context,
return contentMessage
}
private fun getNormalMessage() = message.message
private fun getNormalMessage(): CharSequence {
var quoteViewModel: MessageViewModel? = null
if (quote != null) {
val quoteMessage: Message = quote!!
quoteViewModel = MessageViewModel(context, token, quoteMessage, settings, parser, messagesRepository)
}
return parser.renderMarkdown(message.message, quoteViewModel, urlsWithMeta)
}
private fun getSystemMessage(content: String): CharSequence {
val spannableMsg = SpannableString(content)
......
package chat.rocket.android.chatroom.viewmodel
import android.content.Context
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.TokenRepository
import chat.rocket.core.model.Message
import chat.rocket.core.model.Value
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import javax.inject.Inject
class MessageViewModelMapper @Inject constructor(private val context: Context, private val tokenRepository: TokenRepository) {
class MessageViewModelMapper @Inject constructor(private val context: Context,
private val tokenRepository: TokenRepository,
private val messageParser: MessageParser,
private val messagesRepository: MessagesRepository) {
suspend fun mapToViewModel(message: Message, settings: Map<String, Value<Any>>?) = MessageViewModel(context, tokenRepository.get(), message, settings)
suspend fun mapToViewModel(message: Message, settings: Map<String, Value<Any>>?): MessageViewModel = withContext(CommonPool) {
MessageViewModel(
this@MessageViewModelMapper.context,
tokenRepository.get(),
message,
settings,
messageParser,
messagesRepository
)
}
suspend fun mapToViewModelList(messageList: List<Message>, settings: Map<String, Value<Any>>?): List<MessageViewModel> {
return messageList.map { MessageViewModel(context, tokenRepository.get(), it, settings) }
return messageList.map { MessageViewModel(context, tokenRepository.get(), it, settings, messageParser, messagesRepository) }
}
}
\ No newline at end of file
......@@ -5,22 +5,18 @@ import android.arch.persistence.room.Room
import android.content.Context
import android.content.SharedPreferences
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.app.utils.CustomImageFormatConfigurator
import chat.rocket.android.authentication.infraestructure.MemoryTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.dagger.qualifier.ForFresco
import chat.rocket.android.helper.FrescoAuthInterceptor
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.*
import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.util.PlatformLogger
......@@ -38,7 +34,11 @@ import kotlinx.coroutines.experimental.Job
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.il.AsyncDrawableLoader
import ru.noties.markwon.spans.SpannableTheme
import timber.log.Timber
import java.util.concurrent.Executors
import javax.inject.Singleton
@Module
......@@ -188,7 +188,7 @@ class AppModule {
@Provides
@Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder().add(AppJsonAdapterFactory.INSTANCE).build()
return Moshi.Builder().add(AppJsonAdapterFactory.INSTANCE).build()
}
@Provides
......@@ -196,4 +196,32 @@ class AppModule {
fun provideMultiServerTokenRepository(repository: LocalRepository, moshi: Moshi): MultiServerTokenRepository {
return SharedPreferencesMultiServerTokenRepository(repository, moshi)
}
@Provides
@Singleton
fun provideMessageRepository(): MessagesRepository {
return MemoryMessagesRepository()
}
@Provides
@Singleton
fun provideConfiguration(context: Application, client: OkHttpClient): SpannableConfiguration {
val res = context.resources
return SpannableConfiguration.builder(context)
.asyncDrawableLoader(AsyncDrawableLoader.builder()
.client(client)
.executorService(Executors.newCachedThreadPool())
.resources(res)
.build())
.theme(SpannableTheme.builder()
.linkColor(res.getColor(R.color.colorAccent))
.build())
.build()
}
@Provides
@Singleton
fun provideMessageParser(context: Application, configuration: SpannableConfiguration): MessageParser {
return MessageParser(context, configuration)
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.app.Application
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.text.Layout
import android.text.Spannable
import android.text.Spanned
import android.text.TextPaint
import android.text.style.*
import android.view.View
import chat.rocket.android.R
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.core.model.url.Url
import org.commonmark.node.BlockQuote
import ru.noties.markwon.Markwon
import ru.noties.markwon.SpannableBuilder
import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.renderer.SpannableMarkdownVisitor
import java.util.regex.Pattern
import javax.inject.Inject
class MessageParser @Inject constructor(val context: Application, private val configuration: SpannableConfiguration) {
private val parser = Markwon.createParser()
private val userPattern = Pattern.compile("(@[\\w.]+)",
Pattern.MULTILINE or Pattern.CASE_INSENSITIVE)
/**
* Render a markdown text message to Spannable.
*
* @param text The text message containing markdown syntax.
* @param quote An optional message to be quoted either by a quote or reply action.
* @param urls A list of urls to convert to markdown link syntax.
*
* @return A Spannable with the parsed markdown.
*/
fun renderMarkdown(text: String, quote: MessageViewModel?, urls: List<Url>): CharSequence {
val builder = SpannableBuilder()
var content: String = text
// Replace all url links to markdown url syntax.
for (url in urls) {
content = content.replace(url.url, "[${url.url}](${url.url})")
}
val parentNode = parser.parse(toLenientMarkdown(content))
parentNode.accept(SpannableMarkdownVisitor(configuration, builder))
quote?.apply {
var quoteNode = parser.parse("> $sender $time")
parentNode.appendChild(quoteNode)
quoteNode.accept(QuoteMessageSenderVisitor(context, configuration, builder, sender.length))
quoteNode = parser.parse("> ${toLenientMarkdown(quote.getOriginalMessage())}")
quoteNode.accept(QuoteMessageBodyVisitor(context, configuration, builder))
}
val result = builder.text()
applySpans(result)
return result
}
private fun applySpans(text: CharSequence) {
val matcher = userPattern.matcher(text)
val result = text as Spannable
while (matcher.find()) {
val user = matcher.group(1)
val start = matcher.start()
//TODO: should check if username actually exists prior to applying.
result.setSpan(UsernameClickableSpan(), start, start + user.length, 0)
}
}
/**
* Convert to a lenient markdown consistent with Rocket.Chat web markdown instead of the official specs.
*/
private fun toLenientMarkdown(text: String): String {
return text.trim().replace("\\*(.+)\\*".toRegex()) { "**${it.groupValues[1].trim()}**" }
.replace("\\~(.+)\\~".toRegex()) { "~~${it.groupValues[1].trim()}~~" }
.replace("\\_(.+)\\_".toRegex()) { "_${it.groupValues[1].trim()}_" }
}
class QuoteMessageSenderVisitor(private val context: Context,
configuration: SpannableConfiguration,
private val builder: SpannableBuilder,
private val senderNameLength: Int) : SpannableMarkdownVisitor(configuration, builder) {
override fun visit(blockQuote: BlockQuote) {
// mark current length
val length = builder.length()
// pass to super to apply markdown
super.visit(blockQuote)
val res = context.resources
val timeOffsetStart = length + senderNameLength + 1
builder.setSpan(QuoteMarginSpan(context.getDrawable(R.drawable.quote), 10), length, builder.length())
builder.setSpan(StyleSpan(Typeface.BOLD), length, length + senderNameLength)
builder.setSpan(ForegroundColorSpan(Color.BLACK), length, builder.length())
// set time spans
builder.setSpan(AbsoluteSizeSpan(res.getDimensionPixelSize(R.dimen.message_time_text_size)),
timeOffsetStart, builder.length())
builder.setSpan(ForegroundColorSpan(res.getColor(R.color.darkGray)),
timeOffsetStart, builder.length())
}
}
class QuoteMessageBodyVisitor(private val context: Context,
configuration: SpannableConfiguration,
private val builder: SpannableBuilder) : SpannableMarkdownVisitor(configuration, builder) {
override fun visit(blockQuote: BlockQuote) {
// mark current length
val length = builder.length()
// pass to super to apply markdown
super.visit(blockQuote)
builder.setSpan(QuoteMarginSpan(context.getDrawable(R.drawable.quote), 10), length, builder.length())
}
}
class QuoteMarginSpan(quoteDrawable: Drawable, private var pad: Int) : LeadingMarginSpan, LineHeightSpan {
private val drawable: Drawable = quoteDrawable
override fun getLeadingMargin(first: Boolean): Int {
return drawable.intrinsicWidth + pad
}
override fun drawLeadingMargin(c: Canvas, p: Paint, x: Int, dir: Int,
top: Int, baseline: Int, bottom: Int,
text: CharSequence, start: Int, end: Int,
first: Boolean, layout: Layout) {
val st = (text as Spanned).getSpanStart(this)
val ix = x
val itop = layout.getLineTop(layout.getLineForOffset(st))
val dw = drawable.intrinsicWidth
val dh = drawable.intrinsicHeight
// XXX What to do about Paint?
drawable.setBounds(ix, itop, ix + dw, itop + layout.height)
drawable.draw(c)
}
override fun chooseHeight(text: CharSequence, start: Int, end: Int,
spanstartv: Int, v: Int,
fm: Paint.FontMetricsInt) {
if (end == (text as Spanned).getSpanEnd(this)) {
val ht = drawable.intrinsicHeight
var need = ht - (v + fm.descent - fm.ascent - spanstartv)
if (need > 0)
fm.descent += need
need = ht - (v + fm.bottom - fm.top - spanstartv)
if (need > 0)
fm.bottom += need
}
}
}
class UsernameClickableSpan : ClickableSpan() {
override fun onClick(widget: View) {
//TODO: Implement action when clicking on username, like showing user profile.
}
override fun updateDrawState(ds: TextPaint) {
ds.color = ds.linkColor
ds.isUnderlineText = false
}
}
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.core.model.Message
interface MessagesRepository {
/**
* Get message by its message id.
*
* @param id The id of the message to get.
* @return The Message object given by the id or null if message wasn't found.
*/
fun getById(id: String): Message?
/**
* Get all messages from the current room id.
*
* @param rid The room id.
* @return A list of Message objects for the room with given room id or an empty list.
*/
fun getByRoomId(rid: String): List<Message>
/**
* Get all messages. Use carefully!
* @return All messages or an empty list.
*/
fun getAll(): List<Message>
/**
* Save a single message object.
*
* @param The message object to saveAll.
*/
fun save(message: Message)
/**
* Save a list of messages.
*/
fun saveAll(newMessages: List<Message>)
/**
* Removes all messages.
*/
fun clear()
/**
* Remove message by id.
*
* @param id The id of the message to remove.
*/
fun removeById(id: String)
/**
* Remove all messages from a given room.
*
* @param rid The room id where messages are to be removed.
*/
fun removeByRoomId(rid: String)
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.model.Message
class MemoryMessagesRepository : MessagesRepository {
private val messages: HashMap<String, Message> = HashMap()
override fun getById(id: String): Message? {
return messages[id]
}
override fun getByRoomId(rid: String): List<Message> {
return messages.filter { it.value.roomId == rid }.values.toList()
}
override fun getAll(): List<Message> = messages.values.toList()
override fun save(message: Message) {
messages[message.id] = message
}
override fun saveAll(newMessages: List<Message>) {
for (msg in newMessages) {
messages[msg.id] = msg
}
}
override fun clear() {
messages.clear()
}
override fun removeById(id: String) {
messages.remove(id)
}
override fun removeByRoomId(rid: String) {
val roomMessages = messages.filter { it.value.roomId == rid }.values
roomMessages.forEach {
messages.remove(it.roomId)
}
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import ru.noties.markwon.Markwon
import com.jakewharton.rxbinding2.widget.RxTextView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
......@@ -18,6 +19,13 @@ fun String.ifEmpty(value: String): String {
return this
}
fun CharSequence.ifEmpty(value: String): CharSequence {
if (isEmpty()) {
return value
}
return this
}
fun View.setVisible(visible: Boolean) {
visibility = if (visible) {
View.VISIBLE
......@@ -36,6 +44,16 @@ var TextView.textContent: String
text = value
}
var TextView.content: CharSequence
get() = text
set(value) {
Markwon.unscheduleDrawables(this)
Markwon.unscheduleTableRows(this)
text = value
Markwon.scheduleDrawables(this)
Markwon.scheduleTableRows(this)
}
var TextView.hintContent: String
get() = hint.toString()
set(value) {
......
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/darkGray" />
<corners android:radius="2dp" />
<size
android:width="4dp"
android:height="4dp" />
</shape>
\ No newline at end of file
......@@ -17,5 +17,6 @@
<color name="white">#FFFFFFFF</color>
<color name="black">#FF000000</color>
<color name="darkGray">#a0a0a0</color>
</resources>
\ No newline at end of file
......@@ -9,4 +9,6 @@
<dimen name="fab_elevation">6dp</dimen>
<dimen name="message_time_text_size">12sp</dimen>
</resources>
\ No newline at end of file
package chat.rocket.android;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
package chat.rocket.android
import chat.rocket.android.server.infraestructure.MemoryMessagesRepository
import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType
import org.hamcrest.CoreMatchers.notNullValue
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Test
import org.hamcrest.CoreMatchers.`is` as isEqualTo
class MemoryMessagesRepositoryTest {
val repository = MemoryMessagesRepository()
val msg = Message(
id = "messageId",
roomId = "GENERAL",
message = "Beam me up, Scotty.",
timestamp = 1511443964815,
attachments = null,
sender = null,
avatar = null,
channels = null,
editedAt = null,
editedBy = null,
groupable = true,
mentions = null,
parseUrls = false,
senderAlias = null,
type = MessageType.MessageRemoved(),
updatedAt = 1511443964815,
urls = null
)
val msg2 = Message(
id = "messageId2",
roomId = "sandbox",
message = "Highly Illogical",
timestamp = 1511443964818,
attachments = null,
sender = null,
avatar = null,
channels = null,
editedAt = null,
editedBy = null,
groupable = true,
mentions = null,
parseUrls = false,
senderAlias = null,
type = MessageType.MessageRemoved(),
updatedAt = 1511443964818,
urls = null
)
@Before
fun setup() {
repository.clear()
}
@Test
fun `save() should save a single message`() {
assertThat(repository.getAll().size, isEqualTo(0))
repository.save(msg)
val allMessages = repository.getAll()
assertThat(allMessages.size, isEqualTo(1))
allMessages[0].apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
}
@Test
fun `saveAll() should all saved messages`() {
assertThat(repository.getAll().size, isEqualTo(0))
repository.saveAll(listOf(msg, msg2))
val allMessages = repository.getAll()
assertThat(allMessages.size, isEqualTo(2))
allMessages[0].apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
allMessages[1].apply {
assertThat(id, isEqualTo("messageId2"))
assertThat(message, isEqualTo("Highly Illogical"))
assertThat(roomId, isEqualTo("sandbox"))
}
}
@Test
fun `getById() should return a single message`() {
repository.saveAll(listOf(msg, msg2))
var singleMsg = repository.getById("messageId")
assertThat(singleMsg, notNullValue())
singleMsg!!.apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
singleMsg = repository.getById("messageId2")
assertThat(singleMsg, notNullValue())
singleMsg!!.apply {
assertThat(id, isEqualTo("messageId2"))
assertThat(message, isEqualTo("Highly Illogical"))
assertThat(roomId, isEqualTo("sandbox"))
}
}
@Test
fun `getByRoomId() should return all messages for room id or an empty list`() {
repository.saveAll(listOf(msg, msg2))
var roomMessages = repository.getByRoomId("faAad32fkasods2")
assertThat(roomMessages.isEmpty(), isEqualTo(true))
roomMessages = repository.getByRoomId("sandbox")
assertThat(roomMessages.size, isEqualTo(1))
roomMessages[0].apply {
assertThat(id, isEqualTo("messageId2"))
assertThat(message, isEqualTo("Highly Illogical"))
assertThat(roomId, isEqualTo("sandbox"))
}
roomMessages = repository.getByRoomId("GENERAL")
assertThat(roomMessages.size, isEqualTo(1))
roomMessages[0].apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
}
}
\ No newline at end of file
......@@ -27,6 +27,7 @@ ext {
frescoImageViewer : '0.5.0',
androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3',
markwon : '1.0.3',
textDrawable : '1.0.2', // Remove this library after https://github.com/RocketChat/Rocket.Chat/pull/9492 is merged
moshiLazyAdapters : '2.1', // Even declared on the SDK we need to add this library here because java.lang.NoClassDefFoundError: Failed resolution of: Lcom/serjltt/moshi/adapters/FallbackEnum;
......@@ -89,6 +90,9 @@ ext {
textDrawable : "com.github.rocketchat:textdrawable:${versions.textDrawable}",
markwon : "ru.noties:markwon:${versions.markwon}",
markwonImageLoader : "ru.noties:markwon-image-loader:${versions.markwon}",
moshiLazyAdapters : "com.serjltt.moshi:moshi-lazy-adapters:${versions.moshiLazyAdapters}",
// For testing
......
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