Room : start to handle read markers. Only used when you open the room at the moment.

This commit is contained in:
ganfra 2019-01-28 17:56:23 +01:00
parent 9a42c121e4
commit b2e2c14e69
13 changed files with 212 additions and 7 deletions

View File

@ -50,6 +50,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
observeRoomSummary() observeRoomSummary()
observeTimeline() observeTimeline()
room.loadRoomMembersIfNeeded() room.loadRoomMembersIfNeeded()
room.markLatestAsRead(callback = object : MatrixCallback<Void> {})
} }


fun accept(action: RoomDetailActions) { fun accept(action: RoomDetailActions) {

View File

@ -2,5 +2,5 @@


<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/dark_grey" /> <solid android:color="@color/grey_lynch" />
</shape> </shape>

View File

@ -52,7 +52,7 @@
android:gravity="center" android:gravity="center"
android:minWidth="24dp" android:minWidth="24dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="10sp" android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -16,4 +16,5 @@
<color name="cool_grey">#a5aab2</color> <color name="cool_grey">#a5aab2</color>
<color name="pale_grey_two">#ebedf8</color> <color name="pale_grey_two">#ebedf8</color>
<color name="brown_grey">#a5a5a5</color> <color name="brown_grey">#a5a5a5</color>
<color name="grey_lynch">#61708B</color>
</resources> </resources>

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -26,7 +27,7 @@ import im.vector.matrix.android.api.util.Cancelable
/** /**
* This interface defines methods to interact within a room. * This interface defines methods to interact within a room.
*/ */
interface Room : TimelineService, SendService { interface Room : TimelineService, SendService, ReadService {


/** /**
* The roomId of this room * The roomId of this room

View File

@ -0,0 +1,31 @@
/*
* 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.api.session.room.read

import im.vector.matrix.android.api.MatrixCallback

interface ReadService {

fun markLatestAsRead(callback: MatrixCallback<Void>)

fun markAllAsRead(callback: MatrixCallback<Void>)

fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>)

fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>)

}

View File

@ -16,6 +16,7 @@


package im.vector.matrix.android.internal.database.query package im.vector.matrix.android.internal.database.query


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.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.EventEntityFields
@ -48,6 +49,12 @@ internal fun EventEntity.Companion.where(realm: Realm,
} }
} }


internal fun EventEntity.Companion.latestEvent(realm: Realm,
roomId: String): EventEntity? {
val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
return chunkEntity?.events?.where()?.sort(EventEntityFields.DISPLAY_INDEX)?.findFirst()
}



internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? { internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
if (from != null) { if (from != null) {
@ -62,7 +69,6 @@ internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = t
.findFirst() .findFirst()
} }



internal fun RealmQuery<EventEntity>.last(since: Int? = null, strict: Boolean = false): EventEntity? { internal fun RealmQuery<EventEntity>.last(since: Int? = null, strict: Boolean = false): EventEntity? {
if (since != null) { if (since != null) {
if (strict) { if (strict) {

View File

@ -22,10 +22,11 @@ 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.Event 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.send.SendService
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -49,6 +50,7 @@ internal data class DefaultRoom(
private val monarchy by inject<Monarchy>() private val monarchy by inject<Monarchy>()
private val timelineService by inject<TimelineService> { parametersOf(roomId) } private val timelineService by inject<TimelineService> { parametersOf(roomId) }
private val sendService by inject<SendService> { parametersOf(roomId) } private val sendService by inject<SendService> { parametersOf(roomId) }
private val readService by inject<ReadService> { parametersOf(roomId) }
private val taskExecutor by inject<TaskExecutor>() private val taskExecutor by inject<TaskExecutor>()


override val roomSummary: LiveData<RoomSummary> by lazy { override val roomSummary: LiveData<RoomSummary> by lazy {
@ -76,5 +78,21 @@ internal data class DefaultRoom(
return sendService.sendTextMessage(text, callback) return sendService.sendTextMessage(text, callback)
} }


override fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>) {
readService.setReadReceipt(eventId, callback)
}

override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>) {
readService.setReadMarkers(fullyReadEventId, readReceiptEventId, callback)
}

override fun markAllAsRead(callback: MatrixCallback<Void>) {
readService.markAllAsRead(callback)
}

override fun markLatestAsRead(callback: MatrixCallback<Void>) {
readService.markLatestAsRead(callback)
}



} }

View File

@ -24,7 +24,12 @@ import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
import retrofit2.Call import retrofit2.Call
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query


internal interface RoomAPI { internal interface RoomAPI {


@ -100,5 +105,14 @@ internal interface RoomAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}")
fun getEvent(@Path("roomId") roomId: String, @Path("eventId") eventId: String): Call<Event> fun getEvent(@Path("roomId") roomId: String, @Path("eventId") eventId: String): Call<Event>


/**
* Send read markers.
*
* @param roomId the room id
* @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>



} }

View File

@ -17,14 +17,18 @@
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.auth.data.SessionParams
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.internal.session.room.send.EventFactory
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
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.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.send.EventFactory
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
@ -63,6 +67,10 @@ class RoomModule {
DefaultGetContextOfEventTask(get(), get()) as GetContextOfEventTask DefaultGetContextOfEventTask(get(), get()) as GetContextOfEventTask
} }


scope(DefaultSession.SCOPE) {
DefaultSetReadMarkersTask(get()) as SetReadMarkersTask
}

scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
val sessionParams = get<SessionParams>() val sessionParams = get<SessionParams>()
EventFactory(sessionParams.credentials) EventFactory(sessionParams.credentials)
@ -79,5 +87,9 @@ class RoomModule {
DefaultSendService(roomId, get(), get()) as SendService DefaultSendService(roomId, get(), get()) as SendService
} }


factory { (roomId: String) ->
DefaultReadService(roomId, get(), get(), get()) as ReadService
}

} }
} }

View File

@ -0,0 +1,59 @@
/*
* 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.session.room.read

import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.read.ReadService
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

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)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}

override fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}

override fun setReadMarkers(fullyReadEventId: String, readReceiptEventId: String?, callback: MatrixCallback<Void>) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = readReceiptEventId)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
}

private fun getLatestEvent(): EventEntity? {
return monarchy.fetchManaged { EventEntity.latestEvent(it, roomId) }
}

}

View File

@ -0,0 +1,51 @@
/*
* 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.session.room.read

import arrow.core.Try
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

internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Void> {

data class Params(
val roomId: String,
val fullyReadEventId: String?,
val readReceiptEventId: String?
)
}

private const val READ_MARKER = "m.fully_read"
private const val READ_RECEIPT = "m.read"

internal class DefaultSetReadMarkersTask(private val roomAPI: RoomAPI
) : SetReadMarkersTask {

override fun execute(params: SetReadMarkersTask.Params): Try<Void> {
val markers = HashMap<String, String>()
if (params.fullyReadEventId?.isNotEmpty() == true) {
markers[READ_MARKER] = params.fullyReadEventId
}
if (params.readReceiptEventId?.isNotEmpty() == true) {
markers[READ_RECEIPT] = params.readReceiptEventId
}
return executeRequest {
apiCall = roomAPI.sendReadMarker(params.roomId, markers)
}
}
}

View File

@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.util
import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm import io.realm.Realm
import io.realm.RealmModel
import java.util.concurrent.atomic.AtomicReference


internal fun Monarchy.tryTransactionSync(transaction: (realm: Realm) -> Unit): Try<Unit> { internal fun Monarchy.tryTransactionSync(transaction: (realm: Realm) -> Unit): Try<Unit> {
return Try { return Try {
@ -30,4 +32,13 @@ internal fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit):
return Try { return Try {
this.writeAsync(transaction) this.writeAsync(transaction)
} }
}

fun <T : RealmModel> Monarchy.fetchManaged(query: (Realm) -> T?): T? {
val ref = AtomicReference<T>()
doWithRealm { realm ->
val result = query.invoke(realm)
ref.set(result)
}
return ref.get()
} }