Unverified Commit 2f934213 authored by Leonardo Aramaki's avatar Leonardo Aramaki Committed by GitHub

Merge branch 'develop-2.x' into new/color-attachment

parents 7c3e43aa 3e480b2f
...@@ -102,13 +102,18 @@ ...@@ -102,13 +102,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="true"
android:permission="android.permission.BIND_JOB_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>
...@@ -16,6 +16,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView, ...@@ -16,6 +16,7 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
private val serverInteractor: SaveCurrentServerInteractor, private val serverInteractor: SaveCurrentServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor) { private val getAccountsInteractor: GetAccountsInteractor) {
fun connect(server: String) { fun connect(server: String) {
if (!server.isValidUrl()) { if (!server.isValidUrl()) {
view.showInvalidServerUrlMessage() view.showInvalidServerUrlMessage()
......
...@@ -122,12 +122,22 @@ class ChatRoomAdapter( ...@@ -122,12 +122,22 @@ class ChatRoomAdapter(
} }
fun prependData(dataSet: List<BaseViewModel<*>>) { fun prependData(dataSet: List<BaseViewModel<*>>) {
val item = dataSet.firstOrNull { newItem -> val item = dataSet.indexOfFirst { newItem ->
this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } > -1 this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } > -1
} }
if (item == null) { if (item == -1) {
this.dataSet.addAll(0, dataSet) this.dataSet.addAll(0, dataSet)
notifyItemRangeInserted(0, dataSet.size) notifyItemRangeInserted(0, dataSet.size)
} else {
dataSet.forEach { item ->
val index = this.dataSet.indexOfFirst {
item.messageId == it.messageId && item.viewType == it.viewType
}
if (index > -1) {
this.dataSet[index] = item
notifyItemChanged(index)
}
}
} }
} }
......
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
...@@ -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
...@@ -52,7 +54,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -52,7 +54,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val roomsRepository: RoomRepository, private val roomsRepository: RoomRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
factory: ConnectionManagerFactory, factory: ConnectionManagerFactory,
private val mapper: ViewModelMapper) { private val mapper: ViewModelMapper,
private val jobSchedulerInteractor: JobSchedulerInteractor) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val manager = factory.create(currentServer) private val manager = factory.create(currentServer)
private val client = manager.client private val client = manager.client
...@@ -70,14 +73,18 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -70,14 +73,18 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val messages = if (offset == 0L) {
retryIO(description = "messages chatRoom: $chatRoomId, type: $chatRoomType, offset: $offset") { val localMessages = messagesRepository.getByRoomId(chatRoomId)
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result val oldMessages = mapper.map(localMessages)
if (oldMessages.isNotEmpty()) {
view.showMessages(oldMessages)
loadMissingMessages()
} else {
loadAndShowMessages(chatRoomId, chatRoomType, offset)
}
} else {
loadAndShowMessages(chatRoomId, chatRoomType, offset)
} }
messagesRepository.saveAll(messages)
val messagesViewModels = mapper.map(messages)
view.showMessages(messagesViewModels)
// TODO: For now we are marking the room as read if we can get the messages (I mean, no exception occurs) // TODO: For now we are marking the room as read if we can get the messages (I mean, no exception occurs)
// but should mark only when the user see the first unread message. // but should mark only when the user see the first unread message.
...@@ -85,7 +92,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -85,7 +92,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
subscribeMessages(chatRoomId) subscribeMessages(chatRoomId)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() Timber.e(ex)
ex.message?.let { ex.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
...@@ -101,28 +108,58 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -101,28 +108,58 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
} }
} }
private suspend fun loadAndShowMessages(chatRoomId: String, chatRoomType: String, offset: Long = 0) {
val messages =
retryIO(description = "messages chatRoom: $chatRoomId, type: $chatRoomType, offset: $offset") {
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
}
messagesRepository.saveAll(messages)
val allMessages = mapper.map(messages)
view.showMessages(allMessages)
}
fun sendMessage(chatRoomId: String, text: String, messageId: String?) { fun sendMessage(chatRoomId: String, text: String, messageId: String?) {
launchUI(strategy) { launchUI(strategy) {
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 message = retryIO {
if (messageId == null) {
val id = UUID.randomUUID().toString() val id = UUID.randomUUID().toString()
val message = if (messageId == null) {
val username = localRepository.username()
val newMessage = Message(
id = id,
roomId = chatRoomId,
message = text,
timestamp = Instant.now().toEpochMilli(),
sender = SimpleUser(null, username, username),
attachments = null,
avatar = currentServer.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,
isTemporary = true
)
messagesRepository.save(newMessage)
view.showNewMessage(mapper.map(newMessage))
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 { jobSchedulerInteractor.scheduleSendingMessages()
view.showMessage(it) } finally {
}.ifNull { view.enableSendMessageButton()
view.showGenericErrorMessage()
}
view.enableSendMessageButton(true)
} }
} }
} }
...@@ -189,6 +226,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -189,6 +226,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
} }
if (state is State.Connected) { if (state is State.Connected) {
jobSchedulerInteractor.scheduleSendingMessages()
loadMissingMessages() loadMissingMessages()
} }
} }
...@@ -563,12 +601,13 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView, ...@@ -563,12 +601,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.domain.MessagesRepository
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
class MessageService : JobService() {
@Inject
lateinit var factory: ConnectionManagerFactory
@Inject
lateinit var currentServerRepository: CurrentServerRepository
@Inject
lateinit var messageRepository: MessagesRepository
override fun onCreate() {
super.onCreate()
AndroidInjection.inject(this)
}
override fun onStopJob(params: JobParameters?): Boolean {
return false
}
override fun onStartJob(params: JobParameters?): Boolean {
launch(CommonPool) {
val currentServer = currentServerRepository.get()
if (currentServer != null) {
retrySendingMessages(params, currentServer)
jobFinished(params, false)
}
}
return true
}
private suspend fun retrySendingMessages(params: JobParameters?, currentServer: String) {
val temporaryMessages = messageRepository.getAllUnsent()
.sortedWith(compareBy(Message::timestamp))
if (temporaryMessages.isNotEmpty()) {
val connectionManager = factory.create(currentServer)
val client = connectionManager.client
temporaryMessages.forEach { message ->
try {
client.sendMessage(
message = message.message,
messageId = message.id,
roomId = message.roomId,
avatar = message.avatar,
attachments = message.attachments,
alias = message.senderAlias
)
messageRepository.save(message.copy(isTemporary = false))
Timber.d("Sent scheduled message given by id: ${message.id}")
} catch (ex: RocketChatException) {
Timber.e(ex)
if (ex.message?.contains("E11000", true) == true) {
// XXX: Temporary solution. We need proper error codes from the api.
messageRepository.save(message.copy(isTemporary = false))
}
jobFinished(params, true)
}
}
}
}
companion object {
const val RETRY_SEND_MESSAGE_ID = 1
}
}
\ No newline at end of file
...@@ -4,7 +4,6 @@ import android.Manifest ...@@ -4,7 +4,6 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
...@@ -70,8 +69,10 @@ private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen" ...@@ -70,8 +69,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
...@@ -316,15 +317,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -316,15 +317,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun enableSendMessageButton(sendFailed: Boolean) { override fun enableSendMessageButton() {
ui { ui {
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() {
ui { ui {
...@@ -589,6 +589,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -589,6 +589,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
var textMessage = citation ?: "" var textMessage = citation ?: ""
textMessage += text_message.textContent textMessage += text_message.textContent
sendMessage(textMessage) sendMessage(textMessage)
clearMessageComposition()
} }
button_show_attachment_options.setOnClickListener { button_show_attachment_options.setOnClickListener {
......
...@@ -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
......
...@@ -15,7 +15,8 @@ data class AuthorAttachmentViewModel( ...@@ -15,7 +15,8 @@ data class AuthorAttachmentViewModel(
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
......
...@@ -226,12 +226,13 @@ class ViewModelMapper @Inject constructor(private val context: Context, ...@@ -226,12 +226,13 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val time = getTime(message.timestamp) val time = getTime(message.timestamp)
val avatar = getUserAvatar(message) val avatar = getUserAvatar(message)
val preview = mapMessagePreview(message) val preview = mapMessagePreview(message)
val isTemp = message.isTemporary ?: false
val content = getContent(stripMessageQuotes(message)) val content = getContent(stripMessageQuotes(message))
MessageViewModel(message = stripMessageQuotes(message), rawData = message, MessageViewModel(message = stripMessageQuotes(message), rawData = message,
messageId = message.id, avatar = avatar!!, time = time, senderName = sender, messageId = message.id, avatar = avatar!!, time = time, senderName = sender,
content = content, isPinned = message.pinned, reactions = getReactions(message), content = content, isPinned = message.pinned, reactions = getReactions(message),
isFirstUnread = false, preview = preview) isFirstUnread = false, preview = preview, isTemporary = isTemp)
} }
private suspend fun mapMessagePreview(message: Message): Message { private suspend fun mapMessagePreview(message: Message): Message {
......
...@@ -40,6 +40,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -40,6 +40,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val saveChatRoomsInteractor: SaveChatRoomsInteractor, private val saveChatRoomsInteractor: SaveChatRoomsInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val viewModelMapper: ViewModelMapper, private val viewModelMapper: ViewModelMapper,
private val jobSchedulerInteractor: JobSchedulerInteractor,
settingsRepository: SettingsRepository, settingsRepository: SettingsRepository,
factory: ConnectionManagerFactory) { factory: ConnectionManagerFactory) {
private val manager: ConnectionManager = factory.create(serverInteractor.get()!!) private val manager: ConnectionManager = factory.create(serverInteractor.get()!!)
...@@ -258,6 +259,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -258,6 +259,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
} }
if (state is State.Connected) { if (state is State.Connected) {
jobSchedulerInteractor.scheduleSendingMessages()
reloadRooms() reloadRooms()
updateRooms() updateRooms()
} }
......
...@@ -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>()*/
} }
...@@ -2,7 +2,10 @@ package chat.rocket.android.dagger.module ...@@ -2,7 +2,10 @@ package chat.rocket.android.dagger.module
import android.app.Application import android.app.Application
import android.app.NotificationManager import android.app.NotificationManager
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.arch.persistence.room.Room import android.arch.persistence.room.Room
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.systemService import androidx.core.content.systemService
...@@ -11,6 +14,7 @@ import chat.rocket.android.R ...@@ -11,6 +14,7 @@ import chat.rocket.android.R
import chat.rocket.android.app.RocketChatDatabase import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.ForFresco import chat.rocket.android.dagger.qualifier.ForFresco
import chat.rocket.android.helper.FrescoAuthInterceptor import chat.rocket.android.helper.FrescoAuthInterceptor
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
...@@ -19,19 +23,17 @@ import chat.rocket.android.infrastructure.SharedPrefsLocalRepository ...@@ -19,19 +23,17 @@ import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.push.GroupedPush import chat.rocket.android.push.GroupedPush
import chat.rocket.android.push.PushManager import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository import chat.rocket.android.server.infraestructure.*
import chat.rocket.android.server.infraestructure.MemoryMessagesRepository
import chat.rocket.android.server.infraestructure.MemoryRoomRepository
import chat.rocket.android.server.infraestructure.MemoryUsersRepository
import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.util.AppJsonAdapterFactory import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date
import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.AttachmentAdapterFactory
import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory
import com.facebook.imagepipeline.core.ImagePipelineConfig import com.facebook.imagepipeline.core.ImagePipelineConfig
...@@ -121,7 +123,7 @@ class AppModule { ...@@ -121,7 +123,7 @@ class AppModule {
@Provides @Provides
@ForFresco @ForFresco
@Singleton @Singleton
fun provideFrescoAuthIntercepter(tokenRepository: TokenRepository, currentServerInteractor: GetCurrentServerInteractor): Interceptor { fun provideFrescoAuthInterceptor(tokenRepository: TokenRepository, currentServerInteractor: GetCurrentServerInteractor): Interceptor {
return FrescoAuthInterceptor(tokenRepository, currentServerInteractor) return FrescoAuthInterceptor(tokenRepository, currentServerInteractor)
} }
...@@ -202,10 +204,16 @@ class AppModule { ...@@ -202,10 +204,16 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideMoshi(): Moshi { fun provideMoshi(logger: PlatformLogger,
currentServerInteractor: GetCurrentServerInteractor):
Moshi {
val url = currentServerInteractor.get() ?: ""
return Moshi.Builder() return Moshi.Builder()
.add(FallbackSealedClassJsonAdapter.ADAPTER_FACTORY) .add(FallbackSealedClassJsonAdapter.ADAPTER_FACTORY)
.add(AppJsonAdapterFactory.INSTANCE) .add(AppJsonAdapterFactory.INSTANCE)
.add(AttachmentAdapterFactory(Logger(logger, url)))
.add(java.lang.Long::class.java, ISO8601Date::class.java, TimestampAdapter(CalendarISO8601Converter()))
.add(Long::class.java, ISO8601Date::class.java, TimestampAdapter(CalendarISO8601Converter()))
.build() .build()
} }
...@@ -217,8 +225,9 @@ class AppModule { ...@@ -217,8 +225,9 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideMessageRepository(): MessagesRepository { fun provideMessageRepository(context: Application, moshi: Moshi, currentServerInteractor: GetCurrentServerInteractor): MessagesRepository {
return MemoryMessagesRepository() val preferences = context.getSharedPreferences("messages", Context.MODE_PRIVATE)
return SharedPreferencesMessagesRepository(preferences, moshi, currentServerInteractor)
} }
@Provides @Provides
...@@ -278,4 +287,22 @@ class AppModule { ...@@ -278,4 +287,22 @@ class AppModule {
getSettingsInteractor: GetSettingsInteractor): PushManager { getSettingsInteractor: GetSettingsInteractor): PushManager {
return PushManager(groupedPushes, manager, moshi, getAccountInteractor, getSettingsInteractor, context) return PushManager(groupedPushes, manager, moshi, getAccountInteractor, getSettingsInteractor, context)
} }
@Provides
fun provideJobScheduler(context: Application): JobScheduler {
return context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
}
@Provides
fun provideSendMessageJob(context: Application): JobInfo {
return JobInfo.Builder(MessageService.RETRY_SEND_MESSAGE_ID,
ComponentName(context, MessageService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build()
}
@Provides
fun provideJobSchedulerInteractor(jobScheduler: JobScheduler, jobInfo: JobInfo): JobSchedulerInteractor {
return JobSchedulerInteractorImpl(jobScheduler, jobInfo)
}
} }
\ No newline at end of file
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
...@@ -24,4 +24,5 @@ interface LocalRepository { ...@@ -24,4 +24,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
...@@ -23,9 +23,7 @@ import chat.rocket.core.internal.rest.logout ...@@ -23,9 +23,7 @@ import chat.rocket.core.internal.rest.logout
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.unregisterPushToken import chat.rocket.core.internal.rest.unregisterPushToken
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -39,7 +37,7 @@ class MainPresenter @Inject constructor( ...@@ -39,7 +37,7 @@ class MainPresenter @Inject constructor(
private val navHeaderMapper: NavHeaderViewModelMapper, private val navHeaderMapper: NavHeaderViewModelMapper,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
private val removeAccountInterector: RemoveAccountInterector, private val removeAccountInteractor: RemoveAccountInteractor,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
getSettingsInteractor: GetSettingsInteractor, getSettingsInteractor: GetSettingsInteractor,
managerFactory: ConnectionManagerFactory managerFactory: ConnectionManagerFactory
...@@ -105,7 +103,7 @@ class MainPresenter @Inject constructor( ...@@ -105,7 +103,7 @@ class MainPresenter @Inject constructor(
try { try {
disconnect() disconnect()
removeAccountInterector.remove(currentServer) removeAccountInteractor.remove(currentServer)
tokenRepository.remove(currentServer) tokenRepository.remove(currentServer)
navigator.toNewServer() navigator.toNewServer()
} catch (ex: Exception) { } catch (ex: Exception) {
......
package chat.rocket.android.server.domain
interface JobSchedulerInteractor {
/**
* Schedule job to retry previously failed sending messages.
*/
fun scheduleSendingMessages()
}
\ No newline at end of file
...@@ -11,7 +11,7 @@ interface MessagesRepository { ...@@ -11,7 +11,7 @@ interface MessagesRepository {
* *
* @return The Message object given by the id or null if message wasn't found. * @return The Message object given by the id or null if message wasn't found.
*/ */
fun getById(id: String): Message? suspend fun getById(id: String): Message?
/** /**
* Get all messages from the current room id. * Get all messages from the current room id.
...@@ -19,7 +19,7 @@ interface MessagesRepository { ...@@ -19,7 +19,7 @@ interface MessagesRepository {
* @param rid The room id. * @param rid The room id.
* @return A list of Message objects for the room with given room id or an empty list. * @return A list of Message objects for the room with given room id or an empty list.
*/ */
fun getByRoomId(rid: String): List<Message> suspend fun getByRoomId(rid: String): List<Message>
/** /**
* Get most recent messages up to count different users. * Get most recent messages up to count different users.
...@@ -29,43 +29,47 @@ interface MessagesRepository { ...@@ -29,43 +29,47 @@ interface MessagesRepository {
* *
* @return List of last count messages. * @return List of last count messages.
*/ */
fun getRecentMessages(rid: String, count: Long): List<Message> suspend fun getRecentMessages(rid: String, count: Long): List<Message>
/** /**
* Get all messages. Use carefully! * Get all messages. Use carefully!
* *
* @return All messages or an empty list. * @return All messages or an empty list.
*/ */
fun getAll(): List<Message> suspend fun getAll(): List<Message>
/** /**
* Save a single message object. * Save a single message object.
* *
* @param The message object to saveAll. * @param The message object to saveAll.
*/ */
fun save(message: Message) suspend fun save(message: Message)
/** /**
* Save a list of messages. * Save a list of messages.
*/ */
fun saveAll(newMessages: List<Message>) suspend fun saveAll(newMessages: List<Message>)
/** /**
* Removes all messages. * Removes all messages.
*/ */
fun clear() suspend fun clear()
/** /**
* Remove message by id. * Remove message by id.
* *
* @param id The id of the message to remove. * @param id The id of the message to remove.
*/ */
fun removeById(id: String) suspend fun removeById(id: String)
/** /**
* Remove all messages from a given room. * Remove all messages from a given room.
* *
* @param rid The room id where messages are to be removed. * @param rid The room id where messages are to be removed.
*/ */
fun removeByRoomId(rid: String) suspend fun removeByRoomId(rid: String)
suspend fun getAllUnsent(): List<Message>
suspend fun getUnsentByRoomId(roomId: String): List<Message>
} }
\ No newline at end of file
...@@ -2,7 +2,7 @@ package chat.rocket.android.server.domain ...@@ -2,7 +2,7 @@ package chat.rocket.android.server.domain
import javax.inject.Inject import javax.inject.Inject
class RemoveAccountInterector @Inject constructor(val repository: AccountsRepository) { class RemoveAccountInteractor @Inject constructor(val repository: AccountsRepository) {
suspend fun remove(serverUrl: String) { suspend fun remove(serverUrl: String) {
repository.remove(serverUrl) repository.remove(serverUrl)
} }
......
package chat.rocket.android.server.infraestructure
import android.app.job.JobInfo
import android.app.job.JobScheduler
import chat.rocket.android.server.domain.JobSchedulerInteractor
import timber.log.Timber
import javax.inject.Inject
/**
* Uses the Android framework [JobScheduler].
*/
class JobSchedulerInteractorImpl @Inject constructor(
private val jobScheduler: JobScheduler,
private val jobInfo: JobInfo
) : JobSchedulerInteractor {
override fun scheduleSendingMessages() {
Timber.d("Scheduling unsent messages to send...")
jobScheduler.schedule(jobInfo)
}
}
\ No newline at end of file
...@@ -2,45 +2,67 @@ package chat.rocket.android.server.infraestructure ...@@ -2,45 +2,67 @@ package chat.rocket.android.server.infraestructure
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
class MemoryMessagesRepository : MessagesRepository { class MemoryMessagesRepository : MessagesRepository {
private val messages: HashMap<String, Message> = HashMap() private val messages: HashMap<String, Message> = HashMap()
override fun getById(id: String): Message? { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
return messages[id] return@withContext messages[id]
} }
override fun getByRoomId(rid: String): List<Message> { override suspend fun getByRoomId(rid: String): List<Message> = withContext(CommonPool) {
return messages.filter { it.value.roomId == rid }.values.toList() return@withContext messages.filter { it.value.roomId == rid }.values.toList()
} }
override fun getRecentMessages(rid: String, count: Long): List<Message> { override suspend fun getRecentMessages(rid: String, count: Long): List<Message> = withContext(CommonPool) {
return getByRoomId(rid).sortedByDescending { it.timestamp } return@withContext getByRoomId(rid).sortedByDescending { it.timestamp }
.distinctBy { it.sender }.take(count.toInt()) .distinctBy { it.sender }.take(count.toInt())
} }
override fun getAll(): List<Message> = messages.values.toList() override suspend fun getAll(): List<Message> = withContext(CommonPool) {
return@withContext messages.values.toList()
}
override suspend fun getUnsentByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
val allByRoomId = getByRoomId(roomId)
if (allByRoomId.isEmpty()) {
return@withContext emptyList<Message>()
}
return@withContext allByRoomId.filter { it.isTemporary ?: false && it.roomId == roomId }
}
override fun save(message: Message) { override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
val all = getAll()
if (all.isEmpty()) {
return@withContext emptyList<Message>()
}
return@withContext all.filter { it.isTemporary ?: false }
}
override suspend fun save(message: Message) = withContext(CommonPool) {
messages[message.id] = message messages[message.id] = message
} }
override fun saveAll(newMessages: List<Message>) { override suspend fun saveAll(newMessages: List<Message>) = withContext(CommonPool) {
for (msg in newMessages) { for (msg in newMessages) {
messages[msg.id] = msg messages[msg.id] = msg
} }
} }
override fun clear() { override suspend fun clear() = withContext(CommonPool) {
messages.clear() messages.clear()
} }
override fun removeById(id: String) { override suspend fun removeById(id: String) {
withContext(CommonPool) {
messages.remove(id) messages.remove(id)
} }
}
override fun removeByRoomId(rid: String) { override suspend fun removeByRoomId(rid: String) = withContext(CommonPool) {
val roomMessages = messages.filter { it.value.roomId == rid }.values val roomMessages = messages.filter { it.value.roomId == rid }.values
roomMessages.forEach { roomMessages.forEach {
messages.remove(it.roomId) messages.remove(it.roomId)
......
package chat.rocket.android.server.infraestructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.model.Message
import com.squareup.moshi.Moshi
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
class SharedPreferencesMessagesRepository(
private val prefs: SharedPreferences,
private val moshi: Moshi,
private val currentServerInteractor: GetCurrentServerInteractor
) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
if (prefs.all.values.isEmpty()) {
return@withContext null
}
val adapter = moshi.adapter<Message>(Message::class.java)
val values = prefs.all.entries.filter { it.key.startsWith(server) }
.map { it.value } as Collection<String>
return@withContext values.map { adapter.fromJson(it) }.firstOrNull { it?.id == id }
}
return@withContext null
}
override suspend fun getByRoomId(rid: String): List<Message> = withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
val adapter = moshi.adapter<Message>(Message::class.java)
if (prefs.all.values.isEmpty()) {
return@withContext emptyList<Message>()
}
val values = prefs.all.entries.filter { it.key.startsWith(server) }
.map { it.value } as Collection<String>
return@withContext values.mapNotNull { adapter.fromJson(it) }.filter {
it.roomId == rid
}.toList().sortedWith(compareBy(Message::timestamp)).reversed()
}
return@withContext emptyList<Message>()
}
override suspend fun getRecentMessages(rid: String, count: Long): List<Message> = withContext(CommonPool) {
return@withContext getByRoomId(rid).sortedByDescending { it.timestamp }
.distinctBy { it.sender }.take(count.toInt())
}
override suspend fun getAll(): List<Message> = withContext(CommonPool) {
val adapter = moshi.adapter<Message>(Message::class.java)
if (prefs.all.values.isEmpty()) {
return@withContext emptyList<Message>()
}
currentServerInteractor.get()?.also { server ->
val values = prefs.all.entries.filter { it.key.startsWith(server) }
.map { it.value } as Collection<String>
return@withContext values.mapNotNull { adapter.fromJson(it) }
}
return@withContext emptyList<Message>()
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
if (prefs.all.values.isEmpty()) {
return@withContext emptyList<Message>()
}
currentServerInteractor.get()?.also { server ->
val values = prefs.all.entries.filter { it.key.startsWith(server) }
.map { it.value } as Collection<String>
val adapter = moshi.adapter<Message>(Message::class.java)
return@withContext values.mapNotNull { adapter.fromJson(it) }
.filter { it.isTemporary ?: false }
}
return@withContext emptyList<Message>()
}
override suspend fun getUnsentByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
val allByRoomId = getByRoomId(roomId)
if (allByRoomId.isEmpty()) {
return@withContext emptyList<Message>()
}
return@withContext allByRoomId.filter { it.isTemporary ?: false }
}
override suspend fun save(message: Message) {
withContext(CommonPool) {
currentServerInteractor.get()?.also {
val adapter = moshi.adapter<Message>(Message::class.java)
prefs.edit().putString("${it}_${message.id}", adapter.toJson(message)).apply()
}
}
}
override suspend fun saveAll(newMessages: List<Message>) {
withContext(CommonPool) {
currentServerInteractor.get()?.also {
val adapter = moshi.adapter<Message>(Message::class.java)
val editor = prefs.edit()
for (msg in newMessages) {
editor.putString("${it}_${msg.id}", adapter.toJson(msg))
}
editor.apply()
}
}
}
override suspend fun clear() = withContext(CommonPool) {
prefs.edit().clear().apply()
}
override suspend fun removeById(id: String) {
withContext(CommonPool) {
currentServerInteractor.get()?.also {
prefs.edit().putString("${it}_$id", null).apply()
}
}
}
override suspend fun removeByRoomId(rid: String) {
withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
val adapter = moshi.adapter<Message>(Message::class.java)
val editor = prefs.edit()
prefs.all.entries.forEach {
val value = it.value
if (value is String) {
val message = adapter.fromJson(value)
if (message?.roomId == rid) {
editor.putString("${server}_${message.id}", null)
}
}
}
editor.apply()
}
}
}
}
\ 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