From 5297512f87f20b888992ffe4d337b0e831a09a5f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 31 Aug 2021 11:50:23 +0200 Subject: [PATCH] Support Space explore pagination --- changelog.d/3693.feature | 1 + .../api/session/room/model/SpaceChildInfo.kt | 2 +- .../android/sdk/api/session/space/Space.kt | 6 +- .../sdk/api/session/space/SpaceService.kt | 13 +- .../session/space/model/SpaceChildContent.kt | 12 +- .../database/mapper/RoomSummaryMapper.kt | 2 +- .../relationship/RoomChildRelationInfo.kt | 4 +- .../room/summary/RoomSummaryUpdater.kt | 2 +- .../internal/session/space/DefaultSpace.kt | 55 +++--- .../session/space/DefaultSpaceService.kt | 106 +++++----- .../internal/session/space/JoinSpaceTask.kt | 65 +++---- .../session/space/ResolveSpaceInfoTask.kt | 47 +++-- .../sdk/internal/session/space/SpaceApi.kt | 26 ++- .../space/SpaceChildSummaryResponse.kt | 22 ++- .../session/space/SpaceHierarchySummary.kt | 28 +++ .../session/space/SpaceSummaryParams.kt | 34 ---- .../internal/session/space/SpacesResponse.kt | 5 +- .../session/space/peeking/PeekSpaceTask.kt | 6 +- .../session/space/peeking/SpacePeekResult.kt | 6 +- .../upgrade/UpgradeRoomViewModelTask.kt | 2 +- .../room/list/RoomListSectionBuilderSpace.kt | 7 +- .../features/matrixto/SpaceCardRenderer.kt | 2 +- .../spaces/create/CreateSpaceViewModelTask.kt | 2 +- .../explore/SpaceDirectoryController.kt | 33 +++- .../spaces/explore/SpaceDirectoryFragment.kt | 29 +-- .../spaces/explore/SpaceDirectoryState.kt | 13 +- .../explore/SpaceDirectoryViewAction.kt | 1 + .../spaces/explore/SpaceDirectoryViewModel.kt | 184 +++++++++++++++--- .../manage/SpaceManageRoomsViewModel.kt | 8 +- .../spaces/preview/SpacePreviewViewModel.kt | 4 +- 30 files changed, 463 insertions(+), 264 deletions(-) create mode 100644 changelog.d/3693.feature create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceHierarchySummary.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt diff --git a/changelog.d/3693.feature b/changelog.d/3693.feature new file mode 100644 index 0000000000..6351ded522 --- /dev/null +++ b/changelog.d/3693.feature @@ -0,0 +1 @@ +Space summary pagination \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt index 8cd2a0538d..7d3109fb6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt @@ -27,7 +27,7 @@ data class SpaceChildInfo( val avatarUrl: String?, val order: String?, val activeMemberCount: Int?, - val autoJoin: Boolean, +// val autoJoin: Boolean, val viaServers: List, val parentRoomId: String?, val suggested: Boolean?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 3bae6126e0..207050be7d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -36,7 +36,7 @@ interface Space { suspend fun addChildren(roomId: String, viaServers: List?, order: String?, - autoJoin: Boolean = false, +// autoJoin: Boolean = false, suggested: Boolean? = false) fun getChildInfo(roomId: String): SpaceChildContent? @@ -46,8 +46,8 @@ interface Space { @Throws suspend fun setChildrenOrder(roomId: String, order: String?) - @Throws - suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) +// @Throws +// suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) @Throws suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index e5288e4045..5a8a0398f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.api.session.space import android.net.Uri import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.internal.session.space.SpaceHierarchySummary import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult typealias SpaceSummaryQueryParams = RoomSummaryQueryParams @@ -58,10 +59,18 @@ interface SpaceService { /** * Get's information of a space by querying the server + * @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true. + * @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. + * @param maxDepth: Optional: The maximum depth in the tree (from the root room) to return. + * @param from: Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided, + * then the parameters given for suggested_only and max_depth must be the same. */ suspend fun querySpaceChildren(spaceId: String, suggestedOnly: Boolean? = null, - autoJoinedOnly: Boolean? = null): Pair> + limit: Int? = null, + from: String? = null, + // when paginating, pass back the m.space.child state events + knownStateList: List? = null): SpaceHierarchySummary /** * Get a live list of space summaries. This list is refreshed as soon as the data changes. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt index 0c33cfa1e6..b55f90cef0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -40,12 +40,12 @@ data class SpaceChildContent( * or consist of more than 50 characters, are forbidden and should be ignored if received.) */ @Json(name = "order") val order: String? = null, - /** - * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should - * be automatically joined by members of that space. - * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.) - */ - @Json(name = "auto_join") val autoJoin: Boolean? = false, +// /** +// * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should +// * be automatically joined by members of that space. +// * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.) +// */ +// @Json(name = "auto_join") val autoJoin: Boolean? = false, /** * If `suggested` is set to `true`, that indicates that the child should be advertised to diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index c32c019625..0cf431c340 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -88,7 +88,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa avatarUrl = it.childSummaryEntity?.avatarUrl, activeMemberCount = it.childSummaryEntity?.joinedMembersCount, order = it.order, - autoJoin = it.autoJoin ?: false, +// autoJoin = it.autoJoin ?: false, viaServers = it.viaServers.toList(), parentRoomId = roomSummaryEntity.roomId, suggested = it.suggested, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt index 2efea7f118..5bad334afc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt @@ -43,7 +43,7 @@ internal class RoomChildRelationInfo( data class SpaceChildInfo( val roomId: String, val order: String?, - val autoJoin: Boolean, +// val autoJoin: Boolean, val viaServers: List ) @@ -71,7 +71,7 @@ internal class RoomChildRelationInfo( SpaceChildInfo( roomId = it.stateKey, order = scc.validOrder(), - autoJoin = scc.autoJoin ?: false, +// autoJoin = scc.autoJoin ?: false, viaServers = via ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 842c9d3aba..89a3533946 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -220,7 +220,7 @@ internal class RoomSummaryUpdater @Inject constructor( this.childRoomId = child.roomId this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst() this.order = child.order - this.autoJoin = child.autoJoin +// this.autoJoin = child.autoJoin this.viaServers.addAll(child.viaServers) } ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 7b513678e3..8a6bbc18fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -51,7 +51,7 @@ internal class DefaultSpace( override suspend fun addChildren(roomId: String, viaServers: List?, order: String?, - autoJoin: Boolean, +// autoJoin: Boolean, suggested: Boolean?) { // Find best via val bestVia = viaServers @@ -69,7 +69,6 @@ internal class DefaultSpace( stateKey = roomId, body = SpaceChildContent( via = bestVia, - autoJoin = autoJoin, order = order, suggested = suggested ).toContent() @@ -90,7 +89,7 @@ internal class DefaultSpace( body = SpaceChildContent( order = null, via = null, - autoJoin = null, +// autoJoin = null, suggested = null ).toContent() ) @@ -115,35 +114,35 @@ internal class DefaultSpace( body = SpaceChildContent( order = order, via = existing.via, - autoJoin = existing.autoJoin, +// autoJoin = existing.autoJoin, suggested = existing.suggested ).toContent() ) } - override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) { - val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) - .firstOrNull() - ?.content.toModel() - ?: throw IllegalArgumentException("$roomId is not a child of this space") - - if (existing.autoJoin == autoJoin) { - // nothing to do? - return - } - - // edit state event and set via to null - room.sendStateEvent( - eventType = EventType.STATE_SPACE_CHILD, - stateKey = roomId, - body = SpaceChildContent( - order = existing.order, - via = existing.via, - autoJoin = autoJoin, - suggested = existing.suggested - ).toContent() - ) - } +// override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) { +// val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) +// .firstOrNull() +// ?.content.toModel() +// ?: throw IllegalArgumentException("$roomId is not a child of this space") +// +// if (existing.autoJoin == autoJoin) { +// // nothing to do? +// return +// } +// +// // edit state event and set via to null +// room.sendStateEvent( +// eventType = EventType.STATE_SPACE_CHILD, +// stateKey = roomId, +// body = SpaceChildContent( +// order = existing.order, +// via = existing.via, +// autoJoin = autoJoin, +// suggested = existing.suggested +// ).toContent() +// ) +// } override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) { val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) @@ -162,7 +161,7 @@ internal class DefaultSpace( body = SpaceChildContent( order = existing.order, via = existing.via, - autoJoin = existing.autoJoin, +// autoJoin = existing.autoJoin, suggested = suggested ).toContent() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 0c5c0416f9..e23179b9e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.space import android.net.Uri import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset @@ -108,53 +110,65 @@ internal class DefaultSpaceService @Inject constructor( override suspend fun querySpaceChildren(spaceId: String, suggestedOnly: Boolean?, - autoJoinedOnly: Boolean?): Pair> { - return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response -> + limit: Int?, + from: String?, + knownStateList: List?): SpaceHierarchySummary { + return resolveSpaceInfoTask.execute( + ResolveSpaceInfoTask.Params( + spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly + ) + ).let { response -> val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId } - Pair( - first = RoomSummary( - roomId = spaceDesc?.roomId ?: spaceId, - roomType = spaceDesc?.roomType, - name = spaceDesc?.name ?: "", - displayName = spaceDesc?.name ?: "", - topic = spaceDesc?.topic ?: "", - joinedMembersCount = spaceDesc?.numJoinedMembers, - avatarUrl = spaceDesc?.avatarUrl ?: "", - encryptionEventTs = null, - typingUsers = emptyList(), - isEncrypted = false, - flattenParentIds = emptyList() - ), - second = response.rooms - ?.filter { it.roomId != spaceId } - ?.flatMap { childSummary -> - response.events - ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD } - ?.mapNotNull { childStateEv -> - // create a child entry for everytime this room is the child of a space - // beware that a room could appear then twice in this list - childStateEv.content.toModel()?.let { childStateEvContent -> - SpaceChildInfo( - childRoomId = childSummary.roomId, - isKnown = true, - roomType = childSummary.roomType, - name = childSummary.name, - topic = childSummary.topic, - avatarUrl = childSummary.avatarUrl, - order = childStateEvContent.order, - autoJoin = childStateEvContent.autoJoin ?: false, - viaServers = childStateEvContent.via.orEmpty(), - activeMemberCount = childSummary.numJoinedMembers, - parentRoomId = childStateEv.roomId, - suggested = childStateEvContent.suggested, - canonicalAlias = childSummary.canonicalAlias, - aliases = childSummary.aliases, - worldReadable = childSummary.worldReadable - ) - } - }.orEmpty() - } - .orEmpty() + val root = RoomSummary( + roomId = spaceDesc?.roomId ?: spaceId, + roomType = spaceDesc?.roomType, + name = spaceDesc?.name ?: "", + displayName = spaceDesc?.name ?: "", + topic = spaceDesc?.topic ?: "", + joinedMembersCount = spaceDesc?.numJoinedMembers, + avatarUrl = spaceDesc?.avatarUrl ?: "", + encryptionEventTs = null, + typingUsers = emptyList(), + isEncrypted = false, + flattenParentIds = emptyList(), + canonicalAlias = spaceDesc?.canonicalAlias, + joinRules = RoomJoinRules.PUBLIC.takeIf { spaceDesc?.worldReadable == true } + ) + val children = response.rooms + ?.filter { it.roomId != spaceId } + ?.flatMap { childSummary -> + (spaceDesc?.childrenState ?: knownStateList) + ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD } + ?.mapNotNull { childStateEv -> + // create a child entry for everytime this room is the child of a space + // beware that a room could appear then twice in this list + childStateEv.content.toModel()?.let { childStateEvContent -> + SpaceChildInfo( + childRoomId = childSummary.roomId, + isKnown = true, + roomType = childSummary.roomType, + name = childSummary.name, + topic = childSummary.topic, + avatarUrl = childSummary.avatarUrl, + order = childStateEvContent.order, +// autoJoin = childStateEvContent.autoJoin ?: false, + viaServers = childStateEvContent.via.orEmpty(), + activeMemberCount = childSummary.numJoinedMembers, + parentRoomId = childStateEv.roomId, + suggested = childStateEvContent.suggested, + canonicalAlias = childSummary.canonicalAlias, + aliases = childSummary.aliases, + worldReadable = childSummary.worldReadable + ) + } + }.orEmpty() + } + .orEmpty() + SpaceHierarchySummary( + rootSummary = root, + children = children, + childrenState = spaceDesc?.childrenState.orEmpty(), + nextToken = response.nextBatch ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index e9d5ba5193..7eeaed4ff6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.space import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -84,39 +83,39 @@ internal class DefaultJoinSpaceTask @Inject constructor( // after that i should have the children (? do I need to paginate to get state) val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias) Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}") - summary?.spaceChildren?.forEach { +// summary?.spaceChildren?.forEach { // val childRoomSummary = it.roomSummary ?: return@forEach - Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}") - if (it.autoJoin) { - // I should try to join as well - if (it.roomType == RoomType.SPACE) { - // recursively join auto-joined child of this space? - when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) { - JoinSpaceResult.Success -> { - // nop - } - is JoinSpaceResult.Fail -> { - errors[it.childRoomId] = subspaceJoinResult.error - } - is JoinSpaceResult.PartialSuccess -> { - errors.putAll(subspaceJoinResult.failedRooms) - } - } - } else { - try { - Timber.v("## Space: Joining room child ${it.childRoomId}") - joinRoomTask.execute(JoinRoomTask.Params( - roomIdOrAlias = it.childRoomId, - reason = "Auto-join parent space", - viaServers = it.viaServers - )) - } catch (failure: Throwable) { - errors[it.childRoomId] = failure - Timber.e("## Space: Failed to join room child ${it.childRoomId}") - } - } - } - } +// Timber.v("## Space: Processing child :[${it.childRoomId}] suggested:${it.suggested}") +// if (it.autoJoin) { +// // I should try to join as well +// if (it.roomType == RoomType.SPACE) { +// // recursively join auto-joined child of this space? +// when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) { +// JoinSpaceResult.Success -> { +// // nop +// } +// is JoinSpaceResult.Fail -> { +// errors[it.childRoomId] = subspaceJoinResult.error +// } +// is JoinSpaceResult.PartialSuccess -> { +// errors.putAll(subspaceJoinResult.failedRooms) +// } +// } +// } else { +// try { +// Timber.v("## Space: Joining room child ${it.childRoomId}") +// joinRoomTask.execute(JoinRoomTask.Params( +// roomIdOrAlias = it.childRoomId, +// reason = "Auto-join parent space", +// viaServers = it.viaServers +// )) +// } catch (failure: Throwable) { +// errors[it.childRoomId] = failure +// Timber.e("## Space: Failed to join room child ${it.childRoomId}") +// } +// } +// } +// } return if (errors.isEmpty()) { JoinSpaceResult.Success diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt index d2be49b70b..0d305fcff4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt @@ -24,23 +24,24 @@ import javax.inject.Inject internal interface ResolveSpaceInfoTask : Task { data class Params( val spaceId: String, - val maxRoomPerSpace: Int?, - val limit: Int, - val batchToken: String?, - val suggestedOnly: Boolean?, - val autoJoinOnly: Boolean? +// val maxRoomPerSpace: Int?, + val limit: Int?, + val maxDepth: Int?, + val from: String?, + val suggestedOnly: Boolean? +// val autoJoinOnly: Boolean? ) { - companion object { - fun withId(spaceId: String, suggestedOnly: Boolean?, autoJoinOnly: Boolean?) = - Params( - spaceId = spaceId, - maxRoomPerSpace = 10, - limit = 20, - batchToken = null, - suggestedOnly = suggestedOnly, - autoJoinOnly = autoJoinOnly - ) - } +// companion object { +// fun withId(spaceId: String, suggestedOnly: Boolean?) = +// Params( +// spaceId = spaceId, +// // maxRoomPerSpace = 10, +// limit = 20, +// from = null, +// suggestedOnly = suggestedOnly +// // autoJoinOnly = autoJoinOnly +// ) +// } } } @@ -49,15 +50,13 @@ internal class DefaultResolveSpaceInfoTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver ) : ResolveSpaceInfoTask { override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse { - val body = SpaceSummaryParams( - maxRoomPerSpace = params.maxRoomPerSpace, - limit = params.limit, - batch = params.batchToken ?: "", - autoJoinedOnly = params.autoJoinOnly, - suggestedOnly = params.suggestedOnly - ) return executeRequest(globalErrorReceiver) { - spaceApi.getSpaces(params.spaceId, body) + spaceApi.getSpaceHierarchy( + spaceId = params.spaceId, + suggestedOnly = params.suggestedOnly, + limit = params.limit, + maxDepth = params.maxDepth, + from = params.from) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt index 0fcc95fdb3..4d3de2c8a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.space import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.http.Body -import retrofit2.http.POST +import retrofit2.http.GET import retrofit2.http.Path +import retrofit2.http.Query internal interface SpaceApi { @@ -37,7 +37,23 @@ internal interface SpaceApi { * - MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md * - https://hackmd.io/fNYh4tjUT5mQfR1uuRzWDA */ - @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces") - suspend fun getSpaces(@Path("roomId") spaceId: String, - @Body params: SpaceSummaryParams): SpacesResponse +// @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces") +// suspend fun getSpaces(@Path("roomId") spaceId: String, +// @Body params: SpaceSummaryParams): SpacesResponse + + /** + * @param limit: Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. + * @param max_depth: Optional: The maximum depth in the tree (from the root room) to return. + * The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer. + * + * @param from: Optional. Pagination token given to retrieve the next set of rooms. + * Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same. + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/hierarchy") + suspend fun getSpaceHierarchy( + @Path("roomId") spaceId: String, + @Query("suggested_only") suggestedOnly: Boolean?, + @Query("limit") limit: Int?, + @Query("max_depth") maxDepth: Int?, + @Query("from") from: String?): SpacesResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt index 5021ff638f..74faa2653a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt @@ -18,14 +18,21 @@ package org.matrix.android.sdk.internal.session.space import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Event +/** + * The fields are the same as those returned by /publicRooms (see spec), with the addition of: + * room_type: the value of the m.type field from the room's m.room.create event, if any. + * children_state: The m.space.child events of the room. For each event, only the following fields are included1: type, state_key, content, room_id, sender, with the addition of: + * origin_server_ts: This is required for sorting of rooms as specified below. + */ @JsonClass(generateAdapter = true) internal data class SpaceChildSummaryResponse( - /** - * The total number of state events which point to or from this room (inbound/outbound edges). - * This includes all m.space.child events in the room, in addition to m.room.parent events which point to this room as a parent. - */ - @Json(name = "num_refs") val numRefs: Int? = null, +// /** +// * The total number of state events which point to or from this room (inbound/outbound edges). +// * This includes all m.space.child events in the room, in addition to m.room.parent events which point to this room as a parent. +// */ +// @Json(name = "num_refs") val numRefs: Int? = null, /** * The room type, which is m.space for subspaces. @@ -33,6 +40,11 @@ internal data class SpaceChildSummaryResponse( */ @Json(name = "room_type") val roomType: String? = null, + /** The m.space.child events of the room. For each event, only the following fields are included: + * type, state_key, content, room_id, sender, with the addition of origin_server_ts: This is required for sorting of rooms as specified below. + */ + @Json(name = "children_state") val childrenState: List? = null, + /** * Aliases of the room. May be empty. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceHierarchySummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceHierarchySummary.kt new file mode 100644 index 0000000000..bb362b416b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceHierarchySummary.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 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 org.matrix.android.sdk.internal.session.space + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo + +data class SpaceHierarchySummary( + val rootSummary: RoomSummary, + val children: List, + val childrenState: List, + val nextToken: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt deleted file mode 100644 index 013db1c286..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.session.space - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class SpaceSummaryParams( - /** The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1 */ - @Json(name = "max_rooms_per_space") val maxRoomPerSpace: Int?, - /** The maximum number of rooms/subspaces to return, server can override this, default: 100 */ - @Json(name = "limit") val limit: Int?, - /** A token to use if this is a subsequent HTTP hit, default: "". */ - @Json(name = "batch") val batch: String = "", - /** whether we should only return children with the "suggested" flag set. */ - @Json(name = "suggested_only") val suggestedOnly: Boolean?, - /** whether we should only return children with the "suggested" flag set. */ - @Json(name = "auto_join_only") val autoJoinedOnly: Boolean? -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt index 20d63c8814..7d22dce96b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt @@ -18,14 +18,11 @@ package org.matrix.android.sdk.internal.session.space import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.events.model.Event @JsonClass(generateAdapter = true) internal data class SpacesResponse( /** Its presence indicates that there are more results to return. */ @Json(name = "next_batch") val nextBatch: String? = null, /** Rooms information like name/avatar/type ... */ - @Json(name = "rooms") val rooms: List? = null, - /** These are the edges of the graph. The objects in the array are complete (or stripped?) m.room.parent or m.space.child events. */ - @Json(name = "events") val events: List? = null + @Json(name = "rooms") val rooms: List? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt index f6b156a6fb..5cbaaa45c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt @@ -103,7 +103,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( // can't peek :/ spaceChildResults.add( SpaceChildPeekResult( - childId, childPeek, entry.second?.autoJoin, entry.second?.order + childId, childPeek, entry.second?.order ) ) // continue to next child @@ -116,7 +116,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( SpaceSubChildPeekResult( childId, childPeek, - entry.second?.autoJoin, +// entry.second?.autoJoin, entry.second?.order, peekChildren(childStateEvents, depth + 1, maxDepth) ) @@ -127,7 +127,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( Timber.v("## SPACE_PEEK: room child $entry") spaceChildResults.add( SpaceChildPeekResult( - childId, childPeek, entry.second?.autoJoin, entry.second?.order + childId, childPeek, entry.second?.order ) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt index 1df62e94e8..44d879f05d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt @@ -28,21 +28,21 @@ data class SpacePeekSummary( interface ISpaceChild { val id: String val roomPeekResult: PeekResult - val default: Boolean? +// val default: Boolean? val order: String? } data class SpaceChildPeekResult( override val id: String, override val roomPeekResult: PeekResult, - override val default: Boolean? = null, +// override val default: Boolean? = null, override val order: String? = null ) : ISpaceChild data class SpaceSubChildPeekResult( override val id: String, override val roomPeekResult: PeekResult, - override val default: Boolean?, +// override val default: Boolean?, override val order: String?, val children: List ) : ISpaceChild diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt index 32c8e6ee92..8859aaeacf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt @@ -80,7 +80,7 @@ class UpgradeRoomViewModelTask @Inject constructor( roomId = updatedRoomId, viaServers = currentInfo.via, order = currentInfo.order, - autoJoin = currentInfo.autoJoin ?: false, +// autoJoin = currentInfo.autoJoin ?: false, suggested = currentInfo.suggested ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 13a6fc0d2d..7063281853 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -230,8 +230,11 @@ class RoomListSectionBuilderSpace( Observable.just(emptyList()) } else { liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { - val spaceSum = tryOrNull { session.spaceService().querySpaceChildren(selectedSpace.roomId, suggestedOnly = true) } - val value = spaceSum?.second.orEmpty().distinctBy { it.childRoomId } + val spaceSum = tryOrNull { + session.spaceService() + .querySpaceChildren(selectedSpace.roomId, suggestedOnly = true, null, null) + } + val value = spaceSum?.children.orEmpty().distinctBy { it.childRoomId } // i need to check if it's already joined. val filtered = value.filter { session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true diff --git a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt index e51490a59c..88d65e23b1 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt @@ -62,7 +62,7 @@ class SpaceCardRenderer @Inject constructor( inCard.matrixToAccessImage.isVisible = true inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private) } - val memberCount = spaceSummary.otherMemberIds.size + val memberCount = spaceSummary.joinedMembersCount?.let { it - 1 } ?: 0 if (memberCount != 0) { inCard.matrixToMemberPills.isVisible = true inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index 1440efe6fe..c68b8a0b9b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -134,7 +134,7 @@ class CreateSpaceViewModelTask @Inject constructor( timeout.roomID } val via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList() - createdSpace!!.addChildren(roomId, via, null, autoJoin = false, suggested = true) + createdSpace!!.addChildren(roomId, via, null, suggested = true) // set canonical session.spaceService().setSpaceParent( roomId, diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index 1e67da7a4d..c76eec257b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -18,8 +18,10 @@ package im.vector.app.features.spaces.explore import android.view.View import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.epoxy.VisibilityState import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.errorWithRetryItem @@ -54,13 +56,15 @@ class SpaceDirectoryController @Inject constructor( fun onRoomClick(spaceChildInfo: SpaceChildInfo) fun retry() fun addExistingRooms(spaceId: String) + fun loadAdditionalItemsIfNeeded() } var listener: InteractionListener? = null override fun buildModels(data: SpaceDirectoryState?) { val host = this - val results = data?.spaceSummaryApiResult + val currentRootId = data?.hierarchyStack?.lastOrNull() ?: data?.spaceId ?: return + val results = data?.apiResults?.get(currentRootId) if (results is Incomplete) { loadingItem { @@ -94,7 +98,9 @@ class SpaceDirectoryController @Inject constructor( } } } else { - val flattenChildInfo = results?.invoke() + val hierarchySummary = results?.invoke() + val flattenChildInfo = hierarchySummary + ?.children ?.filter { it.parentRoomId == (data.hierarchyStack.lastOrNull() ?: data.spaceId) } @@ -132,6 +138,7 @@ class SpaceDirectoryController @Inject constructor( // if it's known use that matrixItem because it would have a better computed name val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem() ?: info.toMatrixItem() + spaceChildInfoItem { id(info.childRoomId) matrixItem(matrixItem) @@ -162,6 +169,28 @@ class SpaceDirectoryController @Inject constructor( } } } + if (hierarchySummary?.nextToken != null) { + val paginationStatus = data.paginationStatus[currentRootId] ?: Uninitialized + if (paginationStatus is Fail) { + errorWithRetryItem { + id("error_${currentRootId}_${hierarchySummary.nextToken}") + text(host.errorFormatter.toHumanReadable(paginationStatus.error)) + listener { host.listener?.retry() } + } + } else { + loadingItem { + id("pagination_${currentRootId}_${hierarchySummary.nextToken}") + showLoader(true) + onVisibilityStateChanged { _, _, visibilityState -> + // Do something with the new visibility state + if (visibilityState == VisibilityState.VISIBLE) { + // we can trigger a seamless load of additional items + host.listener?.loadAdditionalItemsIfNeeded() + } + } + } + } + } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 323d8a7c87..9714cebd4e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -25,6 +25,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.text.toSpannable import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -72,6 +73,7 @@ class SpaceDirectoryFragment @Inject constructor( FragmentSpaceDirectoryBinding.inflate(layoutInflater, container, false) private val viewModel by activityViewModel(SpaceDirectoryViewModel::class) + private val epoxyVisibilityTracker = EpoxyVisibilityTracker() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -84,6 +86,7 @@ class SpaceDirectoryFragment @Inject constructor( } epoxyController.listener = this views.spaceDirectoryList.configureWith(epoxyController) + epoxyVisibilityTracker.attach(views.spaceDirectoryList) viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) { invalidateOptionsMenu() @@ -95,6 +98,7 @@ class SpaceDirectoryFragment @Inject constructor( override fun onDestroyView() { epoxyController.listener = null + epoxyVisibilityTracker.detach(views.spaceDirectoryList) views.spaceDirectoryList.cleanup() super.onDestroyView() } @@ -102,21 +106,20 @@ class SpaceDirectoryFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> epoxyController.setData(state) - val currentParent = state.hierarchyStack.lastOrNull()?.let { currentParent -> - state.spaceSummaryApiResult.invoke()?.firstOrNull { it.childRoomId == currentParent } - } + val currentParentId = state.hierarchyStack.lastOrNull() - if (currentParent == null) { + if (currentParentId == null) { + // it's the root val title = getString(R.string.space_explore_activity_title) views.toolbar.title = title - - spaceCardRenderer.render(state.spaceSummary.invoke(), emptyList(), this, views.spaceCard) } else { - val title = currentParent.name ?: currentParent.canonicalAlias ?: getString(R.string.space_explore_activity_title) + val title = state.currentRootSummary?.name + ?: state.currentRootSummary?.canonicalAlias + ?: getString(R.string.space_explore_activity_title) views.toolbar.title = title - - spaceCardRenderer.render(currentParent, emptyList(), this, views.spaceCard) } + + spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard) } override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state -> @@ -170,6 +173,10 @@ class SpaceDirectoryFragment @Inject constructor( addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms)) } + override fun loadAdditionalItemsIfNeeded() { + viewModel.handle(SpaceDirectoryViewAction.LoadAdditionalItemsIfNeeded) + } + override fun onUrlClicked(url: String, title: String): Boolean { permalinkHandler .launch(requireActivity(), url, null) @@ -206,7 +213,5 @@ class SpaceDirectoryFragment @Inject constructor( // nothing? return false } -// override fun navigateToRoom(roomId: String) { -// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId)) -// } + } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt index 21541a51ab..f762ac7629 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt @@ -18,28 +18,27 @@ package im.vector.app.features.spaces.explore import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.internal.session.space.SpaceHierarchySummary data class SpaceDirectoryState( // The current filter val spaceId: String, val currentFilter: String = "", - val spaceSummary: Async = Uninitialized, - val spaceSummaryApiResult: Async> = Uninitialized, + val apiResults: Map> = emptyMap(), + val currentRootSummary: RoomSummary? = null, val childList: List = emptyList(), val hierarchyStack: List = emptyList(), - // True if more result are available server side - val hasMore: Boolean = false, // Set of joined roomId / spaces, val joinedRoomsIds: Set = emptySet(), // keys are room alias or roomId val changeMembershipStates: Map = emptyMap(), val canAddRooms: Boolean = false, - // cached room summaries of known rooms - val knownRoomSummaries : List = emptyList() + // cached room summaries of known rooms, we use it because computed room name would be better using it + val knownRoomSummaries : List = emptyList(), + val paginationStatus: Map> = emptyMap() ) : MvRxState { constructor(args: SpaceDirectoryArgs) : this( spaceId = args.spaceId diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt index 83cdf2916d..3ced017d61 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt @@ -26,4 +26,5 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction { data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction() object HandleBack : SpaceDirectoryViewAction() object Retry : SpaceDirectoryViewAction() + object LoadAdditionalItemsIfNeeded : SpaceDirectoryViewAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index de31f43322..578223cd36 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -23,6 +23,7 @@ import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -34,6 +35,8 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -67,11 +70,11 @@ class SpaceDirectoryViewModel @AssistedInject constructor( setState { copy( childList = spaceSum?.spaceChildren ?: emptyList(), - spaceSummary = spaceSum?.let { Success(spaceSum) } ?: Loading() + currentRootSummary = spaceSum ) } - refreshFromApi() + refreshFromApi(initialState.spaceId) observeJoinedRooms() observeMembershipChanges() observePermissions() @@ -93,29 +96,44 @@ class SpaceDirectoryViewModel @AssistedInject constructor( .disposeOnClear() } - private fun refreshFromApi() { + private fun refreshFromApi(rootId: String?) = withState { state -> + val spaceId = rootId ?: initialState.spaceId setState { copy( - spaceSummaryApiResult = Loading() + apiResults = state.apiResults.toMutableMap().apply { + this[spaceId] = Loading() + }.toMap() ) } viewModelScope.launch(Dispatchers.IO) { + val cachedResults = state.apiResults.toMutableMap() try { - val query = session.spaceService().querySpaceChildren(initialState.spaceId) - val knownSummaries = query.second.mapNotNull { + val query = session.spaceService().querySpaceChildren( + spaceId, + limit = 10 + ) + val knownSummaries = query.children.mapNotNull { session.getRoomSummary(it.childRoomId) ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) - } + }.distinctBy { it.roomId } setState { copy( - spaceSummaryApiResult = Success(query.second), - knownRoomSummaries = knownSummaries + apiResults = cachedResults.apply { + this[spaceId] = Success(query) + }, + currentRootSummary = query.rootSummary, + paginationStatus = state.paginationStatus.toMutableMap().apply { + this[spaceId] = Uninitialized + }.toMap(), + knownRoomSummaries = (state.knownRoomSummaries + knownSummaries).distinctBy { it.roomId }, ) } } catch (failure: Throwable) { setState { copy( - spaceSummaryApiResult = Fail(failure) + apiResults = cachedResults.apply { + this[spaceId] = Fail(failure) + } ) } } @@ -149,39 +167,143 @@ class SpaceDirectoryViewModel @AssistedInject constructor( override fun handle(action: SpaceDirectoryViewAction) { when (action) { - is SpaceDirectoryViewAction.ExploreSubSpace -> { - setState { - copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId)) - } + is SpaceDirectoryViewAction.ExploreSubSpace -> { + handleExploreSubSpace(action) } - SpaceDirectoryViewAction.HandleBack -> { - withState { - if (it.hierarchyStack.isEmpty()) { - _viewEvents.post(SpaceDirectoryViewEvents.Dismiss) - } else { - setState { - copy( - hierarchyStack = hierarchyStack.dropLast(1) - ) - } - } - } + SpaceDirectoryViewAction.HandleBack -> { + handleBack() } - is SpaceDirectoryViewAction.JoinOrOpen -> { + is SpaceDirectoryViewAction.JoinOrOpen -> { handleJoinOrOpen(action.spaceChildInfo) } - is SpaceDirectoryViewAction.NavigateToRoom -> { + is SpaceDirectoryViewAction.NavigateToRoom -> { _viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId)) } - is SpaceDirectoryViewAction.ShowDetails -> { + is SpaceDirectoryViewAction.ShowDetails -> { // This is temporary for now to at least display something for the space beta // It's not ideal as it's doing some peeking that is not needed. session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let { _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) } } - SpaceDirectoryViewAction.Retry -> { - refreshFromApi() + SpaceDirectoryViewAction.Retry -> { + handleRetry() + } + SpaceDirectoryViewAction.LoadAdditionalItemsIfNeeded -> { + loadAdditionalItemsIfNeeded() + } + } + } + + private fun handleBack() = withState { state -> + if (state.hierarchyStack.isEmpty()) { + _viewEvents.post(SpaceDirectoryViewEvents.Dismiss) + } else { + val newStack = state.hierarchyStack.dropLast(1) + val newRootId = newStack.lastOrNull() ?: initialState.spaceId + val rootSummary = state.apiResults[newRootId]?.invoke()?.rootSummary + setState { + copy( + hierarchyStack = hierarchyStack.dropLast(1), + currentRootSummary = rootSummary + ) + } + } + } + + private fun handleRetry() = withState { state -> + refreshFromApi(state.hierarchyStack.lastOrNull() ?: initialState.spaceId) + } + + private fun handleExploreSubSpace(action: SpaceDirectoryViewAction.ExploreSubSpace) = withState { state -> + val newRootId = action.spaceChildInfo.childRoomId + val curSum = RoomSummary( + roomId = action.spaceChildInfo.childRoomId, + roomType = action.spaceChildInfo.roomType, + name = action.spaceChildInfo.name ?: "", + canonicalAlias = action.spaceChildInfo.canonicalAlias, + topic = action.spaceChildInfo.topic ?: "", + joinedMembersCount = action.spaceChildInfo.activeMemberCount, + avatarUrl = action.spaceChildInfo.avatarUrl ?: "", + isEncrypted = false, + joinRules = if (action.spaceChildInfo.worldReadable) RoomJoinRules.PUBLIC else RoomJoinRules.PRIVATE, + encryptionEventTs = 0, + typingUsers = emptyList() + ) + setState { + copy( + hierarchyStack = hierarchyStack + listOf(newRootId), + currentRootSummary = curSum + ) + } + val shouldLoad = when (state.apiResults[newRootId]) { + Uninitialized -> true + is Loading -> false + is Success -> false + is Fail -> true + null -> true + } + + if (shouldLoad) { + refreshFromApi(newRootId) + } + } + + private fun loadAdditionalItemsIfNeeded() = withState { state -> + val currentRootId = state.hierarchyStack.lastOrNull() ?: initialState.spaceId + val mutablePaginationStatus = state.paginationStatus.toMutableMap().apply { + if (this[currentRootId] == null) { + this[currentRootId] = Uninitialized + } + } + + if (mutablePaginationStatus[currentRootId] is Loading) return@withState + + setState { + copy(paginationStatus = mutablePaginationStatus.toMap()) + } + + viewModelScope.launch(Dispatchers.IO) { + val cachedResults = state.apiResults.toMutableMap() + try { + val currentResponse = cachedResults[currentRootId]?.invoke() + if (currentResponse == null) { + // nothing to paginate through... + setState { + copy(paginationStatus = mutablePaginationStatus.apply { this[currentRootId] = Uninitialized }.toMap()) + } + return@launch + } + val query = session.spaceService().querySpaceChildren( + currentRootId, + limit = 10, + from = currentResponse.nextToken, + knownStateList = currentResponse.childrenState + ) + val knownSummaries = query.children.mapNotNull { + session.getRoomSummary(it.childRoomId) + ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) + }.distinctBy { it.roomId } + + cachedResults[currentRootId] = Success( + currentResponse.copy( + children = currentResponse.children + query.children, + nextToken = query.nextToken, + ) + ) + setState { + copy( + apiResults = cachedResults.toMap(), + paginationStatus = mutablePaginationStatus.apply { this[currentRootId] = Success(Unit) }.toMap(), + knownRoomSummaries = (state.knownRoomSummaries + knownSummaries).distinctBy { it.roomId } + ) + } + } catch (failure: Throwable) { + setState { + copy( + paginationStatus = mutablePaginationStatus.apply { this[currentRootId] = Fail(failure) }.toMap() + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt index d1e1d8f0fb..6db6bb4b46 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt @@ -50,7 +50,7 @@ class SpaceManageRoomsViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { val apiResult = runCatchingToAsync { - session.spaceService().querySpaceChildren(spaceId = initialState.spaceId).second + session.spaceService().querySpaceChildren(spaceId = initialState.spaceId).children } setState { copy( @@ -131,8 +131,8 @@ class SpaceManageRoomsViewModel @AssistedInject constructor( roomId = info.childRoomId, viaServers = info.viaServers, order = info.order, - suggested = suggested, - autoJoin = info.autoJoin + suggested = suggested +// autoJoin = info.autoJoin ) } catch (failure: Throwable) { errorList.add(failure) @@ -156,7 +156,7 @@ class SpaceManageRoomsViewModel @AssistedInject constructor( } viewModelScope.launch(Dispatchers.IO) { val apiResult = runCatchingToAsync { - session.spaceService().querySpaceChildren(spaceId = initialState.spaceId).second + session.spaceService().querySpaceChildren(spaceId = initialState.spaceId).children } setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 61328d2a1c..0f1afd8371 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -151,7 +151,7 @@ class SpacePreviewViewModel @AssistedInject constructor( setState { copy( spaceInfo = Success( - resolveResult.first.let { + resolveResult.rootSummary.let { ChildInfo( roomId = it.roomId, avatarUrl = it.avatarUrl, @@ -165,7 +165,7 @@ class SpacePreviewViewModel @AssistedInject constructor( } ), childInfoList = Success( - resolveResult.second.map { + resolveResult.children.map { ChildInfo( roomId = it.childRoomId, avatarUrl = it.avatarUrl,