Commit b5238704 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Add possibility to send offline messages

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