Commit b5238704 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Add possibility to send offline messages

parent 5b1ce1b6
...@@ -100,13 +100,18 @@ ...@@ -100,13 +100,18 @@
<action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".chatroom.service.MessageService"
android:exported="false">
</service>
<meta-data <meta-data
android:name="io.fabric.ApiKey" android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" /> android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
<activity android:name=".settings.about.ui.AboutActivity" <activity
android:theme="@style/AppTheme"/> android:name=".settings.about.ui.AboutActivity"
android:theme="@style/AppTheme" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -123,6 +123,8 @@ class ChatRoomAdapter( ...@@ -123,6 +123,8 @@ class ChatRoomAdapter(
if (item == null) { if (item == null) {
this.dataSet.addAll(0, dataSet) this.dataSet.addAll(0, dataSet)
notifyItemRangeInserted(0, dataSet.size) notifyItemRangeInserted(0, dataSet.size)
} else {
dataSet.forEach { updateItem(it) }
} }
} }
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.graphics.Color
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.View import android.view.View
import chat.rocket.android.chatroom.viewmodel.MessageViewModel import chat.rocket.android.chatroom.viewmodel.MessageViewModel
...@@ -8,9 +9,9 @@ import kotlinx.android.synthetic.main.avatar.view.* ...@@ -8,9 +9,9 @@ import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.* import kotlinx.android.synthetic.main.item_message.view.*
class MessageViewHolder( class MessageViewHolder(
itemView: View, itemView: View,
listener: ActionsListener, listener: ActionsListener,
reactionListener: EmojiReactionListener? = null reactionListener: EmojiReactionListener? = null
) : BaseViewHolder<MessageViewModel>(itemView, listener, reactionListener) { ) : BaseViewHolder<MessageViewModel>(itemView, listener, reactionListener) {
init { init {
...@@ -29,6 +30,9 @@ class MessageViewHolder( ...@@ -29,6 +30,9 @@ class MessageViewHolder(
text_sender.text = data.senderName text_sender.text = data.senderName
text_content.text = data.content text_content.text = data.content
image_avatar.setImageURI(data.avatar) image_avatar.setImageURI(data.avatar)
text_content.setTextColor(
if (data.isTemporary) Color.GRAY else Color.BLACK
)
} }
} }
} }
\ No newline at end of file
package chat.rocket.android.chatroom.di
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.module.AppModule
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class MessageServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideMessageService(): MessageService
}
\ No newline at end of file
...@@ -12,6 +12,7 @@ import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewMo ...@@ -12,6 +12,7 @@ import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewMo
import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.username
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.state import chat.rocket.android.server.infraestructure.state
...@@ -20,6 +21,7 @@ import chat.rocket.android.util.extensions.launchUI ...@@ -20,6 +21,7 @@ import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.SimpleUser
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -106,15 +108,42 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -106,15 +108,42 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
view.disableSendMessageButton() view.disableSendMessageButton()
try { try {
// ignore message for now, will receive it on the stream // ignore message for now, will receive it on the stream
val id = UUID.randomUUID().toString()
val message = retryIO { val message = retryIO {
if (messageId == null) { if (messageId == null) {
val id = UUID.randomUUID().toString() val username = localRepository.username()
val newMessage = Message(
id = id,
roomId = chatRoomId,
message = text,
timestamp = Instant.now().epochSecond,
sender = SimpleUser(null, username, username),
attachments = null,
avatar = username?.avatarUrl(username),
channels = null,
editedAt = null,
editedBy = null,
groupable = false,
parseUrls = false,
pinned = false,
mentions = emptyList(),
reactions = null,
senderAlias = null,
type = null,
updatedAt = null,
urls = null
)
val offlineMessage = mapper.map(newMessage).map {
it.isTemporary = true
it
}
view.showNewMessage(offlineMessage)
client.sendMessage(id, chatRoomId, text) client.sendMessage(id, chatRoomId, text)
} else { } else {
client.updateMessage(chatRoomId, messageId, text) client.updateMessage(chatRoomId, messageId, text)
} }
} }
view.enableSendMessageButton(false) view.enableSendMessageButton()
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error sending message...") Timber.d(ex, "Error sending message...")
ex.message?.let { ex.message?.let {
...@@ -122,7 +151,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -122,7 +151,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
view.enableSendMessageButton(true) } finally {
view.enableSendMessageButton()
} }
} }
} }
...@@ -214,34 +244,34 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -214,34 +244,34 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
val roomType = roomTypeOf(chatRoomType!!) val roomType = roomTypeOf(chatRoomType!!)
messagesRepository.getByRoomId(chatRoomId!!) messagesRepository.getByRoomId(chatRoomId!!)
.sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage -> .sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage ->
val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString() val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString()
try { try {
val messages = retryIO(description = "history($chatRoomId, $roomType, $instant)") { val messages = retryIO(description = "history($chatRoomId, $roomType, $instant)") {
client.history(chatRoomId!!, roomType, count = 50, client.history(chatRoomId!!, roomType, count = 50,
oldest = instant) oldest = instant)
} }
Timber.d("History: $messages") Timber.d("History: $messages")
if (messages.result.isNotEmpty()) { if (messages.result.isNotEmpty()) {
val models = mapper.map(messages.result) val models = mapper.map(messages.result)
messagesRepository.saveAll(messages.result) messagesRepository.saveAll(messages.result)
launchUI(strategy) { launchUI(strategy) {
view.showNewMessage(models) view.showNewMessage(models)
} }
if (messages.result.size == 50) { if (messages.result.size == 50) {
// we loaded at least count messages, try one more to fetch more messages // we loaded at least count messages, try one more to fetch more messages
loadMissingMessages() loadMissingMessages()
}
}
} catch (ex: Exception) {
// TODO - we need to better treat connection problems here, but no let gaps
// on the messages list
Timber.d(ex, "Error fetching channel history")
ex.printStackTrace()
} }
} }
} catch (ex: Exception) {
// TODO - we need to better treat connection problems here, but no let gaps
// on the messages list
Timber.d(ex, "Error fetching channel history")
ex.printStackTrace()
}
}
} }
} }
} }
...@@ -563,12 +593,13 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -563,12 +593,13 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
// failed, command is not valid so post it // failed, command is not valid so post it
sendMessage(roomId, text, null) sendMessage(roomId, text, null)
} }
view.enableSendMessageButton(false)
} }
} catch (ex: RocketChatException) { } catch (ex: RocketChatException) {
Timber.e(ex) Timber.e(ex)
// command is not valid, post it // command is not valid, post it
sendMessage(roomId, text, null) sendMessage(roomId, text, null)
} finally {
view.enableSendMessageButton()
} }
} }
} }
......
...@@ -92,10 +92,8 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -92,10 +92,8 @@ interface ChatRoomView : LoadingView, MessageView {
/** /**
* Enables the send message button. * Enables the send message button.
*
* @param sendFailed Whether the sent message has failed.
*/ */
fun enableSendMessageButton(sendFailed: Boolean) fun enableSendMessageButton()
/** /**
* Clears the message composition. * Clears the message composition.
......
package chat.rocket.android.chatroom.service
import android.app.job.JobParameters
import android.app.job.JobService
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import javax.inject.Inject
class MessageService : JobService() {
@Inject
lateinit var factory: ConnectionManagerFactory
@Inject
lateinit var currentServerRepository: CurrentServerRepository
override fun onStopJob(params: JobParameters?): Boolean {
return true
}
override fun onStartJob(params: JobParameters?): Boolean {
launch(CommonPool) {
val currentServer = currentServerRepository.get()
if (currentServer != null) {
val connectionManager = factory.create(currentServer)
}
jobFinished(params, false)
}
return true
}
}
\ No newline at end of file
...@@ -70,8 +70,10 @@ private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen" ...@@ -70,8 +70,10 @@ private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen"
private const val BUNDLE_CHAT_ROOM_IS_SUBSCRIBED = "chat_room_is_subscribed" private const val BUNDLE_CHAT_ROOM_IS_SUBSCRIBED = "chat_room_is_subscribed"
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener { class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener {
@Inject lateinit var presenter: ChatRoomPresenter @Inject
@Inject lateinit var parser: MessageParser lateinit var presenter: ChatRoomPresenter
@Inject
lateinit var parser: MessageParser
private lateinit var adapter: ChatRoomAdapter private lateinit var adapter: ChatRoomAdapter
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
private lateinit var chatRoomName: String private lateinit var chatRoomName: String
...@@ -310,12 +312,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -310,12 +312,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_send.isEnabled = false button_send.isEnabled = false
} }
override fun enableSendMessageButton(sendFailed: Boolean) { override fun enableSendMessageButton() {
button_send.isEnabled = true button_send.isEnabled = true
text_message.isEnabled = true text_message.isEnabled = true
if (!sendFailed) { clearMessageComposition()
clearMessageComposition()
}
} }
override fun clearMessageComposition() { override fun clearMessageComposition() {
......
...@@ -13,7 +13,8 @@ data class AudioAttachmentViewModel( ...@@ -13,7 +13,8 @@ data class AudioAttachmentViewModel(
override val id: Long, override val id: Long,
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseFileAttachmentViewModel<AudioAttachment> { ) : BaseFileAttachmentViewModel<AudioAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.AUDIO_ATTACHMENT.viewType get() = BaseViewModel.ViewType.AUDIO_ATTACHMENT.viewType
......
...@@ -5,17 +5,18 @@ import chat.rocket.core.model.Message ...@@ -5,17 +5,18 @@ import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.AuthorAttachment import chat.rocket.core.model.attachment.AuthorAttachment
data class AuthorAttachmentViewModel( data class AuthorAttachmentViewModel(
override val attachmentUrl: String, override val attachmentUrl: String,
val id: Long, val id: Long,
val name: CharSequence?, val name: CharSequence?,
val icon: String?, val icon: String?,
val fields: CharSequence?, val fields: CharSequence?,
override val message: Message, override val message: Message,
override val rawData: AuthorAttachment, override val rawData: AuthorAttachment,
override val messageId: String, override val messageId: String,
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseAttachmentViewModel<AuthorAttachment> { ) : BaseAttachmentViewModel<AuthorAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.AUTHOR_ATTACHMENT.viewType get() = BaseViewModel.ViewType.AUTHOR_ATTACHMENT.viewType
......
...@@ -12,6 +12,7 @@ interface BaseViewModel<out T> { ...@@ -12,6 +12,7 @@ interface BaseViewModel<out T> {
var reactions: List<ReactionViewModel> var reactions: List<ReactionViewModel>
var nextDownStreamMessage: BaseViewModel<*>? var nextDownStreamMessage: BaseViewModel<*>?
var preview: Message? var preview: Message?
var isTemporary: Boolean
enum class ViewType(val viewType: Int) { enum class ViewType(val viewType: Int) {
MESSAGE(0), MESSAGE(0),
......
...@@ -13,7 +13,8 @@ data class ImageAttachmentViewModel( ...@@ -13,7 +13,8 @@ data class ImageAttachmentViewModel(
override val id: Long, override val id: Long,
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseFileAttachmentViewModel<ImageAttachment> { ) : BaseFileAttachmentViewModel<ImageAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.IMAGE_ATTACHMENT.viewType get() = BaseViewModel.ViewType.IMAGE_ATTACHMENT.viewType
......
...@@ -14,7 +14,8 @@ data class MessageAttachmentViewModel( ...@@ -14,7 +14,8 @@ data class MessageAttachmentViewModel(
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
var messageLink: String? = null, var messageLink: String? = null,
override var preview: Message? = null override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseViewModel<Message> { ) : BaseViewModel<Message> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.MESSAGE_ATTACHMENT.viewType get() = BaseViewModel.ViewType.MESSAGE_ATTACHMENT.viewType
......
...@@ -15,7 +15,8 @@ data class MessageViewModel( ...@@ -15,7 +15,8 @@ data class MessageViewModel(
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
var isFirstUnread: Boolean var isFirstUnread: Boolean,
override var isTemporary: Boolean = false
) : BaseMessageViewModel<Message> { ) : BaseMessageViewModel<Message> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.MESSAGE.viewType get() = BaseViewModel.ViewType.MESSAGE.viewType
......
...@@ -14,7 +14,8 @@ data class UrlPreviewViewModel( ...@@ -14,7 +14,8 @@ data class UrlPreviewViewModel(
val thumbUrl: String?, val thumbUrl: String?,
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseViewModel<Url> { ) : BaseViewModel<Url> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.URL_PREVIEW.viewType get() = BaseViewModel.ViewType.URL_PREVIEW.viewType
......
...@@ -13,7 +13,8 @@ data class VideoAttachmentViewModel( ...@@ -13,7 +13,8 @@ data class VideoAttachmentViewModel(
override val id: Long, override val id: Long,
override var reactions: List<ReactionViewModel>, override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>? = null, override var nextDownStreamMessage: BaseViewModel<*>? = null,
override var preview: Message? = null override var preview: Message? = null,
override var isTemporary: Boolean = false
) : BaseFileAttachmentViewModel<VideoAttachment> { ) : BaseFileAttachmentViewModel<VideoAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseViewModel.ViewType.VIDEO_ATTACHMENT.viewType get() = BaseViewModel.ViewType.VIDEO_ATTACHMENT.viewType
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.dagger ...@@ -2,6 +2,7 @@ package chat.rocket.android.dagger
import android.app.Application import android.app.Application
import chat.rocket.android.app.RocketChatApplication import chat.rocket.android.app.RocketChatApplication
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.module.ActivityBuilder import chat.rocket.android.dagger.module.ActivityBuilder
import chat.rocket.android.dagger.module.AppModule import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.dagger.module.ReceiverBuilder import chat.rocket.android.dagger.module.ReceiverBuilder
...@@ -29,6 +30,8 @@ interface AppComponent { ...@@ -29,6 +30,8 @@ interface AppComponent {
fun inject(service: FirebaseTokenService) fun inject(service: FirebaseTokenService)
fun inject(service: MessageService)
/*@Component.Builder /*@Component.Builder
abstract class Builder : AndroidInjector.Builder<RocketChatApplication>()*/ abstract class Builder : AndroidInjector.Builder<RocketChatApplication>()*/
} }
package chat.rocket.android.dagger.module package chat.rocket.android.dagger.module
import chat.rocket.android.chatroom.di.MessageServiceProvider
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.push.FirebaseTokenService import chat.rocket.android.push.FirebaseTokenService
import chat.rocket.android.push.GcmListenerService import chat.rocket.android.push.GcmListenerService
import chat.rocket.android.push.di.FirebaseTokenServiceProvider import chat.rocket.android.push.di.FirebaseTokenServiceProvider
...@@ -14,4 +16,7 @@ import dagger.android.ContributesAndroidInjector ...@@ -14,4 +16,7 @@ import dagger.android.ContributesAndroidInjector
@ContributesAndroidInjector(modules = [GcmListenerServiceProvider::class]) @ContributesAndroidInjector(modules = [GcmListenerServiceProvider::class])
abstract fun bindGcmListenerService(): GcmListenerService abstract fun bindGcmListenerService(): GcmListenerService
@ContributesAndroidInjector(modules = [MessageServiceProvider::class])
abstract fun bindMessageService(): MessageService
} }
\ No newline at end of file
package chat.rocket.android.infrastructure package chat.rocket.android.infrastructure
import chat.rocket.core.model.Myself
interface LocalRepository { interface LocalRepository {
fun save(key: String, value: String?) fun save(key: String, value: String?)
...@@ -24,4 +26,5 @@ interface LocalRepository { ...@@ -24,4 +26,5 @@ interface LocalRepository {
} }
} }
fun LocalRepository.checkIfMyself(username: String) = get(LocalRepository.CURRENT_USERNAME_KEY) == username fun LocalRepository.checkIfMyself(username: String) = username() == username
\ No newline at end of file fun LocalRepository.username() = get(LocalRepository.CURRENT_USERNAME_KEY)
\ 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