Timeline : send read-receipt when scrolling. Still need to handle read marker.

This commit is contained in:
ganfra
2019-01-30 18:39:54 +01:00
parent bfaab52b41
commit 6113bba703
17 changed files with 198 additions and 49 deletions

View File

@ -18,14 +18,24 @@ package im.vector.matrix.android.api.session.room.read
import im.vector.matrix.android.api.MatrixCallback
/**
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
*/
interface ReadService {
fun markLatestAsRead(callback: MatrixCallback<Void>)
/**
* Force the read marker to be set on the latest event.
*/
fun markAllAsRead(callback: MatrixCallback<Void>)
/**
* Set the read receipt on the event with provided eventId.
*/
fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>)
fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>)
/**
* Set the read marker on the event with provided eventId.
*/
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Void>)
}

View File

@ -18,11 +18,11 @@ package im.vector.matrix.android.internal.di
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.UnitConverterFactory
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import okreplay.OkReplayInterceptor
import org.koin.dsl.module.module
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import timber.log.Timber
@ -62,10 +62,6 @@ class NetworkModule {
MoshiProvider.providesMoshi()
}
single {
MoshiConverterFactory.create(get()) as Converter.Factory
}
single {
NetworkConnectivityChecker(get())
}
@ -73,7 +69,8 @@ class NetworkModule {
factory {
Retrofit.Builder()
.client(get())
.addConverterFactory(get())
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(get()))
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.network
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
object UnitConverterFactory : Converter.Factory() {
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>,
retrofit: Retrofit): Converter<ResponseBody, *>? {
return if (type == Unit::class.java) UnitConverter else null
}
private object UnitConverter : Converter<ResponseBody, Unit> {
override fun convert(value: ResponseBody) {
value.close()
}
}
}

View File

@ -47,6 +47,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
sessionParams
}
scope(DefaultSession.SCOPE) {
sessionParams.credentials
}
scope(DefaultSession.SCOPE) {
val context = get<Context>()
val childPath = sessionParams.credentials.userId.md5()

View File

@ -26,13 +26,13 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.fetchManaged
import im.vector.matrix.android.internal.util.fetchCopied
internal class DefaultRoomService(private val monarchy: Monarchy,
private val roomFactory: RoomFactory) : RoomService {
override fun getRoom(roomId: String): Room? {
monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null
monarchy.fetchCopied { RoomEntity.where(it, roomId).findFirst() } ?: return null
return roomFactory.instantiate(roomId)
}

View File

@ -112,7 +112,7 @@ internal interface RoomAPI {
* @param markers the read markers
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers")
fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map<String, String>): Call<Void>
fun sendReadMarker(@Path("roomId") roomId: String, @Body markers: Map<String, String>): Call<Unit>
}

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
@ -34,6 +35,7 @@ import java.util.concurrent.Executors
internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
private val monarchy: Monarchy,
private val credentials: Credentials,
private val paginationTask: PaginationTask,
private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask,

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
@ -58,16 +57,15 @@ class RoomModule {
}
scope(DefaultSession.SCOPE) {
DefaultSetReadMarkersTask(get()) as SetReadMarkersTask
DefaultSetReadMarkersTask(get(), get(),get()) as SetReadMarkersTask
}
scope(DefaultSession.SCOPE) {
val sessionParams = get<SessionParams>()
EventFactory(sessionParams.credentials)
EventFactory(get())
}
scope(DefaultSession.SCOPE) {
RoomFactory(get(), get(), get(), get(), get(), get(), get())
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get())
}
}

View File

@ -23,22 +23,16 @@ import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchManaged
import im.vector.matrix.android.internal.util.fetchCopied
internal class DefaultReadService(private val roomId: String,
private val monarchy: Monarchy,
private val setReadMarkersTask: SetReadMarkersTask,
private val taskExecutor: TaskExecutor) : ReadService {
override fun markLatestAsRead(callback: MatrixCallback<Void>) {
val lastEvent = getLatestEvent()
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = lastEvent?.eventId)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}
override fun markAllAsRead(callback: MatrixCallback<Void>) {
val lastEvent = getLatestEvent()
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = lastEvent?.eventId, readReceiptEventId = null)
val latestEvent = getLatestEvent()
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}
@ -47,13 +41,14 @@ internal class DefaultReadService(private val roomId: String,
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}
override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = readReceiptEventId)
override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Void>) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}
private fun getLatestEvent(): EventEntity? {
return monarchy.fetchManaged { EventEntity.latestEvent(it, roomId) }
return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) }
}
}

View File

@ -17,11 +17,22 @@
package im.vector.matrix.android.internal.session.room.read
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionAsync
internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Void> {
internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> {
data class Params(
val roomId: String,
@ -33,19 +44,54 @@ internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Void> {
private const val READ_MARKER = "m.fully_read"
private const val READ_RECEIPT = "m.read"
internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI
internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI,
private val credentials: Credentials,
private val monarchy: Monarchy
) : SetReadMarkersTask {
override fun execute(params: SetReadMarkersTask.Params): Try<Void> {
override fun execute(params: SetReadMarkersTask.Params): Try<Unit> {
val markers = HashMap<String, String>()
if (params.fullyReadEventId?.isNotEmpty() == true) {
markers[READ_MARKER] = params.fullyReadEventId
}
if (params.readReceiptEventId?.isNotEmpty() == true) {
if (params.readReceiptEventId?.isNotEmpty() == true && !isEventRead(params.roomId, params.readReceiptEventId)) {
updateNotificationCountIfNecessary(params.roomId, params.readReceiptEventId)
markers[READ_RECEIPT] = params.readReceiptEventId
}
return executeRequest {
apiCall = roomAPI.sendReadMarker(params.roomId, markers)
return if (markers.isEmpty()) {
Try.just(Unit)
} else {
executeRequest {
apiCall = roomAPI.sendReadMarker(params.roomId, markers)
}
}
}
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
monarchy.tryTransactionAsync { realm ->
val isLatestReceived = EventEntity.latestEvent(realm, eventId)?.eventId == eventId
if (isLatestReceived) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: return@tryTransactionAsync
roomSummary.notificationCount = 0
roomSummary.highlightCount = 0
}
}
}
private fun isEventRead(roomId: String, eventId: String): Boolean {
var isEventRead = false
monarchy.doWithRealm {
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
?: return@doWithRealm
val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex
?: -1
val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex ?: -1
isEventRead = eventToCheckIndex >= readReceiptIndex
}
return isEventRead
}
}

View File

@ -34,10 +34,10 @@ internal fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit):
}
}
fun <T : RealmModel> Monarchy.fetchManaged(query: (Realm) -> T?): T? {
fun <T : RealmModel> Monarchy.fetchCopied(query: (Realm) -> T?): T? {
val ref = AtomicReference<T>()
doWithRealm { realm ->
val result = query.invoke(realm)
val result = query.invoke(realm)?.let { realm.copyFromRealm(it) }
ref.set(result)
}
return ref.get()