Send messages : add local echo. Maybe add directly to the pagedlist if possible

This commit is contained in:
ganfra 2018-11-25 16:17:47 +01:00
parent e4c23b757e
commit 660ba5436b
13 changed files with 129 additions and 30 deletions

View File

@ -8,7 +8,9 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R import im.vector.riotredesign.R
@ -58,7 +60,9 @@ class RoomDetailFragment : RiotFragment() {
val textMessage = composerEditText.text.toString() val textMessage = composerEditText.text.toString()
if (textMessage.isNotBlank()) { if (textMessage.isNotBlank()) {
composerEditText.text = null composerEditText.text = null
room.sendTextMessage(textMessage) room.sendTextMessage(textMessage, object : MatrixCallback<Event> {

})
} }
} }
} }

View File

@ -58,7 +58,7 @@ class TimelineEventController(private val roomId: String,


val item = when (event.root.type) { val item = when (event.root.type) {
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date) EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date)
else -> textItemFactory.create(event) else -> textItemFactory.create(event)
} }
item item
?.onBind { timeline?.loadAround(index) } ?.onBind { timeline?.loadAround(index) }

View File

@ -6,7 +6,7 @@ import com.squareup.moshi.Types
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType


typealias Content = Map<String, Any> typealias Content = Map<String, @JvmSuppressWildcards Any>


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Event( data class Event(

View File

@ -1,10 +1,12 @@
package im.vector.matrix.android.api.session.room package im.vector.matrix.android.api.session.room


import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable


interface SendService { interface SendService {


fun sendTextMessage(text: String): Cancelable fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable




} }

View File

@ -0,0 +1,44 @@
package im.vector.matrix.android.api.session.room.send

import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.MessageContent
import im.vector.matrix.android.api.session.room.model.MessageType
import im.vector.matrix.android.internal.di.MoshiProvider

internal class EventFactory(private val credentials: Credentials) {

private val moshi = MoshiProvider.providesMoshi()

fun createTextEvent(roomId: String, text: String): Event {
val content = MessageContent(type = MessageType.MSGTYPE_TEXT, body = text)

return Event(
roomId = roomId,
originServerTs = dummyOriginServerTs(),
sender = credentials.userId,
eventId = dummyEventId(roomId),
type = EventType.MESSAGE,
content = toContent(content)
)
}

private fun dummyOriginServerTs(): Long {
return System.currentTimeMillis()
}

private fun dummyEventId(roomId: String): String {
return roomId + "-" + dummyOriginServerTs()
}

@Suppress("UNCHECKED_CAST")
private inline fun <reified T> toContent(data: T?): Content? {
val moshiAdapter = moshi.adapter(T::class.java)
val jsonValue = moshiAdapter.toJsonValue(data)
return jsonValue as? Content?
}


}

View File

@ -35,6 +35,10 @@ internal fun ChunkEntity.addAll(events: List<Event>,
} }
} }


internal fun ChunkEntity.updateDisplayIndexes() {
events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index }
}

internal fun ChunkEntity.addOrUpdate(event: Event, internal fun ChunkEntity.addOrUpdate(event: Event,
direction: PaginationDirection, direction: PaginationDirection,
stateIndexOffset: Int = 0) { stateIndexOffset: Int = 0) {
@ -69,7 +73,7 @@ internal fun ChunkEntity.addOrUpdate(event: Event,


internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
} ?: defaultValue } ?: defaultValue
} }

View File

@ -13,7 +13,7 @@ internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
} }


internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) { internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
chunkEntity.events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index } chunkEntity.updateDisplayIndexes()
if (!chunks.contains(chunkEntity)) { if (!chunks.contains(chunkEntity)) {
chunks.add(chunkEntity) chunks.add(chunkEntity)
} }

View File

@ -23,6 +23,10 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module


override fun invoke(): ModuleDefinition = module(override = true) { override fun invoke(): ModuleDefinition = module(override = true) {


scope(DefaultSession.SCOPE) {
sessionParams
}

scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RealmConfiguration.Builder() RealmConfiguration.Builder()
.name(sessionParams.credentials.userId) .name(sessionParams.credentials.userId)

View File

@ -6,6 +6,7 @@ import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.TimelineHolder
@ -61,14 +62,14 @@ internal data class DefaultRoom(


private fun areAllMembersLoaded(): Boolean { private fun areAllMembersLoaded(): Boolean {
return monarchy return monarchy
.fetchAllCopiedSync { RoomEntity.where(it, roomId) } .fetchAllCopiedSync { RoomEntity.where(it, roomId) }
.firstOrNull() .firstOrNull()
?.areAllMembersLoaded ?: false ?.areAllMembersLoaded ?: false
} }




override fun sendTextMessage(text: String): Cancelable { override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
return sendService.sendTextMessage(text) return sendService.sendTextMessage(text, callback)
} }


} }

