MSC 2946 WIP

This commit is contained in:
Valere 2021-01-15 09:57:29 +01:00
parent 57f17620b5
commit 186024b271
16 changed files with 600 additions and 117 deletions

View file

@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.session.space
import androidx.lifecycle.LiveData
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.peeking.SpacePeekResult
typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
@ -43,6 +45,11 @@ interface SpaceService {
*/
suspend fun peekSpace(spaceId: String) : SpacePeekResult
/**
* Get's information of a space by querying the server
*/
suspend fun querySpaceChildren(spaceId: String) : Pair<RoomSummary, List<SpaceChildInfo>>
/**
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of List[SpaceSummary]
@ -69,4 +76,6 @@ interface SpaceService {
reason: String? = null,
viaServers: List<String> = emptyList(),
autoJoinChild: List<ChildAutoJoinInfo>) : JoinSpaceResult
suspend fun rejectInvite(spaceId: String, reason: String?)
}

View file

@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
import org.matrix.android.sdk.internal.session.search.SearchModule
import org.matrix.android.sdk.internal.session.signout.SignOutModule
import org.matrix.android.sdk.internal.session.space.SpaceModule
import org.matrix.android.sdk.internal.session.sync.SyncModule
import org.matrix.android.sdk.internal.session.sync.SyncTask
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
@ -91,7 +92,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
FederationModule::class,
CallModule::class,
SearchModule::class,
ThirdPartyModule::class
ThirdPartyModule::class,
SpaceModule::class
]
)
@SessionScope

View file

@ -90,11 +90,7 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
import org.matrix.android.sdk.internal.session.space.DefaultJoinSpaceTask
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
import org.matrix.android.sdk.internal.session.space.JoinSpaceTask
import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
import retrofit2.Retrofit
@Module

View file

@ -18,12 +18,17 @@ package org.matrix.android.sdk.internal.session.space
import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
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.space.CreateSpaceParams
import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.space.SpaceSummary
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.RoomGetter
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
@ -31,6 +36,7 @@ import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
@ -50,6 +56,8 @@ internal class DefaultSpaceService @Inject constructor(
private val roomGetter: RoomGetter,
private val spaceSummaryDataSource: SpaceSummaryDataSource,
private val peekSpaceTask: PeekSpaceTask,
private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
private val leaveRoomTask: LeaveRoomTask,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
private val taskExecutor: TaskExecutor
) : SpaceService {
@ -76,7 +84,55 @@ internal class DefaultSpaceService @Inject constructor(
return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
}
override suspend fun joinSpace(spaceIdOrAlias: String, reason: String?, viaServers: List<String>, autoJoinChild: List<SpaceService.ChildAutoJoinInfo>): SpaceService.JoinSpaceResult {
override suspend fun querySpaceChildren(spaceId: String): Pair<RoomSummary, List<SpaceChildInfo>> {
return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId)).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
),
second = response.rooms
?.filter { it.roomId != spaceId }
?.map { childSummary ->
val childStateEv = response.events
?.firstOrNull { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
?.content.toModel<SpaceChildContent>()
SpaceChildInfo(
roomSummary = RoomSummary(
roomId = childSummary.roomId,
roomType = childSummary.roomType,
name = childSummary.name ?: "",
displayName = childSummary.name ?: "",
topic = childSummary.topic ?: "",
joinedMembersCount = childSummary.numJoinedMembers,
avatarUrl = childSummary.avatarUrl ?: "",
encryptionEventTs = null,
typingUsers = emptyList(),
isEncrypted = false
),
order = childStateEv?.order,
present = childStateEv?.present ?: false,
autoJoin = childStateEv?.default ?: false,
viaServers = childStateEv?.via ?: emptyList()
)
} ?: emptyList()
)
}
}
override suspend fun joinSpace(spaceIdOrAlias: String,
reason: String?,
viaServers: List<String>,
autoJoinChild: List<SpaceService.ChildAutoJoinInfo>): SpaceService.JoinSpaceResult {
try {
joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers))
// TODO partial success
@ -100,4 +156,8 @@ internal class DefaultSpaceService @Inject constructor(
return SpaceService.JoinSpaceResult.Fail(throwable)
}
}
override suspend fun rejectInvite(spaceId: String, reason: String?) {
leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason))
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 New Vector Ltd
* 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.

View file

@ -0,0 +1,45 @@
/*
* 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 org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
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?
) {
companion object {
fun withId(spaceId: String) = Params(spaceId, 10, 20, null)
}
}
}
internal class DefaultResolveSpaceInfoTask @Inject constructor(
private val spaceApi: SpaceApi
) : ResolveSpaceInfoTask {
override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse {
val body = SpaceSummaryParams(maxRoomPerSpace = params.maxRoomPerSpace, limit = params.limit, batch = params.batchToken ?: "")
return executeRequest<SpacesResponse>(null) {
apiCall = spaceApi.getSpaces(params.spaceId, body)
}
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Path
internal interface SpaceApi {
/**
*
* POST /_matrix/client/r0/rooms/{roomID}/spaces
* {
* "max_rooms_per_space": 5, // The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1.
* "limit": 100, // The maximum number of rooms/subspaces to return, server can override this, default: 100.
* "batch": "opaque_string" // A token to use if this is a subsequent HTTP hit, default: "".
* }
*
* MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/spaces")
fun getSpaces(@Path("roomId") spaceId: String,
@Body params: SpaceSummaryParams
): Call<SpacesResponse>
}

View file

@ -0,0 +1,96 @@
/*
* 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 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 room type, which is m.space for subspaces.
* It can be omitted if there is no room type in which case it should be interpreted as a normal room.
*/
@Json(name = "room_type") val roomType: String? = null,
/**
* Aliases of the room. May be empty.
*/
@Json(name = "aliases")
val aliases: List<String>? = null,
/**
* The canonical alias of the room, if any.
*/
@Json(name = "canonical_alias")
val canonicalAlias: String? = null,
/**
* The name of the room, if any.
*/
@Json(name = "name")
val name: String? = null,
/**
* Required. The number of members joined to the room.
*/
@Json(name = "num_joined_members")
val numJoinedMembers: Int = 0,
/**
* Required. The ID of the room.
*/
@Json(name = "room_id")
val roomId: String,
/**
* The topic of the room, if any.
*/
@Json(name = "topic")
val topic: String? = null,
/**
* Required. Whether the room may be viewed by guest users without joining.
*/
@Json(name = "world_readable")
val worldReadable: Boolean = false,
/**
* Required. Whether guest users may join the room and participate in it. If they can,
* they will be subject to ordinary power level rules like any other user.
*/
@Json(name = "guest_can_join")
val guestCanJoin: Boolean = false,
/**
* The URL for the room's avatar, if one is set.
*/
@Json(name = "avatar_url")
val avatarUrl: String? = null,
/**
* Undocumented item
*/
@Json(name = "m.federate")
val isFederated: Boolean = false
)

