mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Udpate since msc 1772
This commit is contained in:
parent
a8d7c25244
commit
c8916ee83c
33 changed files with 544 additions and 99 deletions
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* 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.session.space
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
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.RoomSummaryQueryParams
|
||||
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.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class SpaceCreationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
fun createSimplePublicSpace() {
|
||||
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
|
||||
val roomName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
val spaceId: String
|
||||
runBlocking {
|
||||
spaceId = session.spaceService().createSpace(roomName, topic, null, true)
|
||||
// wait a bit to let the summry update it self :/
|
||||
delay(400)
|
||||
}
|
||||
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
assertEquals(roomName, syncedSpace?.asRoom()?.roomSummary()?.name, "Room name should be set")
|
||||
assertEquals(topic, syncedSpace?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
|
||||
// assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set")
|
||||
|
||||
assertNotNull(syncedSpace, "Space should be found by Id")
|
||||
val creationEvent = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
|
||||
val createContent = creationEvent?.content.toModel<RoomCreateContent>()
|
||||
assertEquals(RoomType.SPACE, createContent?.type, "Room type should be space")
|
||||
|
||||
var powerLevelsContent: PowerLevelsContent? = null
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel<PowerLevelsContent>()
|
||||
powerLevelsContent = toModel
|
||||
toModel != null
|
||||
}
|
||||
}
|
||||
assertEquals(100, powerLevelsContent?.eventsDefault, "Space-rooms should be created with a power level for events_default of 100")
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJoinSimplePublicSpace() {
|
||||
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||
val bobSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||
|
||||
val roomName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
val spaceId: String
|
||||
runBlocking {
|
||||
spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true)
|
||||
// wait a bit to let the summry update it self :/
|
||||
delay(400)
|
||||
}
|
||||
|
||||
// Try to join from bob, it's a public space no need to invite
|
||||
|
||||
val joinResult: SpaceService.JoinSpaceResult
|
||||
runBlocking {
|
||||
joinResult = bobSession.spaceService().joinSpace(spaceId)
|
||||
}
|
||||
|
||||
assertEquals(SpaceService.JoinSpaceResult.Success, joinResult)
|
||||
|
||||
val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
|
||||
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
|
||||
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimplePublicSpaceWithChildren() {
|
||||
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||
val bobSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||
|
||||
val roomName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
val spaceId: String
|
||||
val firstChild: String
|
||||
val secondChild: String
|
||||
|
||||
spaceId = runBlocking { aliceSession.spaceService().createSpace(roomName, topic, null, true) }
|
||||
val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
|
||||
|
||||
// create a room
|
||||
firstChild = runBlocking {
|
||||
awaitCallback<String> {
|
||||
aliceSession.createRoom(CreateRoomParams().apply {
|
||||
this.name = "FirstRoom"
|
||||
this.topic = "Description of first room"
|
||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
}, it)
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true)
|
||||
}
|
||||
|
||||
secondChild = runBlocking {
|
||||
awaitCallback {
|
||||
aliceSession.createRoom(CreateRoomParams().apply {
|
||||
this.name = "SecondRoom"
|
||||
this.topic = "Description of second room"
|
||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
}, it)
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false)
|
||||
}
|
||||
|
||||
// Try to join from bob, it's a public space no need to invite
|
||||
|
||||
val joinResult = runBlocking {
|
||||
bobSession.spaceService().joinSpace(spaceId)
|
||||
}
|
||||
|
||||
assertEquals(SpaceService.JoinSpaceResult.Success, joinResult)
|
||||
|
||||
val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
|
||||
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
|
||||
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
|
||||
|
||||
// check if bob has joined automatically the first room
|
||||
|
||||
val bobMembershipFirstRoom = bobSession.getRoom(firstChild)?.roomSummary()?.membership
|
||||
assertEquals(Membership.JOIN, bobMembershipFirstRoom, "Bob should have joined this room")
|
||||
RoomSummaryQueryParams.Builder()
|
||||
|
||||
val spaceSummaryBobPov = bobSession.spaceService().getSpaceSummaries(roomSummaryQueryParams {
|
||||
this.roomId = QueryStringValue.Equals(spaceId)
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
}).firstOrNull()
|
||||
|
||||
assertEquals(2, spaceSummaryBobPov?.children?.size ?: -1, "Unexpected number of children")
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
}
|
|
@ -51,9 +51,13 @@ object EventType {
|
|||
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
|
||||
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||
// const val STATE_SPACE_CHILD = "m.space.child"
|
||||
|
||||
// const val STATE_SPACE_CHILD = "m.space.child"
|
||||
const val STATE_SPACE_CHILD = "org.matrix.msc1772.space.child"
|
||||
|
||||
// const val STATE_SPACE_PARENT = "m.space.parent"
|
||||
const val STATE_SPACE_PARENT = "org.matrix.msc1772.space.parent"
|
||||
|
||||
/**
|
||||
* Note that this Event has been deprecated, see
|
||||
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
|
||||
|
@ -76,6 +80,7 @@ object EventType {
|
|||
const val CALL_NEGOTIATE = "m.call.negotiate"
|
||||
const val CALL_REJECT = "m.call.reject"
|
||||
const val CALL_HANGUP = "m.call.hangup"
|
||||
|
||||
// This type is not processed by the client, just sent to the server
|
||||
const val CALL_REPLACES = "m.call.replaces"
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package org.matrix.android.sdk.api.session.room.alias
|
||||
|
||||
sealed class RoomAliasError : Throwable() {
|
||||
object AliasEmpty : RoomAliasError()
|
||||
object AliasIsBlank : RoomAliasError()
|
||||
object AliasNotAvailable : RoomAliasError()
|
||||
object AliasInvalid : RoomAliasError()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.api.session.room.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PowerLevelsContentOverride(
|
||||
/**
|
||||
* The level required to ban a user. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "ban") val ban: Int? = null,
|
||||
/**
|
||||
* The level required to kick a user. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "kick") val kick: Int? = null,
|
||||
/**
|
||||
* The level required to invite a user. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "invite") val invite: Int? = null,
|
||||
/**
|
||||
* The level required to redact an event. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "redact") val redact: Int? = null,
|
||||
/**
|
||||
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
|
||||
*/
|
||||
@Json(name = "events_default") val eventsDefault: Int? = null,
|
||||
/**
|
||||
* The level required to send specific event types. This is a mapping from event type to power level required.
|
||||
*/
|
||||
@Json(name = "events") val events: Map<String, Int>? = null,
|
||||
/**
|
||||
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
|
||||
*/
|
||||
@Json(name = "users_default") val usersDefault: Int? = null,
|
||||
/**
|
||||
* The power levels for specific users. This is a mapping from user_id to power level for that user.
|
||||
*/
|
||||
@Json(name = "users") val users: Map<String, Int>? = null,
|
||||
/**
|
||||
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "state_default") val stateDefault: Int? = null,
|
||||
/**
|
||||
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
|
||||
*/
|
||||
@Json(name = "notifications") val notifications: Map<String, Any>? = null
|
||||
)
|
|
@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session.room.model
|
|||
|
||||
data class SpaceChildInfo(
|
||||
val roomSummary: IRoomSummary?,
|
||||
val present: Boolean,
|
||||
val order: String?,
|
||||
val autoJoin: Boolean,
|
||||
val viaServers: List<String>
|
||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.create
|
|||
|
||||
import android.net.Uri
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContentOverride
|
||||
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.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
|
@ -125,7 +125,7 @@ open class CreateRoomParams {
|
|||
/**
|
||||
* The power level content to override in the default power level event
|
||||
*/
|
||||
var powerLevelContentOverride: PowerLevelsContent? = null
|
||||
var powerLevelContentOverride: PowerLevelsContentOverride? = null
|
||||
|
||||
/**
|
||||
* Mark as a direct message room.
|
||||
|
@ -149,6 +149,6 @@ open class CreateRoomParams {
|
|||
|
||||
companion object {
|
||||
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
|
||||
private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type"
|
||||
private const val CREATION_CONTENT_KEY_ROOM_TYPE = "org.matrix.msc1772.type"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
* @return the power level
|
||||
*/
|
||||
fun getUserPowerLevelValue(userId: String): Int {
|
||||
return powerLevelsContent.users.getOrElse(userId) {
|
||||
powerLevelsContent.usersDefault
|
||||
}
|
||||
return powerLevelsContent.users?.get(userId)
|
||||
?: powerLevelsContent.usersDefault
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,12 +16,20 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.space
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContentOverride
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
|
||||
class CreateSpaceParams : CreateRoomParams() {
|
||||
|
||||
init {
|
||||
// Space-rooms are distinguished from regular messaging rooms by the m.room.type of m.space
|
||||
roomType = RoomType.SPACE
|
||||
|
||||
// Space-rooms should be created with a power level for events_default of 100,
|
||||
// to prevent the rooms accidentally/maliciously clogging up with messages from random members of the space.
|
||||
powerLevelContentOverride = PowerLevelsContentOverride(
|
||||
eventsDefault = 100
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ interface Space {
|
|||
|
||||
fun asRoom() : Room
|
||||
|
||||
suspend fun addRoom(roomId: String)
|
||||
suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean = false)
|
||||
|
||||
suspend fun removeRoom(roomId: String)
|
||||
|
||||
// fun getChildren() : List<IRoomSummary>
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.space
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -31,6 +32,11 @@ interface SpaceService {
|
|||
*/
|
||||
suspend fun createSpace(params: CreateSpaceParams): String
|
||||
|
||||
/**
|
||||
* Just a shortcut for space creation for ease of use
|
||||
*/
|
||||
suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String
|
||||
|
||||
/**
|
||||
* Get a space from a roomId
|
||||
* @param roomId the roomId to look for.
|
||||
|
@ -43,12 +49,12 @@ interface SpaceService {
|
|||
* Use this call get preview of children of this space, particularly useful to get a
|
||||
* preview of rooms that you did not join yet.
|
||||
*/
|
||||
suspend fun peekSpace(spaceId: String) : SpacePeekResult
|
||||
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>>
|
||||
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.
|
||||
|
@ -64,8 +70,9 @@ interface SpaceService {
|
|||
)
|
||||
|
||||
sealed class JoinSpaceResult {
|
||||
object Success: JoinSpaceResult()
|
||||
data class Fail(val error: Throwable?): JoinSpaceResult()
|
||||
object Success : JoinSpaceResult()
|
||||
data class Fail(val error: Throwable) : JoinSpaceResult()
|
||||
|
||||
/** Success fully joined the space, but failed to join all or some of it's rooms */
|
||||
data class PartialSuccess(val failedRooms: Map<String, Throwable>) : JoinSpaceResult()
|
||||
|
||||
|
@ -74,8 +81,7 @@ interface SpaceService {
|
|||
|
||||
suspend fun joinSpace(spaceIdOrAlias: String,
|
||||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList(),
|
||||
autoJoinChild: List<ChildAutoJoinInfo>) : JoinSpaceResult
|
||||
viaServers: List<String> = emptyList()): JoinSpaceResult
|
||||
|
||||
suspend fun rejectInvite(spaceId: String, reason: String?)
|
||||
}
|
||||
|
|
|
@ -31,13 +31,9 @@ import com.squareup.moshi.JsonClass
|
|||
data class SpaceChildContent(
|
||||
/**
|
||||
* Key which gives a list of candidate servers that can be used to join the room
|
||||
* Children where via is not present are ignored.
|
||||
*/
|
||||
@Json(name = "via") val via: List<String>? = null,
|
||||
/**
|
||||
* present: true key is included to distinguish from a deleted state event
|
||||
* Children where present is not present or is not set to true are ignored.
|
||||
*/
|
||||
@Json(name = "present") val present: Boolean? = false,
|
||||
/**
|
||||
* The order key is a string which is used to provide a default ordering of siblings in the room list.
|
||||
* (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last.
|
||||
|
@ -46,8 +42,25 @@ data class SpaceChildContent(
|
|||
*/
|
||||
@Json(name = "order") val order: String? = null,
|
||||
/**
|
||||
* The default flag on a child listing allows a space admin to list the "default" sub-spaces and rooms in that space.
|
||||
* This means that when a user joins the parent space, they will automatically be joined to those default children.
|
||||
* 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 = "default") val default: Boolean? = false
|
||||
)
|
||||
@Json(name = "auto_join") val autoJoin: Boolean? = false
|
||||
) {
|
||||
/**
|
||||
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
|
||||
* or consist of more than 50 characters, are forbidden and should be ignored if received.)
|
||||
*/
|
||||
fun validOrder(): String? {
|
||||
order?.let {
|
||||
if (order.length > 50) return null
|
||||
if (!ORDER_VALID_CHAR_REGEX.matches(it)) return null
|
||||
}
|
||||
return order
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val ORDER_VALID_CHAR_REGEX = "[ -~]+".toRegex()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.api.session.space.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Rooms can claim parents via the m.space.parent state event.
|
||||
* {
|
||||
* "type": "m.space.parent",
|
||||
* "state_key": "!space:example.com",
|
||||
* "content": {
|
||||
* "via": ["example.com"],
|
||||
* "present": true,
|
||||
* "canonical": true,
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SpaceParentContent(
|
||||
/**
|
||||
* Key which gives a list of candidate servers that can be used to join the parent.
|
||||
* Parents where via is not present are ignored.
|
||||
*/
|
||||
@Json(name = "via") val via: List<String>? = null,
|
||||
/**
|
||||
* present: true key is included to distinguish from a deleted state event
|
||||
* Parent where present is not present (sic) or is not set to true are ignored.
|
||||
*/
|
||||
@Json(name = "present") val present: Boolean? = false,
|
||||
/**
|
||||
* Canonical determines whether this is the main parent for the space.
|
||||
* When a user joins a room with a canonical parent, clients may switch to view the room
|
||||
* in the context of that space, peeking into it in order to find other rooms and group them together.
|
||||
* In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
|
||||
* if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
|
||||
*/
|
||||
@Json(name = "canonical") val canonical: Boolean? = false
|
||||
)
|
|
@ -209,8 +209,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
|
||||
val spaceChildInfoSchema = realm.schema.create("SpaceChildInfoEntity")
|
||||
?.addField(SpaceChildInfoEntityFields.ORDER, String::class.java)
|
||||
?.addField(SpaceChildInfoEntityFields.PRESENT, Boolean::class.java)
|
||||
?.setNullable(SpaceChildInfoEntityFields.PRESENT, true)
|
||||
?.addRealmListField(SpaceChildInfoEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||
?.addRealmObjectField(SpaceChildInfoEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ internal class SpaceSummaryMapper @Inject constructor(private val roomSummaryMap
|
|||
SpaceChildInfo(
|
||||
roomSummary = it.roomSummaryEntity?.let { rs -> roomSummaryMapper.map(rs) },
|
||||
autoJoin = it.autoJoin ?: false,
|
||||
present = it.present ?: false,
|
||||
viaServers = it.viaServers.map { it },
|
||||
order = it.order
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
@ -24,8 +24,6 @@ import io.realm.RealmObject
|
|||
*/
|
||||
internal open class SpaceChildInfoEntity(
|
||||
var viaServers: RealmList<String> = RealmList(),
|
||||
// it's an active child of the space if and only if present is not null and true
|
||||
var present: Boolean? = null,
|
||||
// Use for alphabetic ordering of this child
|
||||
var order: String? = null,
|
||||
// If true, this child should be join when parent is joined
|
||||
|
|
|
@ -36,7 +36,11 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
|
|||
@Throws(RoomAliasError::class)
|
||||
suspend fun check(aliasLocalPart: String?) {
|
||||
if (aliasLocalPart.isNullOrEmpty()) {
|
||||
throw RoomAliasError.AliasEmpty
|
||||
// don't check empty or not provided alias
|
||||
return
|
||||
}
|
||||
if (aliasLocalPart.isBlank()) {
|
||||
throw RoomAliasError.AliasIsBlank
|
||||
}
|
||||
// Check alias availability
|
||||
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.room.create
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContentOverride
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
|
||||
|
@ -111,5 +111,5 @@ internal data class CreateRoomBody(
|
|||
* The power level content to override in the default power level event
|
||||
*/
|
||||
@Json(name = "power_level_content_override")
|
||||
val powerLevelContentOverride: PowerLevelsContent?
|
||||
val powerLevelContentOverride: PowerLevelsContentOverride?
|
||||
)
|
||||
|
|
|
@ -41,28 +41,31 @@ internal class RoomRelationshipHelper(private val realm: Realm,
|
|||
|
||||
data class SpaceChildInfo(
|
||||
val roomId: String,
|
||||
val present: Boolean,
|
||||
val order: String?,
|
||||
val autoJoin: Boolean,
|
||||
val viaServers: List<String>
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the ordered list of valid child description.
|
||||
*/
|
||||
fun getDirectChildrenDescriptions(): List<SpaceChildInfo> {
|
||||
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
|
||||
.findAll()
|
||||
// .filter { ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.present == true }
|
||||
.mapNotNull {
|
||||
// ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()
|
||||
ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.let { scc ->
|
||||
Timber.d("## Space child desc state event $scc")
|
||||
SpaceChildInfo(
|
||||
roomId = it.stateKey,
|
||||
present = scc.present ?: false,
|
||||
order = scc.order,
|
||||
autoJoin = scc.default ?: false,
|
||||
viaServers = scc.via ?: emptyList()
|
||||
)
|
||||
// Children where via is not present are ignored.
|
||||
scc.via?.let { via ->
|
||||
SpaceChildInfo(
|
||||
roomId = it.stateKey,
|
||||
order = scc.validOrder(),
|
||||
autoJoin = scc.autoJoin ?: false,
|
||||
viaServers = via
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortedBy { it.order }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
|
|||
eventsDefault = content.eventsDefault,
|
||||
events = content.events,
|
||||
usersDefault = content.usersDefault,
|
||||
users = content.users,
|
||||
users = content.users ?: emptyMap(),
|
||||
stateDefault = content.stateDefault,
|
||||
notifications = content.notifications.mapValues { content.notificationLevel(it.key) }
|
||||
)
|
||||
|
|
|
@ -99,6 +99,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
|
||||
val roomType = ContentMapper.map(roomCreateEvent?.content).toModel<RoomCreateContent>()?.type
|
||||
roomSummaryEntity.roomType = roomType
|
||||
Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
|
||||
|
||||
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
|
||||
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
|
@ -176,7 +177,6 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
realm.createObject<SpaceChildInfoEntity>().apply {
|
||||
this.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, it.roomId)
|
||||
this.order = it.order
|
||||
this.present = it.present
|
||||
this.autoJoin = it.autoJoin
|
||||
}.also {
|
||||
Timber.v("## Space: Updating summary for room $roomId with children $it")
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.space
|
||||
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
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
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.space.Space
|
||||
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||
|
@ -28,11 +30,34 @@ class DefaultSpace(private val room: Room) : Space {
|
|||
return room
|
||||
}
|
||||
|
||||
override suspend fun addRoom(roomId: String) {
|
||||
override suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean) {
|
||||
asRoom().sendStateEvent(
|
||||
eventType = EventType.STATE_SPACE_CHILD,
|
||||
stateKey = roomId,
|
||||
body = SpaceChildContent(present = true).toContent()
|
||||
body = SpaceChildContent(
|
||||
via = viaServers,
|
||||
autoJoin = autoJoin,
|
||||
order = order
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun removeRoom(roomId: String) {
|
||||
val existing = asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||
.firstOrNull()
|
||||
?.content.toModel<SpaceChildContent>()
|
||||
?: // should we throw here?
|
||||
return
|
||||
|
||||
// edit state event and set via to null
|
||||
asRoom().sendStateEvent(
|
||||
eventType = EventType.STATE_SPACE_CHILD,
|
||||
stateKey = roomId,
|
||||
body = SpaceChildContent(
|
||||
order = existing.order,
|
||||
via = null,
|
||||
autoJoin = existing.autoJoin
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.space
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
@ -23,6 +24,7 @@ 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.room.model.create.CreateRoomPreset
|
||||
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
|
||||
|
@ -66,6 +68,15 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
return createRoomTask.execute(params)
|
||||
}
|
||||
|
||||
override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
|
||||
return createSpace(CreateSpaceParams().apply {
|
||||
this.name = name
|
||||
this.topic = topic
|
||||
this.preset = if (isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT
|
||||
this.avatarUri = avatarUri
|
||||
})
|
||||
}
|
||||
|
||||
override fun getSpace(spaceId: String): Space? {
|
||||
return roomGetter.getRoom(spaceId)
|
||||
?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE }
|
||||
|
@ -120,8 +131,7 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
isEncrypted = false
|
||||
),
|
||||
order = childStateEv?.order,
|
||||
present = childStateEv?.present ?: false,
|
||||
autoJoin = childStateEv?.default ?: false,
|
||||
autoJoin = childStateEv?.autoJoin ?: false,
|
||||
viaServers = childStateEv?.via ?: emptyList()
|
||||
)
|
||||
} ?: emptyList()
|
||||
|
@ -131,30 +141,8 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
|
||||
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
|
||||
return SpaceService.JoinSpaceResult.Success
|
||||
// val childJoinFailures = mutableMapOf<String, Throwable>()
|
||||
// autoJoinChild.forEach { info ->
|
||||
// // TODO what if the child is it self a subspace with some default children?
|
||||
// try {
|
||||
// joinRoomTask.execute(JoinRoomTask.Params(info.roomIdOrAlias, null, info.viaServers))
|
||||
// } catch (failure: Throwable) {
|
||||
// // TODO, i could already be a member of this room, handle that as it should not be an error in this context
|
||||
// childJoinFailures[info.roomIdOrAlias] = failure
|
||||
// }
|
||||
// }
|
||||
// return if (childJoinFailures.isEmpty()) {
|
||||
// SpaceService.JoinSpaceResult.Success
|
||||
// } else {
|
||||
// SpaceService.JoinSpaceResult.PartialSuccess(childJoinFailures)
|
||||
// }
|
||||
} catch (throwable: Throwable) {
|
||||
return SpaceService.JoinSpaceResult.Fail(throwable)
|
||||
}
|
||||
viaServers: List<String>): SpaceService.JoinSpaceResult {
|
||||
return joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers))
|
||||
}
|
||||
|
||||
override suspend fun rejectInvite(spaceId: String, reason: String?) {
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.space
|
|||
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
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.SpaceService
|
||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields
|
||||
|
@ -32,7 +32,7 @@ import timber.log.Timber
|
|||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface JoinSpaceTask : Task<JoinSpaceTask.Params, Unit> {
|
||||
internal interface JoinSpaceTask : Task<JoinSpaceTask.Params, SpaceService.JoinSpaceResult> {
|
||||
data class Params(
|
||||
val roomIdOrAlias: String,
|
||||
val reason: String?,
|
||||
|
@ -48,13 +48,17 @@ internal class DefaultJoinSpaceTask @Inject constructor(
|
|||
private val spaceSummaryDataSource: SpaceSummaryDataSource
|
||||
) : JoinSpaceTask {
|
||||
|
||||
override suspend fun execute(params: JoinSpaceTask.Params) {
|
||||
override suspend fun execute(params: JoinSpaceTask.Params): SpaceService.JoinSpaceResult {
|
||||
Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...")
|
||||
joinRoomTask.execute(JoinRoomTask.Params(
|
||||
params.roomIdOrAlias,
|
||||
params.reason,
|
||||
params.viaServers
|
||||
))
|
||||
try {
|
||||
joinRoomTask.execute(JoinRoomTask.Params(
|
||||
params.roomIdOrAlias,
|
||||
params.reason,
|
||||
params.viaServers
|
||||
))
|
||||
} catch (failure: Throwable) {
|
||||
return SpaceService.JoinSpaceResult.Fail(failure)
|
||||
}
|
||||
Timber.v("## Space: < Joining root space done for ${params.roomIdOrAlias}")
|
||||
// we want to wait for sync result to check for auto join rooms
|
||||
|
||||
|
@ -73,19 +77,32 @@ internal class DefaultJoinSpaceTask @Inject constructor(
|
|||
}
|
||||
} catch (exception: TimeoutCancellationException) {
|
||||
Timber.w("## Space: > Error created with timeout")
|
||||
throw CreateRoomFailure.CreatedWithTimeout
|
||||
return SpaceService.JoinSpaceResult.PartialSuccess(emptyMap())
|
||||
}
|
||||
|
||||
val errors = HashMap<String, Throwable>()
|
||||
Timber.v("## Space: > Sync done ...")
|
||||
// after that i should have the children (? do i nead to paginate to get state)
|
||||
// after that i should have the children (? do I need to paginate to get state)
|
||||
val summary = spaceSummaryDataSource.getSpaceSummary(params.roomIdOrAlias)
|
||||
Timber.v("## Space: Found space summary Name:[${summary?.roomSummary?.name}] children: ${summary?.children?.size}")
|
||||
summary?.children?.forEach {
|
||||
val childRoomSummary = it.roomSummary ?: return@forEach
|
||||
Timber.v("## Space: Processing child :[${childRoomSummary.roomId}] present: ${it.present} autoJoin:${it.autoJoin}")
|
||||
if (it.present && it.autoJoin) {
|
||||
Timber.v("## Space: Processing child :[${childRoomSummary.roomId}] autoJoin:${it.autoJoin}")
|
||||
if (it.autoJoin) {
|
||||
// I should try to join as well
|
||||
if (childRoomSummary.roomType == RoomType.SPACE) {
|
||||
// recursively join auto-joined child of this space?
|
||||
when (val subspaceJoinResult = this.execute(JoinSpaceTask.Params(it.roomSummary.roomId, null, it.viaServers))) {
|
||||
SpaceService.JoinSpaceResult.Success -> {
|
||||
// nop
|
||||
}
|
||||
is SpaceService.JoinSpaceResult.Fail -> {
|
||||
errors[it.roomSummary.roomId] = subspaceJoinResult.error
|
||||
}
|
||||
is SpaceService.JoinSpaceResult.PartialSuccess -> {
|
||||
errors.putAll(subspaceJoinResult.failedRooms)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Timber.v("## Space: Joining room child ${childRoomSummary.roomId}")
|
||||
|
@ -95,12 +112,18 @@ internal class DefaultJoinSpaceTask @Inject constructor(
|
|||
viaServers = it.viaServers
|
||||
))
|
||||
} catch (failure: Throwable) {
|
||||
// todo keep track for partial success
|
||||
errors[it.roomSummary.roomId] = failure
|
||||
Timber.e("## Space: Failed to join room child ${childRoomSummary.roomId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
SpaceService.JoinSpaceResult.Success
|
||||
} else {
|
||||
SpaceService.JoinSpaceResult.PartialSuccess(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -75,8 +75,8 @@ internal class DefaultPeekSpaceTask @Inject constructor(
|
|||
val childRoomsIds = stateEvents
|
||||
.filter {
|
||||
it.type == EventType.STATE_SPACE_CHILD && !it.stateKey.isNullOrEmpty()
|
||||
// Children where present is not present or is not set to true are ignored.
|
||||
&& it.content?.toModel<SpaceChildContent>()?.present == true
|
||||
// Children where via is not present are ignored.
|
||||
&& it.content?.toModel<SpaceChildContent>()?.via != null
|
||||
}
|
||||
.map { it.stateKey to it.content?.toModel<SpaceChildContent>() }
|
||||
|
||||
|
@ -101,7 +101,7 @@ internal class DefaultPeekSpaceTask @Inject constructor(
|
|||
// can't peek :/
|
||||
spaceChildResults.add(
|
||||
SpaceChildPeekResult(
|
||||
childId, childPeek, entry.second?.default, entry.second?.order
|
||||
childId, childPeek, entry.second?.autoJoin, entry.second?.order
|
||||
)
|
||||
)
|
||||
// continue to next child
|
||||
|
@ -114,7 +114,7 @@ internal class DefaultPeekSpaceTask @Inject constructor(
|
|||
SpaceSubChildPeekResult(
|
||||
childId,
|
||||
childPeek,
|
||||
entry.second?.default,
|
||||
entry.second?.autoJoin,
|
||||
entry.second?.order,
|
||||
peekChildren(childStateEvents, depth + 1, maxDepth)
|
||||
)
|
||||
|
@ -125,7 +125,7 @@ internal class DefaultPeekSpaceTask @Inject constructor(
|
|||
Timber.v("## SPACE_PEEK: room child $entry")
|
||||
spaceChildResults.add(
|
||||
SpaceChildPeekResult(
|
||||
childId, childPeek, entry.second?.default, entry.second?.order
|
||||
childId, childPeek, entry.second?.autoJoin, entry.second?.order
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -48,7 +48,9 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||
CONFETTI("/confetti", "<message>", R.string.command_confetti),
|
||||
SNOW("/snow", "<message>", R.string.command_snow),
|
||||
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space),
|
||||
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space);
|
||||
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space),
|
||||
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space),
|
||||
LEAVE_ROOM("/leave", "<roomId?>", R.string.command_description_leave_room);
|
||||
|
||||
val length
|
||||
get() = command.length + 1
|
||||
|
|
|
@ -318,6 +318,18 @@ object CommandParser {
|
|||
rawCommand
|
||||
)
|
||||
}
|
||||
Command.JOIN_SPACE.command -> {
|
||||
val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim()
|
||||
ParsedCommand.JoinSpace(
|
||||
spaceIdOrAlias
|
||||
)
|
||||
}
|
||||
Command.LEAVE_ROOM.command -> {
|
||||
val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim()
|
||||
ParsedCommand.LeaveRoom(
|
||||
spaceIdOrAlias
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Unknown command
|
||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||
|
|
|
@ -59,4 +59,6 @@ sealed class ParsedCommand {
|
|||
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
||||
class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand()
|
||||
class AddToSpace(val spaceId: String) : ParsedCommand()
|
||||
class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand()
|
||||
class LeaveRoom(val roomId: String) : ParsedCommand()
|
||||
}
|
||||
|
|
|
@ -831,7 +831,13 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
invitedUserIds.addAll(slashCommandResult.invitees)
|
||||
}
|
||||
val spaceId = session.spaceService().createSpace(params)
|
||||
session.spaceService().getSpace(spaceId)?.addRoom(state.roomId)
|
||||
session.spaceService().getSpace(spaceId)
|
||||
?.addChildren(
|
||||
state.roomId,
|
||||
listOf(session.sessionParams.homeServerHost ?: ""),
|
||||
null,
|
||||
true
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
|
@ -842,7 +848,37 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is ParsedCommand.AddToSpace -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
session.spaceService().getSpace(slashCommandResult.spaceId)?.addRoom(room.roomId)
|
||||
session.spaceService().getSpace(slashCommandResult.spaceId)
|
||||
?.addChildren(
|
||||
room.roomId,
|
||||
listOf(session.sessionParams.homeServerHost ?: ""),
|
||||
null,
|
||||
false
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
}
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.JoinSpace -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
}
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.LeaveRoom -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
awaitCallback {
|
||||
session.getRoom(slashCommandResult.roomId)?.leave(null, it)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ import com.airbnb.mvrx.Success
|
|||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
|
@ -37,6 +37,8 @@ import kotlinx.coroutines.launch
|
|||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||
|
@ -182,6 +184,15 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
return@withState
|
||||
}
|
||||
|
||||
if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public
|
||||
&& state.roomVisibilityType.aliasLocalPart.isBlank()) {
|
||||
// we require an alias for public rooms
|
||||
setState {
|
||||
copy(asyncCreateRoomRequest = Fail(CreateRoomFailure.AliasError(RoomAliasError.AliasIsBlank)))
|
||||
}
|
||||
return@withState
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(asyncCreateRoomRequest = Loading())
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class RoomAliasErrorFormatter @Inject constructor(
|
|||
) {
|
||||
fun format(roomAliasError: RoomAliasError?): String? {
|
||||
return when (roomAliasError) {
|
||||
is RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty
|
||||
is RoomAliasError.AliasIsBlank -> R.string.create_room_alias_empty
|
||||
is RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use
|
||||
is RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid
|
||||
else -> null
|
||||
|
|
|
@ -100,7 +100,7 @@ class SpacePreviewViewModel @AssistedInject constructor(
|
|||
// trigger modal loading
|
||||
_viewEvents.post(SpacePreviewViewEvents.StartJoining)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val joinResult = session.spaceService().joinSpace(initialState.idOrAlias, null, spaceVia, emptyList())
|
||||
val joinResult = session.spaceService().joinSpace(initialState.idOrAlias, null, spaceVia)
|
||||
when (joinResult) {
|
||||
SpaceService.JoinSpaceResult.Success,
|
||||
is SpaceService.JoinSpaceResult.PartialSuccess -> {
|
||||
|
|
|
@ -3248,6 +3248,9 @@
|
|||
<string name="dev_tools_event_content_hint">Event content</string>
|
||||
|
||||
<string name="command_description_create_space">Create a community</string>
|
||||
<string name="command_description_create_space">Create a Spcae</string>
|
||||
<string name="command_description_join_space">Join the Space with the given id</string>
|
||||
<string name="command_description_leave_room">Leave room with given id (or current room if null)</string>
|
||||
|
||||
<string name="event_status_a11y_sending">Sending</string>
|
||||
<string name="event_status_a11y_sent">Sent</string>
|
||||
|
|
Loading…
Reference in a new issue