Room : load all room members on first room opening

This commit is contained in:
ganfra 2018-10-25 16:55:41 +02:00
parent 7ecbe09661
commit 2faba01662
16 changed files with 174 additions and 15 deletions

View File

@ -43,6 +43,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
super.onActivityCreated(savedInstanceState)
setupRecyclerView()
room = currentSession.getRoom(roomId)!!
room.loadRoomMembersIfNeeded()
room.liveTimeline().observe(this, Observer { renderEvents(it) })
}


View File

@ -4,8 +4,12 @@ import im.vector.matrix.android.api.failure.Failure

interface MatrixCallback<in T> {

fun onSuccess(data: T)
fun onSuccess(data: T) {
//no-op
}

fun onFailure(failure: Failure)
fun onFailure(failure: Failure){
//no-op
}

}

View File

@ -4,6 +4,7 @@ import android.arch.lifecycle.LiveData
import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.util.Cancelable

interface Room {

@ -15,4 +16,5 @@ interface Room {

fun getNumberOfJoinedMembers(): Int

fun loadRoomMembersIfNeeded(): Cancelable
}

View File

@ -2,12 +2,21 @@ package im.vector.matrix.android.api.session.room.model

import com.squareup.moshi.Json

enum class Membership {
enum class Membership(val value: String) {

@Json(name = "invite") INVITE,
@Json(name = "join") JOIN,
@Json(name = "knock") KNOCK,
@Json(name = "leave") LEAVE,
@Json(name = "ban") BAN;
@Json(name = "invite")
INVITE("invite"),

@Json(name = "join")
JOIN("join"),

@Json(name = "knock")
KNOCK("knock"),

@Json(name = "leave")
LEAVE("leave"),

@Json(name = "ban")
BAN("ban");

}

View File

@ -1,5 +1,8 @@
package im.vector.matrix.android.api.util

interface Cancelable {
fun cancel()
}
fun cancel() {
//no-op
}
}


View File

@ -8,7 +8,8 @@ import io.realm.annotations.PrimaryKey
import kotlin.properties.Delegates

open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList()
var chunks: RealmList<ChunkEntity> = RealmList(),
var areAllMembersLoaded: Boolean = false
) : RealmObject() {

private var membershipStr: String = MyMembership.NONE.name

View File

@ -8,6 +8,11 @@ import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort

fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventEntity> {
return realm.where(EventEntity::class.java)
.equalTo("eventId", eventId)
}

fun EventEntity.Companion.where(realm: Realm, roomId: String, type: String? = null): RealmQuery<EventEntity> {
val query = realm.where(EventEntity::class.java)
.equalTo("chunk.room.roomId", roomId)

View File

@ -4,8 +4,8 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.session.room.DefaultRoomService
import im.vector.matrix.android.internal.session.room.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition

View File

@ -4,17 +4,23 @@ import android.arch.lifecycle.LiveData
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.Room
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.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntity
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.query.where
import im.vector.matrix.android.internal.session.events.interceptor.MessageEventInterceptor
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import io.realm.Sort
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject
@ -26,7 +32,10 @@ data class DefaultRoom(
) : Room, KoinComponent {

private val paginationRequest by inject<PaginationRequest>()
private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>()
private val syncTokenStore by inject<SyncTokenStore>()
private val monarchy by inject<Monarchy>()

private val boundaryCallback = TimelineBoundaryCallback(paginationRequest, roomId, monarchy, Executors.newSingleThreadExecutor())
private val eventInterceptors = ArrayList<EnrichedEventInterceptor>()

@ -71,5 +80,22 @@ data class DefaultRoom(
return roomSummary?.joinedMembersCount ?: 0
}

override fun loadRoomMembersIfNeeded(): Cancelable {
return if (areAllMembersLoaded()) {
object : Cancelable {}
} else {
val token = syncTokenStore.getLastToken()
loadRoomMembersRequest.execute(roomId, token, Membership.LEAVE, object : MatrixCallback<Boolean> {})
}
}


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


}

View File

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

import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.model.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent
import kotlinx.coroutines.Deferred
import retrofit2.Response
@ -28,4 +29,20 @@ interface RoomAPI {
): Deferred<Response<TokenChunkEvent>>


/**
* Get all members of a room
*
* @param roomId the room id where to get the members
* @param syncToken the sync token (optional)
* @param membership to include only one type of membership (optional)
* @param notMembership to exclude one type of membership (optional)
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members")
fun getMembers(@Path("roomId") roomId: String,
@Query("at") syncToken: String?,
@Query("membership") membership: String?,
@Query("not_membership") notMembership: String?
): Deferred<Response<RoomMembersResponse>>


}

View File

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

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.timeline.PaginationRequest
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
@ -20,5 +21,10 @@ class RoomModule : Module {
scope(DefaultSession.SCOPE) {
PaginationRequest(get(), get(), get(), get())
}

scope(DefaultSession.SCOPE) {
LoadRoomMembersRequest(get(), get(), get(), get())
}

}.invoke()
}

View File

@ -12,6 +12,7 @@ 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.query.last
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import io.realm.Realm
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean

View File

@ -0,0 +1,74 @@
package im.vector.matrix.android.internal.session.room.members

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.leftIfNull
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findAllRoomMembers
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.session.room.model.RoomMembersResponse
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val stateEventsChunkHandler: StateEventsChunkHandler) {

fun execute(roomId: String,
streamToken: String?,
excludeMembership: Membership? = null,
callback: MatrixCallback<Boolean>
): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val responseOrFailure = execute(roomId, streamToken, excludeMembership)
responseOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(true) })
}
return CancelableCoroutine(job)
}

//TODO : manage stream token (we have 404 on some rooms actually)
private suspend fun execute(roomId: String,
streamToken: String?,
excludeMembership: Membership?) = withContext(coroutineDispatchers.io) {

return@withContext executeRequest<RoomMembersResponse> {
apiCall = roomAPI.getMembers(roomId, null, null, excludeMembership?.value)
}.leftIfNull {
Failure.Unknown(RuntimeException("RoomMembersResponse shouldn't be null"))
}.flatMap { response ->
try {
insertInDb(response, roomId)
Either.right(response)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
}
}

private fun insertInDb(response: RoomMembersResponse, roomId: String) {
monarchy.runTransactionSync { realm ->
// We ignore all the already known members
val roomMembers = EventEntity.findAllRoomMembers(realm, roomId)
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)

RoomEntity
.where(realm, roomId).findFirst()
?.let { it.areAllMembersLoaded = true }
}
}

}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/

package im.vector.matrix.android.internal.session.room
package im.vector.matrix.android.internal.session.room.members

import android.content.Context
import com.zhuinden.monarchy.Monarchy

View File

@ -1,4 +1,4 @@
package im.vector.matrix.android.internal.session.room
package im.vector.matrix.android.internal.session.room.members

import im.vector.matrix.android.api.session.room.model.RoomMember

View File

@ -0,0 +1,10 @@
package im.vector.matrix.android.internal.session.room.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event

@JsonClass(generateAdapter = true)
data class RoomMembersResponse(
@Json(name = "chunk") val roomMemberEvents: List<Event>
)