View File

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room


import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.MessageContent import im.vector.matrix.android.api.session.room.model.MessageContent
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
@ -62,7 +63,7 @@ internal interface RoomAPI {
fun send(@Path("txId") txId: String, fun send(@Path("txId") txId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventType") eventType: String, @Path("eventType") eventType: String,
@Body content: MessageContent @Body content: Content?
): Call<SendResponse> ): Call<SendResponse>


/** /**

View File

@ -1,7 +1,9 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room


import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.room.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.api.session.room.send.EventFactory
import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.send.DefaultSendService
@ -39,9 +41,14 @@ class RoomModule : Module {
DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback) as TimelineHolder DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback) as TimelineHolder
} }


scope(DefaultSession.SCOPE) {
val sessionParams = get<SessionParams>()
EventFactory(sessionParams.credentials)
}

factory { factory {
val roomId: String = it[0] val roomId: String = it[0]
DefaultSendService(roomId) as SendService DefaultSendService(roomId, get(), get()) as SendService
} }


}.invoke() }.invoke()

View File

@ -1,23 +1,43 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send


import androidx.work.* import androidx.work.*
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.SendService import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.send.EventFactory
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.updateDisplayIndexes
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import im.vector.matrix.android.internal.util.tryTransactionAsync
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


private const val SEND_WORK = "SEND_WORK" private const val SEND_WORK = "SEND_WORK"


internal class DefaultSendService(private val roomId: String) : SendService { internal class DefaultSendService(private val roomId: String,
private val eventFactory: EventFactory,
private val monarchy: Monarchy) : SendService {


private val sendConstraints = Constraints.Builder() private val sendConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()


override fun sendTextMessage(text: String): Cancelable { override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
val event = eventFactory.createTextEvent(roomId, text)


val sendContentWorkerParams = SendEventWorker.Params(roomId, text) monarchy.tryTransactionAsync { realm ->
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
?: return@tryTransactionAsync
chunkEntity.addOrUpdate(event, PaginationDirection.FORWARDS)
chunkEntity.updateDisplayIndexes()
}

val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
val workData = WorkerParamsFactory.toData(sendContentWorkerParams) val workData = WorkerParamsFactory.toData(sendContentWorkerParams)


val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>() val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>()

View File

@ -4,12 +4,14 @@ import android.content.Context
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.EventType import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.MessageContent import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.MessageType import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.WorkerParamsFactory
import im.vector.matrix.android.internal.util.tryTransactionSync
import org.koin.standalone.KoinComponent import org.koin.standalone.KoinComponent
import org.koin.standalone.inject import org.koin.standalone.inject


@ -20,25 +22,35 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
val roomId: String, val roomId: String,
val text: String val event: Event
) )


private val roomAPI by inject<RoomAPI>() private val roomAPI by inject<RoomAPI>()
private val monarchy by inject<Monarchy>()


override fun doWork(): Result { override fun doWork(): Result {


val sendWorkerParameters = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.FAILURE ?: return Result.FAILURE


val fakeId = sendWorkerParameters.roomId + "-" + System.currentTimeMillis() if (params.event.eventId == null) {
return Result.FAILURE
}

val result = executeRequest<SendResponse> { val result = executeRequest<SendResponse> {
apiCall = roomAPI.send( apiCall = roomAPI.send(
fakeId, params.event.eventId,
sendWorkerParameters.roomId, params.roomId,
EventType.MESSAGE, params.event.type,
MessageContent(MessageType.MSGTYPE_TEXT, sendWorkerParameters.text) params.event.content
) )
} }
result.flatMap { sendResponse ->
monarchy.tryTransactionSync { realm ->
val dummyEventEntity = EventEntity.where(realm, params.event.eventId).findFirst()
dummyEventEntity?.eventId = sendResponse.eventId
}
}
return result.fold({ Result.RETRY }, { Result.SUCCESS }) return result.fold({ Result.RETRY }, { Result.SUCCESS })
} }