Update send message to account for guid returned by servers
This commit is contained in:
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
|
|||||||
@@ -36,8 +36,13 @@ class MessageListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class OutgoingMessage(
|
||||||
|
val requestGuid: String,
|
||||||
|
val message: Message,
|
||||||
|
)
|
||||||
|
|
||||||
private var conversation: Conversation? = null
|
private var conversation: Conversation? = null
|
||||||
private val pendingMessages: MutableStateFlow<List<Message>> = MutableStateFlow(listOf())
|
private val pendingMessages: MutableStateFlow<List<OutgoingMessage>> = MutableStateFlow(listOf())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -45,14 +50,14 @@ class MessageListViewModel @Inject constructor(
|
|||||||
// By now, the repository should've committed this to the store.
|
// By now, the repository should've committed this to the store.
|
||||||
repository.messageDeliveredChannel.collectLatest { event ->
|
repository.messageDeliveredChannel.collectLatest { event ->
|
||||||
pendingMessages.value =
|
pendingMessages.value =
|
||||||
pendingMessages.value.filter { it.guid != event.message.guid }
|
pendingMessages.value.filter { it.requestGuid != event.requestGuid }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val messages: Flow<List<Message>>
|
val messages: Flow<List<Message>>
|
||||||
get() = repository.messagesChanged(conversation!!)
|
get() = repository.messagesChanged(conversation!!)
|
||||||
.combine(pendingMessages) { a, b -> a.union(b) }
|
.combine(pendingMessages) { a, b -> a.union(b.map { it.message }) }
|
||||||
.map { messages ->
|
.map { messages ->
|
||||||
messages
|
messages
|
||||||
.sortedBy { it.date }
|
.sortedBy { it.date }
|
||||||
@@ -70,12 +75,12 @@ class MessageListViewModel @Inject constructor(
|
|||||||
conversation = conversation!!,
|
conversation = conversation!!,
|
||||||
)
|
)
|
||||||
|
|
||||||
pendingMessages.value = pendingMessages.value + listOf(outgoingMessage)
|
val outgoingGUID = repository.enqueueOutgoingMessage(outgoingMessage, conversation!!)
|
||||||
repository.enqueueOutgoingMessage(outgoingMessage, conversation!!)
|
pendingMessages.value = pendingMessages.value + listOf(OutgoingMessage(outgoingGUID, outgoingMessage))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPendingMessage(message: Message): Boolean {
|
fun isPendingMessage(message: Message): Boolean {
|
||||||
return pendingMessages.value.contains(message)
|
return pendingMessages.value.any { it.message.guid == message.guid }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun synchronize() = viewModelScope.launch {
|
fun synchronize() = viewModelScope.launch {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.buzzert.kordophone.backend.db
|
package net.buzzert.kordophone.backend.db
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import io.realm.kotlin.MutableRealm
|
import io.realm.kotlin.MutableRealm
|
||||||
import io.realm.kotlin.Realm
|
import io.realm.kotlin.Realm
|
||||||
import io.realm.kotlin.RealmConfiguration
|
import io.realm.kotlin.RealmConfiguration
|
||||||
@@ -15,6 +16,7 @@ import net.buzzert.kordophone.backend.db.model.toDatabaseConversation
|
|||||||
import net.buzzert.kordophone.backend.db.model.toDatabaseMessage
|
import net.buzzert.kordophone.backend.db.model.toDatabaseMessage
|
||||||
import net.buzzert.kordophone.backend.db.model.toRealmInstant
|
import net.buzzert.kordophone.backend.db.model.toRealmInstant
|
||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
|
import net.buzzert.kordophone.backend.server.REPO_LOG
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
|
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
|
||||||
import net.buzzert.kordophone.backend.model.Message as ModelMessage
|
import net.buzzert.kordophone.backend.model.Message as ModelMessage
|
||||||
@@ -41,7 +43,15 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val realm = Realm.open(realmConfig)
|
private val realm = runCatching {
|
||||||
|
Realm.open(realmConfig)
|
||||||
|
}.recover {
|
||||||
|
// We're just a caching layer, so in the event of a migration error, just delete and start over.
|
||||||
|
Log.d(REPO_LOG, "Error opening (${it.message}). Recovering by deleting database.")
|
||||||
|
Realm.deleteRealm(realmConfig)
|
||||||
|
|
||||||
|
return@recover Realm.open(realmConfig)
|
||||||
|
}.getOrThrow()
|
||||||
|
|
||||||
// Flow for watching changes to the database
|
// Flow for watching changes to the database
|
||||||
val conversationChanges: Flow<List<ModelConversation>>
|
val conversationChanges: Flow<List<ModelConversation>>
|
||||||
@@ -122,16 +132,6 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearOutgoingMessages() {
|
|
||||||
realm.query(Message::class, "isOutgoing == true").find().forEach { message ->
|
|
||||||
realm.writeBlocking {
|
|
||||||
findLatest(message)?.let {
|
|
||||||
delete(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fetchMessages(conversation: ModelConversation): List<ModelMessage> {
|
fun fetchMessages(conversation: ModelConversation): List<ModelMessage> {
|
||||||
val dbConversation = getConversationByGuid(conversation.guid)
|
val dbConversation = getConversationByGuid(conversation.guid)
|
||||||
return dbConversation.messages.map { it.toMessage(dbConversation.toConversation()) }
|
return dbConversation.messages.map { it.toMessage(dbConversation.toConversation()) }
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ open class Message(
|
|||||||
var date: RealmInstant,
|
var date: RealmInstant,
|
||||||
|
|
||||||
var conversationGUID: GUID,
|
var conversationGUID: GUID,
|
||||||
var isOutgoing: Boolean,
|
|
||||||
): RealmObject
|
): RealmObject
|
||||||
{
|
{
|
||||||
constructor() : this(
|
constructor() : this(
|
||||||
@@ -31,7 +30,6 @@ open class Message(
|
|||||||
sender = null,
|
sender = null,
|
||||||
date = RealmInstant.now(),
|
date = RealmInstant.now(),
|
||||||
conversationGUID = ObjectId().toString(),
|
conversationGUID = ObjectId().toString(),
|
||||||
isOutgoing = false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toMessage(parentConversation: ModelConversation): ModelMessage {
|
fun toMessage(parentConversation: ModelConversation): ModelMessage {
|
||||||
@@ -53,6 +51,5 @@ fun ModelMessage.toDatabaseMessage(outgoing: Boolean = false): Message {
|
|||||||
sender = from.sender
|
sender = from.sender
|
||||||
date = from.date.toInstant().toRealmInstant()
|
date = from.date.toInstant().toRealmInstant()
|
||||||
conversationGUID = from.conversation.guid
|
conversationGUID = from.conversation.guid
|
||||||
isOutgoing = outgoing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import net.buzzert.kordophone.backend.model.GUID
|
|||||||
import net.buzzert.kordophone.backend.model.Message
|
import net.buzzert.kordophone.backend.model.Message
|
||||||
|
|
||||||
data class MessageDeliveredEvent(
|
data class MessageDeliveredEvent(
|
||||||
val guid: GUID,
|
|
||||||
val message: Message,
|
val message: Message,
|
||||||
val conversation: Conversation,
|
val conversation: Conversation,
|
||||||
|
val requestGuid: GUID,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ data class SendMessageRequest(
|
|||||||
val transferGUIDs: List<String>?,
|
val transferGUIDs: List<String>?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class SendMessageResponse(
|
||||||
|
@SerializedName("guid")
|
||||||
|
val sentMessageGUID: String,
|
||||||
|
)
|
||||||
|
|
||||||
interface APIInterface {
|
interface APIInterface {
|
||||||
@GET("/version")
|
@GET("/version")
|
||||||
suspend fun getVersion(): ResponseBody
|
suspend fun getVersion(): ResponseBody
|
||||||
@@ -41,7 +46,7 @@ interface APIInterface {
|
|||||||
): Response<List<Message>>
|
): Response<List<Message>>
|
||||||
|
|
||||||
@POST("/sendMessage")
|
@POST("/sendMessage")
|
||||||
suspend fun sendMessage(@Body request: SendMessageRequest): Response<Void>
|
suspend fun sendMessage(@Body request: SendMessageRequest): Response<SendMessageResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResponseDecodeError(val response: ResponseBody): Exception()
|
class ResponseDecodeError(val response: ResponseBody): Exception()
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class ChatRepository(
|
|||||||
private data class OutgoingMessageInfo (
|
private data class OutgoingMessageInfo (
|
||||||
val message: Message,
|
val message: Message,
|
||||||
val conversation: Conversation,
|
val conversation: Conversation,
|
||||||
val guid: GUID,
|
val requestGuid: GUID,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val apiInterface = apiClient.getAPIInterface()
|
private val apiInterface = apiClient.getAPIInterface()
|
||||||
@@ -133,12 +133,6 @@ class ChatRepository(
|
|||||||
val serverConversations = fetchConversations()
|
val serverConversations = fetchConversations()
|
||||||
database.updateConversations(serverConversations)
|
database.updateConversations(serverConversations)
|
||||||
|
|
||||||
// Delete outgoing conversations
|
|
||||||
// This is an unfortunate limitation in that we don't know what outgoing GUIDs are going to
|
|
||||||
// be before we send them.
|
|
||||||
// TODO: Keep this in mind when syncing messages after a certain GUID. The outgoing GUIDs are fake.
|
|
||||||
database.clearOutgoingMessages()
|
|
||||||
|
|
||||||
// Sync top N number of conversations' message content
|
// Sync top N number of conversations' message content
|
||||||
Log.d(REPO_LOG, "Synchronizing messages")
|
Log.d(REPO_LOG, "Synchronizing messages")
|
||||||
val sortedConversations = conversations.sortedBy { it.date }.reversed()
|
val sortedConversations = conversations.sortedBy { it.date }.reversed()
|
||||||
@@ -198,23 +192,39 @@ class ChatRepository(
|
|||||||
while (true) {
|
while (true) {
|
||||||
val outgoingMessageRequest = outgoingMessageQueue.poll()?.let {
|
val outgoingMessageRequest = outgoingMessageQueue.poll()?.let {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val guid = it.guid
|
val outgoingMessage = it.message
|
||||||
val message = it.message
|
|
||||||
val conversation = it.conversation
|
val conversation = it.conversation
|
||||||
|
val requestGuid = it.requestGuid
|
||||||
|
|
||||||
Log.d(REPO_LOG, "Sending message to $conversation: $message")
|
Log.d(REPO_LOG, "Sending message to $conversation: $outgoingMessage")
|
||||||
|
|
||||||
val result = apiInterface.sendMessage(
|
val result = apiInterface.sendMessage(
|
||||||
SendMessageRequest(
|
SendMessageRequest(
|
||||||
conversationGUID = conversation.guid,
|
conversationGUID = conversation.guid,
|
||||||
body = message.text,
|
body = outgoingMessage.text,
|
||||||
transferGUIDs = null,
|
transferGUIDs = null,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result.isSuccessful) {
|
if (result.isSuccessful) {
|
||||||
Log.d(REPO_LOG, "Successfully sent message.")
|
val messageGuid = result.body()?.sentMessageGUID ?: outgoingMessage.guid
|
||||||
_messageDeliveredChannel.emit(MessageDeliveredEvent(guid, message, conversation))
|
Log.d(REPO_LOG, "Successfully sent message: $messageGuid")
|
||||||
|
|
||||||
|
val newMessage = Message(
|
||||||
|
guid = messageGuid,
|
||||||
|
text = outgoingMessage.text,
|
||||||
|
sender = null,
|
||||||
|
conversation = it.conversation,
|
||||||
|
date = outgoingMessage.date
|
||||||
|
)
|
||||||
|
|
||||||
|
_messageDeliveredChannel.emit(
|
||||||
|
MessageDeliveredEvent(
|
||||||
|
newMessage,
|
||||||
|
conversation,
|
||||||
|
requestGuid
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Log.e(REPO_LOG, "Error sending message. Enqueuing for retry.")
|
Log.e(REPO_LOG, "Error sending message. Enqueuing for retry.")
|
||||||
outgoingMessageQueue.add(it)
|
outgoingMessageQueue.add(it)
|
||||||
|
|||||||
Reference in New Issue
Block a user