Merge pull request #6150 from element-hq/feature/fga/space_ui_tweaks
Iterate on Space related UI
This commit is contained in:
@@ -82,10 +82,6 @@ class ConfigureRoomPresenter(
|
||||
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
|
||||
private var pendingPermissionRequest = false
|
||||
|
||||
init {
|
||||
dataStore.setIsSpace(isSpace)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): ConfigureRoomState {
|
||||
val canAddRoomToSpace by featureFlagService.isFeatureEnabledFlow(FeatureFlags.CreateSpaces).collectAsState(false)
|
||||
@@ -123,9 +119,10 @@ class ConfigureRoomPresenter(
|
||||
} else {
|
||||
persistentListOf()
|
||||
}
|
||||
|
||||
val parentSpace = spaces.find { it.roomId == initialParentSpaceId }
|
||||
parentSpace?.let { dataStore.setParentSpace(it) }
|
||||
parentSpace?.let {
|
||||
dataStore.setParentSpace(parentSpace = parentSpace, updateVisibility = true)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(cameraPermissionState.permissionGranted) {
|
||||
@@ -152,21 +149,42 @@ class ConfigureRoomPresenter(
|
||||
// 2. If it has a parent space.
|
||||
// 3. If knocking is enabled.
|
||||
val parentSpace = createRoomConfig.parentSpace
|
||||
val availableJoinRules = remember(createRoomConfig.parentSpace, isSpace, isKnockFeatureEnabled) {
|
||||
val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled) {
|
||||
when {
|
||||
isSpace && parentSpace != null -> TODO("Adding a space to a parent space is not supported yet! How did you get here?")
|
||||
parentSpace == null || parentSpace.joinRule == JoinRule.Public -> listOfNotNull(
|
||||
JoinRuleItem.PublicVisibility.Public,
|
||||
JoinRuleItem.PublicVisibility.AskToJoin.takeIf { !isSpace && isKnockFeatureEnabled },
|
||||
JoinRuleItem.Private,
|
||||
JoinRuleItem.PrivateVisibility.Private,
|
||||
).toImmutableList()
|
||||
else -> listOfNotNull(
|
||||
JoinRuleItem.PublicVisibility.Restricted(parentSpace.roomId),
|
||||
JoinRuleItem.PublicVisibility.AskToJoinRestricted(parentSpace.roomId).takeIf { !isSpace && isKnockFeatureEnabled },
|
||||
JoinRuleItem.Private,
|
||||
JoinRuleItem.PrivateVisibility.Restricted(parentSpace.roomId),
|
||||
JoinRuleItem.PrivateVisibility.AskToJoinRestricted(parentSpace.roomId).takeIf { isKnockFeatureEnabled },
|
||||
JoinRuleItem.PrivateVisibility.Private,
|
||||
).toImmutableList()
|
||||
}
|
||||
}
|
||||
val currentJoinRule = createRoomConfig.visibilityState.joinRuleItem
|
||||
LaunchedEffect(availableJoinRules, currentJoinRule) {
|
||||
// Find matching rule by type (ignoring parentSpaceId parameter for Restricted types)
|
||||
val matchingRule = when (currentJoinRule) {
|
||||
is JoinRuleItem.PrivateVisibility.Restricted ->
|
||||
availableJoinRules.filterIsInstance<JoinRuleItem.PrivateVisibility.Restricted>().firstOrNull()
|
||||
is JoinRuleItem.PrivateVisibility.AskToJoinRestricted ->
|
||||
availableJoinRules.filterIsInstance<JoinRuleItem.PrivateVisibility.AskToJoinRestricted>().firstOrNull()
|
||||
else -> availableJoinRules.find { it == currentJoinRule }
|
||||
}
|
||||
when {
|
||||
matchingRule == null -> {
|
||||
// No matching type fallback to Private (always available)
|
||||
dataStore.setJoinRule(JoinRuleItem.PrivateVisibility.Private)
|
||||
}
|
||||
matchingRule != currentJoinRule -> {
|
||||
// Same type but different params (e.g., different parentSpaceId), update
|
||||
dataStore.setJoinRule(matchingRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createRoom(config: CreateRoomConfig) {
|
||||
createRoomAction.value = AsyncAction.Uninitialized
|
||||
@@ -193,7 +211,7 @@ class ConfigureRoomPresenter(
|
||||
}
|
||||
}
|
||||
is ConfigureRoomEvents.SetParentSpace -> {
|
||||
dataStore.setParentSpace(event.space)
|
||||
dataStore.setParentSpace(event.space, false)
|
||||
}
|
||||
ConfigureRoomEvents.CancelCreateRoom -> {
|
||||
createRoomAction.value = AsyncAction.Uninitialized
|
||||
@@ -210,6 +228,7 @@ class ConfigureRoomPresenter(
|
||||
roomAddressValidity = roomAddressValidity.value,
|
||||
availableJoinRules = availableJoinRules,
|
||||
spaces = spaces,
|
||||
isSpace = isSpace,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
@@ -220,35 +239,41 @@ class ConfigureRoomPresenter(
|
||||
) = launch {
|
||||
suspend {
|
||||
val avatarUrl = config.avatarUri?.let { uploadAvatar(it.toUri()) }
|
||||
val params = if (config.visibilityState is RoomVisibilityState.Public) {
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = false,
|
||||
isDirect = false,
|
||||
visibility = RoomVisibility.Public,
|
||||
joinRuleOverride = config.visibilityState.joinRuleItem.toJoinRule()
|
||||
// No need to specify the public join rule override, since the preset is already PUBLIC_CHAT
|
||||
.takeIf { it != JoinRule.Public },
|
||||
preset = RoomPreset.PUBLIC_CHAT,
|
||||
invite = config.invites.map { it.userId },
|
||||
avatar = avatarUrl,
|
||||
roomAliasName = config.visibilityState.roomAddress(),
|
||||
isSpace = isSpace,
|
||||
)
|
||||
} else {
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = config.visibilityState is RoomVisibilityState.Private,
|
||||
isDirect = false,
|
||||
visibility = RoomVisibility.Private,
|
||||
historyVisibilityOverride = RoomHistoryVisibility.Invited,
|
||||
preset = RoomPreset.PRIVATE_CHAT,
|
||||
invite = config.invites.map { it.userId },
|
||||
avatar = avatarUrl,
|
||||
isSpace = isSpace,
|
||||
)
|
||||
val params = when (config.visibilityState) {
|
||||
is RoomVisibilityState.Public -> {
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = false,
|
||||
isDirect = false,
|
||||
visibility = RoomVisibility.Public,
|
||||
joinRuleOverride = config.visibilityState.joinRuleItem.toJoinRule()
|
||||
// No need to specify the public join rule override, since the preset is already PUBLIC_CHAT
|
||||
.takeIf { it != JoinRule.Public },
|
||||
preset = RoomPreset.PUBLIC_CHAT,
|
||||
invite = config.invites.map { it.userId },
|
||||
avatar = avatarUrl,
|
||||
roomAliasName = config.visibilityState.roomAddress(),
|
||||
isSpace = isSpace,
|
||||
)
|
||||
}
|
||||
is RoomVisibilityState.Private -> {
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = true,
|
||||
isDirect = false,
|
||||
visibility = RoomVisibility.Private,
|
||||
historyVisibilityOverride = RoomHistoryVisibility.Invited,
|
||||
joinRuleOverride = config.visibilityState.joinRuleItem.toJoinRule()
|
||||
// No need to specify the Invite join rule override, since the preset is already PRIVATE_CHAT
|
||||
.takeIf { it != JoinRule.Invite },
|
||||
preset = RoomPreset.PRIVATE_CHAT,
|
||||
invite = config.invites.map { it.userId },
|
||||
avatar = avatarUrl,
|
||||
isSpace = isSpace,
|
||||
)
|
||||
}
|
||||
}
|
||||
val roomId = matrixClient.createRoom(params)
|
||||
.onFailure { failure ->
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.element.android.libraries.permissions.api.PermissionsState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class ConfigureRoomState(
|
||||
val isSpace: Boolean,
|
||||
val config: CreateRoomConfig,
|
||||
val avatarActions: ImmutableList<AvatarAction>,
|
||||
val createRoomAction: AsyncAction<RoomId>,
|
||||
@@ -28,5 +29,6 @@ data class ConfigureRoomState(
|
||||
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||
) {
|
||||
val isValid: Boolean = config.roomName?.isNotEmpty() == true &&
|
||||
(config.visibilityState is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid)
|
||||
(config.visibilityState is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid) &&
|
||||
config.visibilityState.joinRuleItem in availableJoinRules
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||
roomAddressValidity = RoomAddressValidity.Valid,
|
||||
),
|
||||
aConfigureRoomState(
|
||||
isSpace = true,
|
||||
config = CreateRoomConfig(
|
||||
isSpace = true,
|
||||
roomName = "Space 101",
|
||||
topic = "Space topic for this space when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
visibilityState = RoomVisibilityState.Public(
|
||||
@@ -95,13 +95,11 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||
),
|
||||
aConfigureRoomState(
|
||||
config = CreateRoomConfig(
|
||||
isSpace = false,
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
parentSpace = null,
|
||||
visibilityState = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Space-101"),
|
||||
joinRuleItem = JoinRuleItem.PublicVisibility.Restricted(aSpaceRoom().roomId),
|
||||
visibilityState = RoomVisibilityState.Private(
|
||||
joinRuleItem = JoinRuleItem.PrivateVisibility.Restricted(aSpaceRoom().roomId),
|
||||
),
|
||||
),
|
||||
spaces = listOf(aSpaceRoom()),
|
||||
@@ -109,13 +107,11 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||
),
|
||||
aConfigureRoomState(
|
||||
config = CreateRoomConfig(
|
||||
isSpace = false,
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
parentSpace = aSpaceRoom(canonicalAlias = RoomAlias("#a-space-room:example.org")),
|
||||
visibilityState = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Space-101"),
|
||||
joinRuleItem = JoinRuleItem.PublicVisibility.Restricted(aSpaceRoom().roomId),
|
||||
visibilityState = RoomVisibilityState.Private(
|
||||
joinRuleItem = JoinRuleItem.PrivateVisibility.Restricted(aSpaceRoom().roomId),
|
||||
),
|
||||
),
|
||||
spaces = listOf(aSpaceRoom()),
|
||||
@@ -126,6 +122,7 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||
|
||||
fun aConfigureRoomState(
|
||||
config: CreateRoomConfig = CreateRoomConfig(),
|
||||
isSpace: Boolean = false,
|
||||
isKnockFeatureEnabled: Boolean = true,
|
||||
avatarActions: List<AvatarAction> = emptyList(),
|
||||
createRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
@@ -134,21 +131,22 @@ fun aConfigureRoomState(
|
||||
roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Valid,
|
||||
availableVisibilityOptions: List<JoinRuleItem> = if (config.parentSpace != null) {
|
||||
listOfNotNull(
|
||||
JoinRuleItem.PublicVisibility.Restricted(config.parentSpace.roomId),
|
||||
JoinRuleItem.PublicVisibility.AskToJoinRestricted(config.parentSpace.roomId).takeIf { isKnockFeatureEnabled },
|
||||
JoinRuleItem.Private,
|
||||
JoinRuleItem.PrivateVisibility.Restricted(config.parentSpace.roomId),
|
||||
JoinRuleItem.PrivateVisibility.AskToJoinRestricted(config.parentSpace.roomId).takeIf { isKnockFeatureEnabled },
|
||||
JoinRuleItem.PrivateVisibility.Private,
|
||||
)
|
||||
} else {
|
||||
listOfNotNull(
|
||||
JoinRuleItem.PublicVisibility.Public,
|
||||
JoinRuleItem.PublicVisibility.AskToJoin.takeIf { isKnockFeatureEnabled },
|
||||
JoinRuleItem.Private,
|
||||
JoinRuleItem.PrivateVisibility.Private,
|
||||
)
|
||||
},
|
||||
spaces: List<SpaceRoom> = emptyList(),
|
||||
eventSink: (ConfigureRoomEvents) -> Unit = { },
|
||||
) = ConfigureRoomState(
|
||||
config = config,
|
||||
isSpace = isSpace,
|
||||
avatarActions = avatarActions.toImmutableList(),
|
||||
createRoomAction = createRoomAction,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
|
||||
@@ -14,7 +14,9 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -76,7 +78,7 @@ fun ConfigureRoomView(
|
||||
onCreateRoomSuccess: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isSpace = state.config.isSpace
|
||||
val isSpace = state.isSpace
|
||||
val focusManager = LocalFocusManager.current
|
||||
val isAvatarActionsSheetVisible = remember { mutableStateOf(false) }
|
||||
|
||||
@@ -105,7 +107,6 @@ fun ConfigureRoomView(
|
||||
.imePadding()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.consumeWindowInsets(padding),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
RoomNameWithAvatar(
|
||||
isSpace = isSpace,
|
||||
@@ -115,20 +116,20 @@ fun ConfigureRoomView(
|
||||
onAvatarClick = ::onAvatarClick,
|
||||
onChangeRoomName = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
RoomTopic(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
topic = state.config.topic.orEmpty(),
|
||||
onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
|
||||
)
|
||||
|
||||
if (!state.config.isSpace && state.spaces.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
if (!state.isSpace && state.spaces.isNotEmpty()) {
|
||||
SelectParentSpaceOptions(
|
||||
spaces = state.spaces,
|
||||
selectedSpace = state.config.parentSpace,
|
||||
onSelectSpace = { state.eventSink(ConfigureRoomEvents.SetParentSpace(it)) },
|
||||
)
|
||||
}
|
||||
|
||||
RoomJoinRuleOptions(
|
||||
options = state.availableJoinRules,
|
||||
selected = state.config.visibilityState.joinRuleItem,
|
||||
@@ -138,20 +139,17 @@ fun ConfigureRoomView(
|
||||
state.eventSink(ConfigureRoomEvents.JoinRuleChanged(it))
|
||||
},
|
||||
)
|
||||
|
||||
if (state.config.visibilityState !is RoomVisibilityState.Private) {
|
||||
Column {
|
||||
ListSectionHeader(title = stringResource(R.string.screen_create_room_room_address_section_title))
|
||||
RoomAddressField(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
address = state.config.visibilityState.roomAddress().getOrNull().orEmpty(),
|
||||
homeserverName = state.homeserverName,
|
||||
addressValidity = state.roomAddressValidity,
|
||||
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
|
||||
label = null,
|
||||
supportingText = stringResource(R.string.screen_create_room_room_address_section_footer),
|
||||
)
|
||||
}
|
||||
ListSectionHeader(title = stringResource(R.string.screen_create_room_room_address_section_title))
|
||||
RoomAddressField(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
address = state.config.visibilityState.roomAddress().getOrNull().orEmpty(),
|
||||
homeserverName = state.homeserverName,
|
||||
addressValidity = state.roomAddressValidity,
|
||||
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
|
||||
label = null,
|
||||
supportingText = stringResource(R.string.screen_create_room_room_address_section_footer),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +215,9 @@ private fun RoomNameWithAvatar(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(end = 8.dp).size(AvatarSize.EditRoomDetails.dp),
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.size(AvatarSize.EditRoomDetails.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val avatarState = remember(avatarUri) {
|
||||
@@ -272,12 +272,13 @@ private fun RoomTopic(
|
||||
internal fun ConfigureRoomOptions(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
hasDivider: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.selectableGroup()
|
||||
) {
|
||||
ListSectionHeader(title = title)
|
||||
ListSectionHeader(title = title, hasDivider = hasDivider)
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -302,10 +303,10 @@ private fun RoomJoinRuleOptions(
|
||||
size = RoundedIconAtomSize.Big,
|
||||
imageVector = when (item) {
|
||||
JoinRuleItem.PublicVisibility.Public -> CompoundIcons.Public()
|
||||
is JoinRuleItem.PublicVisibility.Restricted -> CompoundIcons.Space()
|
||||
is JoinRuleItem.PrivateVisibility.Restricted -> CompoundIcons.Space()
|
||||
JoinRuleItem.PublicVisibility.AskToJoin,
|
||||
is JoinRuleItem.PublicVisibility.AskToJoinRestricted -> CompoundIcons.UserAdd()
|
||||
JoinRuleItem.Private -> CompoundIcons.Lock()
|
||||
is JoinRuleItem.PrivateVisibility.AskToJoinRestricted -> CompoundIcons.UserAdd()
|
||||
JoinRuleItem.PrivateVisibility.Private -> CompoundIcons.Lock()
|
||||
},
|
||||
tint = if (isSelected) ElementTheme.colors.iconPrimary else ElementTheme.colors.iconSecondary,
|
||||
backgroundTint = Color.Transparent,
|
||||
@@ -314,28 +315,28 @@ private fun RoomJoinRuleOptions(
|
||||
headlineContent = {
|
||||
val title = when (item) {
|
||||
JoinRuleItem.PublicVisibility.Public -> stringResource(R.string.screen_create_room_room_access_section_public_option_title)
|
||||
is JoinRuleItem.PublicVisibility.Restricted -> stringResource(R.string.screen_create_room_room_access_section_restricted_option_title)
|
||||
is JoinRuleItem.PrivateVisibility.Restricted -> stringResource(R.string.screen_create_room_room_access_section_restricted_option_title)
|
||||
JoinRuleItem.PublicVisibility.AskToJoin -> stringResource(R.string.screen_create_room_room_access_section_knocking_option_title)
|
||||
is JoinRuleItem.PublicVisibility.AskToJoinRestricted -> stringResource(
|
||||
is JoinRuleItem.PrivateVisibility.AskToJoinRestricted -> stringResource(
|
||||
R.string.screen_create_room_room_access_section_knocking_restricted_option_title
|
||||
)
|
||||
JoinRuleItem.Private -> stringResource(R.string.screen_create_room_room_access_section_private_option_title)
|
||||
JoinRuleItem.PrivateVisibility.Private -> stringResource(R.string.screen_create_room_room_access_section_private_option_title)
|
||||
}
|
||||
Text(text = title)
|
||||
},
|
||||
supportingContent = {
|
||||
val description = when (item) {
|
||||
JoinRuleItem.PublicVisibility.Public -> stringResource(R.string.screen_create_room_room_access_section_public_option_description)
|
||||
is JoinRuleItem.PublicVisibility.Restricted -> stringResource(
|
||||
is JoinRuleItem.PrivateVisibility.Restricted -> stringResource(
|
||||
R.string.screen_create_room_room_access_section_restricted_option_description,
|
||||
parentSpace?.displayName.orEmpty()
|
||||
)
|
||||
JoinRuleItem.PublicVisibility.AskToJoin -> stringResource(R.string.screen_create_room_room_access_section_knocking_option_description)
|
||||
is JoinRuleItem.PublicVisibility.AskToJoinRestricted -> stringResource(
|
||||
is JoinRuleItem.PrivateVisibility.AskToJoinRestricted -> stringResource(
|
||||
R.string.screen_create_room_room_access_section_knocking_restricted_option_description,
|
||||
parentSpace?.displayName.orEmpty()
|
||||
)
|
||||
JoinRuleItem.Private -> stringResource(R.string.screen_create_room_room_access_section_private_option_description)
|
||||
JoinRuleItem.PrivateVisibility.Private -> stringResource(R.string.screen_create_room_room_access_section_private_option_description)
|
||||
}
|
||||
Text(text = description)
|
||||
},
|
||||
|
||||
@@ -14,11 +14,10 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
data class CreateRoomConfig(
|
||||
val isSpace: Boolean = false,
|
||||
val roomName: String? = null,
|
||||
val topic: String? = null,
|
||||
val avatarUri: String? = null,
|
||||
val invites: ImmutableList<MatrixUser> = persistentListOf(),
|
||||
val visibilityState: RoomVisibilityState = RoomVisibilityState.Private(),
|
||||
val visibilityState: RoomVisibilityState = RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private),
|
||||
val parentSpace: SpaceRoom? = null,
|
||||
)
|
||||
|
||||
@@ -72,7 +72,9 @@ class CreateRoomConfigStore(
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(
|
||||
visibilityState = when (joinRule) {
|
||||
JoinRuleItem.Private -> RoomVisibilityState.Private()
|
||||
is JoinRuleItem.PrivateVisibility -> RoomVisibilityState.Private(
|
||||
joinRuleItem = joinRule
|
||||
)
|
||||
is JoinRuleItem.PublicVisibility -> {
|
||||
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(config.roomName.orEmpty())
|
||||
RoomVisibilityState.Public(
|
||||
@@ -99,17 +101,16 @@ class CreateRoomConfigStore(
|
||||
}
|
||||
}
|
||||
|
||||
fun setIsSpace(isSpace: Boolean) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(isSpace = isSpace)
|
||||
}
|
||||
}
|
||||
|
||||
fun setParentSpace(parentSpace: SpaceRoom?) {
|
||||
fun setParentSpace(parentSpace: SpaceRoom?, updateVisibility: Boolean) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
val visibilityState = if (parentSpace != null && updateVisibility) {
|
||||
RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Restricted(parentSpace.roomId))
|
||||
} else {
|
||||
config.visibilityState
|
||||
}
|
||||
config.copy(
|
||||
parentSpace = parentSpace,
|
||||
visibilityState = RoomVisibilityState.Private(),
|
||||
visibilityState = visibilityState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface JoinRuleItem {
|
||||
data object Private : JoinRuleItem
|
||||
sealed interface PrivateVisibility : JoinRuleItem {
|
||||
data object Private : PrivateVisibility
|
||||
data class Restricted(val parentSpaceId: RoomId) : PrivateVisibility
|
||||
data class AskToJoinRestricted(val parentSpaceId: RoomId) : PrivateVisibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Those join rule items that represent public visibility of the room/space.
|
||||
@@ -27,18 +31,16 @@ sealed interface JoinRuleItem {
|
||||
sealed interface PublicVisibility : JoinRuleItem {
|
||||
data object Public : PublicVisibility
|
||||
data object AskToJoin : PublicVisibility
|
||||
data class Restricted(val parentSpaceId: RoomId) : PublicVisibility
|
||||
data class AskToJoinRestricted(val parentSpaceId: RoomId) : PublicVisibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a [JoinRuleItem] option into a [JoinRule].
|
||||
*/
|
||||
fun toJoinRule(): JoinRule = when (this) {
|
||||
Private -> JoinRule.Invite
|
||||
PrivateVisibility.Private -> JoinRule.Invite
|
||||
is PrivateVisibility.Restricted -> JoinRule.Restricted(persistentListOf(AllowRule.RoomMembership(parentSpaceId)))
|
||||
is PrivateVisibility.AskToJoinRestricted -> JoinRule.KnockRestricted(persistentListOf(AllowRule.RoomMembership(parentSpaceId)))
|
||||
PublicVisibility.Public -> JoinRule.Public
|
||||
PublicVisibility.AskToJoin -> JoinRule.Knock
|
||||
is PublicVisibility.Restricted -> JoinRule.Restricted(persistentListOf(AllowRule.RoomMembership(parentSpaceId)))
|
||||
is PublicVisibility.AskToJoinRestricted -> JoinRule.KnockRestricted(persistentListOf(AllowRule.RoomMembership(parentSpaceId)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Optional
|
||||
|
||||
sealed interface RoomVisibilityState {
|
||||
val joinRuleItem: JoinRuleItem
|
||||
data class Private(override val joinRuleItem: JoinRuleItem.Private = JoinRuleItem.Private) : RoomVisibilityState
|
||||
data class Private(override val joinRuleItem: JoinRuleItem.PrivateVisibility) : RoomVisibilityState
|
||||
|
||||
data class Public(
|
||||
val roomAddress: RoomAddress,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -21,7 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
@@ -30,7 +29,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
||||
@@ -55,6 +53,7 @@ internal fun SelectParentSpaceOptions(
|
||||
var displaySelectSpaceBottomSheet by remember { mutableStateOf(false) }
|
||||
ConfigureRoomOptions(
|
||||
title = stringResource(CommonStrings.common_space),
|
||||
hasDivider = false,
|
||||
modifier = modifier
|
||||
) {
|
||||
ListItem(
|
||||
@@ -62,22 +61,16 @@ internal fun SelectParentSpaceOptions(
|
||||
Text(
|
||||
text = selectedSpace?.displayName
|
||||
?: stringResource(R.string.screen_create_room_space_selection_no_space_title),
|
||||
maxLines = 1
|
||||
maxLines = 1,
|
||||
color = ElementTheme.colors.textPrimary
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = if (selectedSpace != null) {
|
||||
selectedSpace.canonicalAlias?.value.orEmpty()
|
||||
} else {
|
||||
stringResource(R.string.screen_create_room_space_selection_no_space_description)
|
||||
},
|
||||
maxLines = 1
|
||||
)
|
||||
supportingContent = selectedSpace?.canonicalAlias?.let { alias ->
|
||||
{
|
||||
Text(text = alias.value, maxLines = 1)
|
||||
}
|
||||
},
|
||||
leadingContent = if (selectedSpace == null) {
|
||||
ListItemContent.Icon(IconSource.Vector(CompoundIcons.Home()))
|
||||
} else {
|
||||
leadingContent = selectedSpace?.let {
|
||||
ListItemContent.Custom({
|
||||
val avatarData = AvatarData(
|
||||
id = selectedSpace.roomId.value,
|
||||
@@ -119,7 +112,7 @@ internal fun SelectParentSpaceOptions(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.SelectParentSpaceBottomSheet(
|
||||
private fun SelectParentSpaceBottomSheet(
|
||||
spaces: ImmutableList<SpaceRoom>,
|
||||
selectedSpace: SpaceRoom?,
|
||||
onSelectSpace: (SpaceRoom?) -> Unit,
|
||||
@@ -133,19 +126,10 @@ private fun ColumnScope.SelectParentSpaceBottomSheet(
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
stringResource(R.string.screen_create_room_space_selection_no_space_title),
|
||||
text = stringResource(R.string.screen_create_room_space_selection_no_space_option),
|
||||
maxLines = 1
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
stringResource(R.string.screen_create_room_space_selection_no_space_description),
|
||||
maxLines = 1
|
||||
)
|
||||
},
|
||||
leadingContent = ListItemContent.Icon(
|
||||
IconSource.Vector(CompoundIcons.Home())
|
||||
),
|
||||
trailingContent = ListItemContent.RadioButton(
|
||||
selected = selectedSpace == null
|
||||
),
|
||||
@@ -157,29 +141,31 @@ private fun ColumnScope.SelectParentSpaceBottomSheet(
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
space.displayName,
|
||||
text = space.displayName,
|
||||
maxLines = 1
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
space.canonicalAlias?.value.orEmpty(),
|
||||
maxLines = 1
|
||||
)
|
||||
supportingContent = space.canonicalAlias?.let { alias ->
|
||||
{
|
||||
Text(
|
||||
text = alias.value,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
},
|
||||
leadingContent = ListItemContent.Custom({
|
||||
val avatarData =
|
||||
AvatarData(
|
||||
id = space.roomId.value,
|
||||
name = space.displayName,
|
||||
url = space.avatarUrl,
|
||||
size = AvatarSize.SelectParentSpace,
|
||||
)
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
avatarType = AvatarType.Space()
|
||||
val avatarData =
|
||||
AvatarData(
|
||||
id = space.roomId.value,
|
||||
name = space.displayName,
|
||||
url = space.avatarUrl,
|
||||
size = AvatarSize.SelectParentSpace,
|
||||
)
|
||||
}),
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
avatarType = AvatarType.Space()
|
||||
)
|
||||
}),
|
||||
trailingContent = ListItemContent.RadioButton(
|
||||
selected = selectedSpace == space
|
||||
),
|
||||
@@ -201,7 +187,8 @@ internal fun SelectParentSpaceBottomSheetPreview() =
|
||||
canonicalAlias = RoomAlias(
|
||||
"#a-room-alias:example.org"
|
||||
)
|
||||
)
|
||||
),
|
||||
aSpaceRoom()
|
||||
),
|
||||
selectedSpace = null,
|
||||
) {}
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
@@ -89,7 +90,7 @@ class ConfigureRoomPresenterTest {
|
||||
assertThat(initialState.config.topic).isNull()
|
||||
assertThat(initialState.config.invites).isEmpty()
|
||||
assertThat(initialState.config.avatarUri).isNull()
|
||||
assertThat(initialState.config.visibilityState).isEqualTo(RoomVisibilityState.Private())
|
||||
assertThat(initialState.config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private))
|
||||
assertThat(initialState.createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(initialState.homeserverName).isEqualTo("matrix.org")
|
||||
}
|
||||
@@ -234,7 +235,8 @@ class ConfigureRoomPresenterTest {
|
||||
|
||||
matrixClient.givenCreateRoomResult(createRoomResult)
|
||||
|
||||
val parentSpace = aSpaceRoom()
|
||||
// Use a public parent space so AskToJoin is a valid option
|
||||
val parentSpace = aSpaceRoom(joinRule = JoinRule.Public)
|
||||
initialState.eventSink(ConfigureRoomEvents.SetParentSpace(parentSpace))
|
||||
assertThat(awaitItem().config.parentSpace).isEqualTo(parentSpace)
|
||||
|
||||
@@ -275,7 +277,8 @@ class ConfigureRoomPresenterTest {
|
||||
|
||||
matrixClient.givenCreateRoomResult(createRoomResult)
|
||||
|
||||
val parentSpace = aSpaceRoom()
|
||||
// Use a public parent space so AskToJoin is a valid option
|
||||
val parentSpace = aSpaceRoom(joinRule = JoinRule.Public)
|
||||
initialState.eventSink(ConfigureRoomEvents.SetParentSpace(parentSpace))
|
||||
assertThat(awaitItem().config.parentSpace).isEqualTo(parentSpace)
|
||||
|
||||
@@ -484,16 +487,19 @@ class ConfigureRoomPresenterTest {
|
||||
assertThat(awaitItem().config.visibilityState).isInstanceOf(RoomVisibilityState.Public::class.java)
|
||||
|
||||
// Then check changing the parent space resets it to private
|
||||
// (via LaunchedEffect fallback since Public is not in availableJoinRules for non-public parent)
|
||||
initialState.eventSink(ConfigureRoomEvents.SetParentSpace(aSpaceRoom()))
|
||||
assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private())
|
||||
skipItems(1) // Skip intermediate state
|
||||
assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private))
|
||||
|
||||
// If we change the join rule back to public
|
||||
initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public))
|
||||
assertThat(awaitItem().config.visibilityState).isInstanceOf(RoomVisibilityState.Public::class.java)
|
||||
skipItems(1) // Skip intermediate state (Public is still invalid)
|
||||
assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private))
|
||||
|
||||
// Then remove the parent space, it'll be private again
|
||||
// Then remove the parent space, the join rule stays private
|
||||
initialState.eventSink(ConfigureRoomEvents.SetParentSpace(null))
|
||||
assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private())
|
||||
assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ import org.junit.Test
|
||||
class JoinRuleItemTest {
|
||||
@Test
|
||||
fun `toJoinRule works as expected`() {
|
||||
assertThat(JoinRuleItem.Private.toJoinRule()).isEqualTo(JoinRule.Invite)
|
||||
assertThat(JoinRuleItem.PrivateVisibility.Private.toJoinRule()).isEqualTo(JoinRule.Invite)
|
||||
assertThat(JoinRuleItem.PublicVisibility.Public.toJoinRule()).isEqualTo(JoinRule.Public)
|
||||
assertThat(JoinRuleItem.PublicVisibility.AskToJoin.toJoinRule()).isEqualTo(JoinRule.Knock)
|
||||
assertThat(JoinRuleItem.PublicVisibility.Restricted(A_ROOM_ID).toJoinRule())
|
||||
assertThat(JoinRuleItem.PrivateVisibility.Restricted(A_ROOM_ID).toJoinRule())
|
||||
.isEqualTo(JoinRule.Restricted(persistentListOf(AllowRule.RoomMembership(A_ROOM_ID))))
|
||||
assertThat(JoinRuleItem.PublicVisibility.AskToJoinRestricted(A_ROOM_ID).toJoinRule())
|
||||
assertThat(JoinRuleItem.PrivateVisibility.AskToJoinRestricted(A_ROOM_ID).toJoinRule())
|
||||
.isEqualTo(JoinRule.KnockRestricted(persistentListOf(AllowRule.RoomMembership(A_ROOM_ID))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ fun HomeSpacesView(
|
||||
item {
|
||||
SpaceHeaderView(
|
||||
avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader),
|
||||
alias = space.spaceRoom.canonicalAlias,
|
||||
name = space.spaceRoom.displayName,
|
||||
topic = space.spaceRoom.topic,
|
||||
visibility = space.spaceRoom.visibility,
|
||||
|
||||
@@ -41,8 +41,8 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewAliasAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewSubtitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
||||
@@ -514,7 +514,7 @@ private fun IncompleteContent(
|
||||
title = {
|
||||
when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
RoomPreviewSubtitleAtom(roomIdOrAlias.identifier)
|
||||
RoomPreviewAliasAtom(roomIdOrAlias.identifier)
|
||||
}
|
||||
is RoomIdOrAlias.Id -> {
|
||||
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
||||
@@ -566,13 +566,12 @@ private fun DefaultLoadedContent(
|
||||
}
|
||||
},
|
||||
subtitle = {
|
||||
when {
|
||||
contentState.details is LoadedDetails.Space -> {
|
||||
SpaceInfoRow(visibility = SpaceRoomVisibility.fromJoinRule(contentState.joinRule))
|
||||
}
|
||||
contentState.alias != null -> {
|
||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||
}
|
||||
if (contentState.alias != null) {
|
||||
RoomPreviewAliasAtom(contentState.alias.value)
|
||||
}
|
||||
if (contentState.details is LoadedDetails.Space) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
SpaceInfoRow(visibility = SpaceRoomVisibility.fromJoinRule(contentState.joinRule))
|
||||
}
|
||||
},
|
||||
description = {
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewSubtitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewAliasAtom
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
@@ -54,7 +54,7 @@ fun RoomAliasResolverView(
|
||||
containerColor = Color.Transparent,
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 32.dp
|
||||
vertical = 24.dp
|
||||
),
|
||||
topBar = {
|
||||
RoomAliasResolverTopBar(onBackClick = onBackClick)
|
||||
@@ -121,7 +121,7 @@ private fun RoomAliasResolverContent(
|
||||
PlaceholderAtom(width = AvatarSize.RoomPreviewHeader.dp, height = AvatarSize.RoomPreviewHeader.dp)
|
||||
},
|
||||
title = {
|
||||
RoomPreviewSubtitleAtom(roomAlias.value)
|
||||
RoomPreviewAliasAtom(roomAlias.value)
|
||||
},
|
||||
subtitle = {
|
||||
if (isLoading) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(FlowPreview::class)
|
||||
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -47,10 +49,13 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Inject
|
||||
class SpacePresenter(
|
||||
@@ -80,13 +85,16 @@ class SpacePresenter(
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
val hasMoreToLoad by remember {
|
||||
spaceRoomList.paginationStatusFlow.mapState { status ->
|
||||
when (status) {
|
||||
is SpaceRoomList.PaginationStatus.Idle -> status.hasMoreToLoad
|
||||
SpaceRoomList.PaginationStatus.Loading -> true
|
||||
spaceRoomList.paginationStatusFlow
|
||||
.mapState { status ->
|
||||
when (status) {
|
||||
is SpaceRoomList.PaginationStatus.Idle -> status.hasMoreToLoad
|
||||
SpaceRoomList.PaginationStatus.Loading -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
}.collectAsState()
|
||||
// Debounce to give more time for spaceRoomList to updates
|
||||
.debounce(100.milliseconds)
|
||||
}.collectAsState(true)
|
||||
|
||||
val permissions by room.permissionsAsState(SpacePermissions.DEFAULT) { perms ->
|
||||
perms.spacePermissions()
|
||||
|
||||
@@ -50,6 +50,7 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.space.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
@@ -76,6 +77,7 @@ import io.element.android.libraries.designsystem.theme.components.HorizontalDivi
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
@@ -170,6 +172,7 @@ fun SpaceView(
|
||||
state.eventSink(SpaceEvents.ShowTopicViewer(topic))
|
||||
},
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
onAddRoomClick = onAddRoomClick,
|
||||
)
|
||||
JoinFailuresEffect(
|
||||
hasAnyFailure = state.hasAnyJoinFailures,
|
||||
@@ -243,6 +246,7 @@ private fun SpaceViewContent(
|
||||
onRoomClick: (spaceRoom: SpaceRoom) -> Unit,
|
||||
onTopicClick: (String) -> Unit,
|
||||
onCreateRoomClick: () -> Unit,
|
||||
onAddRoomClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier.fillMaxSize()) {
|
||||
@@ -256,6 +260,7 @@ private fun SpaceViewContent(
|
||||
Column {
|
||||
SpaceHeaderView(
|
||||
avatarData = spaceInfo.getAvatarData(AvatarSize.SpaceHeader),
|
||||
alias = spaceInfo.canonicalAlias,
|
||||
name = spaceInfo.name,
|
||||
topic = spaceInfo.topic,
|
||||
topicMaxLines = 2,
|
||||
@@ -271,7 +276,10 @@ private fun SpaceViewContent(
|
||||
|
||||
if (state.children.isEmpty() && state.canEditSpaceGraph && !state.hasMoreToLoad) {
|
||||
item {
|
||||
EmptySpaceView(onCreateRoomClick = onCreateRoomClick)
|
||||
EmptySpaceView(
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
onAddRoomClick = onAddRoomClick,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
itemsIndexed(
|
||||
@@ -332,7 +340,10 @@ private fun SpaceViewContent(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptySpaceView(onCreateRoomClick: () -> Unit) {
|
||||
private fun EmptySpaceView(
|
||||
onCreateRoomClick: () -> Unit,
|
||||
onAddRoomClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(bottom = 24.dp),
|
||||
@@ -340,15 +351,25 @@ private fun EmptySpaceView(onCreateRoomClick: () -> Unit) {
|
||||
IconTitleSubtitleMolecule(
|
||||
title = stringResource(R.string.screen_space_empty_state_title),
|
||||
subTitle = null,
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.Room()),
|
||||
iconStyle = BigIcon.Style.Default(vectorIcon = CompoundIcons.Room(), usePrimaryTint = true),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 24.dp),
|
||||
)
|
||||
Button(
|
||||
text = stringResource(R.string.screen_space_add_room_action),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Plus()),
|
||||
onClick = onCreateRoomClick,
|
||||
)
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_add_existing_rooms),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Plus()),
|
||||
onClick = onAddRoomClick,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_create_room),
|
||||
onClick = onCreateRoomClick,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.space.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
@@ -215,9 +214,25 @@ class SpaceViewTest {
|
||||
),
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
)
|
||||
rule.clickOn(R.string.screen_space_add_room_action)
|
||||
rule.clickOn(CommonStrings.action_create_room)
|
||||
onCreateRoomClick.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking add existing room button calls the expected callback`() {
|
||||
val onAddRoomClick = lambdaRecorder<Unit> { }
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = emptyList(),
|
||||
hasMoreToLoad = false,
|
||||
isManageMode = true,
|
||||
canManageRooms = true,
|
||||
),
|
||||
onAddRoomClick = onAddRoomClick,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_add_existing_rooms)
|
||||
onAddRoomClick.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceView(
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import android.content.ClipData
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.toClipEntry
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toDp
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewAliasAtom(
|
||||
alias: String,
|
||||
modifier: Modifier = Modifier,
|
||||
copiable: Boolean = true
|
||||
) {
|
||||
val clipboard: Clipboard = LocalClipboard.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clickable(enabled = copiable) {
|
||||
coroutineScope.launch {
|
||||
val clipData = ClipData.newPlainText(alias, alias)
|
||||
clipboard.setClipEntry(clipData.toClipEntry())
|
||||
}
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(weight = 1f, fill = false),
|
||||
text = alias,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
if (copiable) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Copy(),
|
||||
contentDescription = stringResource(id = CommonStrings.action_copy),
|
||||
tint = ElementTheme.colors.iconSecondaryAlpha,
|
||||
modifier = Modifier.size(ElementTheme.typography.fontBodyLgRegular.fontSize.toDp())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomPreviewAliasAtomPreview() = ElementPreview {
|
||||
RoomPreviewAliasAtom(
|
||||
alias = "#room-alias:matrix.org",
|
||||
copiable = true
|
||||
)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewSubtitleAtom(subtitle: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = subtitle,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
package io.element.android.libraries.designsystem.atomic.organisms
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -19,12 +20,12 @@ import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewOrganism(
|
||||
avatar: @Composable () -> Unit,
|
||||
title: @Composable () -> Unit,
|
||||
subtitle: @Composable () -> Unit,
|
||||
avatar: @Composable ColumnScope.() -> Unit,
|
||||
title: @Composable ColumnScope.() -> Unit,
|
||||
subtitle: @Composable ColumnScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
description: @Composable (() -> Unit)? = null,
|
||||
memberCount: @Composable (() -> Unit)? = null,
|
||||
description: @Composable (ColumnScope.() -> Unit)? = null,
|
||||
memberCount: @Composable (ColumnScope.() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package io.element.android.libraries.designsystem.components.avatar.internal
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -16,6 +17,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||
@@ -34,19 +36,26 @@ internal fun SpaceAvatar(
|
||||
contentDescription: String? = null,
|
||||
) {
|
||||
val size = forcedAvatarSize ?: avatarData.size.dp
|
||||
val avatarShape = avatarType.avatarShape(size)
|
||||
val commonModifier = modifier
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = ElementTheme.colors.iconQuaternaryAlpha,
|
||||
shape = avatarShape,
|
||||
)
|
||||
when {
|
||||
avatarType.isTombstoned -> TombstonedRoomAvatar(
|
||||
size = size,
|
||||
avatarShape = avatarType.avatarShape(size),
|
||||
modifier = modifier,
|
||||
avatarShape = avatarShape,
|
||||
modifier = commonModifier,
|
||||
contentDescription = contentDescription,
|
||||
)
|
||||
else -> InitialOrImageAvatar(
|
||||
avatarData = avatarData,
|
||||
hideAvatarImage = hideAvatarImage,
|
||||
avatarShape = avatarType.avatarShape(size),
|
||||
avatarShape = avatarShape,
|
||||
forcedAvatarSize = forcedAvatarSize,
|
||||
modifier = modifier,
|
||||
modifier = commonModifier,
|
||||
contentDescription = contentDescription,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
sealed interface SpaceRoomVisibility {
|
||||
data object Private : SpaceRoomVisibility
|
||||
data object Public : SpaceRoomVisibility
|
||||
data object Restricted : SpaceRoomVisibility
|
||||
data object SpaceMembers : SpaceRoomVisibility
|
||||
|
||||
companion object {
|
||||
fun fromJoinRule(joinRule: JoinRule?): SpaceRoomVisibility = when (joinRule) {
|
||||
JoinRule.Public -> Public
|
||||
is JoinRule.Restricted, is JoinRule.KnockRestricted -> Restricted
|
||||
is JoinRule.Restricted, is JoinRule.KnockRestricted -> SpaceMembers
|
||||
// Else fallback to Private
|
||||
else -> Private
|
||||
}
|
||||
|
||||
@@ -9,13 +9,17 @@
|
||||
package io.element.android.libraries.matrix.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewAliasAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||
@@ -26,6 +30,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomVisibility
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -39,6 +44,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
fun SpaceHeaderView(
|
||||
avatarData: AvatarData,
|
||||
name: String?,
|
||||
alias: RoomAlias?,
|
||||
topic: String?,
|
||||
visibility: SpaceRoomVisibility,
|
||||
heroes: ImmutableList<MatrixUser>,
|
||||
@@ -66,7 +72,15 @@ fun SpaceHeaderView(
|
||||
}
|
||||
},
|
||||
subtitle = {
|
||||
SpaceInfoRow(visibility = visibility)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
if (alias != null) {
|
||||
RoomPreviewAliasAtom(alias = alias.value)
|
||||
}
|
||||
SpaceInfoRow(visibility = visibility)
|
||||
}
|
||||
},
|
||||
description = if (topic.isNullOrBlank()) {
|
||||
null
|
||||
@@ -100,6 +114,7 @@ internal fun SpaceHeaderViewPreview() = ElementPreview {
|
||||
url = "anUrl",
|
||||
size = AvatarSize.SpaceHeader,
|
||||
),
|
||||
alias = RoomAlias("#spaceAlias:matrix.org"),
|
||||
name = "Space name",
|
||||
topic = "Space topic: " + LoremIpsum(40).values.first(),
|
||||
topicMaxLines = 2,
|
||||
|
||||
@@ -117,7 +117,7 @@ internal fun SpaceInfoRowPreview() = ElementPreview {
|
||||
visibility = SpaceRoomVisibility.Public
|
||||
)
|
||||
SpaceInfoRow(
|
||||
visibility = SpaceRoomVisibility.Restricted
|
||||
visibility = SpaceRoomVisibility.SpaceMembers
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,9 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -100,22 +98,16 @@ fun SpaceRoomItemView(
|
||||
showIndicator = showUnreadIndicator
|
||||
)
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
SubtitleRow(
|
||||
visibilityIcon = spaceRoom.visibilityIcon(),
|
||||
subtitle = spaceRoom.subtitle()
|
||||
)
|
||||
VisibilityRow(visibility = spaceRoom.visibility)
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
val info = spaceRoom.info()
|
||||
if (info.isNotBlank()) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = info,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = pluralStringResource(CommonPlurals.common_member_count, spaceRoom.numJoinedMembers, spaceRoom.numJoinedMembers),
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
if (bottomAction != null) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
@@ -129,29 +121,26 @@ fun SpaceRoomItemView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SubtitleRow(
|
||||
visibilityIcon: ImageVector?,
|
||||
subtitle: String,
|
||||
private fun VisibilityRow(
|
||||
visibility: SpaceRoomVisibility,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (visibilityIcon != null) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.padding(end = 4.dp),
|
||||
imageVector = visibilityIcon,
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconTertiary,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.padding(end = 4.dp),
|
||||
imageVector = visibility.icon,
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconTertiary,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = subtitle,
|
||||
text = visibility.label,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -219,36 +208,6 @@ private fun SpaceRoomItemScaffold(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun SpaceRoom.subtitle(): String {
|
||||
return if (isSpace) {
|
||||
visibility.label
|
||||
} else {
|
||||
pluralStringResource(CommonPlurals.common_member_count, numJoinedMembers, numJoinedMembers)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun SpaceRoom.info(): String {
|
||||
return if (isSpace) {
|
||||
pluralStringResource(CommonPlurals.common_member_count, numJoinedMembers, numJoinedMembers)
|
||||
} else {
|
||||
topic.orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceRoom.visibilityIcon(): ImageVector? {
|
||||
// Don't show any icon for restricted rooms as it's the default and would add noise
|
||||
return if (visibility == SpaceRoomVisibility.Restricted) {
|
||||
null
|
||||
} else {
|
||||
visibility.icon
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewsDayNight
|
||||
internal fun SpaceRoomItemViewPreview(@PreviewParameter(SpaceRoomProvider::class) spaceRoom: SpaceRoom) = ElementPreview {
|
||||
|
||||
@@ -32,7 +32,7 @@ val SpaceRoomVisibility.icon: ImageVector
|
||||
return when (this) {
|
||||
SpaceRoomVisibility.Private -> CompoundIcons.LockSolid()
|
||||
SpaceRoomVisibility.Public -> CompoundIcons.Public()
|
||||
SpaceRoomVisibility.Restricted -> CompoundIcons.Space()
|
||||
SpaceRoomVisibility.SpaceMembers -> CompoundIcons.Space()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ val SpaceRoomVisibility.label: String
|
||||
@ReadOnlyComposable
|
||||
get() {
|
||||
return when (this) {
|
||||
SpaceRoomVisibility.Private -> stringResource(CommonStrings.common_private_space)
|
||||
SpaceRoomVisibility.Public -> stringResource(CommonStrings.common_public_space)
|
||||
SpaceRoomVisibility.Restricted -> stringResource(CommonStrings.common_shared_space)
|
||||
SpaceRoomVisibility.Private -> stringResource(CommonStrings.common_private)
|
||||
SpaceRoomVisibility.Public -> stringResource(CommonStrings.common_public)
|
||||
SpaceRoomVisibility.SpaceMembers -> stringResource(CommonStrings.common_space_members)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user