mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Merge pull request #3663 from vector-im/feature/bca/room_caps_restriced
Feature/bca/room caps restricted
This commit is contained in:
commit
60caac4214
45 changed files with 2104 additions and 151 deletions
1
changelog.d/3509.feature
Normal file
1
changelog.d/3509.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Spaces - Support Restricted Room via room capabilities API
|
1
changelog.d/3665.feature
Normal file
1
changelog.d/3665.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Spaces | Support restricted room access in room settings
|
|
@ -40,7 +40,63 @@ data class HomeServerCapabilities(
|
||||||
*/
|
*/
|
||||||
val roomVersions: RoomVersionCapabilities? = null
|
val roomVersions: RoomVersionCapabilities? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
enum class RoomCapabilitySupport {
|
||||||
|
SUPPORTED,
|
||||||
|
SUPPORTED_UNSTABLE,
|
||||||
|
UNSUPPORTED,
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a feature is supported by the homeserver.
|
||||||
|
* @return
|
||||||
|
* UNKNOWN if the server does not implement room caps
|
||||||
|
* UNSUPPORTED if this feature is not supported
|
||||||
|
* SUPPORTED if this feature is supported by a stable version
|
||||||
|
* SUPPORTED_UNSTABLE if this feature is supported by an unstable version
|
||||||
|
* (unstable version should only be used for dev/experimental purpose)
|
||||||
|
*/
|
||||||
|
fun isFeatureSupported(feature: String): RoomCapabilitySupport {
|
||||||
|
if (roomVersions?.capabilities == null) return RoomCapabilitySupport.UNKNOWN
|
||||||
|
val info = roomVersions.capabilities[feature] ?: return RoomCapabilitySupport.UNSUPPORTED
|
||||||
|
|
||||||
|
val preferred = info.preferred ?: info.support.lastOrNull()
|
||||||
|
val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred }
|
||||||
|
|
||||||
|
return when {
|
||||||
|
versionCap == null -> {
|
||||||
|
RoomCapabilitySupport.UNKNOWN
|
||||||
|
}
|
||||||
|
versionCap.status == RoomVersionStatus.STABLE -> {
|
||||||
|
RoomCapabilitySupport.SUPPORTED
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
RoomCapabilitySupport.SUPPORTED_UNSTABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean {
|
||||||
|
if (roomVersions?.capabilities == null) return false
|
||||||
|
val info = roomVersions.capabilities[feature] ?: return false
|
||||||
|
|
||||||
|
return info.preferred == byRoomVersion || info.support.contains(byRoomVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this method to know if you should force a version when creating
|
||||||
|
* a room that requires this feature.
|
||||||
|
* You can also use #isFeatureSupported prior to this call to check if the
|
||||||
|
* feature is supported and report some feedback to user.
|
||||||
|
*/
|
||||||
|
fun versionOverrideForFeature(feature: String) : String? {
|
||||||
|
val cap = roomVersions?.capabilities?.get(feature)
|
||||||
|
return cap?.preferred ?: cap?.support?.lastOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||||
|
const val ROOM_CAP_KNOCK = "knock"
|
||||||
|
const val ROOM_CAP_RESTRICTED = "restricted"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,9 @@ package org.matrix.android.sdk.api.session.homeserver
|
||||||
|
|
||||||
data class RoomVersionCapabilities(
|
data class RoomVersionCapabilities(
|
||||||
val defaultRoomVersion: String,
|
val defaultRoomVersion: String,
|
||||||
val supportedVersion: List<RoomVersionInfo>
|
val supportedVersion: List<RoomVersionInfo>,
|
||||||
|
// Keys are capabilities defined per spec, as for now knock or restricted
|
||||||
|
val capabilities: Map<String, RoomCapabilitySupport>?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RoomVersionInfo(
|
data class RoomVersionInfo(
|
||||||
|
@ -26,6 +28,11 @@ data class RoomVersionInfo(
|
||||||
val status: RoomVersionStatus
|
val status: RoomVersionStatus
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class RoomCapabilitySupport(
|
||||||
|
val preferred: String?,
|
||||||
|
val support: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
enum class RoomVersionStatus {
|
enum class RoomVersionStatus {
|
||||||
STABLE,
|
STABLE,
|
||||||
UNSTABLE
|
UNSTABLE
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
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.RoomDirectoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
|
||||||
open class CreateRoomParams {
|
open class CreateRoomParams {
|
||||||
|
@ -162,7 +161,7 @@ open class CreateRoomParams {
|
||||||
|
|
||||||
var roomVersion: String? = null
|
var roomVersion: String? = null
|
||||||
|
|
||||||
var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null
|
var featurePreset: RoomFeaturePreset? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
|
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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.create
|
||||||
|
|
||||||
|
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.homeserver.HomeServerCapabilities
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
|
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.RoomJoinRulesAllowEntry
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||||
|
|
||||||
|
interface RoomFeaturePreset {
|
||||||
|
|
||||||
|
fun updateRoomParams(params: CreateRoomParams)
|
||||||
|
|
||||||
|
fun setupInitialStates(): List<Event>?
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset {
|
||||||
|
|
||||||
|
override fun updateRoomParams(params: CreateRoomParams) {
|
||||||
|
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
|
||||||
|
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
|
||||||
|
params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupInitialStates(): List<Event>? {
|
||||||
|
return listOf(
|
||||||
|
Event(
|
||||||
|
type = EventType.STATE_ROOM_JOIN_RULES,
|
||||||
|
stateKey = "",
|
||||||
|
content = RoomJoinRulesContent(
|
||||||
|
_joinRules = RoomJoinRules.RESTRICTED.value,
|
||||||
|
allowList = restrictedList
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
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.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ interface StateService {
|
||||||
/**
|
/**
|
||||||
* Update the join rule and/or the guest access
|
* Update the join rule and/or the guest access
|
||||||
*/
|
*/
|
||||||
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?)
|
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>? = null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the avatar of the room
|
* Update the avatar of the room
|
||||||
|
@ -91,4 +92,8 @@ interface StateService {
|
||||||
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
|
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
|
||||||
*/
|
*/
|
||||||
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
||||||
|
|
||||||
|
suspend fun setJoinRulePublic()
|
||||||
|
suspend fun setJoinRuleInviteOnly()
|
||||||
|
suspend fun setJoinRuleRestricted(allowList: List<String>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.mapper
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
|
import org.matrix.android.sdk.api.session.homeserver.RoomCapabilitySupport
|
||||||
import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
|
import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
|
||||||
import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
|
import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
|
||||||
import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
|
import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
|
||||||
|
@ -45,19 +46,28 @@ internal object HomeServerCapabilitiesMapper {
|
||||||
roomVersionsJson ?: return null
|
roomVersionsJson ?: return null
|
||||||
|
|
||||||
return tryOrNull {
|
return tryOrNull {
|
||||||
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let {
|
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { roomVersions ->
|
||||||
RoomVersionCapabilities(
|
RoomVersionCapabilities(
|
||||||
defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
|
defaultRoomVersion = roomVersions.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
|
||||||
supportedVersion = it.available.entries.map { entry ->
|
supportedVersion = roomVersions.available?.entries?.map { entry ->
|
||||||
RoomVersionInfo(
|
RoomVersionInfo(entry.key, RoomVersionStatus.STABLE
|
||||||
version = entry.key,
|
.takeIf { entry.value == "stable" }
|
||||||
status = if (entry.value == "stable") {
|
?: RoomVersionStatus.UNSTABLE)
|
||||||
RoomVersionStatus.STABLE
|
}.orEmpty(),
|
||||||
} else {
|
capabilities = roomVersions.roomCapabilities?.entries?.mapNotNull { entry ->
|
||||||
RoomVersionStatus.UNSTABLE
|
(entry.value as? Map<*, *>)?.let {
|
||||||
}
|
val preferred = it["preferred"] as? String ?: return@mapNotNull null
|
||||||
)
|
val support = (it["support"] as? List<*>)?.filterIsInstance<String>()
|
||||||
|
entry.key to RoomCapabilitySupport(preferred, support.orEmpty())
|
||||||
}
|
}
|
||||||
|
}?.toMap()
|
||||||
|
// Just for debug purpose
|
||||||
|
// ?: mapOf(
|
||||||
|
// HomeServerCapabilities.ROOM_CAP_RESTRICTED to RoomCapabilitySupport(
|
||||||
|
// preferred = null,
|
||||||
|
// support = listOf("org.matrix.msc3083")
|
||||||
|
// )
|
||||||
|
// )
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,22 @@ internal data class RoomVersions(
|
||||||
* Required. A detailed description of the room versions the server supports.
|
* Required. A detailed description of the room versions the server supports.
|
||||||
*/
|
*/
|
||||||
@Json(name = "available")
|
@Json(name = "available")
|
||||||
val available: JsonDict
|
val available: JsonDict? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "room_capabilities": {
|
||||||
|
* "knock" : {
|
||||||
|
* "preferred": "7",
|
||||||
|
* "support" : ["7"]
|
||||||
|
* },
|
||||||
|
* "restricted" : {
|
||||||
|
* "preferred": "9",
|
||||||
|
* "support" : ["8", "9"]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@Json(name = "room_capabilities")
|
||||||
|
val roomCapabilities: JsonDict? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// The spec says: If not present, the client should assume that password changes are possible via the API
|
// The spec says: If not present, the client should assume that password changes are possible via the API
|
||||||
|
|
|
@ -17,17 +17,24 @@
|
||||||
package org.matrix.android.sdk.internal.session.permalinks
|
package org.matrix.android.sdk.internal.session.permalinks
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
|
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
|
||||||
|
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.members.roomMemberQueryParams
|
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
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.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
||||||
|
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
internal class ViaParameterFinder @Inject constructor(
|
internal class ViaParameterFinder @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val roomGetterProvider: Provider<RoomGetter>
|
private val roomGetterProvider: Provider<RoomGetter>,
|
||||||
|
private val stateEventDataSource: StateEventDataSource
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun computeViaParams(roomId: String, max: Int): List<String> {
|
fun computeViaParams(roomId: String, max: Int): List<String> {
|
||||||
|
@ -70,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor(
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
|
||||||
|
val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
|
||||||
|
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
|
||||||
|
?.map { it.userId }
|
||||||
|
?.filter { userCanInvite(userId, roomId) }
|
||||||
|
.orEmpty()
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
return userThatCanInvite.map { it.getDomain() }
|
||||||
|
.groupBy { it }
|
||||||
|
.mapValues { it.value.size }
|
||||||
|
.toMutableMap()
|
||||||
|
.let { map -> map.keys.sortedByDescending { map[it] } }
|
||||||
|
.take(max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun userCanInvite(userId: String, roomId: String): Boolean {
|
||||||
|
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||||
|
?.content?.toModel<PowerLevelsContent>()
|
||||||
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
|
||||||
|
return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,10 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.create
|
package org.matrix.android.sdk.internal.session.room.create
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
|
||||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||||
import org.matrix.android.sdk.api.session.identity.toMedium
|
import org.matrix.android.sdk.api.session.identity.toMedium
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
|
||||||
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.RoomJoinRulesContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.util.MimeTypes
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
|
@ -45,7 +39,6 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal class CreateRoomBodyBuilder @Inject constructor(
|
internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
|
private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
|
||||||
private val crossSigningService: CrossSigningService,
|
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val identityStore: IdentityStore,
|
private val identityStore: IdentityStore,
|
||||||
private val fileUploader: FileUploader,
|
private val fileUploader: FileUploader,
|
||||||
|
@ -76,19 +69,18 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.joinRuleRestricted != null) {
|
params.featurePreset?.updateRoomParams(params)
|
||||||
params.roomVersion = "org.matrix.msc3083"
|
|
||||||
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
|
val initialStates = (
|
||||||
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
|
listOfNotNull(
|
||||||
}
|
|
||||||
val initialStates = (listOfNotNull(
|
|
||||||
buildEncryptionWithAlgorithmEvent(params),
|
buildEncryptionWithAlgorithmEvent(params),
|
||||||
buildHistoryVisibilityEvent(params),
|
buildHistoryVisibilityEvent(params),
|
||||||
buildAvatarEvent(params),
|
buildAvatarEvent(params),
|
||||||
buildGuestAccess(params),
|
buildGuestAccess(params)
|
||||||
buildJoinRulesRestricted(params)
|
)
|
||||||
|
+ params.featurePreset?.setupInitialStates().orEmpty()
|
||||||
|
+ buildCustomInitialStates(params)
|
||||||
)
|
)
|
||||||
+ buildCustomInitialStates(params))
|
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
return CreateRoomBody(
|
return CreateRoomBody(
|
||||||
|
@ -158,20 +150,6 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? {
|
|
||||||
return params.joinRuleRestricted
|
|
||||||
?.let { allowList ->
|
|
||||||
Event(
|
|
||||||
type = EventType.STATE_ROOM_JOIN_RULES,
|
|
||||||
stateKey = "",
|
|
||||||
content = RoomJoinRulesContent(
|
|
||||||
_joinRules = RoomJoinRules.RESTRICTED.value,
|
|
||||||
allowList = allowList
|
|
||||||
).toContent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the crypto algorithm to the room creation parameters.
|
* Add the crypto algorithm to the room creation parameters.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room.state
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
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.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
@ -29,17 +29,20 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
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.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||||
import org.matrix.android.sdk.api.session.room.state.StateService
|
import org.matrix.android.sdk.api.session.room.state.StateService
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.MimeTypes
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||||
import java.lang.UnsupportedOperationException
|
import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
|
||||||
|
|
||||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val stateEventDataSource: StateEventDataSource,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
private val sendStateTask: SendStateTask,
|
private val sendStateTask: SendStateTask,
|
||||||
private val fileUploader: FileUploader
|
private val fileUploader: FileUploader,
|
||||||
|
private val viaParameterFinder: ViaParameterFinder
|
||||||
) : StateService {
|
) : StateService {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -126,12 +129,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
|
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>?) {
|
||||||
if (joinRules != null) {
|
if (joinRules != null) {
|
||||||
if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
|
val body = if (joinRules == RoomJoinRules.RESTRICTED) {
|
||||||
|
RoomJoinRulesContent(
|
||||||
|
_joinRules = RoomJoinRules.RESTRICTED.value,
|
||||||
|
allowList = allowList
|
||||||
|
).toContent()
|
||||||
|
} else {
|
||||||
|
mapOf("join_rule" to joinRules)
|
||||||
|
}
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
||||||
body = mapOf("join_rule" to joinRules),
|
body = body,
|
||||||
stateKey = null
|
stateKey = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -160,4 +170,20 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
stateKey = null
|
stateKey = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun setJoinRulePublic() {
|
||||||
|
updateJoinRule(RoomJoinRules.PUBLIC, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setJoinRuleInviteOnly() {
|
||||||
|
updateJoinRule(RoomJoinRules.INVITE, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setJoinRuleRestricted(allowList: List<String>) {
|
||||||
|
// we need to compute correct via parameters and check if PL are correct
|
||||||
|
val allowEntries = allowList.map { spaceId ->
|
||||||
|
RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3))
|
||||||
|
}
|
||||||
|
updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# android\.text\.TextUtils
|
||||||
|
|
||||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||||
enum class===103
|
enum class===105
|
||||||
|
|
||||||
### Do not import temporary legacy classes
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||||
|
|
|
@ -224,6 +224,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".features.roomprofile.RoomProfileActivity" />
|
<activity android:name=".features.roomprofile.RoomProfileActivity" />
|
||||||
|
<activity android:name=".features.roomprofile.settings.joinrule.RoomJoinRuleActivity" />
|
||||||
|
|
||||||
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -108,6 +108,8 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||||
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
||||||
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleChooseRestrictedFragment
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
||||||
|
@ -804,4 +806,14 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(SpaceManageRoomsFragment::class)
|
@FragmentKey(SpaceManageRoomsFragment::class)
|
||||||
fun bindSpaceManageRoomsFragment(fragment: SpaceManageRoomsFragment): Fragment
|
fun bindSpaceManageRoomsFragment(fragment: SpaceManageRoomsFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomJoinRuleFragment::class)
|
||||||
|
fun bindRoomJoinRuleFragment(fragment: RoomJoinRuleFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomJoinRuleChooseRestrictedFragment::class)
|
||||||
|
fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
|
||||||
import im.vector.app.features.roomprofile.RoomProfileActivity
|
import im.vector.app.features.roomprofile.RoomProfileActivity
|
||||||
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
|
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
|
||||||
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
|
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
|
||||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
|
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
|
||||||
|
@ -169,6 +170,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: SpaceCreationActivity)
|
fun inject(activity: SpaceCreationActivity)
|
||||||
fun inject(activity: SpaceExploreActivity)
|
fun inject(activity: SpaceExploreActivity)
|
||||||
fun inject(activity: SpaceManageActivity)
|
fun inject(activity: SpaceManageActivity)
|
||||||
|
fun inject(activity: RoomJoinRuleActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
|
|
@ -40,10 +40,17 @@ class MigrateRoomBottomSheet :
|
||||||
VectorBaseBottomSheetDialogFragment<BottomSheetRoomUpgradeBinding>(),
|
VectorBaseBottomSheetDialogFragment<BottomSheetRoomUpgradeBinding>(),
|
||||||
MigrateRoomViewModel.Factory {
|
MigrateRoomViewModel.Factory {
|
||||||
|
|
||||||
|
enum class MigrationReason {
|
||||||
|
MANUAL,
|
||||||
|
FOR_RESTRICTED
|
||||||
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Args(
|
data class Args(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val newVersion: String
|
val newVersion: String,
|
||||||
|
val reason: MigrationReason = MigrationReason.MANUAL,
|
||||||
|
val customDescription: CharSequence? = null
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -62,11 +69,22 @@ class MigrateRoomBottomSheet :
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
views.headerText.setText(if (state.isPublic) R.string.upgrade_public_room else R.string.upgrade_private_room)
|
views.headerText.setText(if (state.isPublic) R.string.upgrade_public_room else R.string.upgrade_private_room)
|
||||||
|
|
||||||
|
if (state.migrationReason == MigrationReason.MANUAL) {
|
||||||
|
views.descriptionText.text = getString(R.string.upgrade_room_warning)
|
||||||
views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion)
|
views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion)
|
||||||
|
} else if (state.migrationReason == MigrationReason.FOR_RESTRICTED) {
|
||||||
|
views.descriptionText.setTextOrHide(state.customDescription)
|
||||||
|
views.upgradeFromTo.text = getString(R.string.upgrade_room_for_restricted_note)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.autoMigrateMembersAndParents) {
|
||||||
|
views.autoUpdateParent.isVisible = false
|
||||||
|
views.autoInviteSwitch.isVisible = false
|
||||||
|
} else {
|
||||||
views.autoInviteSwitch.isVisible = !state.isPublic && state.otherMemberCount > 0
|
views.autoInviteSwitch.isVisible = !state.isPublic && state.otherMemberCount > 0
|
||||||
|
|
||||||
views.autoUpdateParent.isVisible = state.knownParents.isNotEmpty()
|
views.autoUpdateParent.isVisible = state.knownParents.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
when (state.upgradingStatus) {
|
when (state.upgradingStatus) {
|
||||||
is Loading -> {
|
is Loading -> {
|
||||||
|
@ -143,9 +161,12 @@ class MigrateRoomBottomSheet :
|
||||||
const val REQUEST_KEY = "MigrateRoomBottomSheetRequest"
|
const val REQUEST_KEY = "MigrateRoomBottomSheetRequest"
|
||||||
const val BUNDLE_KEY_REPLACEMENT_ROOM = "BUNDLE_KEY_REPLACEMENT_ROOM"
|
const val BUNDLE_KEY_REPLACEMENT_ROOM = "BUNDLE_KEY_REPLACEMENT_ROOM"
|
||||||
|
|
||||||
fun newInstance(roomId: String, newVersion: String): MigrateRoomBottomSheet {
|
fun newInstance(roomId: String, newVersion: String,
|
||||||
|
reason: MigrationReason = MigrationReason.MANUAL,
|
||||||
|
customDescription: CharSequence? = null
|
||||||
|
): MigrateRoomBottomSheet {
|
||||||
return MigrateRoomBottomSheet().apply {
|
return MigrateRoomBottomSheet().apply {
|
||||||
setArguments(Args(roomId, newVersion))
|
setArguments(Args(roomId, newVersion, reason, customDescription))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,11 +90,23 @@ class MigrateRoomViewModel @AssistedInject constructor(
|
||||||
copy(upgradingStatus = Loading())
|
copy(upgradingStatus = Loading())
|
||||||
}
|
}
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
|
val userToInvite = if (state.autoMigrateMembersAndParents) {
|
||||||
|
summary?.otherMemberIds?.takeIf { !state.isPublic }
|
||||||
|
} else {
|
||||||
|
summary?.otherMemberIds?.takeIf { state.shouldIssueInvites }
|
||||||
|
}.orEmpty()
|
||||||
|
|
||||||
|
val parentSpaceToUpdate = if (state.autoMigrateMembersAndParents) {
|
||||||
|
summary?.flattenParentIds
|
||||||
|
} else {
|
||||||
|
summary?.flattenParentIds?.takeIf { state.shouldUpdateKnownParents }
|
||||||
|
}.orEmpty()
|
||||||
|
|
||||||
val result = upgradeRoomViewModelTask.execute(UpgradeRoomViewModelTask.Params(
|
val result = upgradeRoomViewModelTask.execute(UpgradeRoomViewModelTask.Params(
|
||||||
roomId = state.roomId,
|
roomId = state.roomId,
|
||||||
newVersion = state.newVersion,
|
newVersion = state.newVersion,
|
||||||
userIdsToAutoInvite = summary?.otherMemberIds?.takeIf { state.shouldIssueInvites } ?: emptyList(),
|
userIdsToAutoInvite = userToInvite,
|
||||||
parentSpaceToUpdate = summary?.flattenParentIds?.takeIf { state.shouldUpdateKnownParents } ?: emptyList(),
|
parentSpaceToUpdate = parentSpaceToUpdate,
|
||||||
progressReporter = { indeterminate, progress, total ->
|
progressReporter = { indeterminate, progress, total ->
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
@ -23,6 +23,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||||
data class MigrateRoomViewState(
|
data class MigrateRoomViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val newVersion: String,
|
val newVersion: String,
|
||||||
|
val customDescription: CharSequence? = null,
|
||||||
val currentVersion: String? = null,
|
val currentVersion: String? = null,
|
||||||
val isPublic: Boolean = false,
|
val isPublic: Boolean = false,
|
||||||
val shouldIssueInvites: Boolean = false,
|
val shouldIssueInvites: Boolean = false,
|
||||||
|
@ -32,10 +33,15 @@ data class MigrateRoomViewState(
|
||||||
val upgradingStatus: Async<UpgradeRoomViewModelTask.Result> = Uninitialized,
|
val upgradingStatus: Async<UpgradeRoomViewModelTask.Result> = Uninitialized,
|
||||||
val upgradingProgress: Int = 0,
|
val upgradingProgress: Int = 0,
|
||||||
val upgradingProgressTotal: Int = 0,
|
val upgradingProgressTotal: Int = 0,
|
||||||
val upgradingProgressIndeterminate: Boolean = true
|
val upgradingProgressIndeterminate: Boolean = true,
|
||||||
|
val migrationReason: MigrateRoomBottomSheet.MigrationReason = MigrateRoomBottomSheet.MigrationReason.MANUAL,
|
||||||
|
val autoMigrateMembersAndParents: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
constructor(args: MigrateRoomBottomSheet.Args) : this(
|
constructor(args: MigrateRoomBottomSheet.Args) : this(
|
||||||
roomId = args.roomId,
|
roomId = args.roomId,
|
||||||
newVersion = args.newVersion
|
newVersion = args.newVersion,
|
||||||
|
migrationReason = args.reason,
|
||||||
|
autoMigrateMembersAndParents = args.reason == MigrateRoomBottomSheet.MigrationReason.FOR_RESTRICTED,
|
||||||
|
customDescription = args.customDescription
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,13 @@ package im.vector.app.features.roomdirectory.createroom
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
|
||||||
sealed class CreateRoomAction : VectorViewModelAction {
|
sealed class CreateRoomAction : VectorViewModelAction {
|
||||||
data class SetAvatar(val imageUri: Uri?) : CreateRoomAction()
|
data class SetAvatar(val imageUri: Uri?) : CreateRoomAction()
|
||||||
data class SetName(val name: String) : CreateRoomAction()
|
data class SetName(val name: String) : CreateRoomAction()
|
||||||
data class SetTopic(val topic: String) : CreateRoomAction()
|
data class SetTopic(val topic: String) : CreateRoomAction()
|
||||||
data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction()
|
data class SetVisibility(val rule: RoomJoinRules) : CreateRoomAction()
|
||||||
data class SetRoomAliasLocalPart(val aliasLocalPart: String) : CreateRoomAction()
|
data class SetRoomAliasLocalPart(val aliasLocalPart: String) : CreateRoomAction()
|
||||||
data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction()
|
data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction()
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.dividerItem
|
import im.vector.app.core.epoxy.dividerItem
|
||||||
|
import im.vector.app.core.epoxy.profiles.buildProfileAction
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.discovery.settingsSectionTitleItem
|
import im.vector.app.features.discovery.settingsSectionTitleItem
|
||||||
import im.vector.app.features.form.formAdvancedToggleItem
|
import im.vector.app.features.form.formAdvancedToggleItem
|
||||||
|
@ -29,6 +30,7 @@ import im.vector.app.features.form.formEditableAvatarItem
|
||||||
import im.vector.app.features.form.formSubmitButtonItem
|
import im.vector.app.features.form.formSubmitButtonItem
|
||||||
import im.vector.app.features.form.formSwitchItem
|
import im.vector.app.features.form.formSwitchItem
|
||||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateRoomController @Inject constructor(
|
class CreateRoomController @Inject constructor(
|
||||||
|
@ -83,26 +85,59 @@ class CreateRoomController @Inject constructor(
|
||||||
host.listener?.onTopicChange(text)
|
host.listener?.onTopicChange(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsSectionTitleItem {
|
||||||
|
id("visibility")
|
||||||
|
titleResId(R.string.room_settings_room_access_title)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (viewState.roomJoinRules) {
|
||||||
|
RoomJoinRules.INVITE -> {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "joinRule",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_room_access_private_title),
|
||||||
|
subtitle = stringProvider.getString(R.string.room_settings_room_access_private_description),
|
||||||
|
divider = false,
|
||||||
|
editable = true,
|
||||||
|
action = { host.listener?.selectVisibility() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RoomJoinRules.PUBLIC -> {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "joinRule",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_room_access_public_title),
|
||||||
|
subtitle = stringProvider.getString(R.string.room_settings_room_access_public_description),
|
||||||
|
divider = false,
|
||||||
|
editable = true,
|
||||||
|
action = { host.listener?.selectVisibility() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RoomJoinRules.RESTRICTED -> {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "joinRule",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_room_access_restricted_title),
|
||||||
|
subtitle = stringProvider.getString(R.string.room_create_member_of_space_name_can_join, viewState.parentSpaceSummary?.displayName),
|
||||||
|
divider = false,
|
||||||
|
editable = true,
|
||||||
|
action = { host.listener?.selectVisibility() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// not yet supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
settingsSectionTitleItem {
|
settingsSectionTitleItem {
|
||||||
id("settingsSection")
|
id("settingsSection")
|
||||||
titleResId(R.string.create_room_settings_section)
|
titleResId(R.string.create_room_settings_section)
|
||||||
}
|
}
|
||||||
formSwitchItem {
|
|
||||||
id("public")
|
if (viewState.roomJoinRules == RoomJoinRules.PUBLIC) {
|
||||||
enabled(enableFormElement)
|
|
||||||
title(host.stringProvider.getString(R.string.create_room_public_title))
|
|
||||||
summary(host.stringProvider.getString(R.string.create_room_public_description))
|
|
||||||
switchChecked(viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public)
|
|
||||||
listener { value ->
|
|
||||||
host.listener?.setIsPublic(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) {
|
|
||||||
// Room alias for public room
|
// Room alias for public room
|
||||||
formEditTextItem {
|
formEditTextItem {
|
||||||
id("alias")
|
id("alias")
|
||||||
enabled(enableFormElement)
|
enabled(enableFormElement)
|
||||||
value(viewState.roomVisibilityType.aliasLocalPart)
|
value(viewState.aliasLocalPart)
|
||||||
suffixText(":" + viewState.homeServerName)
|
suffixText(":" + viewState.homeServerName)
|
||||||
prefixText("#")
|
prefixText("#")
|
||||||
hint(host.stringProvider.getString(R.string.room_alias_address_hint))
|
hint(host.stringProvider.getString(R.string.room_alias_address_hint))
|
||||||
|
@ -137,9 +172,10 @@ class CreateRoomController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dividerItem {
|
|
||||||
id("divider1")
|
// dividerItem {
|
||||||
}
|
// id("divider1")
|
||||||
|
// }
|
||||||
formAdvancedToggleItem {
|
formAdvancedToggleItem {
|
||||||
id("showAdvanced")
|
id("showAdvanced")
|
||||||
title(host.stringProvider.getString(if (viewState.showAdvanced) R.string.hide_advanced else R.string.show_advanced))
|
title(host.stringProvider.getString(if (viewState.showAdvanced) R.string.hide_advanced else R.string.show_advanced))
|
||||||
|
@ -169,7 +205,7 @@ class CreateRoomController @Inject constructor(
|
||||||
fun onAvatarChange()
|
fun onAvatarChange()
|
||||||
fun onNameChange(newName: String)
|
fun onNameChange(newName: String)
|
||||||
fun onTopicChange(newTopic: String)
|
fun onTopicChange(newTopic: String)
|
||||||
fun setIsPublic(isPublic: Boolean)
|
fun selectVisibility()
|
||||||
fun setAliasLocalPart(aliasLocalPart: String)
|
fun setAliasLocalPart(aliasLocalPart: String)
|
||||||
fun setIsEncrypted(isEncrypted: Boolean)
|
fun setIsEncrypted(isEncrypted: Boolean)
|
||||||
fun toggleShowAdvanced()
|
fun toggleShowAdvanced()
|
||||||
|
|
|
@ -40,9 +40,12 @@ import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.databinding.FragmentCreateRoomBinding
|
import im.vector.app.databinding.FragmentCreateRoomBinding
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
|
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.toOption
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
@ -64,6 +67,8 @@ class CreateRoomFragment @Inject constructor(
|
||||||
private val viewModel: CreateRoomViewModel by fragmentViewModel()
|
private val viewModel: CreateRoomViewModel by fragmentViewModel()
|
||||||
private val args: CreateRoomArgs by args()
|
private val args: CreateRoomArgs by args()
|
||||||
|
|
||||||
|
private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel
|
||||||
|
|
||||||
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
|
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreateRoomBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreateRoomBinding {
|
||||||
|
@ -74,6 +79,7 @@ class CreateRoomFragment @Inject constructor(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
vectorBaseActivity.setSupportActionBar(views.createRoomToolbar)
|
vectorBaseActivity.setSupportActionBar(views.createRoomToolbar)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
|
||||||
|
setupRoomJoinRuleSharedActionViewModel()
|
||||||
setupWaitingView()
|
setupWaitingView()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
views.createRoomClose.debouncedClicks {
|
views.createRoomClose.debouncedClicks {
|
||||||
|
@ -87,6 +93,16 @@ class CreateRoomFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupRoomJoinRuleSharedActionViewModel() {
|
||||||
|
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
|
||||||
|
roomJoinRuleSharedActionViewModel
|
||||||
|
.observe()
|
||||||
|
.subscribe { action ->
|
||||||
|
viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule))
|
||||||
|
}
|
||||||
|
.disposeOnDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun showFailure(throwable: Throwable) {
|
override fun showFailure(throwable: Throwable) {
|
||||||
// Note: RoomAliasError are displayed directly in the form
|
// Note: RoomAliasError are displayed directly in the form
|
||||||
if (throwable !is CreateRoomFailure.AliasError) {
|
if (throwable !is CreateRoomFailure.AliasError) {
|
||||||
|
@ -130,9 +146,19 @@ class CreateRoomFragment @Inject constructor(
|
||||||
viewModel.handle(CreateRoomAction.SetTopic(newTopic))
|
viewModel.handle(CreateRoomAction.SetTopic(newTopic))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setIsPublic(isPublic: Boolean) {
|
override fun selectVisibility() = withState(viewModel) { state ->
|
||||||
viewModel.handle(CreateRoomAction.SetIsPublic(isPublic))
|
|
||||||
|
val allowed = if (state.supportsRestricted) {
|
||||||
|
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC, RoomJoinRules.RESTRICTED)
|
||||||
|
} else {
|
||||||
|
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC)
|
||||||
}
|
}
|
||||||
|
RoomJoinRuleBottomSheet.newInstance(state.roomJoinRules, allowed.map { it.toOption(false) })
|
||||||
|
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
|
||||||
|
}
|
||||||
|
// override fun setIsPublic(isPublic: Boolean) {
|
||||||
|
// viewModel.handle(CreateRoomAction.SetIsPublic(isPublic))
|
||||||
|
// }
|
||||||
|
|
||||||
override fun setAliasLocalPart(aliasLocalPart: String) {
|
override fun setAliasLocalPart(aliasLocalPart: String) {
|
||||||
viewModel.handle(CreateRoomAction.SetRoomAliasLocalPart(aliasLocalPart))
|
viewModel.handle(CreateRoomAction.SetRoomAliasLocalPart(aliasLocalPart))
|
||||||
|
|
|
@ -32,22 +32,28 @@ import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
|
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
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.failure.CreateRoomFailure
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
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.CreateRoomPreset
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState,
|
class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val rawService: RawService
|
private val rawService: RawService,
|
||||||
|
private val vectorPreferences: VectorPreferences
|
||||||
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
|
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -58,6 +64,27 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
|
||||||
init {
|
init {
|
||||||
initHomeServerName()
|
initHomeServerName()
|
||||||
initAdminE2eByDefault()
|
initAdminE2eByDefault()
|
||||||
|
|
||||||
|
val restrictedSupport = session.getHomeServerCapabilities().isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||||
|
val createRestricted = when (restrictedSupport) {
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultJoinRules = if (initialState.parentSpaceId != null && createRestricted) {
|
||||||
|
RoomJoinRules.RESTRICTED
|
||||||
|
} else {
|
||||||
|
RoomJoinRules.INVITE
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
supportsRestricted = createRestricted,
|
||||||
|
roomJoinRules = defaultJoinRules,
|
||||||
|
parentSpaceSummary = initialState.parentSpaceId?.let { session.getRoomSummary(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initHomeServerName() {
|
private fun initHomeServerName() {
|
||||||
|
@ -80,7 +107,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
isEncrypted = roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Private && adminE2EByDefault,
|
isEncrypted = RoomJoinRules.INVITE == roomJoinRules && adminE2EByDefault,
|
||||||
hsAdminHasDisabledE2E = !adminE2EByDefault
|
hsAdminHasDisabledE2E = !adminE2EByDefault
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +129,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
|
||||||
is CreateRoomAction.SetAvatar -> setAvatar(action)
|
is CreateRoomAction.SetAvatar -> setAvatar(action)
|
||||||
is CreateRoomAction.SetName -> setName(action)
|
is CreateRoomAction.SetName -> setName(action)
|
||||||
is CreateRoomAction.SetTopic -> setTopic(action)
|
is CreateRoomAction.SetTopic -> setTopic(action)
|
||||||
is CreateRoomAction.SetIsPublic -> setIsPublic(action)
|
is CreateRoomAction.SetVisibility -> setVisibility(action)
|
||||||
is CreateRoomAction.SetRoomAliasLocalPart -> setRoomAliasLocalPart(action)
|
is CreateRoomAction.SetRoomAliasLocalPart -> setRoomAliasLocalPart(action)
|
||||||
is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action)
|
is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action)
|
||||||
is CreateRoomAction.Create -> doCreateRoom()
|
is CreateRoomAction.Create -> doCreateRoom()
|
||||||
|
@ -149,36 +176,46 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
|
||||||
|
|
||||||
private fun setTopic(action: CreateRoomAction.SetTopic) = setState { copy(roomTopic = action.topic) }
|
private fun setTopic(action: CreateRoomAction.SetTopic) = setState { copy(roomTopic = action.topic) }
|
||||||
|
|
||||||
private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState {
|
private fun setVisibility(action: CreateRoomAction.SetVisibility) = setState {
|
||||||
if (action.isPublic) {
|
when (action.rule) {
|
||||||
|
RoomJoinRules.PUBLIC -> {
|
||||||
copy(
|
copy(
|
||||||
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(""),
|
roomJoinRules = RoomJoinRules.PUBLIC,
|
||||||
// Reset any error in the form about alias
|
// Reset any error in the form about alias
|
||||||
asyncCreateRoomRequest = Uninitialized,
|
asyncCreateRoomRequest = Uninitialized,
|
||||||
isEncrypted = false
|
isEncrypted = false
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
RoomJoinRules.RESTRICTED -> {
|
||||||
copy(
|
copy(
|
||||||
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Private,
|
roomJoinRules = RoomJoinRules.RESTRICTED,
|
||||||
|
// Reset any error in the form about alias
|
||||||
|
asyncCreateRoomRequest = Uninitialized,
|
||||||
|
isEncrypted = adminE2EByDefault
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// RoomJoinRules.INVITE,
|
||||||
|
// RoomJoinRules.KNOCK,
|
||||||
|
// RoomJoinRules.PRIVATE,
|
||||||
|
else -> {
|
||||||
|
// default to invite
|
||||||
|
copy(
|
||||||
|
roomJoinRules = RoomJoinRules.INVITE,
|
||||||
isEncrypted = adminE2EByDefault
|
isEncrypted = adminE2EByDefault
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) {
|
private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) {
|
||||||
withState { state ->
|
|
||||||
if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) {
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(action.aliasLocalPart),
|
aliasLocalPart = action.aliasLocalPart,
|
||||||
// Reset any error in the form about alias
|
// Reset any error in the form about alias
|
||||||
asyncCreateRoomRequest = Uninitialized
|
asyncCreateRoomRequest = Uninitialized
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Else ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) }
|
private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) }
|
||||||
|
|
||||||
|
@ -187,8 +224,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
|
||||||
return@withState
|
return@withState
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public
|
if (state.roomJoinRules == RoomJoinRules.PUBLIC && state.aliasLocalPart.isNullOrBlank()) {
|
||||||
&& state.roomVisibilityType.aliasLocalPart.isBlank()) {
|
|
||||||
// we require an alias for public rooms
|
// we require an alias for public rooms
|
||||||
setState {
|
setState {
|
||||||
copy(asyncCreateRoomRequest = Fail(CreateRoomFailure.AliasError(RoomAliasError.AliasIsBlank)))
|
copy(asyncCreateRoomRequest = Fail(CreateRoomFailure.AliasError(RoomAliasError.AliasIsBlank)))
|
||||||
|
@ -205,15 +241,30 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
|
||||||
name = state.roomName.takeIf { it.isNotBlank() }
|
name = state.roomName.takeIf { it.isNotBlank() }
|
||||||
topic = state.roomTopic.takeIf { it.isNotBlank() }
|
topic = state.roomTopic.takeIf { it.isNotBlank() }
|
||||||
avatarUri = state.avatarUri
|
avatarUri = state.avatarUri
|
||||||
when (state.roomVisibilityType) {
|
when (state.roomJoinRules) {
|
||||||
is CreateRoomViewState.RoomVisibilityType.Public -> {
|
RoomJoinRules.PUBLIC -> {
|
||||||
// Directory visibility
|
// Directory visibility
|
||||||
visibility = RoomDirectoryVisibility.PUBLIC
|
visibility = RoomDirectoryVisibility.PUBLIC
|
||||||
// Preset
|
// Preset
|
||||||
preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||||
roomAliasName = state.roomVisibilityType.aliasLocalPart
|
roomAliasName = state.aliasLocalPart
|
||||||
}
|
}
|
||||||
is CreateRoomViewState.RoomVisibilityType.Private -> {
|
RoomJoinRules.RESTRICTED -> {
|
||||||
|
state.parentSpaceId?.let {
|
||||||
|
featurePreset = RestrictedRoomPreset(
|
||||||
|
session.getHomeServerCapabilities(),
|
||||||
|
listOf(RoomJoinRulesAllowEntry(
|
||||||
|
state.parentSpaceId,
|
||||||
|
listOf(state.homeServerName)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// RoomJoinRules.KNOCK ->
|
||||||
|
// RoomJoinRules.PRIVATE ->
|
||||||
|
// RoomJoinRules.INVITE
|
||||||
|
else -> {
|
||||||
|
// by default create invite only
|
||||||
// Directory visibility
|
// Directory visibility
|
||||||
visibility = RoomDirectoryVisibility.PRIVATE
|
visibility = RoomDirectoryVisibility.PRIVATE
|
||||||
// Preset
|
// Preset
|
||||||
|
|
|
@ -20,20 +20,24 @@ import android.net.Uri
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import org.matrix.android.sdk.api.extensions.orTrue
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
data class CreateRoomViewState(
|
data class CreateRoomViewState(
|
||||||
val avatarUri: Uri? = null,
|
val avatarUri: Uri? = null,
|
||||||
val roomName: String = "",
|
val roomName: String = "",
|
||||||
val roomTopic: String = "",
|
val roomTopic: String = "",
|
||||||
val roomVisibilityType: RoomVisibilityType = RoomVisibilityType.Private,
|
val roomJoinRules: RoomJoinRules = RoomJoinRules.INVITE,
|
||||||
val isEncrypted: Boolean = false,
|
val isEncrypted: Boolean = false,
|
||||||
val showAdvanced: Boolean = false,
|
val showAdvanced: Boolean = false,
|
||||||
val disableFederation: Boolean = false,
|
val disableFederation: Boolean = false,
|
||||||
val homeServerName: String = "",
|
val homeServerName: String = "",
|
||||||
val hsAdminHasDisabledE2E: Boolean = false,
|
val hsAdminHasDisabledE2E: Boolean = false,
|
||||||
val asyncCreateRoomRequest: Async<String> = Uninitialized,
|
val asyncCreateRoomRequest: Async<String> = Uninitialized,
|
||||||
val parentSpaceId: String?
|
val parentSpaceId: String?,
|
||||||
|
val parentSpaceSummary: RoomSummary? = null,
|
||||||
|
val supportsRestricted: Boolean = false,
|
||||||
|
val aliasLocalPart: String? = null
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: CreateRoomArgs) : this(
|
constructor(args: CreateRoomArgs) : this(
|
||||||
|
@ -47,10 +51,5 @@ data class CreateRoomViewState(
|
||||||
fun isEmpty() = avatarUri == null
|
fun isEmpty() = avatarUri == null
|
||||||
&& roomName.isEmpty()
|
&& roomName.isEmpty()
|
||||||
&& roomTopic.isEmpty()
|
&& roomTopic.isEmpty()
|
||||||
&& (roomVisibilityType as? RoomVisibilityType.Public)?.aliasLocalPart?.isEmpty().orTrue()
|
&& aliasLocalPart.isNullOrEmpty()
|
||||||
|
|
||||||
sealed class RoomVisibilityType {
|
|
||||||
object Private : RoomVisibilityType()
|
|
||||||
data class Public(val aliasLocalPart: String) : RoomVisibilityType()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||||
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
|
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
|
||||||
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
|
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
|
||||||
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
|
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
|
||||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
|
||||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
@ -179,10 +179,8 @@ class RoomSettingsFragment @Inject constructor(
|
||||||
.show(childFragmentManager, "RoomHistoryVisibilityBottomSheet")
|
.show(childFragmentManager, "RoomHistoryVisibilityBottomSheet")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onJoinRuleClicked() = withState(viewModel) { state ->
|
override fun onJoinRuleClicked() {
|
||||||
val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules
|
startActivity(RoomJoinRuleActivity.newIntent(requireContext(), roomProfileArgs.roomId))
|
||||||
RoomJoinRuleBottomSheet.newInstance(currentJoinRule)
|
|
||||||
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleGuestAccess() = withState(viewModel) { state ->
|
override fun onToggleGuestAccess() = withState(viewModel) { state ->
|
||||||
|
|
|
@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
@ -34,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
|
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
|
@ -44,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
|
||||||
import org.matrix.android.sdk.rx.unwrap
|
import org.matrix.android.sdk.rx.unwrap
|
||||||
|
|
||||||
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
|
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val session: Session)
|
private val session: Session)
|
||||||
: VectorViewModel<RoomSettingsViewState, RoomSettingsAction, RoomSettingsViewEvents>(initialState) {
|
: VectorViewModel<RoomSettingsViewState, RoomSettingsAction, RoomSettingsViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@ -73,6 +76,24 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
observeGuestAccess()
|
observeGuestAccess()
|
||||||
observeRoomAvatar()
|
observeRoomAvatar()
|
||||||
observeState()
|
observeState()
|
||||||
|
|
||||||
|
val homeServerCapabilities = session.getHomeServerCapabilities()
|
||||||
|
val canUseRestricted = homeServerCapabilities
|
||||||
|
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion())
|
||||||
|
|
||||||
|
val restrictedSupport = homeServerCapabilities.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||||
|
val couldUpgradeToRestricted = when (restrictedSupport) {
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
supportsRestricted = canUseRestricted,
|
||||||
|
canUpgradeToRestricted = couldUpgradeToRestricted
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeState() {
|
private fun observeState() {
|
||||||
|
|
|
@ -43,7 +43,9 @@ data class RoomSettingsViewState(
|
||||||
val newHistoryVisibility: RoomHistoryVisibility? = null,
|
val newHistoryVisibility: RoomHistoryVisibility? = null,
|
||||||
val newRoomJoinRules: NewJoinRule = NewJoinRule(),
|
val newRoomJoinRules: NewJoinRule = NewJoinRule(),
|
||||||
val showSaveAction: Boolean = false,
|
val showSaveAction: Boolean = false,
|
||||||
val actionPermissions: ActionPermissions = ActionPermissions()
|
val actionPermissions: ActionPermissions = ActionPermissions(),
|
||||||
|
val supportsRestricted: Boolean = false,
|
||||||
|
val canUpgradeToRestricted: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import com.airbnb.mvrx.viewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.di.ScreenComponent
|
||||||
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
|
import im.vector.app.core.extensions.addFragment
|
||||||
|
import im.vector.app.core.extensions.commitTransaction
|
||||||
|
import im.vector.app.core.extensions.toMvRxBundle
|
||||||
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.app.core.utils.toast
|
||||||
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
|
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
|
||||||
|
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedEvents
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||||
|
RoomJoinRuleChooseRestrictedViewModel.Factory {
|
||||||
|
|
||||||
|
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
private lateinit var roomProfileArgs: RoomProfileArgs
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var allowListViewModelFactory: RoomJoinRuleChooseRestrictedViewModel.Factory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
val viewModel: RoomJoinRuleChooseRestrictedViewModel by viewModel()
|
||||||
|
|
||||||
|
override fun create(initialState: RoomJoinRuleChooseRestrictedState) = allowListViewModelFactory.create(initialState)
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initUiAndData() {
|
||||||
|
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(
|
||||||
|
R.id.simpleFragmentContainer,
|
||||||
|
RoomJoinRuleFragment::class.java,
|
||||||
|
roomProfileArgs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
viewModel.selectSubscribe(this, RoomJoinRuleChooseRestrictedState::updatingStatus) {
|
||||||
|
when (it) {
|
||||||
|
Uninitialized -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
is Loading -> {
|
||||||
|
views.simpleActivityWaitingView.isVisible = true
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
withState(viewModel) { state ->
|
||||||
|
if (state.didSwitchToReplacementRoom) {
|
||||||
|
// we should navigate to new room
|
||||||
|
navigator.openRoom(this, state.roomId, null, true)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
views.simpleActivityWaitingView.isVisible = false
|
||||||
|
toast(errorFormatter.toHumanReadable(it.error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
RoomJoinRuleChooseRestrictedEvents.NavigateToChooseRestricted -> navigateToChooseRestricted()
|
||||||
|
is RoomJoinRuleChooseRestrictedEvents.NavigateToUpgradeRoom -> navigateToUpgradeRoom(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supportFragmentManager.setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY, this) { _, bundle ->
|
||||||
|
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
|
||||||
|
viewModel.handle(RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration(replacementRoomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToUpgradeRoom(events: RoomJoinRuleChooseRestrictedEvents.NavigateToUpgradeRoom) {
|
||||||
|
MigrateRoomBottomSheet.newInstance(
|
||||||
|
events.roomId,
|
||||||
|
events.toVersion,
|
||||||
|
MigrateRoomBottomSheet.MigrationReason.FOR_RESTRICTED,
|
||||||
|
events.description
|
||||||
|
).show(supportFragmentManager, "migrate")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToChooseRestricted() {
|
||||||
|
supportFragmentManager.commitTransaction {
|
||||||
|
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
||||||
|
val tag = RoomJoinRuleChooseRestrictedFragment::class.simpleName
|
||||||
|
replace(R.id.simpleFragmentContainer,
|
||||||
|
RoomJoinRuleChooseRestrictedFragment::class.java,
|
||||||
|
this@RoomJoinRuleActivity.roomProfileArgs.toMvRxBundle(),
|
||||||
|
tag
|
||||||
|
).addToBackStack(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newIntent(context: Context, roomId: String): Intent {
|
||||||
|
val roomProfileArgs = RoomProfileArgs(roomId)
|
||||||
|
return Intent(context, RoomJoinRuleActivity::class.java).apply {
|
||||||
|
putExtra(MvRx.KEY_ARG, roomProfileArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.ui.list.ItemStyle
|
||||||
|
import im.vector.app.core.ui.list.genericButtonItem
|
||||||
|
import im.vector.app.core.ui.list.genericFooterItem
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomJoinRuleAdvancedController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val colorProvider: ColorProvider,
|
||||||
|
private val avatarRenderer: AvatarRenderer
|
||||||
|
) : TypedEpoxyController<RoomJoinRuleChooseRestrictedState>() {
|
||||||
|
|
||||||
|
interface InteractionListener {
|
||||||
|
fun didSelectRule(rules: RoomJoinRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionListener: InteractionListener? = null
|
||||||
|
|
||||||
|
override fun buildModels(state: RoomJoinRuleChooseRestrictedState?) {
|
||||||
|
state ?: return
|
||||||
|
val choices = state.choices ?: return
|
||||||
|
|
||||||
|
val host = this
|
||||||
|
|
||||||
|
genericFooterItem {
|
||||||
|
id("header")
|
||||||
|
text(host.stringProvider.getString(R.string.room_settings_room_access_title))
|
||||||
|
centered(false)
|
||||||
|
style(ItemStyle.TITLE)
|
||||||
|
textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
}
|
||||||
|
|
||||||
|
genericFooterItem {
|
||||||
|
id("desc")
|
||||||
|
text(host.stringProvider.getString(R.string.decide_who_can_find_and_join))
|
||||||
|
centered(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invite only
|
||||||
|
RoomJoinRuleRadioAction(
|
||||||
|
roomJoinRule = RoomJoinRules.INVITE,
|
||||||
|
description = stringProvider.getString(R.string.room_settings_room_access_private_description),
|
||||||
|
title = stringProvider.getString(R.string.room_settings_room_access_private_invite_only_title),
|
||||||
|
isSelected = state.currentRoomJoinRules == RoomJoinRules.INVITE
|
||||||
|
).toRadioBottomSheetItem().let {
|
||||||
|
it.listener {
|
||||||
|
interactionListener?.didSelectRule(RoomJoinRules.INVITE)
|
||||||
|
// listener?.didSelectAction(action)
|
||||||
|
}
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choices.firstOrNull { it.rule == RoomJoinRules.RESTRICTED } != null) {
|
||||||
|
val restrictedRule = choices.first { it.rule == RoomJoinRules.RESTRICTED }
|
||||||
|
Timber.w("##@@ ${state.updatedAllowList}")
|
||||||
|
spaceJoinRuleItem {
|
||||||
|
id("restricted")
|
||||||
|
avatarRenderer(host.avatarRenderer)
|
||||||
|
needUpgrade(restrictedRule.needUpgrade)
|
||||||
|
selected(state.currentRoomJoinRules == RoomJoinRules.RESTRICTED)
|
||||||
|
restrictedList(state.updatedAllowList)
|
||||||
|
listener { host.interactionListener?.didSelectRule(RoomJoinRules.RESTRICTED) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public
|
||||||
|
RoomJoinRuleRadioAction(
|
||||||
|
roomJoinRule = RoomJoinRules.PUBLIC,
|
||||||
|
description = stringProvider.getString(R.string.room_settings_room_access_public_description),
|
||||||
|
title = stringProvider.getString(R.string.room_settings_room_access_public_title),
|
||||||
|
isSelected = state.currentRoomJoinRules == RoomJoinRules.PUBLIC
|
||||||
|
).toRadioBottomSheetItem().let {
|
||||||
|
it.listener {
|
||||||
|
interactionListener?.didSelectRule(RoomJoinRules.PUBLIC)
|
||||||
|
}
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
genericButtonItem {
|
||||||
|
id("save")
|
||||||
|
text("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,9 +28,18 @@ import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class JoinRulesOptionSupport(
|
||||||
|
val rule: RoomJoinRules,
|
||||||
|
val needUpgrade: Boolean = false
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
fun RoomJoinRules.toOption(needUpgrade: Boolean) = JoinRulesOptionSupport(this, needUpgrade)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class RoomJoinRuleBottomSheetArgs(
|
data class RoomJoinRuleBottomSheetArgs(
|
||||||
val currentRoomJoinRule: RoomJoinRules
|
val currentRoomJoinRule: RoomJoinRules,
|
||||||
|
val allowedJoinedRules: List<JoinRulesOptionSupport>
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRuleRadioAction>() {
|
class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRuleRadioAction>() {
|
||||||
|
@ -61,9 +70,15 @@ class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRu
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(currentRoomJoinRule: RoomJoinRules): RoomJoinRuleBottomSheet {
|
fun newInstance(currentRoomJoinRule: RoomJoinRules,
|
||||||
|
allowedJoinedRules: List<JoinRulesOptionSupport> = listOf(
|
||||||
|
RoomJoinRules.INVITE, RoomJoinRules.PUBLIC
|
||||||
|
).map { it.toOption(true) }
|
||||||
|
): RoomJoinRuleBottomSheet {
|
||||||
return RoomJoinRuleBottomSheet().apply {
|
return RoomJoinRuleBottomSheet().apply {
|
||||||
setArguments(RoomJoinRuleBottomSheetArgs(currentRoomJoinRule))
|
setArguments(
|
||||||
|
RoomJoinRuleBottomSheetArgs(currentRoomJoinRule, allowedJoinedRules)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class RoomJoinRuleController @Inject constructor(
|
||||||
description = stringProvider.getString(R.string.room_settings_room_access_restricted_description),
|
description = stringProvider.getString(R.string.room_settings_room_access_restricted_description),
|
||||||
title = span {
|
title = span {
|
||||||
+stringProvider.getString(R.string.room_settings_room_access_restricted_title)
|
+stringProvider.getString(R.string.room_settings_room_access_restricted_title)
|
||||||
+ " "
|
+" "
|
||||||
image(
|
image(
|
||||||
drawableProvider.getDrawable(R.drawable.ic_beta_pill)!!,
|
drawableProvider.getDrawable(R.drawable.ic_beta_pill)!!,
|
||||||
"bottom"
|
"bottom"
|
||||||
|
@ -59,6 +59,6 @@ class RoomJoinRuleController @Inject constructor(
|
||||||
},
|
},
|
||||||
isSelected = state.currentRoomJoinRule == RoomJoinRules.RESTRICTED
|
isSelected = state.currentRoomJoinRule == RoomJoinRules.RESTRICTED
|
||||||
)
|
)
|
||||||
)
|
).filter { state.allowedJoinedRules.map { it.rule }.contains(it.roomJoinRule) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.configureWith
|
||||||
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.databinding.FragmentJoinRulesRecyclerBinding
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomJoinRuleFragment @Inject constructor(
|
||||||
|
val controller: RoomJoinRuleAdvancedController,
|
||||||
|
val avatarRenderer: AvatarRenderer
|
||||||
|
) : VectorBaseFragment<FragmentJoinRulesRecyclerBinding>(),
|
||||||
|
OnBackPressed, RoomJoinRuleAdvancedController.InteractionListener {
|
||||||
|
|
||||||
|
private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||||
|
FragmentJoinRulesRecyclerBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||||
|
val hasUnsavedChanges = withState(viewModel) { it.hasUnsavedChanges }
|
||||||
|
val isLoading = withState(viewModel) { it.updatingStatus is Loading }
|
||||||
|
if (!hasUnsavedChanges || isLoading) {
|
||||||
|
requireActivity().finish()
|
||||||
|
} else {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_title_warning)
|
||||||
|
.setMessage(R.string.warning_unsaved_change)
|
||||||
|
.setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ ->
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
super.invalidate()
|
||||||
|
controller.setData(state)
|
||||||
|
if (state.hasUnsavedChanges) {
|
||||||
|
// show discard and save
|
||||||
|
views.cancelButton.isVisible = true
|
||||||
|
views.positiveButton.text = getString(R.string.warning_unsaved_change_discard)
|
||||||
|
views.positiveButton.isVisible = true
|
||||||
|
views.positiveButton.text = getString(R.string.save)
|
||||||
|
views.positiveButton.debouncedClicks {
|
||||||
|
viewModel.handle(RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
views.cancelButton.isVisible = false
|
||||||
|
views.positiveButton.isVisible = true
|
||||||
|
views.positiveButton.text = getString(R.string.ok)
|
||||||
|
views.positiveButton.debouncedClicks { requireActivity().finish() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
views.genericRecyclerView.configureWith(controller, hasFixedSize = true)
|
||||||
|
controller.interactionListener = this
|
||||||
|
views.cancelButton.debouncedClicks { requireActivity().finish() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
views.genericRecyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun didSelectRule(rules: RoomJoinRules) {
|
||||||
|
val isLoading = withState(viewModel) { it.updatingStatus is Loading }
|
||||||
|
if (isLoading) return
|
||||||
|
|
||||||
|
viewModel.handle(RoomJoinRuleChooseRestrictedActions.SelectJoinRules(rules))
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,10 +22,13 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
|
||||||
data class RoomJoinRuleState(
|
data class RoomJoinRuleState(
|
||||||
val currentRoomJoinRule: RoomJoinRules = RoomJoinRules.INVITE,
|
val currentRoomJoinRule: RoomJoinRules = RoomJoinRules.INVITE,
|
||||||
|
val allowedJoinedRules: List<JoinRulesOptionSupport> =
|
||||||
|
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC).map { it.toOption(true) },
|
||||||
val currentGuestAccess: GuestAccess? = null
|
val currentGuestAccess: GuestAccess? = null
|
||||||
) : BottomSheetGenericState() {
|
) : BottomSheetGenericState() {
|
||||||
|
|
||||||
constructor(args: RoomJoinRuleBottomSheetArgs) : this(
|
constructor(args: RoomJoinRuleBottomSheetArgs) : this(
|
||||||
currentRoomJoinRule = args.currentRoomJoinRule
|
currentRoomJoinRule = args.currentRoomJoinRule,
|
||||||
|
allowedJoinedRules = args.allowedJoinedRules
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule
|
||||||
|
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.epoxy.onClick
|
||||||
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_bottom_sheet_joinrule_restricted)
|
||||||
|
abstract class SpaceJoinRuleItem : VectorEpoxyModel<SpaceJoinRuleItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var selected: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var needUpgrade: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var restrictedList: List<MatrixItem> = emptyList()
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
lateinit var listener: ClickListener
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
holder.view.onClick(listener)
|
||||||
|
holder.upgradeRequiredButton.setOnClickListener(DebouncedClickListener(listener))
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
holder.radioImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_radio_on))
|
||||||
|
holder.radioImage.contentDescription = holder.view.context.getString(R.string.a11y_checked)
|
||||||
|
} else {
|
||||||
|
holder.radioImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_radio_off))
|
||||||
|
holder.radioImage.contentDescription = holder.view.context.getString(R.string.a11y_unchecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.upgradeRequiredButton.isVisible = needUpgrade
|
||||||
|
holder.helperText.isVisible = selected
|
||||||
|
|
||||||
|
val items = listOf(holder.space1, holder.space2, holder.space3, holder.space4, holder.space5)
|
||||||
|
holder.spaceMore.isVisible = false
|
||||||
|
items.onEach { it.isVisible = false }
|
||||||
|
if (!needUpgrade) {
|
||||||
|
if (restrictedList.isEmpty()) {
|
||||||
|
holder.listTitle.isVisible = false
|
||||||
|
} else {
|
||||||
|
holder.listTitle.isVisible = true
|
||||||
|
restrictedList.forEachIndexed { index, matrixItem ->
|
||||||
|
if (index < items.size) {
|
||||||
|
items[index].isVisible = true
|
||||||
|
avatarRenderer.render(matrixItem, items[index])
|
||||||
|
} else if (index == items.size) {
|
||||||
|
holder.spaceMore.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.listTitle.isVisible = false
|
||||||
|
holder.helperText.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val radioImage by bind<ImageView>(R.id.radioIcon)
|
||||||
|
val actionTitle by bind<TextView>(R.id.actionTitle)
|
||||||
|
val actionDescription by bind<TextView>(R.id.actionDescription)
|
||||||
|
val upgradeRequiredButton by bind<Button>(R.id.upgradeRequiredButton)
|
||||||
|
val listTitle by bind<TextView>(R.id.listTitle)
|
||||||
|
val space1 by bind<ImageView>(R.id.rest1)
|
||||||
|
val space2 by bind<ImageView>(R.id.rest2)
|
||||||
|
val space3 by bind<ImageView>(R.id.rest3)
|
||||||
|
val space4 by bind<ImageView>(R.id.rest4)
|
||||||
|
val space5 by bind<ImageView>(R.id.rest5)
|
||||||
|
val spaceMore by bind<ImageView>(R.id.rest6)
|
||||||
|
val helperText by bind<TextView>(R.id.helperText)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule.advanced
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
|
import im.vector.app.core.epoxy.noResultItem
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.ui.list.genericFooterItem
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.spaces.manage.roomSelectionItem
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ChooseRestrictedController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val avatarRenderer: AvatarRenderer
|
||||||
|
) : TypedEpoxyController<RoomJoinRuleChooseRestrictedState>() {
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onItemSelected(matrixItem: MatrixItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomJoinRuleChooseRestrictedState?) {
|
||||||
|
data ?: return
|
||||||
|
val host = this
|
||||||
|
|
||||||
|
if (data.filter.isNotEmpty()) {
|
||||||
|
when (val results = data.filteredResults) {
|
||||||
|
Uninitialized,
|
||||||
|
is Fail -> return
|
||||||
|
is Loading -> loadingItem { id("filter_load") }
|
||||||
|
is Success -> {
|
||||||
|
if (results.invoke().isEmpty()) {
|
||||||
|
noResultItem {
|
||||||
|
id("empty")
|
||||||
|
text(host.stringProvider.getString(R.string.no_result_placeholder))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.invoke().forEach { matrixItem ->
|
||||||
|
roomSelectionItem {
|
||||||
|
id(matrixItem.id)
|
||||||
|
matrixItem(matrixItem)
|
||||||
|
avatarRenderer(host.avatarRenderer)
|
||||||
|
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
|
||||||
|
itemClickListener { host.listener?.onItemSelected(matrixItem) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// when no filters
|
||||||
|
genericFooterItem {
|
||||||
|
id("h1")
|
||||||
|
text(host.stringProvider.getString(R.string.space_you_know_that_contains_this_room))
|
||||||
|
centered(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.possibleSpaceCandidate.forEach { matrixItem ->
|
||||||
|
roomSelectionItem {
|
||||||
|
id(matrixItem.id)
|
||||||
|
matrixItem(matrixItem)
|
||||||
|
avatarRenderer(host.avatarRenderer)
|
||||||
|
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
|
||||||
|
itemClickListener { host.listener?.onItemSelected(matrixItem) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.unknownRestricted.isNotEmpty()) {
|
||||||
|
genericFooterItem {
|
||||||
|
id("others")
|
||||||
|
text(host.stringProvider.getString(R.string.other_spaces_or_rooms_you_might_not_know))
|
||||||
|
centered(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.unknownRestricted.forEach { matrixItem ->
|
||||||
|
roomSelectionItem {
|
||||||
|
id(matrixItem.id)
|
||||||
|
matrixItem(matrixItem)
|
||||||
|
avatarRenderer(host.avatarRenderer)
|
||||||
|
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
|
||||||
|
itemClickListener { host.listener?.onItemSelected(matrixItem) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule.advanced
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
|
sealed class RoomJoinRuleChooseRestrictedActions : VectorViewModelAction {
|
||||||
|
data class FilterWith(val filter: String) : RoomJoinRuleChooseRestrictedActions()
|
||||||
|
data class ToggleSelection(val matrixItem: MatrixItem) : RoomJoinRuleChooseRestrictedActions()
|
||||||
|
data class SelectJoinRules(val rules: RoomJoinRules) : RoomJoinRuleChooseRestrictedActions()
|
||||||
|
object DoUpdateJoinRules : RoomJoinRuleChooseRestrictedActions()
|
||||||
|
data class SwitchToRoomAfterMigration(val roomId: String) : RoomJoinRuleChooseRestrictedActions()
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule.advanced
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class RoomJoinRuleChooseRestrictedEvents : VectorViewEvents {
|
||||||
|
object NavigateToChooseRestricted : RoomJoinRuleChooseRestrictedEvents()
|
||||||
|
data class NavigateToUpgradeRoom(val roomId: String, val toVersion: String, val description: CharSequence) : RoomJoinRuleChooseRestrictedEvents()
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
|
||||||
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.configureWith
|
||||||
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.databinding.FragmentSpaceRestrictedSelectBinding
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.ChooseRestrictedController
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||||
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
|
||||||
|
val controller: ChooseRestrictedController,
|
||||||
|
val avatarRenderer: AvatarRenderer
|
||||||
|
) : VectorBaseFragment<FragmentSpaceRestrictedSelectBinding>(),
|
||||||
|
ChooseRestrictedController.Listener,
|
||||||
|
OnBackPressed {
|
||||||
|
|
||||||
|
private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel(RoomJoinRuleChooseRestrictedViewModel::class)
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||||
|
FragmentSpaceRestrictedSelectBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
controller.listener = this
|
||||||
|
views.recyclerView.configureWith(controller)
|
||||||
|
views.roomsFilter.queryTextChanges()
|
||||||
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
|
.subscribeBy {
|
||||||
|
viewModel.handle(RoomJoinRuleChooseRestrictedActions.FilterWith(it.toString()))
|
||||||
|
}
|
||||||
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
|
views.okButton.debouncedClicks {
|
||||||
|
parentFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
controller.listener = null
|
||||||
|
views.recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
super.invalidate()
|
||||||
|
controller.setData(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||||
|
val filter = views.roomsFilter.query
|
||||||
|
if (filter.isEmpty()) {
|
||||||
|
parentFragmentManager.popBackStack()
|
||||||
|
} else {
|
||||||
|
views.roomsFilter.setQuery("", true)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemSelected(matrixItem: MatrixItem) {
|
||||||
|
viewModel.handle(RoomJoinRuleChooseRestrictedActions.ToggleSelection(matrixItem))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule.advanced
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.JoinRulesOptionSupport
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
|
data class RoomJoinRuleChooseRestrictedState(
|
||||||
|
// the currentRoomId
|
||||||
|
val roomId: String,
|
||||||
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
|
val initialRoomJoinRules: RoomJoinRules? = null,
|
||||||
|
val currentRoomJoinRules: RoomJoinRules? = null,
|
||||||
|
val updatedAllowList: List<MatrixItem> = emptyList(),
|
||||||
|
val choices: List<JoinRulesOptionSupport>? = null,
|
||||||
|
val initialAllowList: List<RoomJoinRulesAllowEntry> = emptyList(),
|
||||||
|
val possibleSpaceCandidate: List<MatrixItem> = emptyList(),
|
||||||
|
val unknownRestricted: List<MatrixItem> = emptyList(),
|
||||||
|
val filter: String = "",
|
||||||
|
val filteredResults: Async<List<MatrixItem>> = Uninitialized,
|
||||||
|
val hasUnsavedChanges: Boolean = false,
|
||||||
|
val updatingStatus: Async<Unit> = Uninitialized,
|
||||||
|
val upgradeNeededForRestricted: Boolean = false,
|
||||||
|
val restrictedSupportedByThisVersion: Boolean = false,
|
||||||
|
val restrictedVersionNeeded: String? = null,
|
||||||
|
val didSwitchToReplacementRoom: Boolean = false
|
||||||
|
) : MvRxState {
|
||||||
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
}
|
|
@ -0,0 +1,398 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.roomprofile.settings.joinrule.advanced
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import androidx.core.text.toSpannable
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
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
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.utils.styleMatchingText
|
||||||
|
import im.vector.app.features.roomprofile.settings.joinrule.toOption
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
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.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
|
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.RoomJoinRulesContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
|
||||||
|
class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: RoomJoinRuleChooseRestrictedState,
|
||||||
|
private val session: Session,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) : VectorViewModel<RoomJoinRuleChooseRestrictedState, RoomJoinRuleChooseRestrictedActions, RoomJoinRuleChooseRestrictedEvents>(initialState) {
|
||||||
|
|
||||||
|
var room = session.getRoom(initialState.roomId)!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
initializeForRoom(initialState.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeForRoom(roomId: String) {
|
||||||
|
room = session.getRoom(roomId)!!
|
||||||
|
session.getRoomSummary(roomId)?.let { roomSummary ->
|
||||||
|
val joinRulesContent = room.getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
|
||||||
|
?.content
|
||||||
|
?.toModel<RoomJoinRulesContent>()
|
||||||
|
val initialAllowList = joinRulesContent?.allowList
|
||||||
|
|
||||||
|
val knownParentSpacesAllowed = mutableListOf<MatrixItem>()
|
||||||
|
val unknownAllowedOrRooms = mutableListOf<MatrixItem>()
|
||||||
|
initialAllowList.orEmpty().forEach { entry ->
|
||||||
|
val summary = session.getRoomSummary(entry.spaceID)
|
||||||
|
if (summary == null // it's not known by me
|
||||||
|
|| summary.roomType != RoomType.SPACE // it's not a space
|
||||||
|
|| !roomSummary.flattenParentIds.contains(summary.roomId) // it's not a parent space
|
||||||
|
) {
|
||||||
|
unknownAllowedOrRooms.add(
|
||||||
|
summary?.toMatrixItem() ?: MatrixItem.RoomItem(entry.spaceID, null, null)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
knownParentSpacesAllowed.add(summary.toMatrixItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val possibleSpaceCandidate = knownParentSpacesAllowed.toMutableList()
|
||||||
|
roomSummary.flattenParentIds.mapNotNull {
|
||||||
|
session.getRoomSummary(it)?.toMatrixItem()
|
||||||
|
}.forEach {
|
||||||
|
if (!possibleSpaceCandidate.contains(it)) {
|
||||||
|
possibleSpaceCandidate.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val homeServerCapabilities = session.getHomeServerCapabilities()
|
||||||
|
var safeRule: RoomJoinRules = joinRulesContent?.joinRules ?: RoomJoinRules.INVITE
|
||||||
|
// server is not really checking that, just to be sure let's check
|
||||||
|
val restrictedSupportedByThisVersion = homeServerCapabilities
|
||||||
|
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion())
|
||||||
|
if (safeRule == RoomJoinRules.RESTRICTED
|
||||||
|
&& !restrictedSupportedByThisVersion) {
|
||||||
|
safeRule = RoomJoinRules.INVITE
|
||||||
|
}
|
||||||
|
|
||||||
|
val restrictedSupport = homeServerCapabilities.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||||
|
val couldUpgradeToRestricted = when (restrictedSupport) {
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
val choices = if (restrictedSupportedByThisVersion || couldUpgradeToRestricted) {
|
||||||
|
listOf(
|
||||||
|
RoomJoinRules.INVITE.toOption(false),
|
||||||
|
RoomJoinRules.RESTRICTED.toOption(!restrictedSupportedByThisVersion),
|
||||||
|
RoomJoinRules.PUBLIC.toOption(false)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
RoomJoinRules.INVITE.toOption(false),
|
||||||
|
RoomJoinRules.PUBLIC.toOption(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
roomSummary = Success(roomSummary),
|
||||||
|
initialRoomJoinRules = safeRule,
|
||||||
|
currentRoomJoinRules = safeRule,
|
||||||
|
choices = choices,
|
||||||
|
initialAllowList = initialAllowList.orEmpty(),
|
||||||
|
updatedAllowList = initialAllowList.orEmpty().map {
|
||||||
|
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
|
||||||
|
},
|
||||||
|
possibleSpaceCandidate = possibleSpaceCandidate,
|
||||||
|
unknownRestricted = unknownAllowedOrRooms,
|
||||||
|
restrictedSupportedByThisVersion = restrictedSupportedByThisVersion,
|
||||||
|
upgradeNeededForRestricted = !restrictedSupportedByThisVersion && couldUpgradeToRestricted,
|
||||||
|
restrictedVersionNeeded = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkForChanges() = withState { state ->
|
||||||
|
if (state.initialRoomJoinRules != state.currentRoomJoinRules) {
|
||||||
|
setState {
|
||||||
|
copy(hasUnsavedChanges = true)
|
||||||
|
}
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.currentRoomJoinRules == RoomJoinRules.RESTRICTED) {
|
||||||
|
val allowDidChange = state.initialAllowList.map { it.spaceID } != state.updatedAllowList.map { it.id }
|
||||||
|
setState {
|
||||||
|
copy(hasUnsavedChanges = allowDidChange)
|
||||||
|
}
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(hasUnsavedChanges = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: RoomJoinRuleChooseRestrictedState): RoomJoinRuleChooseRestrictedViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: RoomJoinRuleChooseRestrictedActions) {
|
||||||
|
when (action) {
|
||||||
|
is RoomJoinRuleChooseRestrictedActions.FilterWith -> handleFilter(action)
|
||||||
|
is RoomJoinRuleChooseRestrictedActions.ToggleSelection -> handleToggleSelection(action)
|
||||||
|
is RoomJoinRuleChooseRestrictedActions.SelectJoinRules -> handleSelectRule(action)
|
||||||
|
is RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration -> handleSwitchToRoom(action)
|
||||||
|
RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules -> handleSubmit()
|
||||||
|
}.exhaustive
|
||||||
|
checkForChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSubmit() = withState { state ->
|
||||||
|
setState { copy(updatingStatus = Loading()) }
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
when (state.currentRoomJoinRules) {
|
||||||
|
RoomJoinRules.PUBLIC -> room.setJoinRulePublic()
|
||||||
|
RoomJoinRules.INVITE -> room.setJoinRuleInviteOnly()
|
||||||
|
RoomJoinRules.RESTRICTED -> room.setJoinRuleRestricted(state.updatedAllowList.map { it.id })
|
||||||
|
RoomJoinRules.KNOCK,
|
||||||
|
RoomJoinRules.PRIVATE,
|
||||||
|
null -> {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState { copy(updatingStatus = Success(Unit)) }
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
setState { copy(updatingStatus = Fail(failure)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSelectRule(action: RoomJoinRuleChooseRestrictedActions.SelectJoinRules) = withState { state ->
|
||||||
|
val currentRoomJoinRules = state.currentRoomJoinRules
|
||||||
|
|
||||||
|
val candidate = session.getRoomSummary(state.roomId)
|
||||||
|
?.flattenParentIds
|
||||||
|
?.filter {
|
||||||
|
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == state.roomId } != null
|
||||||
|
}?.mapNotNull {
|
||||||
|
session.getRoomSummary(it)?.toMatrixItem()
|
||||||
|
}?.firstOrNull()
|
||||||
|
val description = if (candidate != null) {
|
||||||
|
stringProvider.getString(R.string.upgrade_room_for_restricted, candidate.getBestName()).toSpannable().let {
|
||||||
|
it.styleMatchingText(candidate.getBestName(), Typeface.BOLD)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stringProvider.getString(R.string.upgrade_room_for_restricted_no_param)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.rules == RoomJoinRules.RESTRICTED && state.upgradeNeededForRestricted) {
|
||||||
|
// let's show the room upgrade bottom sheet
|
||||||
|
_viewEvents.post(
|
||||||
|
RoomJoinRuleChooseRestrictedEvents.NavigateToUpgradeRoom(
|
||||||
|
state.roomId,
|
||||||
|
state.restrictedVersionNeeded ?: "",
|
||||||
|
description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.rules == RoomJoinRules.RESTRICTED && currentRoomJoinRules != RoomJoinRules.RESTRICTED) {
|
||||||
|
// switching to restricted
|
||||||
|
// if allow list is empty, then default to current space parents
|
||||||
|
if (state.updatedAllowList.isEmpty()) {
|
||||||
|
val candidates = session.getRoomSummary(state.roomId)
|
||||||
|
?.flattenParentIds
|
||||||
|
?.filter {
|
||||||
|
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == state.roomId } != null
|
||||||
|
}?.mapNotNull {
|
||||||
|
session.getRoomSummary(it)?.toMatrixItem()
|
||||||
|
}.orEmpty()
|
||||||
|
setState {
|
||||||
|
copy(updatedAllowList = candidates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
currentRoomJoinRules = action.rules
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.rules == RoomJoinRules.RESTRICTED && currentRoomJoinRules == RoomJoinRules.RESTRICTED) {
|
||||||
|
_viewEvents.post(RoomJoinRuleChooseRestrictedEvents.NavigateToChooseRestricted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSwitchToRoom(action: RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration) = withState { state ->
|
||||||
|
viewModelScope.launch {
|
||||||
|
val oldRoomSummary = session.getRoomSummary(state.roomId)
|
||||||
|
val replacementRoomSummary = session.getRoomSummary(action.roomId)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
roomId = action.roomId,
|
||||||
|
roomSummary = replacementRoomSummary?.let { Success(it) } ?: Uninitialized,
|
||||||
|
didSwitchToReplacementRoom = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
initializeForRoom(action.roomId)
|
||||||
|
// set as restricted now
|
||||||
|
val candidates = oldRoomSummary
|
||||||
|
?.flattenParentIds
|
||||||
|
?.filter {
|
||||||
|
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == state.roomId } != null
|
||||||
|
}?.mapNotNull {
|
||||||
|
session.getRoomSummary(it)?.toMatrixItem()
|
||||||
|
}.orEmpty()
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
currentRoomJoinRules = RoomJoinRules.RESTRICTED,
|
||||||
|
updatedAllowList = candidates
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setState { copy(updatingStatus = Loading()) }
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
room.setJoinRuleRestricted(candidates.map { it.id })
|
||||||
|
setState { copy(updatingStatus = Success(Unit)) }
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
setState { copy(updatingStatus = Fail(failure)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleToggleSelection(action: RoomJoinRuleChooseRestrictedActions.ToggleSelection) = withState { state ->
|
||||||
|
val selection = state.updatedAllowList.toMutableList()
|
||||||
|
if (selection.indexOfFirst { action.matrixItem.id == it.id } != -1) {
|
||||||
|
selection.removeAll { it.id == action.matrixItem.id }
|
||||||
|
} else {
|
||||||
|
selection.add(action.matrixItem)
|
||||||
|
}
|
||||||
|
val unknownAllowedOrRooms = mutableListOf<MatrixItem>()
|
||||||
|
|
||||||
|
// we would like to keep initial allowed here to show them unchecked
|
||||||
|
// to make it easier for users to spot the changes
|
||||||
|
val union = mutableListOf<MatrixItem>().apply {
|
||||||
|
addAll(
|
||||||
|
state.initialAllowList.map {
|
||||||
|
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
addAll(selection)
|
||||||
|
}.distinctBy { it.id }.sortedBy { it.id }
|
||||||
|
|
||||||
|
union.forEach { entry ->
|
||||||
|
val summary = session.getRoomSummary(entry.id)
|
||||||
|
if (summary == null) {
|
||||||
|
unknownAllowedOrRooms.add(
|
||||||
|
entry
|
||||||
|
)
|
||||||
|
} else if (summary.roomType != RoomType.SPACE) {
|
||||||
|
unknownAllowedOrRooms.add(entry)
|
||||||
|
} else if (!state.roomSummary.invoke()!!.flattenParentIds.contains(entry.id)) {
|
||||||
|
// it's a space but not a direct parent
|
||||||
|
unknownAllowedOrRooms.add(entry)
|
||||||
|
} else {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
updatedAllowList = selection.toList(),
|
||||||
|
unknownRestricted = unknownAllowedOrRooms
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFilter(action: RoomJoinRuleChooseRestrictedActions.FilterWith) = withState { state ->
|
||||||
|
setState {
|
||||||
|
copy(filter = action.filter, filteredResults = Loading())
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (vectorPreferences.developerMode()) {
|
||||||
|
// in developer mode we let you choose any room or space to restrict to
|
||||||
|
val filteredCandidates = session.getRoomSummaries(
|
||||||
|
roomSummaryQueryParams {
|
||||||
|
excludeType = null
|
||||||
|
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
|
||||||
|
memberships = listOf(Membership.JOIN)
|
||||||
|
}
|
||||||
|
).map { it.toMatrixItem() }
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
filteredResults = Success(filteredCandidates)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// in normal mode you can only restrict to space parents
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
filteredResults = Success(
|
||||||
|
session.getRoomSummary(state.roomId)?.flattenParentIds?.mapNotNull {
|
||||||
|
session.getRoomSummary(it)?.toMatrixItem()
|
||||||
|
}?.filter {
|
||||||
|
it.displayName?.contains(filter, true) == true
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<RoomJoinRuleChooseRestrictedViewModel, RoomJoinRuleChooseRestrictedState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: RoomJoinRuleChooseRestrictedState)
|
||||||
|
: RoomJoinRuleChooseRestrictedViewModel? {
|
||||||
|
val factory = when (viewModelContext) {
|
||||||
|
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||||
|
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||||
|
}
|
||||||
|
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,11 +24,13 @@ import im.vector.app.features.settings.VectorPreferences
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
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.RoomDirectoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
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.CreateRoomPreset
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -93,15 +95,28 @@ class CreateSpaceViewModelTask @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (vectorPreferences.labsUseExperimentalRestricted()) {
|
val homeServerCapabilities = session
|
||||||
|
.getHomeServerCapabilities()
|
||||||
|
val restrictedSupport = homeServerCapabilities
|
||||||
|
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||||
|
|
||||||
|
val createRestricted = when (restrictedSupport) {
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
|
||||||
|
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (createRestricted) {
|
||||||
session.createRoom(CreateRoomParams().apply {
|
session.createRoom(CreateRoomParams().apply {
|
||||||
this.name = roomName
|
this.name = roomName
|
||||||
this.joinRuleRestricted = listOf(
|
this.featurePreset = RestrictedRoomPreset(
|
||||||
|
homeServerCapabilities,
|
||||||
|
listOf(
|
||||||
RoomJoinRulesAllowEntry(
|
RoomJoinRulesAllowEntry(
|
||||||
spaceID = spaceID,
|
spaceID = spaceID,
|
||||||
via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList()
|
via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
if (e2eByDefault) {
|
if (e2eByDefault) {
|
||||||
this.enableEncryption()
|
this.enableEncryption()
|
||||||
}
|
}
|
||||||
|
|
60
vector/src/main/res/layout/fragment_join_rules_recycler.xml
Normal file
60
vector/src/main/res/layout/fragment_join_rules_recycler.xml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:colorBackground">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/genericRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:itemSpacing="1dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/buttonBar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:itemCount="1"
|
||||||
|
tools:listitem="@layout/item_bottom_sheet_joinrule_restricted" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/buttonBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="?vctr_toolbar_background"
|
||||||
|
android:elevation="1dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/genericRecyclerView">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/cancelButton"
|
||||||
|
style="@style/Widget.Vector.Button.Text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/positiveButton"
|
||||||
|
style="@style/Widget.Vector.Button.Positive"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/save" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/waiting_view"
|
||||||
|
layout="@layout/merge_overlay_waiting_view" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
106
vector/src/main/res/layout/fragment_space_restricted_select.xml
Normal file
106
vector/src/main/res/layout/fragment_space_restricted_select.xml
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/coordinatorLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:colorBackground">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
android:id="@+id/spaceExploreCollapsingToolbarLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:contentScrim="?android:colorBackground"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||||
|
app:scrimAnimationDuration="250"
|
||||||
|
app:scrimVisibleHeightTrigger="120dp"
|
||||||
|
app:titleEnabled="false"
|
||||||
|
app:toolbarId="@+id/toolbar">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/Widget.Vector.TextView.ActionBarTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/select_spaces" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:text="@string/decide_which_spaces_can_access"
|
||||||
|
android:textColor="?vctr_content_secondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <com.google.android.material.appbar.MaterialToolbar-->
|
||||||
|
<!-- android:id="@+id/toolbar"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- app:layout_collapseMode="pin"-->
|
||||||
|
<!-- app:title="@string/select_spaces" />-->
|
||||||
|
|
||||||
|
<!-- <TextView-->
|
||||||
|
<!-- style="@style/Widget.Vector.TextView.Body"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:text="@string/decide_which_spaces_can_access" />-->
|
||||||
|
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SearchView
|
||||||
|
android:id="@+id/roomsFilter"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:queryHint="@string/search_hint_room_name" />
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:colorBackground"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
tools:listitem="@layout/item_room_to_add_in_space" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="?vctr_toolbar_background"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Vector.Button.Positive"
|
||||||
|
android:id="@+id/okButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/ok" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,187 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- https://www.figma.com/file/HOGxCoUWoedha639SjD90n/%5BBeta%5D-Restricted-room-access?node-id=58%3A656 -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:minHeight="50dp"
|
||||||
|
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/radioIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="15dp"
|
||||||
|
android:contentDescription="@string/a11y_checked"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="MissingPrefix"
|
||||||
|
tools:src="@drawable/ic_radio_on" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/actionTitle"
|
||||||
|
style="@style/Widget.Vector.TextView.Subtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/room_settings_room_access_restricted_title"
|
||||||
|
android:textColor="?vctr_content_primary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/radioIcon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/radioIcon" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/actionDescription"
|
||||||
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/allow_space_member_to_find_and_access"
|
||||||
|
android:textColor="?vctr_content_secondary"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/actionTitle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/actionTitle"
|
||||||
|
app:layout_goneMarginTop="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/upgradeRequiredButton"
|
||||||
|
style="@style/Widget.Vector.Button.Outlined"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/upgrade_required"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/actionTitle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/actionDescription"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/listTitle"
|
||||||
|
style="@style/Widget.Vector.TextView.Body.Medium"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/spaces_which_can_access"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="?vctr_content_secondary"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/actionTitle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/upgradeRequiredButton"
|
||||||
|
app:layout_goneMarginTop="8dp" />
|
||||||
|
|
||||||
|
<!-- <androidx.recyclerview.widget.RecyclerView-->
|
||||||
|
<!-- android:id="@+id/restrictedSpaceList"-->
|
||||||
|
<!-- android:layout_width="0dp"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||||
|
<!-- app:layout_constraintStart_toStartOf="@id/actionTitle"-->
|
||||||
|
<!-- app:layout_constraintTop_toBottomOf="@id/listTitle"-->
|
||||||
|
<!-- tools:itemCount="3"-->
|
||||||
|
<!-- tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"-->
|
||||||
|
<!-- tools:listitem="@layout/item_space_simple"-->
|
||||||
|
<!-- tools:orientation="horizontal" />-->
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rest1"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@sample/space_avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rest2"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@sample/space_avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rest3"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@sample/space_avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rest4"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@sample/space_avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rest5"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@sample/space_avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/rest6"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/rounded_rect_shape_8"
|
||||||
|
android:backgroundTint="?vctr_header_background"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_more_horizontal"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tint="?vctr_content_secondary"
|
||||||
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.helper.widget.Flow
|
||||||
|
android:id="@+id/spacesFlow"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:constraint_referenced_ids="rest1,rest2,rest3,rest4, rest5, rest6"
|
||||||
|
app:flow_horizontalBias="0"
|
||||||
|
app:flow_horizontalGap="4dp"
|
||||||
|
app:flow_horizontalStyle="packed"
|
||||||
|
app:flow_verticalGap="4dp"
|
||||||
|
app:flow_wrapMode="aligned"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/actionTitle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/listTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/helperText"
|
||||||
|
style="@style/Widget.Vector.TextView.Body.Medium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/tap_to_edit_spaces"
|
||||||
|
android:textColor="?vctr_content_secondary"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/actionTitle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/spacesFlow"
|
||||||
|
app:layout_goneMarginTop="0dp" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1475,11 +1475,21 @@
|
||||||
<string name="room_settings_room_access_entry_knock">Anyone can knock on the room, members can then accept or reject</string>
|
<string name="room_settings_room_access_entry_knock">Anyone can knock on the room, members can then accept or reject</string>
|
||||||
<string name="room_settings_room_access_entry_unknown">Unknown access setting (%s)</string>
|
<string name="room_settings_room_access_entry_unknown">Unknown access setting (%s)</string>
|
||||||
<string name="room_settings_room_access_private_title">Private</string>
|
<string name="room_settings_room_access_private_title">Private</string>
|
||||||
|
<string name="room_settings_room_access_private_invite_only_title">Private (Invite Only)</string>
|
||||||
<string name="room_settings_room_access_private_description">Only people invited can find and join</string>
|
<string name="room_settings_room_access_private_description">Only people invited can find and join</string>
|
||||||
<string name="room_settings_room_access_public_title">Public</string>
|
<string name="room_settings_room_access_public_title">Public</string>
|
||||||
<string name="room_settings_room_access_public_description">Anyone can find the room and join</string>
|
<string name="room_settings_room_access_public_description">Anyone can find the room and join</string>
|
||||||
<string name="room_settings_room_access_restricted_title">Spaces</string>
|
<string name="room_settings_room_access_restricted_title">Space members only</string>
|
||||||
<string name="room_settings_room_access_restricted_description">Anyone in a space with this room can find and join it. Only admins of this room can add it to a space.</string>
|
<string name="room_settings_room_access_restricted_description">Anyone in a space with this room can find and join it. Only admins of this room can add it to a space.</string>
|
||||||
|
<string name="room_create_member_of_space_name_can_join">Members of Space %s can find, preview and join.</string>
|
||||||
|
<string name="allow_space_member_to_find_and_access">Allow space members to find and access.</string>
|
||||||
|
<string name="spaces_which_can_access">Spaces which can access</string>
|
||||||
|
<string name="decide_which_spaces_can_access">Decide which spaces can access this room. If a space is selected its members will be able to find and join Room name.</string>
|
||||||
|
<string name="select_spaces">Select spaces</string>
|
||||||
|
<string name="tap_to_edit_spaces">Tap to edit spaces</string>
|
||||||
|
<string name="decide_who_can_find_and_join">Decide who can find and join this room.</string>
|
||||||
|
<string name="space_you_know_that_contains_this_room">Space you know that contain this room</string>
|
||||||
|
<string name="other_spaces_or_rooms_you_might_not_know">Other spaces or rooms you might not know</string>
|
||||||
|
|
||||||
<!-- Room settings: banned users -->
|
<!-- Room settings: banned users -->
|
||||||
<string name="room_settings_banned_users_title">Banned users</string>
|
<string name="room_settings_banned_users_title">Banned users</string>
|
||||||
|
@ -3435,6 +3445,7 @@
|
||||||
<string name="it_may_take_some_time">Please be patient, it may take some time.</string>
|
<string name="it_may_take_some_time">Please be patient, it may take some time.</string>
|
||||||
|
|
||||||
<string name="upgrade">Upgrade</string>
|
<string name="upgrade">Upgrade</string>
|
||||||
|
<string name="upgrade_required">Upgrade Required</string>
|
||||||
<string name="upgrade_public_room">Upgrade public room</string>
|
<string name="upgrade_public_room">Upgrade public room</string>
|
||||||
<string name="upgrade_private_room">Upgrade private room</string>
|
<string name="upgrade_private_room">Upgrade private room</string>
|
||||||
<string name="upgrade_room_warning">Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.\nThis usually only affects how the room is processed on the server.</string>
|
<string name="upgrade_room_warning">Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.\nThis usually only affects how the room is processed on the server.</string>
|
||||||
|
@ -3442,6 +3453,7 @@
|
||||||
<string name="upgrade_room_auto_invite">Automatically invite users</string>
|
<string name="upgrade_room_auto_invite">Automatically invite users</string>
|
||||||
<string name="upgrade_room_update_parent_space">Automatically update space parent</string>
|
<string name="upgrade_room_update_parent_space">Automatically update space parent</string>
|
||||||
<string name="upgrade_room_no_power_to_manage">You need permission to upgrade a room</string>
|
<string name="upgrade_room_no_power_to_manage">You need permission to upgrade a room</string>
|
||||||
|
<string name="allow_anyone_in_room_to_access">Allow anyone in %s to find and access. You can select other spaces too.</string>
|
||||||
|
|
||||||
<string name="room_using_unstable_room_version">This room is running room version %s, which this homeserver has marked as unstable.</string>
|
<string name="room_using_unstable_room_version">This room is running room version %s, which this homeserver has marked as unstable.</string>
|
||||||
<string name="room_upgrade_to_recommended_version">Upgrade to the recommended room version</string>
|
<string name="room_upgrade_to_recommended_version">Upgrade to the recommended room version</string>
|
||||||
|
@ -3463,4 +3475,9 @@
|
||||||
<string name="error_voice_message_unable_to_record">Cannot record a voice message</string>
|
<string name="error_voice_message_unable_to_record">Cannot record a voice message</string>
|
||||||
<string name="error_voice_message_cannot_reply_or_edit">Cannot reply or edit while voice message is active</string>
|
<string name="error_voice_message_cannot_reply_or_edit">Cannot reply or edit while voice message is active</string>
|
||||||
<string name="voice_message_reply_content">Voice Message (%1$s)</string>
|
<string name="voice_message_reply_content">Voice Message (%1$s)</string>
|
||||||
|
|
||||||
|
<string name="upgrade_room_for_restricted">Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
||||||
|
<string name="upgrade_room_for_restricted_no_param">Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.</string>
|
||||||
|
|
||||||
|
<string name="upgrade_room_for_restricted_note">Please note upgrading will make a new version of the room. All current messages will stay in this archived room.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue