diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt index 96578cd101..d0c3c70561 100644 --- a/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt @@ -9,4 +9,8 @@ fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int) { fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int) { supportFragmentManager.inTransaction { replace(frameId, fragment) } +} + +fun AppCompatActivity.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { + supportFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt index 8400192926..059213126c 100644 --- a/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt @@ -16,4 +16,12 @@ fun Fragment.addChildFragment(fragment: Fragment, frameId: Int) { fun Fragment.replaceChildFragment(fragment: Fragment, frameId: Int) { childFragmentManager.inTransaction { replace(frameId, fragment) } +} + +fun Fragment.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { + fragmentManager?.inTransaction { replace(frameId, fragment).addToBackStack(tag) } +} + +fun Fragment.addChildFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { + childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt index 2bfc08216e..6eb5259d8b 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt @@ -3,19 +3,21 @@ package im.vector.riotredesign.features.home import android.arch.lifecycle.Observer import android.arch.paging.PagedList import android.os.Bundle +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.utils.FragmentArgumentDelegate +import kotlinx.android.synthetic.main.fragment_room_list.* import org.koin.android.ext.android.inject -class RoomDetailFragment : RiotFragment(), RoomController.Callback { +class RoomDetailFragment : RiotFragment() { companion object { @@ -29,11 +31,8 @@ class RoomDetailFragment : RiotFragment(), RoomController.Callback { private val matrix by inject() private val currentSession = matrix.currentSession!! private var roomId by FragmentArgumentDelegate() - private val timelineController = TimelineEventController() - private val room: Room? by lazy { - currentSession.getRoom(roomId) - } + private lateinit var room: Room override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_room_detail, container, false) @@ -41,19 +40,20 @@ class RoomDetailFragment : RiotFragment(), RoomController.Callback { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - if (room == null) { - activity?.onBackPressed() - return - } - room?.liveTimeline()?.observe(this, Observer { renderEvents(it) }) + setupRecyclerView() + room = currentSession.getRoom(roomId)!! + room.liveTimeline().observe(this, Observer { renderEvents(it) }) } private fun renderEvents(events: PagedList?) { timelineController.submitList(events) } - override fun onRoomSelected(room: Room) { - Toast.makeText(context, "Room ${room.roomId} clicked", Toast.LENGTH_SHORT).show() + private fun setupRecyclerView() { + val linearLayoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + linearLayoutManager.stackFromEnd = true + epoxyRecyclerView.layoutManager = linearLayoutManager + epoxyRecyclerView.setController(timelineController) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt index 934f0f9377..4772660bac 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt @@ -2,13 +2,14 @@ package im.vector.riotredesign.features.home import android.arch.lifecycle.Observer import android.os.Bundle +import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.room.Room import im.vector.riotredesign.R -import im.vector.riotredesign.core.extensions.replaceFragment +import im.vector.riotredesign.core.extensions.addFragmentToBackstack import im.vector.riotredesign.core.platform.RiotFragment import kotlinx.android.synthetic.main.fragment_room_list.* import org.koin.android.ext.android.inject @@ -43,7 +44,7 @@ class RoomListFragment : RiotFragment(), RoomController.Callback { override fun onRoomSelected(room: Room) { val detailFragment = RoomDetailFragment.newInstance(room.roomId) - replaceFragment(detailFragment, R.id.homeFragmentContainer) + addFragmentToBackstack(detailFragment, R.id.homeFragmentContainer) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt index f7519ffc09..ac4ff1bfe9 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt @@ -6,14 +6,15 @@ import com.airbnb.epoxy.paging.PagedListEpoxyController import im.vector.matrix.android.api.session.events.model.Event class TimelineEventController : PagedListEpoxyController( - modelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + modelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler(), + diffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() ) { override fun buildItemModel(currentPosition: Int, item: Event?): EpoxyModel<*> { return if (item == null) { LoadingItemModel_().id(-currentPosition) } else { - TimelineEventItem(item.eventId ?: "$currentPosition").id(currentPosition) + TimelineEventItem(item.toString()).id(currentPosition) } } diff --git a/app/src/main/res/layout/item_event.xml b/app/src/main/res/layout/item_event.xml index 4ab22892a8..de61c99230 100644 --- a/app/src/main/res/layout/item_event.xml +++ b/app/src/main/res/layout/item_event.xml @@ -1,10 +1,9 @@ \ No newline at end of file + android:textSize="14sp" /> \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt index c81a26c591..aa975c1dd7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt @@ -1,5 +1,6 @@ package im.vector.matrix.android.internal.database.query +import im.vector.matrix.android.internal.database.DBConstants import im.vector.matrix.android.internal.database.model.ChunkEntity import io.realm.Realm import io.realm.RealmQuery @@ -40,5 +41,7 @@ fun ChunkEntity.Companion.findLastFromRoom(realm: Realm, roomId: String): ChunkE fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults { return realm.where(ChunkEntity::class.java) .`in`("events.eventId", eventIds.toTypedArray()) + .notEqualTo("prevToken", DBConstants.STATE_EVENTS_CHUNK_TOKEN) + .notEqualTo("nextToken", DBConstants.STATE_EVENTS_CHUNK_TOKEN) .findAll() } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 7331c422d3..1c980a9162 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -11,11 +11,15 @@ fun EventEntity.Companion.where(realm: Realm, roomId: String): RealmQuery { - return realm.where(EventEntity::class.java) - .equalTo("chunk.prevToken", chunk.prevToken) - .and() - .equalTo("chunk.nextToken", chunk.nextToken) +fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity?): RealmQuery { + var query = realm.where(EventEntity::class.java) + if (chunk?.prevToken != null) { + query = query.equalTo("chunk.prevToken", chunk.prevToken) + } + if (chunk?.nextToken != null) { + query = query.equalTo("chunk.nextToken", chunk.nextToken) + } + return query } fun RealmResults.getLast(type: String? = null): EventEntity? { @@ -23,5 +27,5 @@ fun RealmResults.getLast(type: String? = null): EventEntity? { if (type != null) { query = query.equalTo("type", type) } - return query.findAll().sort("age").last(null) + return query.findAll().last(null) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index a037f51c49..fdd0ed2605 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -5,7 +5,9 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter object MoshiProvider { - private val moshi: Moshi = Moshi.Builder().add(UriMoshiAdapter()).build() + private val moshi: Moshi = Moshi.Builder() + .add(UriMoshiAdapter()) + .build() fun providesMoshi(): Moshi { return moshi diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 8ab61e59a4..21cca57650 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -8,7 +8,6 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.database.mapper.EventMapper 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.query.where import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback @@ -26,15 +25,17 @@ data class DefaultRoom( override fun liveTimeline(): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> - val lastChunk = ChunkEntity.where(realm, roomId).findAll().last(null) - if (lastChunk == null) { - EventEntity.where(realm, roomId) - } else { - EventEntity.where(realm, lastChunk) - } + ChunkEntity.where(realm, roomId).findAll().last(null).let { it?.events }?.where() } val domainSourceFactory = realmDataSourceFactory.map { EventMapper.map(it) } - val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, 20).setBoundaryCallback(boundaryCallback) + + val pagedListConfig = PagedList.Config.Builder() + .setEnablePlaceholders(false) + .setPageSize(10) + .setPrefetchDistance(5) + .build() + + val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback) return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 793494f558..cf2e9a61ee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -1,5 +1,6 @@ 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.TokenChunkEvent import kotlinx.coroutines.Deferred import retrofit2.Response @@ -18,12 +19,13 @@ interface RoomAPI { * @param limit the maximum number of messages to retrieve. Optional. * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ - @GET("rooms/{roomId}/messages") + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") fun getRoomMessagesFrom(@Path("roomId") roomId: String, @Query("from") from: String, @Query("dir") dir: String, @Query("limit") limit: Int, - @Query("filter") filter: String?): Deferred> + @Query("filter") filter: String? + ): Deferred> } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/PaginationDirection.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/PaginationDirection.kt new file mode 100644 index 0000000000..dbad1ea99a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/PaginationDirection.kt @@ -0,0 +1,15 @@ +package im.vector.matrix.android.internal.session.room.model + +enum class PaginationDirection(val value: String) { + /** + * Forwards when the event is added to the end of the timeline. + * These events come from the /sync stream or from forwards pagination. + */ + FORWARDS("f"), + + /** + * Backwards when the event is added to the start of the timeline. + * These events come from a back pagination. + */ + BACKWARDS("b") +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt index bc1c9b595d..f0fb1be8fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/model/TokenChunkEvent.kt @@ -6,8 +6,8 @@ import im.vector.matrix.android.api.session.events.model.Event @JsonClass(generateAdapter = true) data class TokenChunkEvent( - @Json(name = "start") val prevToken: String? = null, - @Json(name = "end") val nextToken: String? = null, - @Json(name = "chunks") val chunk: List = emptyList(), + @Json(name = "start") val nextToken: String? = null, + @Json(name = "end") val prevToken: String? = null, + @Json(name = "chunk") val chunk: List = emptyList(), @Json(name = "state") val stateEvents: List = emptyList() ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt index 8c8f6aea94..1a2c0d5303 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt @@ -9,6 +9,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.asEntity 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.RoomEntity import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findWithNextToken @@ -16,6 +17,7 @@ import im.vector.matrix.android.internal.database.query.findWithPrevToken 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.PaginationDirection import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -28,8 +30,8 @@ class PaginationRequest(private val roomAPI: RoomAPI, private val coroutineDispatchers: MatrixCoroutineDispatchers) { fun execute(roomId: String, - from: String? = null, - direction: String, + from: String?, + direction: PaginationDirection, limit: Int = 10, filter: String? = null, callback: MatrixCallback @@ -42,22 +44,24 @@ class PaginationRequest(private val roomAPI: RoomAPI, } private suspend fun execute(roomId: String, - from: String? = null, - direction: String, + from: String?, + direction: PaginationDirection, limit: Int = 10, - filter: String? = null) = withContext(coroutineDispatchers.io) { + filter: String?) = withContext(coroutineDispatchers.io) { if (from == null) { - return@withContext Either.left(Failure.Unknown(RuntimeException("From token can't be null"))) + return@withContext Either.left( + Failure.Unknown(RuntimeException("From token shouldn't be null")) + ) } - executeRequest { - apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction, limit, filter) + return@withContext executeRequest { + apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter) }.leftIfNull { Failure.Unknown(RuntimeException("TokenChunkEvent shouldn't be null")) - }.flatMap { + }.flatMap { chunk -> try { - insertInDb(it, roomId) - Either.right(it) + insertInDb(chunk, roomId) + Either.right(chunk) } catch (exception: Exception) { Either.Left(Failure.Unknown(exception)) } @@ -67,7 +71,7 @@ class PaginationRequest(private val roomAPI: RoomAPI, private fun insertInDb(chunkEvent: TokenChunkEvent, roomId: String) { monarchy.runTransactionSync { realm -> val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: return@runTransactionSync + ?: return@runTransactionSync val nextChunk = ChunkEntity.findWithPrevToken(realm, roomId, chunkEvent.nextToken) val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, chunkEvent.prevToken) @@ -75,38 +79,42 @@ class PaginationRequest(private val roomAPI: RoomAPI, val mergedEvents = chunkEvent.chunk + chunkEvent.stateEvents val mergedEventIds = mergedEvents.filter { it.eventId != null }.map { it.eventId!! } val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, mergedEventIds) + val hasOverlapped = chunksOverlapped.isNotEmpty() - val currentChunk: ChunkEntity - if (nextChunk != null) { - currentChunk = nextChunk + val currentChunk = if (nextChunk != null) { + nextChunk } else { - currentChunk = ChunkEntity() + ChunkEntity() } + + + val eventsToAdd = ArrayList() + currentChunk.prevToken = chunkEvent.prevToken mergedEvents.forEach { event -> val eventEntity = event.asEntity().let { realm.copyToRealmOrUpdate(it) } if (!currentChunk.events.contains(eventEntity)) { - currentChunk.events.add(eventEntity) + eventsToAdd.add(0, eventEntity) } } if (prevChunk != null) { - currentChunk.events.addAll(prevChunk.events) + eventsToAdd.addAll(0, prevChunk.events) roomEntity.chunks.remove(prevChunk) - } else if (chunksOverlapped.isNotEmpty()) { + } else if (hasOverlapped) { chunksOverlapped.forEach { chunk -> chunk.events.forEach { event -> if (!currentChunk.events.contains(event)) { - currentChunk.events.add(event) + eventsToAdd.add(0, event) } } roomEntity.chunks.remove(chunk) } } - + currentChunk.events.addAll(0, eventsToAdd) if (!roomEntity.chunks.contains(currentChunk)) { roomEntity.chunks.add(currentChunk) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index c7c3acb1bf..dc1c6a1fa4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -7,6 +7,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.query.findAllIncludingEvents +import im.vector.matrix.android.internal.session.room.model.PaginationDirection import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent import im.vector.matrix.android.internal.util.PagingRequestHelper import java.util.* @@ -25,19 +26,20 @@ class TimelineBoundaryCallback(private val paginationRequest: PaginationRequest, } override fun onItemAtEndLoaded(itemAtEnd: Event) { - helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { - monarchy.doWithRealm { realm -> - if (itemAtEnd.eventId == null) { - return@doWithRealm - } - val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.eventId)).firstOrNull() - paginationRequest.execute(roomId, chunkEntity?.prevToken, "forward", callback = createCallback(it)) - } - } + //Todo handle forward pagination } override fun onItemAtFrontLoaded(itemAtFront: Event) { - //Todo handle forward pagination + helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { + monarchy.doWithRealm { realm -> + if (itemAtFront.eventId == null) { + return@doWithRealm + } + val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.eventId)).firstOrNull() + paginationRequest.execute(roomId, chunkEntity?.prevToken, PaginationDirection.BACKWARDS, callback = createCallback(it)) + } + } + } private fun createCallback(pagingRequestCallback: PagingRequestHelper.Request.Callback) = object : MatrixCallback {