Support Space explore pagination
This commit is contained in:
parent
2982fdc626
commit
5297512f87
1
changelog.d/3693.feature
Normal file
1
changelog.d/3693.feature
Normal file
@ -0,0 +1 @@
|
||||
Space summary pagination
|
@ -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<String>,
|
||||
val parentRoomId: String?,
|
||||
val suggested: Boolean?,
|
||||
|
@ -36,7 +36,7 @@ interface Space {
|
||||
suspend fun addChildren(roomId: String,
|
||||
viaServers: List<String>?,
|
||||
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)
|
||||
|
@ -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<RoomSummary, List<SpaceChildInfo>>
|
||||
limit: Int? = null,
|
||||
from: String? = null,
|
||||
// when paginating, pass back the m.space.child state events
|
||||
knownStateList: List<Event>? = null): SpaceHierarchySummary
|
||||
|
||||
/**
|
||||
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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<String>
|
||||
)
|
||||
|
||||
@ -71,7 +71,7 @@ internal class RoomChildRelationInfo(
|
||||
SpaceChildInfo(
|
||||
roomId = it.stateKey,
|
||||
order = scc.validOrder(),
|
||||
autoJoin = scc.autoJoin ?: false,
|
||||
// autoJoin = scc.autoJoin ?: false,
|
||||
viaServers = via
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
@ -51,7 +51,7 @@ internal class DefaultSpace(
|
||||
override suspend fun addChildren(roomId: String,
|
||||
viaServers: List<String>?,
|
||||
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<SpaceChildContent>()
|
||||
?: 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<SpaceChildContent>()
|
||||
// ?: 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()
|
||||
)
|
||||
|
@ -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<RoomSummary, List<SpaceChildInfo>> {
|
||||
return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response ->
|
||||
limit: Int?,
|
||||
from: String?,
|
||||
knownStateList: List<Event>?): 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<SpaceChildContent>()?.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<SpaceChildContent>()?.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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -24,23 +24,24 @@ import javax.inject.Inject
|
||||
internal interface ResolveSpaceInfoTask : Task<ResolveSpaceInfoTask.Params, SpacesResponse> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<Event>? = null,
|
||||
|
||||
/**
|
||||
* Aliases of the room. May be empty.
|
||||
*/
|
||||
|
@ -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<SpaceChildInfo>,
|
||||
val childrenState: List<Event>,
|
||||
val nextToken: String? = 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?
|
||||
)
|
@ -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<SpaceChildSummaryResponse>? = 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<Event>? = null
|
||||
@Json(name = "rooms") val rooms: List<SpaceChildSummaryResponse>? = null
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
) : ISpaceChild
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -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<RoomSummary> = Uninitialized,
|
||||
val spaceSummaryApiResult: Async<List<SpaceChildInfo>> = Uninitialized,
|
||||
val apiResults: Map<String, Async<SpaceHierarchySummary>> = emptyMap(),
|
||||
val currentRootSummary: RoomSummary? = null,
|
||||
val childList: List<SpaceChildInfo> = emptyList(),
|
||||
val hierarchyStack: List<String> = emptyList(),
|
||||
// True if more result are available server side
|
||||
val hasMore: Boolean = false,
|
||||
// Set of joined roomId / spaces,
|
||||
val joinedRoomsIds: Set<String> = emptySet(),
|
||||
// keys are room alias or roomId
|
||||
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
|
||||
val canAddRooms: Boolean = false,
|
||||
// cached room summaries of known rooms
|
||||
val knownRoomSummaries : List<RoomSummary> = emptyList()
|
||||
// cached room summaries of known rooms, we use it because computed room name would be better using it
|
||||
val knownRoomSummaries : List<RoomSummary> = emptyList(),
|
||||
val paginationStatus: Map<String, Async<Unit>> = emptyMap()
|
||||
) : MvRxState {
|
||||
constructor(args: SpaceDirectoryArgs) : this(
|
||||
spaceId = args.spaceId
|
||||
|
@ -26,4 +26,5 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction {
|
||||
data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction()
|
||||
object HandleBack : SpaceDirectoryViewAction()
|
||||
object Retry : SpaceDirectoryViewAction()
|
||||
object LoadAdditionalItemsIfNeeded : SpaceDirectoryViewAction()
|
||||
}
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user