Merge pull request #3912 from vector-im/feature/bca/m11.10-14

Better expose adding spaces as Subspaces
This commit is contained in:
Benoit Marty 2021-09-17 15:21:14 +02:00 committed by GitHub
commit d0e43a2ebe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 536 additions and 111 deletions

1
changelog.d/3752.feature Normal file
View file

@ -0,0 +1 @@
Better expose adding spaces as Subspaces

View file

@ -8,6 +8,7 @@
<attr name="actionDescription" format="string" />
<attr name="leftIcon" format="reference" />
<attr name="rightIcon" format="reference" />
<attr name="betaAction" format="boolean" />
<attr name="forceStartPadding" format="boolean" />
</declare-styleable>

View file

@ -59,7 +59,20 @@ class AppStateHandler @Inject constructor(
val selectedRoomGroupingObservable = selectedSpaceDataSource.observe()
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? = selectedSpaceDataSource.currentValue?.orNull()
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
// XXX we should somehow make it live :/ just a work around
// For example just after creating a space and switching to it the
// name in the app Bar could show Empty Room, and it will not update unless you
// switch space
return selectedSpaceDataSource.currentValue?.orNull()?.let {
if (it is RoomGroupingMethod.BySpace) {
// try to refresh sum?
it.spaceSummary?.roomId?.let { activeSessionHolder.getSafeActiveSession()?.getRoomSummary(it) }?.let {
RoomGroupingMethod.BySpace(it)
} ?: it
} else it
}
}
fun setCurrentSpace(spaceId: String?, session: Session? = null) {
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return

View file

@ -16,11 +16,14 @@
package im.vector.app.core.extensions
import android.graphics.drawable.Drawable
import android.text.InputType
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import im.vector.app.R
/**
@ -50,3 +53,8 @@ fun View.getMeasurements(): Pair<Int, Int> {
val height = measuredHeight
return width to height
}
fun ImageView.setDrawableOrHide(drawableRes: Drawable?) {
setImageDrawable(drawableRes)
isVisible = drawableRes != null
}

View file

@ -26,6 +26,7 @@ import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.setDrawableOrHide
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.ViewBottomSheetActionButtonBinding
import im.vector.app.features.themes.ThemeUtils
@ -80,7 +81,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
var rightIcon: Drawable? = null
set(value) {
field = value
views.bottomSheetActionIcon.setImageDrawable(value)
views.bottomSheetActionIcon.setDrawableOrHide(value)
}
var tint: Int? = null
@ -95,6 +96,12 @@ class BottomSheetActionButton @JvmOverloads constructor(
value?.let { views.bottomSheetActionTitle.setTextColor(it) }
}
var isBetaAction: Boolean? = null
set(value) {
field = value
views.bottomSheetActionBeta.isVisible = field ?: false
}
init {
inflate(context, R.layout.view_bottom_sheet_action_button, this)
views = ViewBottomSheetActionButtonBinding.bind(this)
@ -109,6 +116,8 @@ class BottomSheetActionButton @JvmOverloads constructor(
tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor))
titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ThemeUtils.getColor(context, R.attr.colorPrimary))
isBetaAction = getBoolean(R.styleable.BottomSheetActionButton_betaAction, false)
}
}
}

View file

@ -29,6 +29,7 @@ import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.badge.BadgeDrawable
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.extensions.commitTransaction
@ -69,7 +70,8 @@ class HomeDetailFragment @Inject constructor(
private val colorProvider: ColorProvider,
private val alertManager: PopupAlertManager,
private val callManager: WebRtcCallManager,
private val vectorPreferences: VectorPreferences
private val vectorPreferences: VectorPreferences,
private val appStateHandler: AppStateHandler
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
KeysBackupBanner.Delegate,
CurrentCallsView.Callback,
@ -204,6 +206,18 @@ class HomeDetailFragment @Inject constructor(
// update notification tab if needed
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
callManager.checkForProtocolsSupportIfNeeded()
// Current space/group is not live so at least refresh toolbar on resume
appStateHandler.getCurrentRoomGroupingMethod()?.let { roomGroupingMethod ->
when (roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> {
onGroupChange(roomGroupingMethod.groupSummary)
}
is RoomGroupingMethod.BySpace -> {
onSpaceChange(roomGroupingMethod.spaceSummary)
}
}
}
}
private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) {

View file

@ -70,7 +70,6 @@ class MatrixToRoomSpaceFragment @Inject constructor(
val matrixItem = peek.roomItem
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
if (peek.roomType == RoomType.SPACE) {
views.matrixToBetaTag.isVisible = true
views.matrixToAccessImage.isVisible = true
if (peek.isPublic) {
views.matrixToAccessText.setTextOrHide(context?.getString(R.string.public_space))
@ -79,8 +78,6 @@ class MatrixToRoomSpaceFragment @Inject constructor(
views.matrixToAccessText.setTextOrHide(context?.getString(R.string.private_space))
views.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
}
} else {
views.matrixToBetaTag.isVisible = false
}
views.matrixToCardNameText.setTextOrHide(peek.name)
views.matrixToCardAliasText.setTextOrHide(peek.alias)

View file

@ -50,7 +50,6 @@ class SpaceCardRenderer @Inject constructor(
inCard.matrixToCardButtonLoading.isVisible = false
avatarRenderer.render(spaceSummary.toMatrixItem(), inCard.matrixToCardAvatar)
inCard.matrixToCardNameText.text = spaceSummary.name
inCard.matrixToBetaTag.isVisible = true
inCard.matrixToCardAliasText.setTextOrHide(spaceSummary.canonicalAlias)
inCard.matrixToCardDescText.setTextOrHide(spaceSummary.topic.linkify(matrixLinkCallback))
if (spaceSummary.isPublic) {
@ -97,7 +96,6 @@ class SpaceCardRenderer @Inject constructor(
inCard.matrixToCardButtonLoading.isVisible = false
avatarRenderer.render(spaceChildInfo.toMatrixItem(), inCard.matrixToCardAvatar)
inCard.matrixToCardNameText.setTextOrHide(spaceChildInfo.name)
inCard.matrixToBetaTag.isVisible = true
inCard.matrixToCardAliasText.setTextOrHide(spaceChildInfo.canonicalAlias)
inCard.matrixToCardDescText.setTextOrHide(spaceChildInfo.topic?.linkify(matrixLinkCallback))
if (spaceChildInfo.worldReadable) {

View file

@ -53,7 +53,10 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
addFragment(
R.id.simpleFragmentContainer,
CreateRoomFragment::class.java,
CreateRoomArgs(intent?.getStringExtra(INITIAL_NAME) ?: "")
CreateRoomArgs(
intent?.getStringExtra(INITIAL_NAME) ?: "",
isSpace = intent?.getBooleanExtra(IS_SPACE, false) ?: false
)
)
}
}
@ -74,10 +77,12 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
companion object {
private const val INITIAL_NAME = "INITIAL_NAME"
private const val IS_SPACE = "IS_SPACE"
fun getIntent(context: Context, initialName: String = ""): Intent {
fun getIntent(context: Context, initialName: String = "", isSpace: Boolean = false): Intent {
return Intent(context, CreateRoomActivity::class.java).apply {
putExtra(INITIAL_NAME, initialName)
putExtra(IS_SPACE, isSpace)
}
}
}

View file

@ -38,6 +38,7 @@ import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentCreateRoomBinding
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
@ -51,11 +52,13 @@ import javax.inject.Inject
@Parcelize
data class CreateRoomArgs(
val initialName: String,
val parentSpaceId: String? = null
val parentSpaceId: String? = null,
val isSpace: Boolean = false
) : Parcelable
class CreateRoomFragment @Inject constructor(
private val createRoomController: CreateRoomController,
private val createSpaceController: CreateSubSpaceController,
val createRoomViewModelFactory: CreateRoomViewModel.Factory,
colorProvider: ColorProvider
) : VectorBaseFragment<FragmentCreateRoomBinding>(),
@ -93,6 +96,11 @@ class CreateRoomFragment @Inject constructor(
}
}
override fun onResume() {
super.onResume()
views.createRoomTitle.text = getString(if (args.isSpace) R.string.create_new_space else R.string.create_new_room)
}
private fun setupRoomJoinRuleSharedActionViewModel() {
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
roomJoinRuleSharedActionViewModel
@ -112,18 +120,26 @@ class CreateRoomFragment @Inject constructor(
private fun setupWaitingView() {
views.waitingView.waitingStatusText.isVisible = true
views.waitingView.waitingStatusText.setText(R.string.create_room_in_progress)
views.waitingView.waitingStatusText.setText(
if (args.isSpace) R.string.create_space_in_progress else R.string.create_room_in_progress
)
}
override fun onDestroyView() {
views.createRoomForm.cleanup()
createRoomController.listener = null
createSpaceController.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
views.createRoomForm.configureWith(createRoomController)
createRoomController.listener = this
if (args.isSpace) {
views.createRoomForm.configureWith(createSpaceController)
createSpaceController.listener = this
} else {
views.createRoomForm.configureWith(createRoomController)
createRoomController.listener = this
}
}
override fun onAvatarDelete() {
@ -153,7 +169,11 @@ class CreateRoomFragment @Inject constructor(
} else {
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC)
}
RoomJoinRuleBottomSheet.newInstance(state.roomJoinRules, allowed.map { it.toOption(false) })
RoomJoinRuleBottomSheet.newInstance(state.roomJoinRules,
allowed.map { it.toOption(false) },
state.isSubSpace,
state.parentSpaceSummary?.displayName
)
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
}
// override fun setIsPublic(isPublic: Boolean) {
@ -203,12 +223,24 @@ class CreateRoomFragment @Inject constructor(
views.waitingView.root.isVisible = async is Loading
if (async is Success) {
// Navigate to freshly created room
navigator.openRoom(requireActivity(), async())
if (state.isSubSpace) {
navigator.switchToSpace(
requireContext(),
async(),
Navigator.PostSwitchSpaceAction.None
)
} else {
navigator.openRoom(requireActivity(), async())
}
sharedActionViewModel.post(RoomDirectorySharedAction.Close)
} else {
// Populate list with Epoxy
createRoomController.setData(state)
if (args.isSpace) {
createSpaceController.setData(state)
} else {
createRoomController.setData(state)
}
}
}
}

View file

@ -42,9 +42,11 @@ 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.failure.CreateRoomFailure
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.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
@ -241,6 +243,18 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
name = state.roomName.takeIf { it.isNotBlank() }
topic = state.roomTopic.takeIf { it.isNotBlank() }
avatarUri = state.avatarUri
if (state.isSubSpace) {
// Space-rooms are distinguished from regular messaging rooms by the m.room.type of m.space
roomType = RoomType.SPACE
// Space-rooms should be created with a power level for events_default of 100,
// to prevent the rooms accidentally/maliciously clogging up with messages from random members of the space.
powerLevelContentOverride = PowerLevelsContent(
eventsDefault = 100
)
}
when (state.roomJoinRules) {
RoomJoinRules.PUBLIC -> {
// Directory visibility

View file

@ -37,12 +37,14 @@ data class CreateRoomViewState(
val parentSpaceId: String?,
val parentSpaceSummary: RoomSummary? = null,
val supportsRestricted: Boolean = false,
val aliasLocalPart: String? = null
val aliasLocalPart: String? = null,
val isSubSpace: Boolean = false
) : MvRxState {
constructor(args: CreateRoomArgs) : this(
roomName = args.initialName,
parentSpaceId = args.parentSpaceId
parentSpaceId = args.parentSpaceId,
isSubSpace = args.isSpace
)
/**

View file

@ -0,0 +1,153 @@
/*
* 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.roomdirectory.createroom
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import im.vector.app.R
import im.vector.app.core.epoxy.profiles.buildProfileAction
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericPillItem
import im.vector.app.features.discovery.settingsSectionTitleItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formEditableSquareAvatarItem
import im.vector.app.features.form.formMultiLineEditTextItem
import im.vector.app.features.form.formSubmitButtonItem
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject
class CreateSubSpaceController @Inject constructor(
private val stringProvider: StringProvider,
private val roomAliasErrorFormatter: RoomAliasErrorFormatter
) : TypedEpoxyController<CreateRoomViewState>() {
var listener: CreateRoomController.Listener? = null
override fun buildModels(viewState: CreateRoomViewState) {
// display the form
buildForm(viewState, viewState.asyncCreateRoomRequest !is Loading)
}
private fun buildForm(data: CreateRoomViewState, enableFormElement: Boolean) {
val host = this
genericPillItem {
id("beta")
imageRes(R.drawable.ic_beta_pill)
tintIcon(false)
text(host.stringProvider.getString(R.string.space_add_space_to_any_space_you_manage))
}
formEditableSquareAvatarItem {
id("avatar")
enabled(enableFormElement)
imageUri(data.avatarUri)
clickListener { host.listener?.onAvatarChange() }
deleteListener { host.listener?.onAvatarDelete() }
}
formEditTextItem {
id("name")
enabled(enableFormElement)
enabled(true)
value(data.roomName)
hint(host.stringProvider.getString(R.string.create_room_name_hint))
onTextChange { text ->
host.listener?.onNameChange(text)
}
}
if (data.roomJoinRules == RoomJoinRules.PUBLIC) {
formEditTextItem {
id("alias")
enabled(enableFormElement)
value(data.aliasLocalPart)
hint(host.stringProvider.getString(R.string.create_space_alias_hint))
suffixText(":" + data.homeServerName)
prefixText("#")
errorMessage(
host.roomAliasErrorFormatter.format(
(((data.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError)
)
onTextChange { value ->
host.listener?.setAliasLocalPart(value)
}
}
}
formMultiLineEditTextItem {
id("topic")
enabled(enableFormElement)
value(data.roomTopic)
hint(host.stringProvider.getString(R.string.create_space_topic_hint))
textSizeSp(16)
onTextChange { text ->
host.listener?.onTopicChange(text)
}
}
settingsSectionTitleItem {
id("visibility")
titleResId(R.string.room_settings_space_access_title)
}
when (data.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, data.parentSpaceSummary?.displayName),
divider = false,
editable = true,
action = { host.listener?.selectVisibility() }
)
}
else -> {
// not yet supported
}
}
formSubmitButtonItem {
id("submit")
enabled(enableFormElement && data.roomName.isNullOrBlank().not())
buttonTitleId(R.string.create_room_action_create)
buttonClickListener { host.listener?.submit() }
}
}
}

View file

@ -39,7 +39,9 @@ fun RoomJoinRules.toOption(needUpgrade: Boolean) = JoinRulesOptionSupport(this,
@Parcelize
data class RoomJoinRuleBottomSheetArgs(
val currentRoomJoinRule: RoomJoinRules,
val allowedJoinedRules: List<JoinRulesOptionSupport>
val allowedJoinedRules: List<JoinRulesOptionSupport>,
val isSpace: Boolean = false,
val parentSpaceName: String?
) : Parcelable
class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRuleRadioAction>() {
@ -73,11 +75,13 @@ class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRu
fun newInstance(currentRoomJoinRule: RoomJoinRules,
allowedJoinedRules: List<JoinRulesOptionSupport> = listOf(
RoomJoinRules.INVITE, RoomJoinRules.PUBLIC
).map { it.toOption(true) }
).map { it.toOption(true) },
isSpace: Boolean = false,
parentSpaceName: String? = null
): RoomJoinRuleBottomSheet {
return RoomJoinRuleBottomSheet().apply {
setArguments(
RoomJoinRuleBottomSheetArgs(currentRoomJoinRule, allowedJoinedRules)
RoomJoinRuleBottomSheetArgs(currentRoomJoinRule, allowedJoinedRules, isSpace, parentSpaceName)
)
}
}

View file

@ -20,8 +20,6 @@ import im.vector.app.R
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController
import me.gujun.android.span.image
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject
@ -30,7 +28,11 @@ class RoomJoinRuleController @Inject constructor(
private val drawableProvider: DrawableProvider
) : BottomSheetGenericController<RoomJoinRuleState, RoomJoinRuleRadioAction>() {
override fun getTitle() = stringProvider.getString(R.string.room_settings_room_access_rules_pref_dialog_title)
override fun getTitle() =
stringProvider.getString(
// generic title for both room and space
R.string.room_settings_access_rules_pref_dialog_title
)
override fun getActions(state: RoomJoinRuleState): List<RoomJoinRuleRadioAction> {
return listOf(
@ -42,21 +44,21 @@ class RoomJoinRuleController @Inject constructor(
),
RoomJoinRuleRadioAction(
roomJoinRule = RoomJoinRules.PUBLIC,
description = stringProvider.getString(R.string.room_settings_room_access_public_description),
description = stringProvider.getString(
if (state.isSpace) R.string.room_settings_space_access_public_description
else R.string.room_settings_room_access_public_description
),
title = stringProvider.getString(R.string.room_settings_room_access_public_title),
isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC
),
RoomJoinRuleRadioAction(
roomJoinRule = RoomJoinRules.RESTRICTED,
description = stringProvider.getString(R.string.room_settings_room_access_restricted_description),
title = span {
+stringProvider.getString(R.string.room_settings_room_access_restricted_title)
+" "
image(
drawableProvider.getDrawable(R.drawable.ic_beta_pill)!!,
"bottom"
)
description = if (state.parentSpaceName != null) {
stringProvider.getString(R.string.room_create_member_of_space_name_can_join, state.parentSpaceName)
} else {
stringProvider.getString(R.string.room_settings_room_access_restricted_description)
},
title = stringProvider.getString(R.string.room_settings_room_access_restricted_title),
isSelected = state.currentRoomJoinRule == RoomJoinRules.RESTRICTED
)
).filter { state.allowedJoinedRules.map { it.rule }.contains(it.roomJoinRule) }

View file

@ -24,11 +24,15 @@ data class RoomJoinRuleState(
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,
val isSpace: Boolean = false,
val parentSpaceName: String?
) : BottomSheetGenericState() {
constructor(args: RoomJoinRuleBottomSheetArgs) : this(
currentRoomJoinRule = args.currentRoomJoinRule,
allowedJoinedRules = args.allowedJoinedRules
allowedJoinedRules = args.allowedJoinedRules,
isSpace = args.isSpace,
parentSpaceName = args.parentSpaceName
)
}

View file

@ -33,7 +33,6 @@ import im.vector.app.databinding.BottomSheetSpaceSettingsBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity
@ -79,10 +78,6 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.spaceBetaTag.debouncedClicks {
bugReporter.openBugReportScreen(requireActivity(), ReportType.SPACE_BETA_FEEDBACK)
}
views.invitePeople.views.bottomSheetActionClickableZone.debouncedClicks {
dismiss()
interactionListener?.onShareSpaceSelected(spaceArgs.spaceId)
@ -106,6 +101,11 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
startActivity(SpaceManageActivity.newIntent(requireActivity(), spaceArgs.spaceId, ManageType.AddRooms))
}
views.addSpaces.views.bottomSheetActionClickableZone.debouncedClicks {
dismiss()
startActivity(SpaceManageActivity.newIntent(requireActivity(), spaceArgs.spaceId, ManageType.AddRoomsOnlySpaces))
}
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {
LeaveSpaceBottomSheet.newInstance(spaceArgs.spaceId).show(childFragmentManager, "LOGOUT")
}
@ -128,6 +128,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
views.invitePeople.isVisible = state.canInvite || state.spaceSummary?.isPublic.orFalse()
views.addRooms.isVisible = state.canAddChild
views.addSpaces.isVisible = state.canAddChild
}
companion object {

View file

@ -34,7 +34,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
private val views: ViewSpaceTypeButtonBinding
var title: String? = null
var title: CharSequence? = null
set(value) {
if (value != title) {
field = value

View file

@ -44,6 +44,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.matrixto.SpaceCardRenderer
import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceAddRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.manage.SpaceManageActivity
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@ -75,6 +76,27 @@ class SpaceDirectoryFragment @Inject constructor(
private val viewModel by activityViewModel(SpaceDirectoryViewModel::class)
private val epoxyVisibilityTracker = EpoxyVisibilityTracker()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener(SpaceAddRoomSpaceChooserBottomSheet.REQUEST_KEY, this) { _, bundle ->
bundle.getString(SpaceAddRoomSpaceChooserBottomSheet.BUNDLE_KEY_ACTION)?.let { action ->
val spaceId = withState(viewModel) { it.spaceId }
when (action) {
SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_ROOMS -> {
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms))
}
SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_SPACES -> {
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRoomsOnlySpaces))
}
else -> {
// nop
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -170,7 +192,7 @@ class SpaceDirectoryFragment @Inject constructor(
}
override fun addExistingRooms(spaceId: String) {
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms))
SpaceAddRoomSpaceChooserBottomSheet.newInstance().show(childFragmentManager, "SpaceAddRoomSpaceChooserBottomSheet")
}
override fun loadAdditionalItemsIfNeeded() {

View file

@ -27,6 +27,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.R
@ -109,11 +110,25 @@ class SpaceAddRoomFragment @Inject constructor(
}.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::shouldShowDMs) {
dmEpoxyController.disabled = !it
dmEpoxyController.disabled = !it
}.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::onlyShowSpaces) {
spaceEpoxyController.disabled = !it
roomEpoxyController.disabled = it
views.createNewRoom.text = if (it) getString(R.string.create_space) else getString(R.string.create_new_room)
val title = if (it) getString(R.string.space_add_existing_spaces) else getString(R.string.space_add_existing_rooms_only)
views.appBarTitle.text = title
}.disposeOnDestroyView()
views.createNewRoom.debouncedClicks {
sharedViewModel.handle(SpaceManagedSharedAction.CreateRoom)
withState(viewModel) { state ->
if (state.onlyShowSpaces) {
sharedViewModel.handle(SpaceManagedSharedAction.CreateSpace)
} else {
sharedViewModel.handle(SpaceManagedSharedAction.CreateRoom)
}
}
}
viewModel.observeViewEvents {

View file

@ -0,0 +1,64 @@
/*
* 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.spaces.manage
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.setFragmentResult
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetAddRoomsOrSpacesToSpaceBinding
class SpaceAddRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetAddRoomsOrSpacesToSpaceBinding>() {
override val showExpanded = true
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
BottomSheetAddRoomsOrSpacesToSpaceBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.addSpaces.views.bottomSheetActionClickableZone.debouncedClicks {
setFragmentResult(REQUEST_KEY, Bundle().apply {
putString(BUNDLE_KEY_ACTION, ACTION_ADD_SPACES)
})
dismiss()
}
views.addRooms.views.bottomSheetActionClickableZone.debouncedClicks {
setFragmentResult(REQUEST_KEY, Bundle().apply {
putString(BUNDLE_KEY_ACTION, ACTION_ADD_ROOMS)
})
dismiss()
}
}
companion object {
const val REQUEST_KEY = "SpaceAddRoomSpaceChooserBottomSheet"
const val BUNDLE_KEY_ACTION = "SpaceAddRoomSpaceChooserBottomSheet.Action"
const val ACTION_ADD_ROOMS = "Action.AddRoom"
const val ACTION_ADD_SPACES = "Action.AddSpaces"
fun newInstance()
: SpaceAddRoomSpaceChooserBottomSheet {
return SpaceAddRoomSpaceChooserBottomSheet()
}
}
}

View file

@ -27,10 +27,12 @@ data class SpaceAddRoomsState(
val spaceName: String = "",
val ignoreRooms: List<String> = emptyList(),
val isSaving: Async<List<String>> = Uninitialized,
val shouldShowDMs : Boolean = false
val shouldShowDMs: Boolean = false,
val onlyShowSpaces: Boolean = false
// val selectionList: Map<String, Boolean> = emptyMap()
) : MvRxState {
constructor(args: SpaceManageArgs) : this(
spaceId = args.spaceId
spaceId = args.spaceId,
onlyShowSpaces = args.manageType == ManageType.AddRoomsOnlySpaces
)
}

View file

@ -127,7 +127,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
copy(
spaceName = spaceSummary?.displayName ?: "",
ignoreRooms = (spaceSummary?.flattenParentIds ?: emptyList()) + listOf(initialState.spaceId),
shouldShowDMs = spaceSummary?.isPublic == false
shouldShowDMs = !onlyShowSpaces && spaceSummary?.isPublic == false
)
}
}

View file

@ -99,7 +99,8 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleLoadingBinding>(),
if (isFirstCreation()) {
withState(sharedViewModel) {
when (it.manageType) {
ManageType.AddRooms -> {
ManageType.AddRooms,
ManageType.AddRoomsOnlySpaces -> {
val simpleName = SpaceAddRoomFragment::class.java.simpleName
if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
supportFragmentManager.commitTransaction {
@ -148,7 +149,14 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleLoadingBinding>(),
CreateRoomArgs("", parentSpaceId = args?.spaceId)
)
}
SpaceManagedSharedViewEvents.NavigateToManageRooms -> {
SpaceManagedSharedViewEvents.NavigateToCreateSpace -> {
addFragmentToBackstack(
R.id.simpleFragmentContainer,
CreateRoomFragment::class.java,
CreateRoomArgs("", parentSpaceId = args?.spaceId, isSpace = true)
)
}
SpaceManagedSharedViewEvents.NavigateToManageRooms -> {
args?.spaceId?.let { spaceId ->
addFragmentToBackstack(
R.id.simpleFragmentContainer,

View file

@ -23,6 +23,7 @@ import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.session.Session
@ -55,9 +56,10 @@ class SpaceManageSharedViewModel @AssistedInject constructor(
SpaceManagedSharedAction.HideLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.HideLoading)
SpaceManagedSharedAction.ShowLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.ShowLoading)
SpaceManagedSharedAction.CreateRoom -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToCreateRoom)
SpaceManagedSharedAction.CreateSpace -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToCreateSpace)
SpaceManagedSharedAction.ManageRooms -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToManageRooms)
SpaceManagedSharedAction.OpenSpaceAliasesSettings -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToAliasSettings)
SpaceManagedSharedAction.OpenSpacePermissionSettings -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToPermissionSettings)
}
}.exhaustive
}
}

View file

@ -20,6 +20,7 @@ import com.airbnb.mvrx.MvRxState
enum class ManageType {
AddRooms,
AddRoomsOnlySpaces,
Settings,
ManageRooms
}

View file

@ -23,6 +23,7 @@ sealed class SpaceManagedSharedAction : VectorViewModelAction {
object ShowLoading : SpaceManagedSharedAction()
object HideLoading : SpaceManagedSharedAction()
object CreateRoom : SpaceManagedSharedAction()
object CreateSpace : SpaceManagedSharedAction()
object ManageRooms : SpaceManagedSharedAction()
object OpenSpaceAliasesSettings : SpaceManagedSharedAction()
object OpenSpacePermissionSettings : SpaceManagedSharedAction()

View file

@ -23,6 +23,7 @@ sealed class SpaceManagedSharedViewEvents : VectorViewEvents {
object ShowLoading : SpaceManagedSharedViewEvents()
object HideLoading : SpaceManagedSharedViewEvents()
object NavigateToCreateRoom : SpaceManagedSharedViewEvents()
object NavigateToCreateSpace : SpaceManagedSharedViewEvents()
object NavigateToManageRooms : SpaceManagedSharedViewEvents()
object NavigateToAliasSettings : SpaceManagedSharedViewEvents()
object NavigateToPermissionSettings : SpaceManagedSharedViewEvents()

View file

@ -25,7 +25,6 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formEditableSquareAvatarItem
import im.vector.app.features.form.formMultiLineEditTextItem
import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.RoomSettingsViewState
import im.vector.app.features.settings.VectorPreferences
@ -106,27 +105,35 @@ class SpaceSettingsController @Inject constructor(
}
val isPublic = (data.newRoomJoinRules.newJoinRules ?: data.currentRoomJoinRules) == RoomJoinRules.PUBLIC
if (vectorPreferences.labsUseExperimentalRestricted()) {
buildProfileAction(
id = "joinRule",
title = stringProvider.getString(R.string.room_settings_room_access_title),
subtitle = data.getJoinRuleWording(stringProvider),
divider = false,
editable = data.actionPermissions.canChangeJoinRule,
action = { if (data.actionPermissions.canChangeJoinRule) callback?.onJoinRuleClicked() }
)
} else {
formSwitchItem {
id("isPublic")
enabled(data.actionPermissions.canChangeJoinRule)
title(host.stringProvider.getString(R.string.make_this_space_public))
switchChecked(isPublic)
listener { value ->
host.callback?.setIsPublic(value)
}
}
}
buildProfileAction(
id = "joinRule",
title = stringProvider.getString(R.string.room_settings_space_access_title),
subtitle = data.getJoinRuleWording(stringProvider),
divider = true,
editable = data.actionPermissions.canChangeJoinRule,
action = { if (data.actionPermissions.canChangeJoinRule) callback?.onJoinRuleClicked() }
)
// if (vectorPreferences.labsUseExperimentalRestricted()) {
// buildProfileAction(
// id = "joinRule",
// title = stringProvider.getString(R.string.room_settings_room_access_title),
// subtitle = data.getJoinRuleWording(stringProvider),
// divider = false,
// editable = data.actionPermissions.canChangeJoinRule,
// action = { if (data.actionPermissions.canChangeJoinRule) callback?.onJoinRuleClicked() }
// )
// } else {
// formSwitchItem {
// id("isPublic")
// enabled(data.actionPermissions.canChangeJoinRule)
// title(host.stringProvider.getString(R.string.make_this_space_public))
// switchChecked(isPublic)
//
// listener { value ->
// host.callback?.setIsPublic(value)
// }
// }
// }
dividerItem {
id("divider")
}

View file

@ -47,7 +47,7 @@ import im.vector.app.features.roomprofile.settings.RoomSettingsAction
import im.vector.app.features.roomprofile.settings.RoomSettingsViewEvents
import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel
import im.vector.app.features.roomprofile.settings.RoomSettingsViewState
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 org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
@ -133,12 +133,6 @@ class SpaceSettingsFragment @Inject constructor(
state.roomSummary()?.let {
views.roomSettingsToolbarTitleView.text = it.displayName
views.roomSettingsToolbarTitleView.setCompoundDrawablesWithIntrinsicBounds(
null,
null,
drawableProvider.getDrawable(R.drawable.ic_beta_pill),
null
)
avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
}
@ -199,10 +193,8 @@ class SpaceSettingsFragment @Inject constructor(
// N/A for space settings screen
}
override fun onJoinRuleClicked() = withState(viewModel) { state ->
val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules
RoomJoinRuleBottomSheet.newInstance(currentJoinRule)
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
override fun onJoinRuleClicked() {
startActivity(RoomJoinRuleActivity.newIntent(requireContext(), roomProfileArgs.roomId))
}
override fun onToggleGuestAccess() = withState(viewModel) { state ->

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:background="?android:colorBackground"
android:orientation="vertical">
<TextView
android:id="@+id/headerText"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/space_add_existing_rooms"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/addRooms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:actionTitle="@string/space_add_child_title"
app:leftIcon="@drawable/ic_fab_add"
app:tint="?vctr_content_primary"
app:titleTextColor="?vctr_content_primary"
tools:actionDescription="" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/addSpaces"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:actionTitle="@string/add_space"
app:betaAction="true"
app:leftIcon="@drawable/ic_fab_add"
app:tint="?vctr_content_primary"
app:titleTextColor="?vctr_content_primary"
tools:actionDescription="" />
</LinearLayout>

View file

@ -38,21 +38,12 @@
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/spaceDescription"
app:layout_constraintEnd_toStartOf="@id/spaceBetaTag"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/spaceAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@sample/spaces.json/data/name" />
<ImageView
android:id="@+id/spaceBetaTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:contentDescription="@string/a11y_beta"
android:src="@drawable/ic_beta_pill"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/spaceDescription"
@ -129,6 +120,16 @@
app:titleTextColor="?vctr_content_primary"
tools:actionDescription="" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/addSpaces"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:actionTitle="@string/add_space"
app:leftIcon="@drawable/ic_fab_add"
app:tint="?vctr_content_primary"
app:titleTextColor="?vctr_content_primary"
app:betaAction="true"
tools:actionDescription="" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/leaveSpace"

View file

@ -28,18 +28,6 @@
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/room_round_avatars" />
<ImageView
android:id="@+id/matrixToBetaTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:contentDescription="@string/a11y_beta"
android:src="@drawable/ic_beta_pill"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/matrixToCardNameText"
style="@style/Widget.Vector.TextView.Subtitle"

View file

@ -12,7 +12,7 @@
<ImageView
android:id="@+id/itemGenericPillImage"
android:layout_width="20dp"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_info"

View file

@ -15,8 +15,7 @@
android:layout_marginTop="8dp"
android:text="@string/spaces_header"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_beta_pill" />
android:textStyle="bold" />
<TextView
style="@style/Widget.Vector.TextView.Body"

View file

@ -45,7 +45,7 @@
android:textColor="?colorPrimary"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/bottomSheetActionSubTitle"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetActionIcon"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetActionBeta"
app:layout_constraintStart_toEndOf="@+id/bottomSheetActionLeftIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
@ -62,12 +62,23 @@
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetActionIcon"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetActionBeta"
app:layout_constraintStart_toStartOf="@+id/bottomSheetActionTitle"
app:layout_constraintTop_toBottomOf="@+id/bottomSheetActionTitle"
tools:text="For maximum security, do this in person"
tools:visibility="visible" />
<ImageView
android:id="@+id/bottomSheetActionBeta"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetActionIcon"
app:layout_constraintStart_toEndOf="@+id/bottomSheetActionTitle"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/ic_beta_pill" />
<ImageView
android:id="@+id/bottomSheetActionIcon"
android:layout_width="48dp"

View file

@ -1476,7 +1476,9 @@
<string name="room_settings_room_read_history_rules_pref_dialog_title">Who can read history?</string>
<string name="room_settings_room_read_history_dialog_subtitle">Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.</string>
<string name="room_settings_room_access_rules_pref_dialog_title">Who can access this room?</string>
<string name="room_settings_access_rules_pref_dialog_title">Who can access?</string>
<string name="room_settings_room_access_title">Room access</string>
<string name="room_settings_space_access_title">Space access</string>
<string name="room_settings_guest_access_title">Allow guests to join</string>
<!-- room settings : alias -->
@ -1533,6 +1535,7 @@
<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_description">Anyone can find the room and join</string>
<string name="room_settings_space_access_public_description">Anyone can find the space and join</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_create_member_of_space_name_can_join">Members of Space %s can find, preview and join.</string>
@ -2187,6 +2190,7 @@
<string name="malformed_message">Malformed event, cannot display</string>
<string name="create_new_room">Create New Room</string>
<string name="create_new_space">Create New Space</string>
<string name="error_no_network">No network. Please check your Internet connection.</string>
<string name="action_change">"Change"</string>
<string name="change_room_directory_network">"Change network"</string>
@ -2672,6 +2676,7 @@
<string name="create_room_alias_empty">Please provide a room address</string>
<string name="create_room_alias_invalid">Some characters are not allowed</string>
<string name="create_room_in_progress">Creating room…</string>
<string name="create_space_in_progress">Creating space…</string>
<string name="login_error_threepid_denied">Your email domain is not authorized to register on this server</string>
@ -3493,6 +3498,9 @@
<string name="pick_tings_to_leave">Pick things to leave</string>
<string name="space_add_existing_rooms">Add existing rooms and space</string>
<string name="space_add_existing_rooms_only">Add existing rooms</string>
<string name="space_add_existing_spaces">Add existing spaces</string>
<string name="space_add_space_to_any_space_you_manage">Add a space to any space you manage.</string>
<string name="space_add_rooms">Add rooms</string>
<string name="spaces_beta_welcome_to_spaces">Welcome to Spaces!</string>
<string name="spaces_beta_welcome_to_spaces_desc">Spaces are a new way to group rooms and people.</string>