Merge pull request #6022 from element-hq/feature/fga/space_manage_rooms
Space : manage rooms
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
|
||||
sealed interface SpaceEvents {
|
||||
@@ -19,4 +20,12 @@ sealed interface SpaceEvents {
|
||||
|
||||
data class ShowTopicViewer(val topic: String) : SpaceEvents
|
||||
data object HideTopicViewer : SpaceEvents
|
||||
|
||||
// Manage mode events
|
||||
data object EnterManageMode : SpaceEvents
|
||||
data object ExitManageMode : SpaceEvents
|
||||
data class ToggleRoomSelection(val roomId: RoomId) : SpaceEvents
|
||||
data object ConfirmRoomRemoval : SpaceEvents
|
||||
data object RemoveSelectedRooms : SpaceEvents
|
||||
data object ClearRemoveAction : SpaceEvents
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.features.space.impl.root
|
||||
|
||||
import io.element.android.features.space.impl.settings.SpaceSettingsPermissions
|
||||
import io.element.android.features.space.impl.settings.spaceSettingsPermissions
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
|
||||
|
||||
/**
|
||||
* Permissions needed for different actions in the Space screen.
|
||||
* @param settingsPermissions Permissions related to space settings.
|
||||
* @param canEditSpaceGraph Whether the user can edit the space graph (add/remove children).
|
||||
*/
|
||||
data class SpacePermissions(
|
||||
val settingsPermissions: SpaceSettingsPermissions,
|
||||
val canEditSpaceGraph: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT = SpacePermissions(
|
||||
settingsPermissions = SpaceSettingsPermissions.DEFAULT,
|
||||
canEditSpaceGraph = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun RoomPermissions.spacePermissions(): SpacePermissions {
|
||||
return SpacePermissions(
|
||||
settingsPermissions = spaceSettingsPermissions(),
|
||||
canEditSpaceGraph = canOwnUserSendState(StateEventType.SpaceChild) || canOwnUserSendState(StateEventType.SpaceParent),
|
||||
)
|
||||
}
|
||||
@@ -23,8 +23,6 @@ import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.toInviteData
|
||||
import io.element.android.features.space.impl.settings.SpaceSettingsPermissions
|
||||
import io.element.android.features.space.impl.settings.spaceSettingsPermissions
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
@@ -40,6 +38,7 @@ import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -48,9 +47,10 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
@Inject
|
||||
class SpacePresenter(
|
||||
@@ -62,6 +62,7 @@ class SpacePresenter(
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val spaceService: SpaceService,
|
||||
) : Presenter<SpaceState> {
|
||||
private var children by mutableStateOf<ImmutableList<SpaceRoom>>(persistentListOf())
|
||||
|
||||
@@ -88,8 +89,8 @@ class SpacePresenter(
|
||||
}
|
||||
}.collectAsState()
|
||||
|
||||
val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms ->
|
||||
perms.spaceSettingsPermissions()
|
||||
val permissions by room.permissionsAsState(SpacePermissions.DEFAULT) { perms ->
|
||||
perms.spacePermissions()
|
||||
}
|
||||
val isSpaceSettingsEnabled by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings)
|
||||
@@ -97,13 +98,37 @@ class SpacePresenter(
|
||||
|
||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||
val canAccessSpaceSettings by remember {
|
||||
derivedStateOf { isSpaceSettingsEnabled && permissions.hasAny(roomInfo.joinRule) }
|
||||
derivedStateOf { isSpaceSettingsEnabled && permissions.settingsPermissions.hasAny(roomInfo.joinRule) }
|
||||
}
|
||||
val canEditSpaceGraph by remember {
|
||||
derivedStateOf { isSpaceSettingsEnabled && permissions.canEditSpaceGraph }
|
||||
}
|
||||
val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
|
||||
val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) }
|
||||
|
||||
var topicViewerState: TopicViewerState by remember { mutableStateOf(TopicViewerState.Hidden) }
|
||||
|
||||
// Manage mode state
|
||||
var isManageMode by remember { mutableStateOf(false) }
|
||||
var selectedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
||||
var removeRoomsAction by remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
var removedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
||||
|
||||
val filteredChildren by remember {
|
||||
derivedStateOf {
|
||||
children
|
||||
.filterNot { it.roomId in removedRoomIds }
|
||||
.let { list ->
|
||||
if (isManageMode) {
|
||||
// In manage mode, only show rooms (not spaces)
|
||||
list.filter { !it.isSpace }
|
||||
} else {
|
||||
list
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(children) {
|
||||
// Remove joined children from the join actions
|
||||
val joinedChildren = children
|
||||
@@ -138,11 +163,60 @@ class SpacePresenter(
|
||||
}
|
||||
SpaceEvents.HideTopicViewer -> topicViewerState = TopicViewerState.Hidden
|
||||
is SpaceEvents.ShowTopicViewer -> topicViewerState = TopicViewerState.Shown(event.topic)
|
||||
|
||||
// Manage mode events
|
||||
SpaceEvents.EnterManageMode -> {
|
||||
isManageMode = true
|
||||
selectedRoomIds = emptySet()
|
||||
}
|
||||
SpaceEvents.ExitManageMode -> {
|
||||
isManageMode = false
|
||||
selectedRoomIds = emptySet()
|
||||
}
|
||||
is SpaceEvents.ToggleRoomSelection -> {
|
||||
selectedRoomIds = if (event.roomId in selectedRoomIds) {
|
||||
selectedRoomIds - event.roomId
|
||||
} else {
|
||||
selectedRoomIds + event.roomId
|
||||
}
|
||||
}
|
||||
SpaceEvents.RemoveSelectedRooms -> {
|
||||
removeRoomsAction = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
SpaceEvents.ConfirmRoomRemoval -> {
|
||||
localCoroutineScope.launch {
|
||||
removeRoomsAction = AsyncAction.Loading
|
||||
val spaceId = spaceRoomList.roomId
|
||||
val roomsToRemove = selectedRoomIds.toSet()
|
||||
val successfullyRemoved = mutableSetOf<RoomId>()
|
||||
val results = roomsToRemove.map { roomId ->
|
||||
async {
|
||||
spaceService.removeChildFromSpace(spaceId, roomId)
|
||||
.onSuccess { successfullyRemoved.add(roomId) }
|
||||
}
|
||||
}
|
||||
results.awaitAll()
|
||||
if (successfullyRemoved.isNotEmpty()) {
|
||||
removedRoomIds = removedRoomIds + successfullyRemoved
|
||||
}
|
||||
val hasError = successfullyRemoved.size < roomsToRemove.size
|
||||
if (hasError) {
|
||||
removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms"))
|
||||
} else {
|
||||
removeRoomsAction = AsyncAction.Success(Unit)
|
||||
isManageMode = false
|
||||
selectedRoomIds = emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
SpaceEvents.ClearRemoveAction -> {
|
||||
removeRoomsAction = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
return SpaceState(
|
||||
currentSpace = currentSpace.getOrNull(),
|
||||
children = children,
|
||||
spaceInfo = roomInfo,
|
||||
children = filteredChildren,
|
||||
seenSpaceInvites = seenSpaceInvites,
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
hasMoreToLoad = hasMoreToLoad,
|
||||
@@ -150,6 +224,10 @@ class SpacePresenter(
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
topicViewerState = topicViewerState,
|
||||
canAccessSpaceSettings = canAccessSpaceSettings,
|
||||
isManageMode = isManageMode,
|
||||
selectedRoomIds = selectedRoomIds.toImmutableSet(),
|
||||
canEditSpaceGraph = canEditSpaceGraph,
|
||||
removeRoomsAction = removeRoomsAction,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
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.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
data class SpaceState(
|
||||
val currentSpace: SpaceRoom?,
|
||||
val spaceInfo: RoomInfo,
|
||||
val children: ImmutableList<SpaceRoom>,
|
||||
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||
val hideInvitesAvatar: Boolean,
|
||||
@@ -27,12 +28,21 @@ data class SpaceState(
|
||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val topicViewerState: TopicViewerState,
|
||||
val canAccessSpaceSettings: Boolean,
|
||||
val isManageMode: Boolean,
|
||||
val selectedRoomIds: ImmutableSet<RoomId>,
|
||||
val canEditSpaceGraph: Boolean,
|
||||
val removeRoomsAction: AsyncAction<Unit>,
|
||||
val eventSink: (SpaceEvents) -> Unit
|
||||
) {
|
||||
fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading
|
||||
val hasAnyFailure: Boolean = joinActions.values.any {
|
||||
fun isSelected(spaceId: RoomId): Boolean = selectedRoomIds.contains(spaceId)
|
||||
val hasAnyJoinFailures: Boolean = joinActions.values.any {
|
||||
it is AsyncAction.Failure
|
||||
}
|
||||
|
||||
val showManageRoomsAction: Boolean = canEditSpaceGraph && children.any { spaceRoom -> !spaceRoom.isSpace }
|
||||
val selectedCount: Int = selectedRoomIds.size
|
||||
val isRemoveButtonEnabled: Boolean = selectedRoomIds.isNotEmpty()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
||||
@@ -15,6 +15,8 @@ import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInvit
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
@@ -27,11 +29,11 @@ open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
||||
override val values: Sequence<SpaceState>
|
||||
get() = sequenceOf(
|
||||
aSpaceState(),
|
||||
aSpaceState(parentSpace = aParentSpace(joinRule = JoinRule.Public)),
|
||||
aSpaceState(parentSpace = aParentSpace(joinRule = JoinRule.Restricted(persistentListOf()))),
|
||||
aSpaceState(spaceInfo = aSpaceInfo(joinRule = JoinRule.Public)),
|
||||
aSpaceState(spaceInfo = aSpaceInfo(joinRule = JoinRule.Restricted(persistentListOf()))),
|
||||
aSpaceState(children = aListOfSpaceRooms()),
|
||||
aSpaceState(
|
||||
parentSpace = aParentSpace(),
|
||||
spaceInfo = aSpaceInfo(),
|
||||
children = aListOfSpaceRooms(),
|
||||
joiningRooms = setOf(RoomId("!spaceId0:example.com")),
|
||||
hasMoreToLoad = false
|
||||
@@ -39,12 +41,31 @@ open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
||||
aSpaceState(
|
||||
topicViewerState = TopicViewerState.Shown(topic = "Space description goes here." + LoremIpsum(20).values.first()),
|
||||
),
|
||||
// Add other states here
|
||||
// Manage mode states
|
||||
aSpaceState(
|
||||
spaceInfo = aSpaceInfo(),
|
||||
children = aListOfSpaceRooms(),
|
||||
isManageMode = true,
|
||||
selectedRoomIds = emptySet(),
|
||||
),
|
||||
aSpaceState(
|
||||
spaceInfo = aSpaceInfo(),
|
||||
children = aListOfSpaceRooms(),
|
||||
isManageMode = true,
|
||||
selectedRoomIds = setOf(RoomId("!spaceId0:example.com"), RoomId("!spaceId1:example.com")),
|
||||
),
|
||||
aSpaceState(
|
||||
spaceInfo = aSpaceInfo(),
|
||||
children = aListOfSpaceRooms(),
|
||||
isManageMode = true,
|
||||
selectedRoomIds = setOf(RoomId("!spaceId0:example.com")),
|
||||
removeRoomsAction = AsyncAction.ConfirmingNoParams,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aSpaceState(
|
||||
parentSpace: SpaceRoom? = aParentSpace(),
|
||||
spaceInfo: RoomInfo = aSpaceInfo(),
|
||||
children: List<SpaceRoom> = emptyList(),
|
||||
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||
joiningRooms: Set<RoomId> = emptySet(),
|
||||
@@ -54,9 +75,13 @@ fun aSpaceState(
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
topicViewerState: TopicViewerState = TopicViewerState.Hidden,
|
||||
canAccessSpaceSettings: Boolean = true,
|
||||
isManageMode: Boolean = false,
|
||||
selectedRoomIds: Set<RoomId> = emptySet(),
|
||||
canManageRooms: Boolean = true,
|
||||
removeRoomsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (SpaceEvents) -> Unit = { },
|
||||
) = SpaceState(
|
||||
currentSpace = parentSpace,
|
||||
spaceInfo = spaceInfo,
|
||||
children = children.toImmutableList(),
|
||||
seenSpaceInvites = seenSpaceInvites.toImmutableSet(),
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
@@ -65,19 +90,52 @@ fun aSpaceState(
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
topicViewerState = topicViewerState,
|
||||
canAccessSpaceSettings = canAccessSpaceSettings,
|
||||
isManageMode = isManageMode,
|
||||
selectedRoomIds = selectedRoomIds.toImmutableSet(),
|
||||
canEditSpaceGraph = canManageRooms,
|
||||
removeRoomsAction = removeRoomsAction,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
private fun aParentSpace(
|
||||
private fun aSpaceInfo(
|
||||
joinRule: JoinRule? = null,
|
||||
): SpaceRoom {
|
||||
return aSpaceRoom(
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
joinRule = joinRule,
|
||||
roomId = RoomId("!spaceId0:example.com"),
|
||||
): RoomInfo {
|
||||
return RoomInfo(
|
||||
id = RoomId("!spaceId0:example.com"),
|
||||
name = "A Space",
|
||||
rawName = "A Space",
|
||||
topic = "Space description goes here. " + LoremIpsum(20).values.first(),
|
||||
avatarUrl = null,
|
||||
isPublic = true,
|
||||
isDirect = false,
|
||||
isEncrypted = false,
|
||||
joinRule = joinRule,
|
||||
isSpace = true,
|
||||
isFavorite = false,
|
||||
canonicalAlias = null,
|
||||
alternativeAliases = persistentListOf(),
|
||||
currentUserMembership = CurrentUserMembership.JOINED,
|
||||
inviter = null,
|
||||
activeMembersCount = 5,
|
||||
invitedMembersCount = 0,
|
||||
joinedMembersCount = 5,
|
||||
roomPowerLevels = null,
|
||||
highlightCount = 0,
|
||||
notificationCount = 0,
|
||||
userDefinedNotificationMode = null,
|
||||
hasRoomCall = false,
|
||||
activeRoomCallParticipants = persistentListOf(),
|
||||
isMarkedUnread = false,
|
||||
numUnreadMessages = 0,
|
||||
numUnreadNotifications = 0,
|
||||
numUnreadMentions = 0,
|
||||
heroes = persistentListOf(),
|
||||
pinnedEventIds = persistentListOf(),
|
||||
creators = persistentListOf(),
|
||||
historyVisibility = RoomHistoryVisibility.Joined,
|
||||
successorRoom = null,
|
||||
roomVersion = "11",
|
||||
privilegedCreatorRole = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,17 @@
|
||||
|
||||
package io.element.android.features.space.impl.root
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -31,6 +38,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
@@ -40,9 +48,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
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.features.space.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule
|
||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||
import io.element.android.libraries.designsystem.components.SimpleModalBottomSheet
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
||||
@@ -51,8 +62,10 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
|
||||
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
|
||||
@@ -61,15 +74,18 @@ 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.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomVisibility
|
||||
import io.element.android.libraries.matrix.ui.components.JoinButton
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceHeaderView
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -85,18 +101,48 @@ fun SpaceView(
|
||||
modifier: Modifier = Modifier,
|
||||
acceptDeclineInviteView: @Composable () -> Unit,
|
||||
) {
|
||||
BackHandler {
|
||||
if (state.isManageMode) {
|
||||
state.eventSink(SpaceEvents.ExitManageMode)
|
||||
} else {
|
||||
onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
SpaceViewTopBar(
|
||||
currentSpace = state.currentSpace,
|
||||
canAccessSpaceSettings = state.canAccessSpaceSettings,
|
||||
onBackClick = onBackClick,
|
||||
onLeaveSpaceClick = onLeaveSpaceClick,
|
||||
onShareSpace = onShareSpace,
|
||||
onSettingsClick = onSettingsClick,
|
||||
onViewMembersClick = onViewMembersClick,
|
||||
)
|
||||
Box {
|
||||
AnimatedVisibility(
|
||||
visible = state.isManageMode,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
ManageModeTopBar(
|
||||
selectedCount = state.selectedCount,
|
||||
isRemoveButtonEnabled = state.isRemoveButtonEnabled,
|
||||
onCancelClick = { state.eventSink(SpaceEvents.ExitManageMode) },
|
||||
onRemoveClick = { state.eventSink(SpaceEvents.RemoveSelectedRooms) },
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = !state.isManageMode,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
SpaceViewTopBar(
|
||||
spaceInfo = state.spaceInfo,
|
||||
canAccessSpaceSettings = state.canAccessSpaceSettings,
|
||||
showManageRoomsAction = state.showManageRoomsAction,
|
||||
onBackClick = onBackClick,
|
||||
onLeaveSpaceClick = onLeaveSpaceClick,
|
||||
onSettingsClick = onSettingsClick,
|
||||
onShareSpace = onShareSpace,
|
||||
onViewMembersClick = onViewMembersClick,
|
||||
onManageRoomsClick = { state.eventSink(SpaceEvents.EnterManageMode) },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = { padding ->
|
||||
Box(
|
||||
@@ -104,15 +150,28 @@ fun SpaceView(
|
||||
) {
|
||||
SpaceViewContent(
|
||||
state = state,
|
||||
onRoomClick = onRoomClick,
|
||||
onRoomClick = { spaceRoom ->
|
||||
if (state.isManageMode) {
|
||||
state.eventSink(SpaceEvents.ToggleRoomSelection(spaceRoom.roomId))
|
||||
} else {
|
||||
onRoomClick(spaceRoom)
|
||||
}
|
||||
},
|
||||
onTopicClick = { topic ->
|
||||
state.eventSink(SpaceEvents.ShowTopicViewer(topic))
|
||||
}
|
||||
)
|
||||
JoinRoomFailureEffect(
|
||||
hasAnyFailure = state.hasAnyFailure,
|
||||
JoinFailuresEffect(
|
||||
hasAnyFailure = state.hasAnyJoinFailures,
|
||||
eventSink = state.eventSink
|
||||
)
|
||||
RemoveRoomsActionView(
|
||||
spaceDisplayName = state.spaceInfo.name ?: state.spaceInfo.id.value,
|
||||
removeRoomsAction = state.removeRoomsAction,
|
||||
selectedCount = state.selectedCount,
|
||||
onConfirm = { state.eventSink(SpaceEvents.ConfirmRoomRemoval) },
|
||||
onDismiss = { state.eventSink(SpaceEvents.ClearRemoveAction) },
|
||||
)
|
||||
acceptDeclineInviteView()
|
||||
}
|
||||
},
|
||||
@@ -128,7 +187,7 @@ fun SpaceView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinRoomFailureEffect(
|
||||
private fun JoinFailuresEffect(
|
||||
hasAnyFailure: Boolean,
|
||||
eventSink: (SpaceEvents) -> Unit,
|
||||
) {
|
||||
@@ -176,22 +235,26 @@ private fun SpaceViewContent(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier.fillMaxSize()) {
|
||||
val currentSpace = state.currentSpace
|
||||
if (currentSpace != null) {
|
||||
item {
|
||||
SpaceHeaderView(
|
||||
avatarData = currentSpace.getAvatarData(AvatarSize.SpaceHeader),
|
||||
name = currentSpace.displayName,
|
||||
topic = currentSpace.topic,
|
||||
topicMaxLines = 2,
|
||||
visibility = currentSpace.visibility,
|
||||
heroes = currentSpace.heroes.toImmutableList(),
|
||||
numberOfMembers = currentSpace.numJoinedMembers,
|
||||
onTopicClick = onTopicClick
|
||||
)
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
val spaceInfo = state.spaceInfo
|
||||
item(key = "space_header") {
|
||||
AnimatedVisibility(
|
||||
!state.isManageMode,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
Column {
|
||||
SpaceHeaderView(
|
||||
avatarData = spaceInfo.getAvatarData(AvatarSize.SpaceHeader),
|
||||
name = spaceInfo.name,
|
||||
topic = spaceInfo.topic,
|
||||
topicMaxLines = 2,
|
||||
visibility = SpaceRoomVisibility.fromJoinRule(spaceInfo.joinRule),
|
||||
heroes = spaceInfo.heroes,
|
||||
numberOfMembers = spaceInfo.joinedMembersCount.toInt(),
|
||||
onTopicClick = onTopicClick
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsIndexed(
|
||||
@@ -200,9 +263,11 @@ private fun SpaceViewContent(
|
||||
) { index, spaceRoom ->
|
||||
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
|
||||
val isCurrentlyJoining = state.isJoining(spaceRoom.roomId)
|
||||
val isSelected = state.isSelected(spaceRoom.roomId)
|
||||
val showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites && !state.isManageMode
|
||||
SpaceRoomItemView(
|
||||
spaceRoom = spaceRoom,
|
||||
showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites,
|
||||
showUnreadIndicator = showUnreadIndicator,
|
||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||
onClick = {
|
||||
onRoomClick(spaceRoom)
|
||||
@@ -210,17 +275,30 @@ private fun SpaceViewContent(
|
||||
onLongClick = {
|
||||
// TODO
|
||||
},
|
||||
trailingAction = spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) {
|
||||
state.eventSink(SpaceEvents.Join(spaceRoom))
|
||||
},
|
||||
bottomAction = spaceRoom.inviteButtons(
|
||||
onAcceptClick = {
|
||||
state.eventSink(SpaceEvents.AcceptInvite(spaceRoom))
|
||||
},
|
||||
onDeclineClick = {
|
||||
state.eventSink(SpaceEvents.DeclineInvite(spaceRoom))
|
||||
trailingAction = if (state.isManageMode) {
|
||||
{
|
||||
Checkbox(
|
||||
checked = isSelected,
|
||||
onCheckedChange = null,
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) {
|
||||
state.eventSink(SpaceEvents.Join(spaceRoom))
|
||||
}
|
||||
},
|
||||
bottomAction = if (state.isManageMode) {
|
||||
null
|
||||
} else {
|
||||
spaceRoom.inviteButtons(
|
||||
onAcceptClick = {
|
||||
state.eventSink(SpaceEvents.AcceptInvite(spaceRoom))
|
||||
},
|
||||
onDeclineClick = {
|
||||
state.eventSink(SpaceEvents.DeclineInvite(spaceRoom))
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
if (index != state.children.lastIndex) {
|
||||
HorizontalDivider()
|
||||
@@ -257,13 +335,15 @@ private fun LoadingMoreIndicator(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SpaceViewTopBar(
|
||||
currentSpace: SpaceRoom?,
|
||||
spaceInfo: RoomInfo,
|
||||
canAccessSpaceSettings: Boolean,
|
||||
showManageRoomsAction: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
onLeaveSpaceClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
onShareSpace: () -> Unit,
|
||||
onViewMembersClick: () -> Unit,
|
||||
onManageRoomsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TopAppBar(
|
||||
@@ -272,16 +352,14 @@ private fun SpaceViewTopBar(
|
||||
BackButton(onClick = onBackClick)
|
||||
},
|
||||
title = {
|
||||
if (currentSpace != null) {
|
||||
val roundedCornerShape = RoundedCornerShape(8.dp)
|
||||
SpaceAvatarAndNameRow(
|
||||
name = currentSpace.displayName,
|
||||
avatarData = currentSpace.getAvatarData(AvatarSize.TimelineRoom),
|
||||
modifier = Modifier
|
||||
.clip(roundedCornerShape)
|
||||
.clickable(enabled = canAccessSpaceSettings, onClick = onSettingsClick)
|
||||
)
|
||||
}
|
||||
val roundedCornerShape = RoundedCornerShape(8.dp)
|
||||
SpaceAvatarAndNameRow(
|
||||
name = spaceInfo.name,
|
||||
avatarData = spaceInfo.getAvatarData(AvatarSize.TimelineRoom),
|
||||
modifier = Modifier
|
||||
.clip(roundedCornerShape)
|
||||
.clickable(enabled = canAccessSpaceSettings, onClick = onSettingsClick)
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
@@ -297,8 +375,19 @@ private fun SpaceViewTopBar(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (showManageRoomsAction) {
|
||||
SpaceMenuItem(
|
||||
titleRes = CommonStrings.action_manage_rooms,
|
||||
icon = CompoundIcons.Edit(),
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onManageRoomsClick()
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
SpaceMenuItem(
|
||||
titleRes = CommonStrings.screen_space_menu_action_members,
|
||||
titleRes = R.string.screen_space_menu_action_members,
|
||||
icon = CompoundIcons.User(),
|
||||
onClick = {
|
||||
showMenu = false
|
||||
@@ -323,6 +412,7 @@ private fun SpaceViewTopBar(
|
||||
}
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
SpaceMenuItem(
|
||||
titleRes = CommonStrings.action_leave_space,
|
||||
icon = CompoundIcons.Leave(),
|
||||
@@ -337,6 +427,39 @@ private fun SpaceViewTopBar(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ManageModeTopBar(
|
||||
selectedCount: Int,
|
||||
isRemoveButtonEnabled: Boolean,
|
||||
onCancelClick: () -> Unit,
|
||||
onRemoveClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
navigationIcon = {
|
||||
BackButton(
|
||||
onClick = onCancelClick,
|
||||
imageVector = CompoundIcons.Close()
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = pluralStringResource(CommonPlurals.common_selected_count, selectedCount, selectedCount),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_remove),
|
||||
onClick = onRemoveClick,
|
||||
enabled = isRemoveButtonEnabled,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceMenuItem(
|
||||
@StringRes titleRes: Int,
|
||||
@@ -425,6 +548,45 @@ private fun SpaceRoom.inviteButtons(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RemoveRoomsActionView(
|
||||
spaceDisplayName: String,
|
||||
removeRoomsAction: AsyncAction<Unit>,
|
||||
selectedCount: Int,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AsyncActionView(
|
||||
async = removeRoomsAction,
|
||||
confirmationDialog = {
|
||||
ConfirmationDialog(
|
||||
title = pluralStringResource(R.plurals.screen_space_remove_rooms_confirmation_title, selectedCount, selectedCount, spaceDisplayName),
|
||||
content = stringResource(R.string.screen_space_remove_rooms_confirmation_content),
|
||||
submitText = stringResource(CommonStrings.action_remove),
|
||||
onSubmitClick = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
destructiveSubmit = true,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Error(),
|
||||
tint = ElementTheme.colors.textCriticalPrimary,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
onRetry = onConfirm,
|
||||
errorTitle = {
|
||||
stringResource(CommonStrings.common_something_went_wrong)
|
||||
},
|
||||
errorMessage = {
|
||||
stringResource(CommonStrings.error_network_or_server_issue)
|
||||
},
|
||||
onSuccess = { onDismiss() },
|
||||
onErrorDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun SpaceViewPreview(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_space_menu_action_members">"Преглед на членовете"</string>
|
||||
<string name="screen_space_settings_leave_space">"Напускане на пространството"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Роли и разрешения"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Защита и поверителност"</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Z následujících místností nebudete odstraněni, protože jste jediným administrátorem:"</string>
|
||||
<string name="screen_leave_space_title">"Opustit %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Jste jediným administrátorem pro %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Zobrazit členy"</string>
|
||||
<string name="screen_space_settings_leave_space">"Opustit prostor"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Role a oprávnění"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Zabezpečení a soukromí"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Du vil ikke blive fjernet fra følgende rum, fordi du er den eneste administrator:"</string>
|
||||
<string name="screen_leave_space_title">"Forlad %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Du er den eneste administrator for %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Vis medlemmer"</string>
|
||||
<string name="screen_space_settings_leave_space">"Forlad gruppe"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roller og tilladelser"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Sikkerhed og privatliv"</string>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Du wirst aus den folgenden Chats nicht entfernt, weil du der einzige Admin bist:"</string>
|
||||
<string name="screen_leave_space_title">"%1$s verlassen?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Du bist der einzige Administrator für %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Mitglieder anzeigen"</string>
|
||||
<string name="screen_space_remove_rooms_confirmation_content">"Das Entfernen eines Chats hat keinen Einfluss auf die Beitrittsregeln. Um die Regeln zu ändern, gehe zu \"Raum Info\" und dann zu \"Datenschutz und Sicherheit\""</string>
|
||||
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||
<item quantity="one">"%1$d chat aus %2$s entfernen"</item>
|
||||
<item quantity="other">"%1$d chats aus %2$s entfernen"</item>
|
||||
</plurals>
|
||||
<string name="screen_space_settings_leave_space">"Space verlassen"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Rollen und Berechtigungen"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Sicherheit & Datenschutz"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Sind ei saa järgnevatest jututubadest eemaldada, kuna oled seal/neis ainus peakasutaja:"</string>
|
||||
<string name="screen_leave_space_title">"Kas lahkud %1$s kogukonnast?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Sa oled siin ainus peakasutaja: %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Vaata liikmeid"</string>
|
||||
<string name="screen_space_settings_leave_space">"Lahku kogukonnast"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Rollid ja õigused"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Turvalisus ja privaatsus"</string>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"از اتاق(های) زیر برداشته نخواهید شد؛ چرا که تنها مدیر هستید:"</string>
|
||||
<string name="screen_leave_space_title">"ترک %1$s؟"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"تنها مدیر %1$s هستید"</string>
|
||||
<string name="screen_space_menu_action_members">"دیدن اعضا"</string>
|
||||
<string name="screen_space_settings_leave_space">"ترک فضا"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"نقشها و اجازهها"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"امنیت و محرمانگی"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Sinua ei poisteta seuraavista huoneista, koska olet ainoa ylläpitäjä:"</string>
|
||||
<string name="screen_leave_space_title">"Haluatko poistua tilasta %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Olet ainoa ylläpitäjä tilassa %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Näytä jäsenet"</string>
|
||||
<string name="screen_space_settings_leave_space">"Poistu tilasta"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roolit ja oikeudet"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Turvallisuus ja yksityisyys"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Vous ne quitterez pas le ou les salons suivants car vous y êtes le seul administrateur:"</string>
|
||||
<string name="screen_leave_space_title">"Quitter %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Vous êtes le seul administrateur de %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Voir les membres"</string>
|
||||
<string name="screen_space_settings_leave_space">"Quitter l’espace"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Rôles & autorisations"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Sécurité & confidentialité"</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Nećete biti uklonjeni iz sljedećih soba jer ste jedini administrator:"</string>
|
||||
<string name="screen_leave_space_title">"Želite li napustiti %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Vi ste jedini administrator za %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Prikaži članove"</string>
|
||||
<string name="screen_space_settings_leave_space">"Napusti prostor"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Uloge i dopuštenja"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Sigurnost i privatnost"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Nem lesz eltávolítva a következő szobá(k)ból, mert ön az egyetlen adminisztrátor:"</string>
|
||||
<string name="screen_leave_space_title">"Kilép innen: %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Ön az egyetlen adminisztrátor itt: %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Tagok megtekintése"</string>
|
||||
<string name="screen_space_settings_leave_space">"Tér elhagyása"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Szerepkörök és jogosultságok"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Biztonság és adatvédelem"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Non verrai rimosso dalle seguenti stanze perché sei l\'unico amministratore:"</string>
|
||||
<string name="screen_leave_space_title">"Uscire da %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Sei l\'unico amministratore di %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Visualizza membri"</string>
|
||||
<string name="screen_space_settings_leave_space">"Esci dallo spazio"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Ruoli e autorizzazioni"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Sicurezza e privacy"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Du vil ikke bli fjernet fra følgende rom fordi du er den eneste administratoren:"</string>
|
||||
<string name="screen_leave_space_title">"Forlat %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Du er den eneste administratoren for %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Vis medlemmer"</string>
|
||||
<string name="screen_space_settings_leave_space">"Forlat område"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roller og tillatelser"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Sikkerhet og personvern"</string>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Você não será removido das seguintes salas porque você é o único administrador:"</string>
|
||||
<string name="screen_leave_space_title">"Sair de %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Você é o único administrador de %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Ver membros"</string>
|
||||
<string name="screen_space_settings_leave_space">"Sair do espaço"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Cargos e permissões"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Segurança e privacidade"</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Nu veți părăsi următoarele camere deoarece sunteți singurul administrator:"</string>
|
||||
<string name="screen_leave_space_title">"Părăsiți %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Sunteți singurul administrator pentru %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Vizualizați membrii"</string>
|
||||
<string name="screen_space_settings_leave_space">"Părăsiți spațiul"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roluri și permisiuni"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Securitate & confidențialitate"</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Вы не будете удалены из следующих комнат, поскольку вы являетесь единственным администратором:"</string>
|
||||
<string name="screen_leave_space_title">"Выйти из %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Вы единственный администратор для %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Просмотреть участников"</string>
|
||||
<string name="screen_space_settings_leave_space">"Покинуть пространство"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Роли и разрешения"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Безопасность и конфиденциальность"</string>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"Z nasledujúcich miestností nebudete odstránený/á, pretože ste jediným správcom:"</string>
|
||||
<string name="screen_leave_space_title">"Opustiť %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"Ste jediným administrátorom pre %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"Zobraziť členov"</string>
|
||||
<string name="screen_space_settings_leave_space">"Opustiť priestor"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roly a povolenia"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Bezpečnosť a súkromie"</string>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"您不會被從以下聊天室移除,因為您是唯一的管理員:"</string>
|
||||
<string name="screen_leave_space_title">"離開 %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"您是 %1$s 唯一的管理員"</string>
|
||||
<string name="screen_space_menu_action_members">"檢視成員"</string>
|
||||
<string name="screen_space_settings_leave_space">"離開空間"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"角色與權限"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"安全與隱私"</string>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"您不会从以下房间中被移除,因为您是唯一的管理员:"</string>
|
||||
<string name="screen_leave_space_title">"离开%1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"您是 %1$s 的唯一管理员"</string>
|
||||
<string name="screen_space_menu_action_members">"查看成员"</string>
|
||||
<string name="screen_space_settings_leave_space">"离开空间"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"角色与权限"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"安全与隐私"</string>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"You will not be removed from the following room(s) because you\'re the only administrator:"</string>
|
||||
<string name="screen_leave_space_title">"Leave %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"You are the only admin for %1$s"</string>
|
||||
<string name="screen_space_menu_action_members">"View members"</string>
|
||||
<string name="screen_space_remove_rooms_confirmation_content">"Removing a room will not affect the room access. To change the access go to Room info > Privacy & security."</string>
|
||||
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||
<item quantity="one">"Remove %1$d room from %2$s"</item>
|
||||
<item quantity="other">"Remove %1$d rooms from %2$s"</item>
|
||||
</plurals>
|
||||
<string name="screen_space_settings_leave_space">"Leave space"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roles & permissions"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Security & privacy"</string>
|
||||
|
||||
@@ -22,20 +22,24 @@ import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||
import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
@@ -59,7 +63,7 @@ class SpacePresenterTest {
|
||||
val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.currentSpace).isNull()
|
||||
assertThat(state.spaceInfo).isNotNull()
|
||||
assertThat(state.children).isEmpty()
|
||||
assertThat(state.seenSpaceInvites).isEmpty()
|
||||
assertThat(state.hideInvitesAvatar).isFalse()
|
||||
@@ -139,23 +143,6 @@ class SpacePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - current space value`() = runTest {
|
||||
val paginateResult = lambdaRecorder<Result<Unit>> {
|
||||
Result.success(Unit)
|
||||
}
|
||||
val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
|
||||
val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
advanceUntilIdle()
|
||||
assertThat(state.currentSpace).isNull()
|
||||
val aSpace = aSpaceRoom()
|
||||
spaceRoomList.emitCurrentSpace(aSpace)
|
||||
assertThat(awaitItem().currentSpace).isEqualTo(aSpace)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - children value`() = runTest {
|
||||
val paginateResult = lambdaRecorder<Result<Unit>> {
|
||||
@@ -353,6 +340,216 @@ class SpacePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - enter manage mode`() = runTest {
|
||||
val presenter = createSpacePresenter()
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.isManageMode).isFalse()
|
||||
state.eventSink(SpaceEvents.EnterManageMode)
|
||||
val manageModeState = awaitItem()
|
||||
assertThat(manageModeState.isManageMode).isTrue()
|
||||
assertThat(manageModeState.selectedRoomIds).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - exit manage mode clears selection`() = runTest {
|
||||
val presenter = createSpacePresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(SpaceEvents.EnterManageMode)
|
||||
initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
initialState.eventSink(SpaceEvents.ExitManageMode)
|
||||
val finalState = expectMostRecentItem()
|
||||
assertThat(finalState.isManageMode).isFalse()
|
||||
assertThat(finalState.selectedRoomIds).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - toggle room selection`() = runTest {
|
||||
val presenter = createSpacePresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(SpaceEvents.EnterManageMode)
|
||||
// Select a room
|
||||
initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
var latestState = expectMostRecentItem()
|
||||
assertThat(latestState.selectedRoomIds).containsExactly(A_ROOM_ID)
|
||||
// Deselect the room
|
||||
latestState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
latestState = expectMostRecentItem()
|
||||
assertThat(latestState.selectedRoomIds).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - remove rooms success`() = runTest {
|
||||
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val aRoom = aSpaceRoom(
|
||||
roomId = A_ROOM_ID,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val fakeSpaceRoomList = FakeSpaceRoomList(
|
||||
initialSpaceRoomsValue = listOf(aRoom),
|
||||
paginateResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createSpacePresenter(
|
||||
spaceRoomList = fakeSpaceRoomList,
|
||||
spaceService = FakeSpaceService(
|
||||
removeChildFromSpaceResult = removeChildFromSpaceResult,
|
||||
),
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem() // Initial empty state
|
||||
advanceUntilIdle()
|
||||
val stateWithChildren = awaitItem()
|
||||
assertThat(stateWithChildren.children).hasSize(1)
|
||||
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
|
||||
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms)
|
||||
stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval)
|
||||
advanceUntilIdle()
|
||||
val successState = expectMostRecentItem()
|
||||
assertThat(successState.removeRoomsAction).isEqualTo(AsyncAction.Success(Unit))
|
||||
assertThat(successState.isManageMode).isFalse()
|
||||
assertThat(successState.children).isEmpty()
|
||||
removeChildFromSpaceResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - remove rooms partial failure`() = runTest {
|
||||
val aRoom1 = aSpaceRoom(
|
||||
roomId = A_ROOM_ID,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val aRoom2 = aSpaceRoom(
|
||||
roomId = A_ROOM_ID_2,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, childId ->
|
||||
if (childId == A_ROOM_ID_2) {
|
||||
Result.failure(AN_EXCEPTION)
|
||||
} else {
|
||||
Result.success(Unit)
|
||||
}
|
||||
}
|
||||
val fakeSpaceRoomList = FakeSpaceRoomList(
|
||||
initialSpaceRoomsValue = listOf(aRoom1, aRoom2),
|
||||
paginateResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createSpacePresenter(
|
||||
spaceRoomList = fakeSpaceRoomList,
|
||||
spaceService = FakeSpaceService(
|
||||
removeChildFromSpaceResult = removeChildFromSpaceResult,
|
||||
),
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem() // Initial empty state
|
||||
advanceUntilIdle()
|
||||
val stateWithChildren = awaitItem()
|
||||
assertThat(stateWithChildren.children).hasSize(2)
|
||||
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
|
||||
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID_2))
|
||||
stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms)
|
||||
stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval)
|
||||
advanceUntilIdle()
|
||||
val failureState = expectMostRecentItem()
|
||||
assertThat(failureState.removeRoomsAction.isFailure()).isTrue()
|
||||
// Successfully removed room should be filtered out
|
||||
assertThat(failureState.children.map { it.roomId }).doesNotContain(A_ROOM_ID)
|
||||
// Failed room should still be present
|
||||
assertThat(failureState.children.map { it.roomId }).contains(A_ROOM_ID_2)
|
||||
removeChildFromSpaceResult.assertions().isCalledExactly(2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - children filtered in manage mode shows only rooms`() = runTest {
|
||||
val aRoom = aSpaceRoom(
|
||||
roomId = A_ROOM_ID,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val aSubSpace = aSpaceRoom(
|
||||
roomId = A_ROOM_ID_2,
|
||||
roomType = RoomType.Space,
|
||||
)
|
||||
val fakeSpaceRoomList = FakeSpaceRoomList(
|
||||
initialSpaceRoomsValue = listOf(aRoom, aSubSpace),
|
||||
paginateResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createSpacePresenter(spaceRoomList = fakeSpaceRoomList)
|
||||
presenter.test {
|
||||
awaitItem() // Initial empty state
|
||||
advanceUntilIdle()
|
||||
val stateWithChildren = awaitItem()
|
||||
// Both room and space visible initially
|
||||
assertThat(stateWithChildren.children).hasSize(2)
|
||||
assertThat(stateWithChildren.isManageMode).isFalse()
|
||||
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
|
||||
val manageModeState = expectMostRecentItem()
|
||||
// Only rooms visible in manage mode
|
||||
assertThat(manageModeState.children).hasSize(1)
|
||||
assertThat(manageModeState.children.first().roomId).isEqualTo(A_ROOM_ID)
|
||||
assertThat(manageModeState.children.first().isSpace).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - removed rooms persist after flow update`() = runTest {
|
||||
val removeChildFromSpaceResult = lambdaRecorder<RoomId, RoomId, Result<Unit>> { _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val aRoom1 = aSpaceRoom(
|
||||
roomId = A_ROOM_ID,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val aRoom2 = aSpaceRoom(
|
||||
roomId = A_ROOM_ID_2,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val aRoom3 = aSpaceRoom(
|
||||
roomId = A_ROOM_ID_3,
|
||||
roomType = RoomType.Room,
|
||||
)
|
||||
val spaceRoomList = FakeSpaceRoomList(
|
||||
initialSpaceRoomsValue = listOf(aRoom1, aRoom2),
|
||||
paginateResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createSpacePresenter(
|
||||
spaceRoomList = spaceRoomList,
|
||||
spaceService = FakeSpaceService(
|
||||
removeChildFromSpaceResult = removeChildFromSpaceResult,
|
||||
),
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem() // Initial empty state
|
||||
advanceUntilIdle()
|
||||
val stateWithChildren = awaitItem()
|
||||
stateWithChildren.eventSink(SpaceEvents.EnterManageMode)
|
||||
stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms)
|
||||
stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval)
|
||||
advanceUntilIdle()
|
||||
val successState = expectMostRecentItem()
|
||||
assertThat(successState.children.map { it.roomId }).doesNotContain(A_ROOM_ID)
|
||||
// Emit new flow update with a new room added (simulating server refresh)
|
||||
spaceRoomList.emitSpaceRooms(listOf(aRoom1, aRoom2, aRoom3))
|
||||
advanceUntilIdle()
|
||||
val afterFlowUpdate = awaitItem()
|
||||
// A_ROOM_ID should still be filtered out even though it's in the new emission
|
||||
assertThat(afterFlowUpdate.children.map { it.roomId }).doesNotContain(A_ROOM_ID)
|
||||
// But the other rooms should be present
|
||||
assertThat(afterFlowUpdate.children.map { it.roomId }).contains(A_ROOM_ID_2)
|
||||
assertThat(afterFlowUpdate.children.map { it.roomId }).contains(A_ROOM_ID_3)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createSpacePresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
room: BaseRoom = FakeBaseRoom(),
|
||||
@@ -365,6 +562,7 @@ class SpacePresenterTest {
|
||||
),
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
||||
spaceSettingsEnabled: Boolean = false,
|
||||
spaceService: FakeSpaceService = FakeSpaceService(),
|
||||
): SpacePresenter {
|
||||
return SpacePresenter(
|
||||
client = client,
|
||||
@@ -379,6 +577,7 @@ class SpacePresenterTest {
|
||||
FeatureFlags.SpaceSettings.key to spaceSettingsEnabled,
|
||||
)
|
||||
),
|
||||
spaceService = spaceService,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,19 @@ package io.element.android.features.space.impl.root
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import org.junit.Test
|
||||
|
||||
class SpaceStateTest {
|
||||
@Test
|
||||
fun `test default state`() {
|
||||
val state = aSpaceState()
|
||||
assertThat(state.hasAnyFailure).isFalse()
|
||||
assertThat(state.hasAnyJoinFailures).isFalse()
|
||||
assertThat(state.isJoining(A_ROOM_ID)).isFalse()
|
||||
}
|
||||
|
||||
@@ -33,7 +35,7 @@ class SpaceStateTest {
|
||||
A_ROOM_ID_3 to AsyncAction.Success(Unit),
|
||||
)
|
||||
)
|
||||
assertThat(state.hasAnyFailure).isTrue()
|
||||
assertThat(state.hasAnyJoinFailures).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -45,4 +47,80 @@ class SpaceStateTest {
|
||||
)
|
||||
assertThat(state.isJoining(A_ROOM_ID)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test isSelected returns true for selected room`() {
|
||||
val state = aSpaceState(
|
||||
selectedRoomIds = setOf(A_ROOM_ID)
|
||||
)
|
||||
assertThat(state.isSelected(A_ROOM_ID)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test isSelected returns false for non-selected room`() {
|
||||
val state = aSpaceState(
|
||||
selectedRoomIds = setOf(A_ROOM_ID)
|
||||
)
|
||||
assertThat(state.isSelected(A_ROOM_ID_2)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test showManageRoomsAction true when canManageRooms and has room children`() {
|
||||
val state = aSpaceState(
|
||||
canManageRooms = true,
|
||||
children = listOf(aSpaceRoom(roomType = RoomType.Room))
|
||||
)
|
||||
assertThat(state.showManageRoomsAction).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test showManageRoomsAction false when canManageRooms but children empty`() {
|
||||
val state = aSpaceState(
|
||||
canManageRooms = true,
|
||||
children = emptyList()
|
||||
)
|
||||
assertThat(state.showManageRoomsAction).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test showManageRoomsAction false when canManageRooms but only space children`() {
|
||||
val state = aSpaceState(
|
||||
canManageRooms = true,
|
||||
children = listOf(aSpaceRoom(roomType = RoomType.Space))
|
||||
)
|
||||
assertThat(state.showManageRoomsAction).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test showManageRoomsAction false when has room children but canManageRooms false`() {
|
||||
val state = aSpaceState(
|
||||
canManageRooms = false,
|
||||
children = listOf(aSpaceRoom(roomType = RoomType.Room))
|
||||
)
|
||||
assertThat(state.showManageRoomsAction).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test selectedCount returns correct count`() {
|
||||
val state = aSpaceState(
|
||||
selectedRoomIds = setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
|
||||
)
|
||||
assertThat(state.selectedCount).isEqualTo(3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test isRemoveButtonEnabled true when selectedRoomIds not empty`() {
|
||||
val state = aSpaceState(
|
||||
selectedRoomIds = setOf(A_ROOM_ID)
|
||||
)
|
||||
assertThat(state.isRemoveButtonEnabled).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test isRemoveButtonEnabled false when selectedRoomIds empty`() {
|
||||
val state = aSpaceState(
|
||||
selectedRoomIds = emptySet()
|
||||
)
|
||||
assertThat(state.isRemoveButtonEnabled).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ 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.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
@@ -29,6 +31,7 @@ import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import io.element.android.tests.testutils.pressBackKey
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
@@ -124,7 +127,7 @@ class SpaceViewTest {
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
parentSpace = aSpaceRoom(topic = A_ROOM_TOPIC),
|
||||
spaceInfo = aRoomInfo(topic = A_ROOM_TOPIC),
|
||||
hasMoreToLoad = false,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
@@ -132,6 +135,71 @@ class SpaceViewTest {
|
||||
rule.onNodeWithText(A_ROOM_TOPIC).performClick()
|
||||
eventsRecorder.assertSingle(SpaceEvents.ShowTopicViewer(A_ROOM_TOPIC))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking back in manage mode emits ExitManageMode event`() {
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
hasMoreToLoad = false,
|
||||
isManageMode = true,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
rule.pressBackKey()
|
||||
eventsRecorder.assertSingle(SpaceEvents.ExitManageMode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on room in manage mode emits ToggleRoomSelection event`() {
|
||||
val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, displayName = A_ROOM_NAME)
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = listOf(aSpaceRoom),
|
||||
hasMoreToLoad = false,
|
||||
isManageMode = true,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
rule.onNodeWithText(A_ROOM_NAME).performClick()
|
||||
eventsRecorder.assertSingle(SpaceEvents.ToggleRoomSelection(A_ROOM_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking remove button emits RemoveSelectedRooms event`() {
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = listOf(aSpaceRoom(roomId = A_ROOM_ID)),
|
||||
hasMoreToLoad = false,
|
||||
isManageMode = true,
|
||||
selectedRoomIds = setOf(A_ROOM_ID),
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_remove)
|
||||
eventsRecorder.assertSingle(SpaceEvents.RemoveSelectedRooms)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking confirm in removal dialog emits ConfirmRoomRemoval event`() {
|
||||
val eventsRecorder = EventsRecorder<SpaceEvents>()
|
||||
rule.setSpaceView(
|
||||
aSpaceState(
|
||||
children = listOf(aSpaceRoom(roomId = A_ROOM_ID)),
|
||||
hasMoreToLoad = false,
|
||||
isManageMode = true,
|
||||
selectedRoomIds = setOf(A_ROOM_ID),
|
||||
removeRoomsAction = AsyncAction.ConfirmingNoParams,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
// Click on the Remove button in the confirmation dialog
|
||||
rule.clickOn(CommonStrings.action_remove, inDialog = true)
|
||||
eventsRecorder.assertSingle(SpaceEvents.ConfirmRoomRemoval)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceView(
|
||||
|
||||
@@ -22,4 +22,12 @@ interface SpaceService {
|
||||
fun spaceRoomList(id: RoomId): SpaceRoomList
|
||||
|
||||
fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle
|
||||
|
||||
/**
|
||||
* Remove a child room from a space.
|
||||
* @param spaceId The space ID from which to remove the child.
|
||||
* @param childId The room ID of the child to remove.
|
||||
* @return A result indicating success or failure.
|
||||
*/
|
||||
suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result<Unit>
|
||||
}
|
||||
|
||||
@@ -98,6 +98,12 @@ class RustSpaceService(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
innerSpaceService.removeChildFromSpace(childId = childId.value, spaceId = spaceId.value)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
innerSpaceService
|
||||
.spaceListUpdate()
|
||||
|
||||
@@ -23,6 +23,7 @@ class FakeSpaceService(
|
||||
private val joinedSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
|
||||
private val spaceRoomListResult: (RoomId) -> SpaceRoomList = { lambdaError() },
|
||||
private val leaveSpaceHandleResult: (RoomId) -> LeaveSpaceHandle = { lambdaError() },
|
||||
private val removeChildFromSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val joinedParentsResult: (RoomId) -> Result<List<SpaceRoom>> = { lambdaError() },
|
||||
private val getSpaceRoomResult: (RoomId) -> SpaceRoom? = { lambdaError() },
|
||||
) : SpaceService {
|
||||
@@ -53,4 +54,8 @@ class FakeSpaceService(
|
||||
override fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle {
|
||||
return leaveSpaceHandleResult(spaceId)
|
||||
}
|
||||
|
||||
override suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result<Unit> = simulateLongTask {
|
||||
removeChildFromSpaceResult(spaceId, childId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,6 @@
|
||||
<string name="screen_share_this_location_action">"Споделяне на това местоположение"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s пространство"</string>
|
||||
<string name="screen_space_list_title">"Пространства"</string>
|
||||
<string name="screen_space_menu_action_members">"Преглед на членовете"</string>
|
||||
<string name="screen_view_location_title">"Местоположение"</string>
|
||||
<string name="settings_version_number">"Версия: %1$s (%2$s)"</string>
|
||||
<string name="test_language_identifier">"bg"</string>
|
||||
|
||||
@@ -483,7 +483,6 @@ Opravdu chcete pokračovat?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s prostor"</string>
|
||||
<string name="screen_space_list_title">"Prostory"</string>
|
||||
<string name="screen_space_menu_action_members">"Zobrazit členy"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Zpráva nebyla odeslána, protože ověřená identita uživatele %1$s se změnila."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Zpráva nebyla odeslána, protože%1$s neověřil(a) všechna zařízení."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Zpráva nebyla odeslána, protože jste neověřili jedno nebo více zařízení."</string>
|
||||
|
||||
@@ -469,7 +469,6 @@ Er du sikker på, at du vil fortsætte?"</string>
|
||||
<string name="screen_space_list_details">"%1$s•%2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s gruppe"</string>
|
||||
<string name="screen_space_list_title">"Grupper"</string>
|
||||
<string name="screen_space_menu_action_members">"Vis medlemmer"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Beskeden blev ikke sendt fordi %1$s s bekræftede identitet blev nulstillet."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Meddelelsen er ikke sendt, fordi %1$s ikke har bekræftet alle enheder."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Beskeden er ikke sendt, fordi du ikke har verificeret en eller flere af dine enheder."</string>
|
||||
|
||||
@@ -483,12 +483,6 @@ Möchtest du wirklich fortfahren?"</string>
|
||||
<string name="screen_space_list_empty_state_title">"Erstelle einen Space, um Chats zu organisieren"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s Space"</string>
|
||||
<string name="screen_space_list_title">"Spaces"</string>
|
||||
<string name="screen_space_menu_action_members">"Mitglieder anzeigen"</string>
|
||||
<string name="screen_space_remove_rooms_confirmation_content">"Das Entfernen eines Chats hat keinen Einfluss auf die Beitrittsregeln. Um die Regeln zu ändern, gehe zu \"Raum Info\" und dann zu \"Datenschutz und Sicherheit\""</string>
|
||||
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||
<item quantity="one">"Chat aus %1$s entfernen"</item>
|
||||
<item quantity="other">"%1$d chats aus %2$s entfernen"</item>
|
||||
</plurals>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Nachricht nicht gesendet, weil sich die verifizierte Identität von %1$s geändert hat."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Die Nachricht wurde nicht gesendet, weil %1$s nicht alle Geräte verifiziert hat."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Die Nachricht wurde nicht gesendet, weil du eines oder mehrere deiner Geräte nicht verifiziert hast."</string>
|
||||
|
||||
@@ -476,7 +476,6 @@ Kas sa oled kindel, et soovid jätkata?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"Kogukond: %1$s"</string>
|
||||
<string name="screen_space_list_title">"Kogukonnad"</string>
|
||||
<string name="screen_space_menu_action_members">"Vaata liikmeid"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on lähtestatud."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Sõnum on saatmata, kuna %1$s pole verifitseerinud kõiki oma seadmeid."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Kuna sa pole üks või enamgi oma seadet verifitseerinud, siis sinu sõnum on saatmata."</string>
|
||||
|
||||
@@ -400,7 +400,6 @@
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s فضا"</string>
|
||||
<string name="screen_space_list_title">"فضاها"</string>
|
||||
<string name="screen_space_menu_action_members">"دیدن اعضا"</string>
|
||||
<string name="screen_view_location_title">"مکان"</string>
|
||||
<string name="settings_version_number">"نگارش : %1$s (%2$s)"</string>
|
||||
<string name="test_language_identifier">"fa"</string>
|
||||
|
||||
@@ -470,7 +470,6 @@ Haluatko varmasti jatkaa?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s tila"</string>
|
||||
<string name="screen_space_list_title">"Tilat"</string>
|
||||
<string name="screen_space_menu_action_members">"Näytä jäsenet"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Viestiä ei lähetetty, koska käyttäjän %1$s vahvistettu identiteetti nollattiin."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Viestiä ei lähetetty, koska %1$s ei ole vahvistanut kaikkia laitteitaan."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Viestiä ei lähetetty, koska et ole vahvistanut yhtä tai useampaa laitettasi."</string>
|
||||
|
||||
@@ -483,7 +483,6 @@ Raison : %1$s."</string>
|
||||
<string name="screen_space_list_empty_state_title">"Créer des espaces pour organiser les salons"</string>
|
||||
<string name="screen_space_list_parent_space">"Espace %1$s"</string>
|
||||
<string name="screen_space_list_title">"Espaces"</string>
|
||||
<string name="screen_space_menu_action_members">"Voir les membres"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Le message n’a pas été envoyé car l’identité vérifiée de %1$s a été réinitialisée."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Le message n’a pas été envoyé car %1$s n’a pas vérifié tous ses appareils."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Message non envoyé car vous n’avez pas vérifié tous vos appareils."</string>
|
||||
|
||||
@@ -485,7 +485,6 @@ Jeste li sigurni da želite nastaviti?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"Prostor %1$s"</string>
|
||||
<string name="screen_space_list_title">"Prostori"</string>
|
||||
<string name="screen_space_menu_action_members">"Prikaži članove"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Poruka nije poslana jer je poništen potvrđeni identitet korisnika %1$s."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Poruka nije poslana jer %1$s nije potvrdio sve uređaje."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Poruka nije poslana jer niste potvrdili jedan svoj uređaj ili više njih."</string>
|
||||
|
||||
@@ -469,7 +469,6 @@ Biztos, hogy folytatja?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s tér"</string>
|
||||
<string name="screen_space_list_title">"Terek"</string>
|
||||
<string name="screen_space_menu_action_members">"Tagok megtekintése"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Az üzenet nem lett elküldve, mert %1$s ellenőrzött személyazonossága megváltozott."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Az üzenet nem lett elküldve, mert %1$s nem ellenőrizte az összes eszközét."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Az üzenet nem lett elküldve, mert egy vagy több eszközét nem ellenőrizte."</string>
|
||||
|
||||
@@ -470,7 +470,6 @@ Sei sicuro di voler continuare?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s spazio"</string>
|
||||
<string name="screen_space_list_title">"Spazi"</string>
|
||||
<string name="screen_space_menu_action_members">"Visualizza membri"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Messaggio non inviato perché l\'identità verificata di %1$s è stata reimpostata."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Messaggio non inviato perché %1$s non ha verificato tutti i dispositivi."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Messaggio non inviato perché non hai verificato uno o più dispositivi."</string>
|
||||
|
||||
@@ -467,7 +467,6 @@ Er du sikker på at du vil fortsette?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s område"</string>
|
||||
<string name="screen_space_list_title">"Områder"</string>
|
||||
<string name="screen_space_menu_action_members">"Vis medlemmer"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Meldingen ble ikke sendt fordi %1$ss verifiserte identitet er tilbakestilt."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Meldingen ble ikke sendt fordi %1$s ikke har verifisert alle enheter."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Meldingen ble ikke sendt fordi du ikke har verifisert en eller flere av enhetene dine."</string>
|
||||
|
||||
@@ -479,7 +479,6 @@ Você tem certeza de que deseja continuar?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"Espaço %1$s"</string>
|
||||
<string name="screen_space_list_title">"Espaços"</string>
|
||||
<string name="screen_space_menu_action_members">"Ver membros"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Mensagem não enviada porque a identidade verificada de %1$s foi redefinida."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"A mensagem não foi enviada porque %1$s não verificou todos os dispositivos."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Mensagem não enviada porque você não verificou um ou mais dos seus dispositivos."</string>
|
||||
|
||||
@@ -484,7 +484,6 @@ Sunteți sigur că doriți să continuați?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"Spațiu %1$s"</string>
|
||||
<string name="screen_space_list_title">"Spații"</string>
|
||||
<string name="screen_space_menu_action_members">"Vizualizați membrii"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Mesajul nu a fost trimis deoarece identitatea verificată a lui %1$s s-a schimbat."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Mesajul nu a fost trimis deoarece %1$s nu a verificat toate dispozitivele."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Mesajul nu a fost trimis deoarece nu ați verificat unul sau mai multe dispozitive."</string>
|
||||
|
||||
@@ -479,7 +479,6 @@
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s пространство"</string>
|
||||
<string name="screen_space_list_title">"Пространства"</string>
|
||||
<string name="screen_space_menu_action_members">"Просмотреть участников"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Сообщение не отправлено, потому что подтвержденная личность %1$s была сброшена."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Сообщение не отправлено, потому что %1$s не проверил одно или несколько устройств."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Сообщение не отправлено, поскольку вы не подтвердили одно или несколько своих устройств."</string>
|
||||
|
||||
@@ -481,7 +481,6 @@ Naozaj chcete pokračovať?"</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s priestor"</string>
|
||||
<string name="screen_space_list_title">"Priestory"</string>
|
||||
<string name="screen_space_menu_action_members">"Zobraziť členov"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Správa nebola odoslaná, pretože sa zmenila overená totožnosť používateľa %1$s."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Správa nebola odoslaná, pretože %1$s neoveril/a všetky zariadenia."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Správa nebola odoslaná, pretože ste neoverili jedno alebo viac svojich zariadení."</string>
|
||||
|
||||
@@ -461,7 +461,6 @@
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s 空間"</string>
|
||||
<string name="screen_space_list_title">"空間"</string>
|
||||
<string name="screen_space_menu_action_members">"檢視成員"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"因為 %1$s 的驗證身份已重設,因此未傳送訊息。"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"訊息未傳送,因為 %1$s 尚未驗證所有裝置。"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"因為您尚未驗證一個或多個裝置,因此未傳送訊息"</string>
|
||||
|
||||
@@ -460,7 +460,6 @@
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s空间"</string>
|
||||
<string name="screen_space_list_title">"空间"</string>
|
||||
<string name="screen_space_menu_action_members">"查看成员"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"消息未发送,因为%1$s的已验证身份已被重置。"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"消息未发送,因为%1$s尚未验证所有设备。"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"消息未发送,因为您有尚未验证的设备。"</string>
|
||||
|
||||
@@ -317,6 +317,10 @@ Reason: %1$s."</string>
|
||||
<string name="common_security">"Security"</string>
|
||||
<string name="common_seen_by">"Seen by"</string>
|
||||
<string name="common_select_account">"Select an account"</string>
|
||||
<plurals name="common_selected_count">
|
||||
<item quantity="one">"%1$d selected"</item>
|
||||
<item quantity="other">"%1$d selected"</item>
|
||||
</plurals>
|
||||
<string name="common_send_to">"Send to"</string>
|
||||
<string name="common_sending">"Sending…"</string>
|
||||
<string name="common_sending_failed">"Sending failed"</string>
|
||||
@@ -484,12 +488,6 @@ Are you sure you want to continue?"</string>
|
||||
<string name="screen_space_list_empty_state_title">"Create spaces to organize rooms"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s space"</string>
|
||||
<string name="screen_space_list_title">"Spaces"</string>
|
||||
<string name="screen_space_menu_action_members">"View members"</string>
|
||||
<string name="screen_space_remove_rooms_confirmation_content">"Removing a room will not affect the room access. To change the access go to Room info > Privacy & security."</string>
|
||||
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||
<item quantity="one">"Remove room from %1$s"</item>
|
||||
<item quantity="other">"Remove %1$d rooms from %2$s"</item>
|
||||
</plurals>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Message not sent because %1$s’s verified identity was reset."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Message not sent because %1$s has not verified all devices."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Message not sent because you have not verified one or more of your devices."</string>
|
||||
|
||||
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.
@@ -233,7 +233,8 @@
|
||||
"name" : ":features:space:impl",
|
||||
"includeRegex" : [
|
||||
"screen\\.leave_space\\..*",
|
||||
"screen\\.space_settings\\..*"
|
||||
"screen\\.space_settings\\..*",
|
||||
"screen\\.space\\..*"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user