View file

@ -0,0 +1,48 @@
/*
* 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 dagger.Binds
import dagger.Module
import dagger.Provides
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
import retrofit2.Retrofit
@Module
internal abstract class SpaceModule {
@Module
companion object {
@Provides
@JvmStatic
@SessionScope
fun providesSpacesAPI(retrofit: Retrofit): SpaceApi {
return retrofit.create(SpaceApi::class.java)
}
}
@Binds
abstract fun bindResolveSpaceTask(task: DefaultResolveSpaceInfoTask): ResolveSpaceInfoTask
@Binds
abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask
@Binds
abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask
}

View file

@ -0,0 +1,30 @@
/*
* 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 = 100,
/** The maximum number of rooms/subspaces to return, server can override this, default: 100 */
@Json(name = "limit") val limit: Int = 100,
/** A token to use if this is a subsequent HTTP hit, default: "".*/
@Json(name = "batch") val batch: String = ""
)

View file

@ -0,0 +1,31 @@
/*
* 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
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
)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 New Vector Ltd
* 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.

View file

@ -17,17 +17,14 @@
package im.vector.app.features.spaces.preview
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import im.vector.app.R
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.genericItemHeader
import im.vector.app.core.utils.TextUtils
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.internal.session.space.peeking.ISpaceChild
import org.matrix.android.sdk.internal.session.space.peeking.SpaceChildPeekResult
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult
import javax.inject.Inject
class SpacePreviewController @Inject constructor(
@ -40,78 +37,100 @@ class SpacePreviewController @Inject constructor(
var interactionListener: InteractionListener? = null
override fun buildModels(data: SpacePreviewState?) {
val result: SpacePeekResult = data?.peekResult?.invoke() ?: return
val result = data?.childInfoList?.invoke() ?: return
when (result) {
is SpacePeekResult.SpacePeekError -> {
genericFooterItem {
id("failed")
// TODO
text("Failed to resolve")
}
val memberCount = data.spaceInfo.invoke()?.memberCount ?: 0
spaceTopSummaryItem {
id("info")
formattedMemberCount(stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount))
topic(data.spaceInfo.invoke()?.topic ?: data.topic ?: "")
}
if (result.isNotEmpty()) {
genericItemHeader {
id("header_rooms")
text(stringProvider.getString(R.string.rooms))
}
is SpacePeekResult.Success -> {
// add summary info
val memberCount = result.summary.roomPeekResult.numJoinedMembers ?: 0
spaceTopSummaryItem {
id("info")
formattedMemberCount(stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount))
topic(result.summary.roomPeekResult.topic ?: "")
}
genericItemHeader {
id("header_rooms")
text(stringProvider.getString(R.string.rooms))
}
buildChildren(result.summary.children, 0)
}
buildChildren(result, 0)
}
}
private fun buildChildren(children: List<ISpaceChild>, depth: Int) {
private fun buildChildren(children: List<ChildInfo>, depth: Int) {
children.forEach { child ->
when (child) {
is SpaceSubChildPeekResult -> {
when (val roomPeekResult = child.roomPeekResult) {
is PeekResult.Success -> {
subSpaceItem {
id(roomPeekResult.roomId)
roomId(roomPeekResult.roomId)
title(roomPeekResult.name)
depth(depth)
avatarUrl(roomPeekResult.avatarUrl)
avatarRenderer(avatarRenderer)
}
buildChildren(child.children, depth + 1)
}
else -> {
// ?? TODO
}
if (child.isSubSpace == true) {
subSpaceItem {
id(child.roomId)
roomId(child.roomId)
title(child.name)
depth(depth)
avatarUrl(child.avatarUrl)
avatarRenderer(avatarRenderer)
}
when (child.children) {
is Loading -> {
loadingItem { id("loading_children_${child.roomId}") }
}
is Success -> {
buildChildren(child.children.invoke(), depth + 1)
}
else -> {
}
}
is SpaceChildPeekResult -> {
// We have to check if the peek result was success
when (val roomPeekResult = child.roomPeekResult) {
is PeekResult.Success -> {
roomChildItem {
id(child.id)
depth(depth)
roomId(roomPeekResult.roomId)
title(roomPeekResult.name ?: "")
topic(roomPeekResult.topic ?: "")
avatarUrl(roomPeekResult.avatarUrl)
memberCount(TextUtils.formatCountToShortDecimal(roomPeekResult.numJoinedMembers ?: 0))
avatarRenderer(avatarRenderer)
}
}
else -> {
// What to do here?
}
}
} else {
roomChildItem {
id(child.roomId)
depth(depth)
roomId(child.roomId)
title(child.name ?: "")
topic(child.topic ?: "")
avatarUrl(child.avatarUrl)
memberCount(TextUtils.formatCountToShortDecimal(child.memberCount ?: 0))
avatarRenderer(avatarRenderer)
}
}
// when (child) {
// is SpaceSubChildPeekResult -> {
// when (val roomPeekResult = child.roomPeekResult) {
// is PeekResult.Success -> {
// subSpaceItem {
// id(roomPeekResult.roomId)
// roomId(roomPeekResult.roomId)
// title(roomPeekResult.name)
// depth(depth)
// avatarUrl(roomPeekResult.avatarUrl)
// avatarRenderer(avatarRenderer)
// }
// buildChildren(child.children, depth + 1)
// }
// else -> {
// // ?? TODO
// }
// }
// }
// is SpaceChildPeekResult -> {
// // We have to check if the peek result was success
// when (val roomPeekResult = child.roomPeekResult) {
// is PeekResult.Success -> {
// roomChildItem {
// id(child.id)
// depth(depth)
// roomId(roomPeekResult.roomId)
// title(roomPeekResult.name ?: "")
// topic(roomPeekResult.topic ?: "")
// avatarUrl(roomPeekResult.avatarUrl)
// memberCount(TextUtils.formatCountToShortDecimal(roomPeekResult.numJoinedMembers ?: 0))
// avatarRenderer(avatarRenderer)
// }
// }
// else -> {
// // What to do here?
// }
// }
// }
// }
}
}
}

View file

@ -40,7 +40,6 @@ import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -99,7 +98,7 @@ class SpacePreviewFragment @Inject constructor(
}
override fun invalidate() = withState(viewModel) {
when (it.peekResult) {
when (it.spaceInfo) {
is Uninitialized,
is Loading -> {
views.spacePreviewPeekingProgress.isVisible = true
@ -141,21 +140,23 @@ class SpacePreviewFragment @Inject constructor(
}
private fun updateToolbar(spacePreviewState: SpacePreviewState) {
when (val preview = spacePreviewState.peekResult.invoke()) {
is SpacePeekResult.Success -> {
val roomPeekResult = preview.summary.roomPeekResult
val mxItem = MatrixItem.RoomItem(roomPeekResult.roomId, roomPeekResult.name, roomPeekResult.avatarUrl)
avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
views.roomPreviewNoPreviewToolbarTitle.text = roomPeekResult.name
}
is SpacePeekResult.SpacePeekError,
null -> {
// what to do here?
val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spacePreviewState.name, spacePreviewState.avatarUrl)
avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
views.roomPreviewNoPreviewToolbarTitle.text = spacePreviewState.name
}
}
// when (val preview = spacePreviewState.peekResult.invoke()) {
// is SpacePeekResult.Success -> {
// val roomPeekResult = preview.summary.roomPeekResult
val spaceName = spacePreviewState.spaceInfo.invoke()?.name ?: spacePreviewState.name ?: ""
val spaceAvatarUrl = spacePreviewState.spaceInfo.invoke()?.avatarUrl ?: spacePreviewState.avatarUrl
val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spaceName, spaceAvatarUrl)
avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
views.roomPreviewNoPreviewToolbarTitle.text = spaceName
// }
// is SpacePeekResult.SpacePeekError,
// null -> {
// // what to do here?
// val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spacePreviewState.name, spacePreviewState.avatarUrl)
// avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
// views.roomPreviewNoPreviewToolbarTitle.text = spacePreviewState.name
// }
// }
}
override fun onStart() {

View file

@ -19,13 +19,25 @@ package im.vector.app.features.spaces.preview
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
data class SpacePreviewState(
val idOrAlias: String,
val name: String? = null,
val topic: String? = null,
val avatarUrl: String? = null,
val peekResult: Async<SpacePeekResult> = Uninitialized
val spaceInfo: Async<ChildInfo> = Uninitialized,
val childInfoList: Async<List<ChildInfo>> = Uninitialized
) : MvRxState {
constructor(args: SpacePreviewArgs) : this(idOrAlias = args.idOrAlias)
}
data class ChildInfo(
val roomId: String,
val avatarUrl: String?,
val name: String?,
val topic: String?,
val memberCount: Int?,
val isSubSpace: Boolean?,
val viaServers: List<String>?,
val children: Async<List<ChildInfo>>
)

View file

@ -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 com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
@ -30,9 +31,12 @@ import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult
import timber.log.Timber
class SpacePreviewViewModel @AssistedInject constructor(
@Assisted private val initialState: SpacePreviewState,
@ -73,31 +77,29 @@ class SpacePreviewViewModel @AssistedInject constructor(
}
}
private fun handleDismissInvite() {
TODO("Not yet implemented")
private fun handleDismissInvite() = withState { state ->
// Here we need to join the space himself as well as the default rooms in that space
// TODO modal loading
viewModelScope.launch(Dispatchers.IO) {
try {
session.spaceService().rejectInvite(initialState.idOrAlias, null)
} catch (failure: Throwable) {
Timber.e(failure, "## Space: Failed to reject invite")
}
}
}
private fun handleAcceptInvite() = withState { state ->
// Here we need to join the space himself as well as the default rooms in that space
val spaceInfo = state.peekResult.invoke() as? SpacePeekResult.Success
// TODO if we have no summary, we cannot find auto join rooms...
// So maybe we should trigger a retry on summary after the join?
val spaceVia = (spaceInfo?.summary?.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList()
val autoJoinChildren = spaceInfo?.summary?.children
?.filter { it.default == true }
?.map {
SpaceService.ChildAutoJoinInfo(
it.id,
// via servers
(it.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList()
)
} ?: emptyList()
val spaceInfo = state.spaceInfo.invoke()
val spaceVia = spaceInfo?.viaServers ?: emptyList()
// trigger modal loading
_viewEvents.post(SpacePreviewViewEvents.StartJoining)
viewModelScope.launch(Dispatchers.IO) {
val joinResult = session.spaceService().joinSpace(spaceInfo?.summary?.idOrAlias ?: initialState.idOrAlias, null, spaceVia, autoJoinChildren)
val joinResult = session.spaceService().joinSpace(initialState.idOrAlias, null, spaceVia, emptyList())
when (joinResult) {
SpaceService.JoinSpaceResult.Success,
is SpaceService.JoinSpaceResult.PartialSuccess -> {
@ -116,20 +118,110 @@ class SpacePreviewViewModel @AssistedInject constructor(
initialized = true
// peek for the room
setState {
copy(peekResult = Loading())
copy(
spaceInfo = Loading(),
childInfoList = Loading()
)
}
viewModelScope.launch(Dispatchers.IO) {
try {
val result = session.spaceService().peekSpace(initialState.idOrAlias)
setState {
copy(peekResult = Success(result))
}
resolveSpaceInfo()
} catch (failure: Throwable) {
setState {
copy(peekResult = Fail(failure))
}
Timber.e(failure, "## Space: Failed to resolve space info. Fallback to picking")
fallBackResolve()
}
}
}
}
private suspend fun resolveSpaceInfo() {
val resolveResult = session.spaceService().querySpaceChildren(initialState.idOrAlias)
setState {
copy(
spaceInfo = Success(
resolveResult.first.let {
ChildInfo(
roomId = it.roomId,
avatarUrl = it.avatarUrl,
name = it.name,
topic = it.topic,
memberCount = it.joinedMembersCount,
isSubSpace = it.roomType == RoomType.SPACE,
children = Uninitialized,
viaServers = null
)
}
),
childInfoList = Success(
resolveResult.second.map {
ChildInfo(
roomId = it.roomSummary?.roomId ?: "",
avatarUrl = it.roomSummary?.avatarUrl,
name = it.roomSummary?.name,
topic = it.roomSummary?.topic,
memberCount = it.roomSummary?.joinedMembersCount,
isSubSpace = it.roomSummary?.roomType == RoomType.SPACE,
children = Uninitialized,
viaServers = null
)
}
)
)
}
}
private suspend fun fallBackResolve() {
try {
val resolveResult: SpacePeekResult = session.spaceService().peekSpace(initialState.idOrAlias)
val spaceInfo = (resolveResult as? SpacePeekResult.Success)?.summary?.roomPeekResult
setState {
copy(
spaceInfo = Success(
ChildInfo(
roomId = spaceInfo?.roomId ?: initialState.idOrAlias,
avatarUrl = spaceInfo?.avatarUrl,
name = spaceInfo?.name,
topic = spaceInfo?.topic,
memberCount = spaceInfo?.numJoinedMembers,
isSubSpace = true,
children = Uninitialized,
viaServers = spaceInfo?.viaServers
)
),
childInfoList = resolveResult.let {
when (it) {
is SpacePeekResult.Success -> {
(resolveResult as SpacePeekResult.Success).summary.children.mapNotNull { spaceChild ->
val roomPeekResult = spaceChild.roomPeekResult
if (roomPeekResult is PeekResult.Success) {
ChildInfo(
roomId = spaceChild.id,
avatarUrl = roomPeekResult.avatarUrl,
name = roomPeekResult.name,
topic = roomPeekResult.topic,
memberCount = roomPeekResult.numJoinedMembers,
isSubSpace = spaceChild is SpaceSubChildPeekResult,
children = Uninitialized,
viaServers = roomPeekResult.viaServers
)
} else {
null
}
}
Success(emptyList())
}
else -> {
Fail(Exception("Failed to get info"))
}
}
})
}
} catch (failure: Throwable) {
setState {
copy(spaceInfo = Fail(failure), childInfoList = Fail(failure))
}
}
}
}