Merge branch 'develop' into feature/fga/space_settings_iteration

This commit is contained in:
ganfra
2025-12-15 16:06:06 +01:00
600 changed files with 3591 additions and 2388 deletions
@@ -81,7 +81,7 @@ private fun SpaceAnnouncementHeader(
showBetaLabel = true,
subTitle = stringResource(id = R.string.screen_space_announcement_subtitle),
iconStyle = BigIcon.Style.Default(
vectorIcon = CompoundIcons.WorkspaceSolid(),
vectorIcon = CompoundIcons.SpaceSolid(),
usePrimaryTint = true,
),
)
@@ -40,7 +40,7 @@ import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEf
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toImmutableList
@@ -132,7 +132,7 @@ class ConfigureRoomPresenter(
cameraPhotoPicker.launch()
} else {
pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
}
AvatarAction.Remove -> dataStore.setAvatarUri(uri = null)
}
@@ -19,7 +19,7 @@ import dev.zacsweers.metro.AssistedInject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.permissions.api.PermissionStateProvider
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
@@ -58,7 +58,7 @@ class NotificationsOptInPresenter(
if (notificationsPermissionsState.permissionGranted) {
callback.onNotificationsOptInFinished()
} else {
notificationsPermissionsState.eventSink(PermissionsEvents.RequestPermissions)
notificationsPermissionsState.eventSink(PermissionsEvent.RequestPermissions)
}
}
NotificationsOptInEvents.NotNowClicked -> {
@@ -28,7 +28,7 @@ enum class HomeNavigationBarItem(
isSelected: Boolean,
) = when (this) {
Chats -> if (isSelected) CompoundIcons.ChatSolid() else CompoundIcons.Chat()
Spaces -> if (isSelected) CompoundIcons.WorkspaceSolid() else CompoundIcons.Workspace()
Spaces -> if (isSelected) CompoundIcons.SpaceSolid() else CompoundIcons.Space()
}
companion object {
@@ -121,7 +121,6 @@ internal fun RoomSummaryRow(
) {
NameAndTimestampRow(
name = room.name,
latestEvent = room.latestEvent,
timestamp = room.timestamp,
isHighlighted = room.isHighlighted
)
@@ -138,7 +137,6 @@ internal fun RoomSummaryRow(
) {
NameAndTimestampRow(
name = room.name,
latestEvent = room.latestEvent,
timestamp = null,
isHighlighted = room.isHighlighted
)
@@ -214,7 +212,6 @@ private fun RoomSummaryScaffoldRow(
@Composable
private fun NameAndTimestampRow(
name: String?,
latestEvent: LatestEvent,
timestamp: String?,
isHighlighted: Boolean,
modifier: Modifier = Modifier
@@ -236,28 +233,6 @@ private fun NameAndTimestampRow(
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Picto
when (latestEvent) {
is LatestEvent.Sending -> {
Spacer(modifier = Modifier.width(4.dp))
Icon(
modifier = Modifier.size(16.dp),
imageVector = CompoundIcons.Time(),
contentDescription = null,
tint = ElementTheme.colors.iconTertiary,
)
}
is LatestEvent.Error -> {
Spacer(modifier = Modifier.width(4.dp))
Icon(
modifier = Modifier.size(16.dp),
imageVector = CompoundIcons.ErrorSolid(),
contentDescription = null,
tint = ElementTheme.colors.iconCriticalPrimary,
)
}
else -> Unit
}
}
// Timestamp
Text(
@@ -302,7 +277,6 @@ private fun MessagePreviewAndIndicatorRow(
) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = spacedBy(28.dp)
) {
if (room.isTombstoned) {
Text(
@@ -316,6 +290,16 @@ private fun MessagePreviewAndIndicatorRow(
)
} else {
if (room.latestEvent is LatestEvent.Error) {
Icon(
modifier = Modifier
.padding(top = 2.dp)
.size(16.dp),
imageVector = CompoundIcons.ErrorSolid(),
// The last message contains the error.
contentDescription = null,
tint = ElementTheme.colors.iconCriticalPrimary,
)
Spacer(modifier = Modifier.width(6.dp))
Text(
modifier = Modifier.weight(1f),
text = stringResource(CommonStrings.common_message_failed_to_send),
@@ -326,6 +310,17 @@ private fun MessagePreviewAndIndicatorRow(
overflow = TextOverflow.Ellipsis,
)
} else {
if (room.latestEvent is LatestEvent.Sending) {
Icon(
modifier = Modifier
.padding(top = 2.dp)
.size(16.dp),
imageVector = CompoundIcons.Time(),
contentDescription = stringResource(CommonStrings.common_sending),
tint = ElementTheme.colors.iconTertiary,
)
Spacer(modifier = Modifier.width(6.dp))
}
val messagePreview = room.latestEvent.content()
val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.orEmpty().toString())
Text(
@@ -339,7 +334,7 @@ private fun MessagePreviewAndIndicatorRow(
)
}
}
Spacer(modifier = Modifier.width(16.dp))
// Call and unread
Row(
modifier = Modifier
@@ -71,7 +71,7 @@ class DefaultPinCodeManager(
lockScreenStore.onWrongPin()
}
}
} catch (failure: Throwable) {
} catch (_: Throwable) {
false
}
}
@@ -18,7 +18,7 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
@Inject
@@ -46,7 +46,7 @@ class QrCodeIntroPresenter(
canContinue = true
} else {
pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
}
}
}
@@ -106,7 +106,7 @@ private fun Content(
QrCodeCameraView(
modifier = Modifier.fillMaxSize(),
onScanQrCode = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) },
renderPreview = state.isScanning,
isScanning = state.isScanning,
)
}
}
+1
View File
@@ -68,6 +68,7 @@ dependencies {
implementation(libs.jsoup)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.ui)
implementation(libs.sigpwned.emoji4j)
@@ -30,6 +30,7 @@ import io.element.android.features.messages.api.timeline.HtmlConverterProvider
import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
@@ -101,6 +102,7 @@ class MessagesPresenter(
@Assisted private val timelinePresenter: Presenter<TimelineState>,
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
private val historyVisibleStatePresenter: Presenter<HistoryVisibleState>,
private val linkPresenter: Presenter<LinkState>,
@Assisted private val actionListPresenter: Presenter<ActionListState>,
private val customReactionPresenter: Presenter<CustomReactionState>,
@@ -152,6 +154,7 @@ class MessagesPresenter(
val timelineState = timelinePresenter.present()
val timelineProtectionState = timelineProtectionPresenter.present()
val identityChangeState = identityChangeStatePresenter.present()
val historyVisibleState = historyVisibleStatePresenter.present()
val actionListState = actionListPresenter.present()
val linkState = linkPresenter.present()
val customReactionState = customReactionPresenter.present()
@@ -274,6 +277,7 @@ class MessagesPresenter(
timelineState = timelineState,
timelineProtectionState = timelineProtectionState,
identityChangeState = identityChangeState,
historyVisibleState = historyVisibleState,
linkState = linkState,
actionListState = actionListState,
customReactionState = customReactionState,
@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
@@ -40,6 +41,7 @@ data class MessagesState(
val timelineState: TimelineState,
val timelineProtectionState: TimelineProtectionState,
val identityChangeState: IdentityChangeState,
val historyVisibleState: HistoryVisibleState,
val linkState: LinkState,
val actionListState: ActionListState,
val customReactionState: CustomReactionState,
@@ -14,7 +14,10 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.crypto.identity.aRoomMemberIdentityStateChange
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.link.aLinkState
@@ -38,6 +41,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@@ -48,6 +52,7 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -83,6 +88,19 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
)
),
aMessagesState(
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange()))
),
aMessagesState(
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
historyVisibleState = aHistoryVisibleState(showAlert = true)
),
aMessagesState(
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())),
historyVisibleState = aHistoryVisibleState(showAlert = true)
)
)
}
@@ -103,6 +121,7 @@ fun aMessagesState(
),
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
identityChangeState: IdentityChangeState = anIdentityChangeState(),
historyVisibleState: HistoryVisibleState = aHistoryVisibleState(),
linkState: LinkState = aLinkState(),
readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(),
actionListState: ActionListState = anActionListState(),
@@ -125,6 +144,7 @@ fun aMessagesState(
voiceMessageComposerState = voiceMessageComposerState,
timelineProtectionState = timelineProtectionState,
identityChangeState = identityChangeState,
historyVisibleState = historyVisibleState,
linkState = linkState,
timelineState = timelineState,
readReceiptBottomSheetState = readReceiptBottomSheetState,
@@ -145,11 +165,9 @@ fun aMessagesState(
)
fun aRoomMemberModerationState(
canKick: Boolean = false,
canBan: Boolean = false,
permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions.DEFAULT,
) = object : RoomMemberModerationState {
override val canKick: Boolean = canKick
override val canBan: Boolean = canBan
override val permissions: RoomMemberModerationPermissions = permissions
override val eventSink: (RoomMemberModerationEvents) -> Unit = {}
}
@@ -53,6 +53,7 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListView
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView
import io.element.android.features.messages.impl.link.LinkEvents
import io.element.android.features.messages.impl.link.LinkView
@@ -486,10 +487,17 @@ private fun MessagesViewComposerBottomSheetContents(
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
if (state.composerState.suggestions.isEmpty() &&
state.composerState.textEditorState is TextEditorState.Markdown) {
IdentityChangeStateView(
state = state.identityChangeState,
onLinkClick = onLinkClick,
)
if (state.identityChangeState.roomMemberIdentityStateChanges.isNotEmpty()) {
IdentityChangeStateView(
state = state.identityChangeState,
onLinkClick = onLinkClick,
)
} else {
HistoryVisibleStateView(
state = state.historyVisibleState,
onLinkClick = onLinkClick,
)
}
}
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
it.identityState == IdentityState.VerificationViolation
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.hash.hash
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
interface HistoryVisibleAcknowledgementRepository {
fun hasAcknowledged(roomId: RoomId): Flow<Boolean>
suspend fun setAcknowledged(roomId: RoomId, value: Boolean)
}
@ContributesBinding(SessionScope::class)
class DefaultHistoryVisibleAcknowledgementRepository(
sessionId: SessionId,
preferenceDataStoreFactory: PreferenceDataStoreFactory,
) : HistoryVisibleAcknowledgementRepository {
val store =
sessionId.value.hash().take(16).let { hash ->
preferenceDataStoreFactory.create("elementx_historyvisible_$hash")
}
override fun hasAcknowledged(roomId: RoomId): Flow<Boolean> {
return store.data.map { prefs ->
val acknowledged = prefs[booleanPreferencesKey(roomId.value)] ?: false
acknowledged
}
}
override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) {
store.edit { prefs ->
prefs[booleanPreferencesKey(roomId.value)] = value
}
}
}
@@ -0,0 +1,12 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
sealed interface HistoryVisibleEvent {
data object Acknowledge : HistoryVisibleEvent
}
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
data class HistoryVisibleState(
val showAlert: Boolean,
val eventSink: (HistoryVisibleEvent) -> Unit,
)
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@Inject
class HistoryVisibleStatePresenter(
private val featureFlagService: FeatureFlagService,
private val repository: HistoryVisibleAcknowledgementRepository,
private val room: JoinedRoom,
) : Presenter<HistoryVisibleState> {
@Composable
override fun present(): HistoryVisibleState {
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false)
val roomInfo by room.roomInfoFlow.collectAsState()
// Implicitly assume the alert is initially acknowledged to avoid flashes in UI.
val acknowledged by repository.hasAcknowledged(room.roomId).collectAsState(initial = true)
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(roomInfo.historyVisibility, acknowledged) {
if (roomInfo.historyVisibility == RoomHistoryVisibility.Joined && acknowledged) {
repository.setAcknowledged(room.roomId, false)
}
}
fun handleEvent(event: HistoryVisibleEvent) {
when (event) {
is HistoryVisibleEvent.Acknowledge -> coroutineScope.setAcknowledged(room.roomId, true)
}
}
return HistoryVisibleState(
showAlert = isFeatureEnabled && roomInfo.historyVisibility != RoomHistoryVisibility.Joined && roomInfo.isEncrypted == true && !acknowledged,
eventSink = ::handleEvent,
)
}
private fun CoroutineScope.setAcknowledged(roomId: RoomId, value: Boolean) = launch {
repository.setAcknowledged(roomId, value)
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
class HistoryVisibleStateProvider : PreviewParameterProvider<HistoryVisibleState> {
override val values: Sequence<HistoryVisibleState>
get() = sequenceOf(
aHistoryVisibleState(showAlert = true),
)
}
internal fun aHistoryVisibleState(
showAlert: Boolean = false,
eventSink: (HistoryVisibleEvent) -> Unit = {},
) = HistoryVisibleState(
showAlert,
eventSink = eventSink,
)
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.appconfig.LearnMoreConfig
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertLevel
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun HistoryVisibleStateView(
state: HistoryVisibleState,
onLinkClick: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
if (!state.showAlert) {
return
}
ComposerAlertMolecule(
modifier = modifier,
avatar = null,
showIcon = true,
level = ComposerAlertLevel.Info,
content = buildAnnotatedString {
val learnMoreStr = stringResource(CommonStrings.action_learn_more)
val fullText = stringResource(CommonStrings.crypto_history_visible, learnMoreStr)
append(fullText)
val learnMoreStartIndex = fullText.lastIndexOf(learnMoreStr)
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
fontWeight = FontWeight.Bold,
color = ElementTheme.colors.textPrimary
),
start = learnMoreStartIndex,
end = learnMoreStartIndex + learnMoreStr.length,
)
addLink(
url = LinkAnnotation.Url(
url = LearnMoreConfig.HISTORY_VISIBLE_URL,
linkInteractionListener = {
onLinkClick(LearnMoreConfig.HISTORY_VISIBLE_URL, true)
}
),
start = learnMoreStartIndex,
end = learnMoreStartIndex + learnMoreStr.length,
)
},
submitText = stringResource(CommonStrings.action_dismiss),
onSubmitClick = { state.eventSink(HistoryVisibleEvent.Acknowledge) },
)
}
@PreviewsDayNight
@Composable
internal fun HistoryVisibleStateViewPreview(
@PreviewParameter(HistoryVisibleStateProvider::class) state: HistoryVisibleState,
) = ElementPreview {
HistoryVisibleStateView(
state = state,
onLinkClick = { _, _ -> },
)
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import androidx.compose.runtime.Composable
import io.element.android.features.messages.impl.MessagesView
import io.element.android.features.messages.impl.aMessagesState
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
@PreviewsDayNight
@Composable
internal fun MessagesViewWithHistoryVisiblePreview() = ElementPreview {
MessagesView(
state = aMessagesState(
composerState = aMessageComposerState(
textEditorState = aTextEditorStateMarkdown(
initialText = "",
initialFocus = false,
)
),
historyVisibleState = aHistoryVisibleState(showAlert = true),
),
onBackClick = {},
onRoomDetailsClick = {},
onEventContentClick = { _, _ -> false },
onUserDataClick = {},
onLinkClick = { _, _ -> },
onSendLocationClick = {},
onCreatePollClick = {},
onJoinCallClick = {},
onViewAllPinnedMessagesClick = {},
knockRequestsBannerView = {}
)
}
@@ -11,6 +11,8 @@ package io.element.android.features.messages.impl.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStatePresenter
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter
@@ -61,4 +63,7 @@ interface MessagesBindsModule {
@Binds
fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter<IdentityChangeState>
@Binds
fun bindHistoryVisibleStatePresenter(presenter: HistoryVisibleStatePresenter): Presenter<HistoryVisibleState>
}
@@ -64,7 +64,7 @@ import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService
@@ -286,7 +286,7 @@ class MessageComposerPresenter(
cameraPhotoPicker.launch()
} else {
pendingEvent = event
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
}
}
MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
@@ -295,7 +295,7 @@ class MessageComposerPresenter(
cameraVideoPicker.launch()
} else {
pendingEvent = event
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
}
}
MessageComposerEvent.PickAttachmentSource.Location -> {
@@ -112,7 +112,7 @@ fun EventDebugInfoView(
private fun prettyJSON(maybeJSON: String): String {
return try {
JSONObject(maybeJSON).toString(2)
} catch (e: JSONException) {
} catch (_: JSONException) {
// Prefer not pretty-printing over crashing if the data is not actually JSON
maybeJSON
}
@@ -33,7 +33,7 @@ import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
@@ -111,7 +111,7 @@ class DefaultVoiceMessageComposerPresenter(
}
else -> {
Timber.i("Voice message permission needed")
permissionState.eventSink(PermissionsEvents.RequestPermissions)
permissionState.eventSink(PermissionsEvent.RequestPermissions)
}
}
}
@@ -176,10 +176,10 @@ class DefaultVoiceMessageComposerPresenter(
localCoroutineScope.deleteRecording()
}
VoiceMessageComposerEvent.DismissPermissionsRationale -> {
permissionState.eventSink(PermissionsEvents.CloseDialog)
permissionState.eventSink(PermissionsEvent.CloseDialog)
}
VoiceMessageComposerEvent.AcceptPermissionRationale -> {
permissionState.eventSink(PermissionsEvents.OpenSystemSettingAndCloseDialog)
permissionState.eventSink(PermissionsEvent.OpenSystemSettingAndCloseDialog)
}
is VoiceMessageComposerEvent.LifecycleEvent -> handleLifecycleEvent(event.event)
VoiceMessageComposerEvent.DismissSendFailureDialog -> {
@@ -17,6 +17,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
import io.element.android.features.messages.impl.fixtures.aMessageEvent
import io.element.android.features.messages.impl.link.aLinkState
@@ -1297,6 +1298,7 @@ class MessagesPresenterTest {
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
timelineProtectionPresenter = { aTimelineProtectionState() },
identityChangeStatePresenter = { anIdentityChangeState() },
historyVisibleStatePresenter = { aHistoryVisibleState() },
linkPresenter = { aLinkState() },
actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
customReactionPresenter = { aCustomReactionState() },
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeHistoryVisibleAcknowledgementRepository(
private val acknowledgements: MutableMap<RoomId, MutableStateFlow<Boolean>> = mutableMapOf()
) : HistoryVisibleAcknowledgementRepository {
override fun hasAcknowledged(roomId: RoomId): Flow<Boolean> {
return acknowledgements.getOrPut(roomId) {
MutableStateFlow(false)
}
}
override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) {
val flow = acknowledgements.getOrPut(roomId) {
MutableStateFlow(value)
}
flow.emit(value)
}
companion object {
/**
* Create the repository with a pre-existing entry.
*/
fun withRoom(roomId: RoomId, acknowledged: Boolean = false): FakeHistoryVisibleAcknowledgementRepository {
return FakeHistoryVisibleAcknowledgementRepository(
mutableMapOf(
roomId to MutableStateFlow(acknowledged)
)
)
}
}
}
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2025 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.messages.impl.crypto.historyvisible
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class HistoryVisibleStatePresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@Test
fun `present - not visible if feature disabled`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true))
val presenter = createHistoryVisibleStatePresenter(room, enabled = false, acknowledged = false)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - initial with room shared, unencrypted`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = false))
val presenter = createHistoryVisibleStatePresenter(room)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - initial with room joined, encrypted`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false))
val presenter = createHistoryVisibleStatePresenter(room)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - initial with room shared, encrypted, unacknowledged`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
val presenter = createHistoryVisibleStatePresenter(room, acknowledged = false)
presenter.test {
val initialState = awaitItem()
assertThat(initialState.showAlert).isFalse()
val nextState = awaitItem()
assertThat(nextState.showAlert).isTrue()
}
}
@Test
fun `present - initial with room shared, encrypted, acknowledged`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
val presenter = createHistoryVisibleStatePresenter(room, acknowledged = true)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - transition from joined + unencrypted, to shared + encrypted`() = runTest {
val room = FakeJoinedRoom()
val featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true))
val repository = FakeHistoryVisibleAcknowledgementRepository()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false))
val presenter = HistoryVisibleStatePresenter(
featureFlagService,
repository,
room,
)
presenter.test {
// emitted by the feature flag service(?)
assertThat(awaitItem().showAlert).isFalse()
// emitted state from room info assignment
assertThat(awaitItem().showAlert).isFalse()
// room is marked as encrypted
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true))
assertThat(awaitItem().showAlert).isFalse()
// room history visibility is changed to shared
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
assertThat(awaitItem().showAlert).isTrue()
// alert is acknowledged
repository.setAcknowledged(room.roomId, true)
assertThat(awaitItem().showAlert).isFalse()
}
}
private fun createHistoryVisibleStatePresenter(
room: JoinedRoom = FakeJoinedRoom(),
enabled: Boolean = true,
acknowledged: Boolean = false
): HistoryVisibleStatePresenter {
return HistoryVisibleStatePresenter(
room = room,
featureFlagService = FakeFeatureFlagService(mapOf("feature.enableKeyShareOnInvite" to enabled)),
repository = FakeHistoryVisibleAcknowledgementRepository.withRoom(room.roomId, acknowledged)
)
}
}
@@ -21,4 +21,5 @@ sealed interface DeveloperSettingsEvents {
data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvents
data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvents
data object ClearCache : DeveloperSettingsEvents
data object VacuumStores : DeveloperSettingsEvents
}
@@ -29,6 +29,7 @@ import io.element.android.features.preferences.impl.developer.tracing.toLogLevel
import io.element.android.features.preferences.impl.model.EnabledFeature
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
@@ -61,6 +62,7 @@ class DeveloperSettingsPresenter(
private val appPreferencesStore: AppPreferencesStore,
private val buildMeta: BuildMeta,
private val enterpriseService: EnterpriseService,
private val vacuumStoresUseCase: VacuumStoresUseCase,
) : Presenter<DeveloperSettingsState> {
@Composable
override fun present(): DeveloperSettingsState {
@@ -151,6 +153,9 @@ class DeveloperSettingsPresenter(
is DeveloperSettingsEvents.SetShowColorPicker -> {
showColorPicker = event.show
}
DeveloperSettingsEvents.VacuumStores -> coroutineScope.launch {
vacuumStoresUseCase()
}
}
}
@@ -146,6 +146,14 @@ fun DeveloperSettingsView(
}
val cache = state.cacheSize
PreferenceCategory(title = "Cache") {
ListItem(
headlineContent = {
Text("Vacuum stores")
},
onClick = {
state.eventSink(DeveloperSettingsEvents.VacuumStores)
}
)
ListItem(
headlineContent = {
Text("Clear cache")
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 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.preferences.impl.tasks
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.matrix.api.MatrixClient
import timber.log.Timber
fun interface VacuumStoresUseCase {
suspend operator fun invoke()
}
@ContributesBinding(AppScope::class)
class DefaultVacuumStoresUseCase(
private val matrixClient: MatrixClient,
) : VacuumStoresUseCase {
override suspend fun invoke() {
matrixClient.performDatabaseVacuum()
.onFailure { Timber.e(it, "Failed to vacuum stores") }
}
}
@@ -35,7 +35,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
@@ -127,7 +127,7 @@ class EditUserProfilePresenter(
cameraPhotoPicker.launch()
} else {
pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
}
AvatarAction.Remove -> {
temporaryUriDeleter.delete(userAvatarUri?.toUri())
@@ -17,6 +17,7 @@ import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
@@ -212,6 +213,23 @@ class DeveloperSettingsPresenterTest {
}
}
@Test
fun `present - VacuumStores action invokes the VacuumStoresUseCase`() = runTest {
var vacuumCalled = false
val presenter = createDeveloperSettingsPresenter(
vacuumStoresUseCase = VacuumStoresUseCase {
vacuumCalled = true
}
)
presenter.test {
val state = awaitItem()
assertThat(vacuumCalled).isFalse()
state.eventSink(DeveloperSettingsEvents.VacuumStores)
skipItems(1)
assertThat(vacuumCalled).isTrue()
}
}
private fun createDeveloperSettingsPresenter(
sessionId: SessionId = A_SESSION_ID,
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(
@@ -230,6 +248,7 @@ class DeveloperSettingsPresenterTest {
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
buildMeta: BuildMeta = aBuildMeta(),
enterpriseService: EnterpriseService = FakeEnterpriseService(),
vacuumStoresUseCase: VacuumStoresUseCase = VacuumStoresUseCase {},
): DeveloperSettingsPresenter {
return DeveloperSettingsPresenter(
sessionId = sessionId,
@@ -240,6 +259,7 @@ class DeveloperSettingsPresenterTest {
appPreferencesStore = preferencesStore,
buildMeta = buildMeta,
enterpriseService = enterpriseService,
vacuumStoresUseCase = vacuumStoresUseCase,
)
}
}
@@ -2,12 +2,9 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_change_permissions_administrators">"Správca"</string>
<string name="screen_room_change_permissions_ban_people">"Zakázať ľudí"</string>
<string name="screen_room_change_permissions_change_settings">"Zmeniť nastavenia"</string>
<string name="screen_room_change_permissions_delete_messages">"Odstrániť správy"</string>
<string name="screen_room_change_permissions_everyone">"Člen"</string>
<string name="screen_room_change_permissions_invite_people">"Pozvať ľudí"</string>
<string name="screen_room_change_permissions_manage_space">"Spravovať priestor"</string>
<string name="screen_room_change_permissions_manage_space_rooms">"Spravovať miestnosti"</string>
<string name="screen_room_change_permissions_member_moderation">"Spravovať členov"</string>
<string name="screen_room_change_permissions_messages_and_content">"Správy a obsah"</string>
<string name="screen_room_change_permissions_moderators">"Moderátor"</string>
@@ -17,7 +14,6 @@
<string name="screen_room_change_permissions_room_name">"Zmeniť názov miestnosti"</string>
<string name="screen_room_change_permissions_room_topic">"Zmeniť tému miestnosti"</string>
<string name="screen_room_change_permissions_send_messages">"Odoslať správy"</string>
<string name="screen_room_change_permissions_title">"Povolenia"</string>
<string name="screen_room_change_role_administrators_title">"Upraviť správcov"</string>
<string name="screen_room_change_role_confirm_add_admin_description">"Túto akciu nebudete môcť vrátiť späť. Zvyšujete úroveň používateľa na rovnakú úroveň výkonu ako máte vy."</string>
<string name="screen_room_change_role_confirm_add_admin_title">"Pridať správcu?"</string>
@@ -26,7 +26,7 @@ data class RoomMemberListState(
val moderationState: RoomMemberModerationState,
val eventSink: (RoomMemberListEvents) -> Unit,
) {
val showBannedSection: Boolean = moderationState.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
}
enum class SelectedSection {
@@ -10,6 +10,7 @@ package io.element.android.features.roomdetails.impl.members
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.map
@@ -99,8 +100,10 @@ fun aRoomMemberModerationState(
canKick: Boolean = false,
): RoomMemberModerationState {
return object : RoomMemberModerationState {
override val canKick: Boolean = canKick
override val canBan: Boolean = canBan
override val permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions(
canBan = canBan,
canKick = canKick,
)
override val eventSink: (RoomMemberModerationEvents) -> Unit = {}
}
}
@@ -84,6 +84,10 @@
<string name="screen_room_member_list_manage_member_unban_title">"Eemalda suhtluskeeld jututoas"</string>
<string name="screen_room_member_list_mode_banned">"Suhtluskeeluga kasutajad"</string>
<string name="screen_room_member_list_mode_members">"Liikmed"</string>
<plurals name="screen_room_member_list_pending_header_title">
<item quantity="one">"%1$d saatis kutse"</item>
<item quantity="other">"%1$d saatis kutse"</item>
</plurals>
<string name="screen_room_member_list_pending_status">"Ootel"</string>
<string name="screen_room_member_list_role_administrator">"Peakasutajad"</string>
<string name="screen_room_member_list_role_moderator">"Moderaatorid"</string>
@@ -133,6 +137,7 @@ Me ei soovita krüptimise kasutamist selliste avalike jututubade puhul, millega
<string name="screen_security_and_privacy_encryption_toggle_title">"Võta läbiv krüptimine kasutusele"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Kõik võivad jututoaga liituda"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kõik"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Halda kogukondi"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Liituda saab vaid kutse olemasolul"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vaid kutsega"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Ligipääs"</string>
@@ -141,9 +141,13 @@ Nous ne recommandons pas dactiver le chiffrement pour les salons que tout le
<string name="screen_security_and_privacy_encryption_toggle_title">"Activer le chiffrement de bout en bout"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Tout le monde peut rejoindre."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Tout le monde"</string>
<string name="screen_security_and_privacy_room_access_footer">"Choisissez les espaces dont les membres peuvent rejoindre ce salon sans invitation. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gérer les espaces"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Seules les personnes invitées peuvent rejoindre."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Sur invitation uniquement"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Accès"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Toute personne se trouvant dans un espace autorisé peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Toute personne de lespace %1$s peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membres de lespace"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Les Espaces ne sont pas encore supportés"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Vous aurez besoin dune adresse pour le rendre visible dans lannuaire public."</string>
@@ -1,19 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string>
<string name="screen_edit_room_address_title">"Adresa miestnosti"</string>
<string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_edit_room_address_title">"Upraviť adresu"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Pri aktualizácii nastavenia oznámenia došlo k chybe."</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie."</string>
<string name="screen_polls_history_title">"Ankety"</string>
<string name="screen_room_change_permissions_administrators">"Iba správcovia"</string>
<string name="screen_room_change_permissions_administrators">"Správca"</string>
<string name="screen_room_change_permissions_ban_people">"Zakázať ľudí"</string>
<string name="screen_room_change_permissions_delete_messages">"Odstrániť správy"</string>
<string name="screen_room_change_permissions_invite_people">"Pozvite ľudí a prijmite žiadosti o pripojenie"</string>
<string name="screen_room_change_permissions_everyone">"Člen"</string>
<string name="screen_room_change_permissions_invite_people">"Pozvať ľudí"</string>
<string name="screen_room_change_permissions_member_moderation">"Spravovať členov"</string>
<string name="screen_room_change_permissions_messages_and_content">"Správy a obsah"</string>
<string name="screen_room_change_permissions_moderators">"Správcovia a moderátori"</string>
<string name="screen_room_change_permissions_remove_people">"Odstrániť ľudí a odmietnuť žiadosti o pripojenie"</string>
<string name="screen_room_change_permissions_moderators">"Moderátor"</string>
<string name="screen_room_change_permissions_remove_people">"Odstrániť ľudí"</string>
<string name="screen_room_change_permissions_room_avatar">"Zmeniť obrázok miestnosti"</string>
<string name="screen_room_change_permissions_room_details">"Upraviť miestnosť"</string>
<string name="screen_room_change_permissions_room_details">"Upraviť podrobnosti"</string>
<string name="screen_room_change_permissions_room_name">"Zmeniť názov miestnosti"</string>
<string name="screen_room_change_permissions_room_topic">"Zmeniť tému miestnosti"</string>
<string name="screen_room_change_permissions_send_messages">"Odoslať správy"</string>
@@ -40,7 +42,7 @@
<string name="screen_room_details_badge_encrypted">"Zašifrované"</string>
<string name="screen_room_details_badge_not_encrypted">"Nešifrované"</string>
<string name="screen_room_details_badge_public">"Verejná miestnosť"</string>
<string name="screen_room_details_edit_room_title">"Upraviť miestnosť"</string>
<string name="screen_room_details_edit_room_title">"Upraviť podrobnosti"</string>
<string name="screen_room_details_edition_error">"Vyskytla sa neznáma chyba a informácie nebolo možné zmeniť."</string>
<string name="screen_room_details_edition_error_title">"Nepodarilo sa aktualizovať miestnosť"</string>
<string name="screen_room_details_encryption_enabled_subtitle">"Správy sú zabezpečené zámkami. Jedine vy a príjemcovia máte jedinečné kľúče na ich odomknutie."</string>
@@ -69,6 +71,13 @@
<string name="screen_room_details_topic_title">"Téma"</string>
<string name="screen_room_details_updating_room">"Aktualizácia miestnosti…"</string>
<string name="screen_room_member_list_banned_empty">"Neexistujú žiadni zablokovaní používatelia."</string>
<plurals name="screen_room_member_list_banned_header_title">
<item quantity="one">"%1$d zakázaný"</item>
<item quantity="few">"%1$d zakázaní"</item>
<item quantity="other">"%1$d zakázaných"</item>
</plurals>
<string name="screen_room_member_list_empty_search_subtitle">"Skontrolujte preklepy alebo skúste nové vyhľadávanie"</string>
<string name="screen_room_member_list_empty_search_title">"Žiadne výsledky pre „%1$s“"</string>
<plurals name="screen_room_member_list_header_title">
<item quantity="one">"%1$d osoba"</item>
<item quantity="few">"%1$d osoby"</item>
@@ -81,8 +90,14 @@
<string name="screen_room_member_list_manage_member_unban_title">"Zrušiť zákaz prístupu do miestnosti"</string>
<string name="screen_room_member_list_mode_banned">"Zakázaní"</string>
<string name="screen_room_member_list_mode_members">"Členovia"</string>
<string name="screen_room_member_list_role_administrator">"Iba správcovia"</string>
<string name="screen_room_member_list_role_moderator">"Správcovia a moderátori"</string>
<plurals name="screen_room_member_list_pending_header_title">
<item quantity="one">"%1$d pozvaný"</item>
<item quantity="few">"%1$d pozvaní"</item>
<item quantity="other">"%1$d pozvaných"</item>
</plurals>
<string name="screen_room_member_list_pending_status">"Čaká na schválenie"</string>
<string name="screen_room_member_list_role_administrator">"Správca"</string>
<string name="screen_room_member_list_role_moderator">"Moderátor"</string>
<string name="screen_room_member_list_role_owner">"Vlastník"</string>
<string name="screen_room_member_list_room_members_header_title">"Členovia miestnosti"</string>
<string name="screen_room_member_list_unbanning_user">"Zrušenie zákazu %1$s"</string>
@@ -109,14 +124,15 @@
<string name="screen_room_roles_and_permissions_messages_and_content">"Správy a obsah"</string>
<string name="screen_room_roles_and_permissions_moderators">"Moderátori"</string>
<string name="screen_room_roles_and_permissions_owners">"Vlastníci"</string>
<string name="screen_room_roles_and_permissions_permissions_header">"Povolenia"</string>
<string name="screen_room_roles_and_permissions_reset">"Obnoviť povolenia"</string>
<string name="screen_room_roles_and_permissions_reset_confirm_description">"Po obnovení oprávnení prídete o aktuálne nastavenia."</string>
<string name="screen_room_roles_and_permissions_reset_confirm_title">"Obnoviť oprávnenia?"</string>
<string name="screen_room_roles_and_permissions_roles_header">"Roly"</string>
<string name="screen_room_roles_and_permissions_room_details">"Podrobnosti o miestnosti"</string>
<string name="screen_room_roles_and_permissions_title">"Roly a povolenia"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu miestnosti"</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Ktokoľvek môže požiadať o pripojenie do miestnosti, ale správca alebo moderátor bude musieť žiadosť prijať."</string>
<string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu"</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Všetci musia požiadať o prístup."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Požiadať o pripojenie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Áno, povoliť šifrovanie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti.
@@ -126,17 +142,22 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_encryption_section_footer">"Po zapnutí už šifrovanie nie je možné vypnúť."</string>
<string name="screen_security_and_privacy_encryption_section_header">"Šifrovanie"</string>
<string name="screen_security_and_privacy_encryption_toggle_title">"Povoliť end-to-end šifrovanie"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Ktokoľvek môže nájsť a pripojiť sa"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Pripojiť sa môže ktokoľvek."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Ľudia sa môžu pripojiť len vtedy, ak sú pozvaní"</string>
<string name="screen_security_and_privacy_room_access_footer">"Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovať priestory"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Pripojiť sa môžu iba pozvaní ľudia."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Iba na pozvánku"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Prístup do miestnosti"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Prístup"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Ktokoľvek v povolených priestoroch sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Ktokoľvek v %1$s sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Členovia priestoru"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Priestory momentálne nie sú podporované"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string>
<string name="screen_security_and_privacy_room_address_section_header">"Adresa miestnosti"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_address_section_header">"Adresa"</string>
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s"</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné v adresári verejných miestností"</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Umožniť nájdenie vyhľadávaním vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné vo verejnom adresári"</string>
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_history_section_header">"Kto môže čítať históriu"</string>
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Len pre členov, odkedy boli pozvaní"</string>
@@ -144,6 +165,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými.
Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera."</string>
<string name="screen_security_and_privacy_room_publishing_section_header">"Zverejnenie miestnosti"</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť miestnosti"</string>
<string name="screen_security_and_privacy_room_visibility_section_footer">"Adresy sú spôsob, ako nájsť a získať prístup do miestností a priestorov. To tiež zabezpečuje, že ich môžete jednoducho zdieľať s ostatnými."</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť"</string>
<string name="screen_security_and_privacy_title">"Bezpečnosť a súkromie"</string>
</resources>
@@ -69,7 +69,7 @@
<string name="screen_room_details_share_room_title">"Share room"</string>
<string name="screen_room_details_title">"Room info"</string>
<string name="screen_room_details_topic_title">"Topic"</string>
<string name="screen_room_details_updating_room">"Updating room…"</string>
<string name="screen_room_details_updating_room">"Updating details…"</string>
<string name="screen_room_member_list_banned_empty">"There are no banned users."</string>
<plurals name="screen_room_member_list_banned_header_title">
<item quantity="one">"%1$d Banned"</item>
@@ -129,8 +129,10 @@
<string name="screen_room_roles_and_permissions_room_details">"Room details"</string>
<string name="screen_room_roles_and_permissions_title">"Roles &amp; permissions"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Add address"</string>
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Anyone in authorized spaces can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Everyone must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Ask to join"</string>
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Anyone in %1$s can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Yes, enable encryption"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.
No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.
@@ -37,7 +37,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
@@ -151,7 +151,7 @@ class RoomDetailsEditPresenter(
cameraPhotoPicker.launch()
} else {
pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
}
AvatarAction.Remove -> {
temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri())
@@ -3,5 +3,5 @@
<string name="screen_room_details_edit_room_title">"Edit details"</string>
<string name="screen_room_details_edition_error">"There was an unknown error and the information couldn\'t be changed."</string>
<string name="screen_room_details_edition_error_title">"Unable to update room"</string>
<string name="screen_room_details_updating_room">"Updating room…"</string>
<string name="screen_room_details_updating_room">"Updating details…"</string>
</resources>
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 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.roommembermoderation.api
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
data class RoomMemberModerationPermissions(
val canKick: Boolean,
val canBan: Boolean,
) {
companion object {
val DEFAULT = RoomMemberModerationPermissions(
canKick = false,
canBan = false,
)
}
}
fun RoomPermissions.roomMemberModerationPermissions(): RoomMemberModerationPermissions {
return RoomMemberModerationPermissions(
canKick = canOwnUserKick(),
canBan = canOwnUserBan(),
)
}
@@ -12,8 +12,7 @@ import androidx.compose.runtime.Immutable
@Immutable
interface RoomMemberModerationState {
val canKick: Boolean
val canBan: Boolean
val permissions: RoomMemberModerationPermissions
val eventSink: (RoomMemberModerationEvents) -> Unit
}
@@ -10,14 +10,14 @@ package io.element.android.features.roommembermoderation.impl
import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class InternalRoomMemberModerationState(
override val canKick: Boolean,
override val canBan: Boolean,
override val permissions: RoomMemberModerationPermissions,
val selectedUser: MatrixUser?,
val actions: ImmutableList<ModerationActionState>,
val kickUserAsyncAction: AsyncAction<Unit>,
@@ -12,6 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -83,8 +84,7 @@ fun anAlice() = MatrixUser(
)
fun aRoomMembersModerationState(
canKick: Boolean = false,
canBan: Boolean = false,
permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions.DEFAULT,
selectedUser: MatrixUser? = null,
actions: List<ModerationActionState> = emptyList(),
kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
@@ -92,8 +92,7 @@ fun aRoomMembersModerationState(
unbanUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (RoomMemberModerationEvents) -> Unit = {},
) = InternalRoomMemberModerationState(
canKick = canKick,
canBan = canBan,
permissions = permissions,
selectedUser = selectedUser,
actions = actions.toImmutableList(),
kickUserAsyncAction = kickUserAsyncAction,
@@ -21,7 +21,9 @@ import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.features.roommembermoderation.api.roomMemberModerationPermissions
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
@@ -55,11 +57,8 @@ class RoomMemberModerationPresenter(
override fun present(): RoomMemberModerationState {
val coroutineScope = rememberCoroutineScope()
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val permissions by room.permissionsAsState(Permissions()) { perms ->
Permissions(
canKick = perms.canOwnUserKick(),
canBan = perms.canOwnUserBan(),
)
val permissions by room.permissionsAsState(RoomMemberModerationPermissions.DEFAULT) { perms ->
perms.roomMemberModerationPermissions()
}
val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value)
@@ -136,8 +135,7 @@ class RoomMemberModerationPresenter(
}
return InternalRoomMemberModerationState(
canKick = permissions.canKick,
canBan = permissions.canBan,
permissions = permissions,
selectedUser = selectedUser,
actions = moderationActions.value,
kickUserAsyncAction = kickUserAsyncAction.value,
@@ -149,7 +147,7 @@ class RoomMemberModerationPresenter(
private fun computeModerationActions(
member: RoomMember?,
permissions: Permissions,
permissions: RoomMemberModerationPermissions,
currentUserMemberPowerLevel: Long,
): ImmutableList<ModerationActionState> {
return buildList {
@@ -13,6 +13,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@@ -49,8 +50,7 @@ class RoomMemberModerationPresenterTest {
val room = aJoinedRoom()
createRoomMemberModerationPresenter(room = room).test {
val initialState = awaitState()
assertThat(initialState.canKick).isFalse()
assertThat(initialState.canBan).isFalse()
assertThat(initialState.permissions).isEqualTo(RoomMemberModerationPermissions.DEFAULT)
assertThat(initialState.selectedUser).isNull()
assertThat(initialState.banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)
assertThat(initialState.kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)
@@ -218,7 +218,7 @@ private fun RoomAccessSection(
Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description))
},
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.SpaceMember, enabled = false),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Workspace())),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Space())),
enabled = false,
)
}
@@ -15,6 +15,7 @@ Me ei soovita krüptimise kasutamist selliste avalike jututubade puhul, millega
<string name="screen_security_and_privacy_encryption_toggle_title">"Võta läbiv krüptimine kasutusele"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Kõik võivad jututoaga liituda"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kõik"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Halda kogukondi"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Liituda saab vaid kutse olemasolul"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vaid kutsega"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Ligipääs"</string>
@@ -15,9 +15,13 @@ Nous ne recommandons pas dactiver le chiffrement pour les salons que tout le
<string name="screen_security_and_privacy_encryption_toggle_title">"Activer le chiffrement de bout en bout"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Tout le monde peut rejoindre."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Tout le monde"</string>
<string name="screen_security_and_privacy_room_access_footer">"Choisissez les espaces dont les membres peuvent rejoindre ce salon sans invitation. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gérer les espaces"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Seules les personnes invitées peuvent rejoindre."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Sur invitation uniquement"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Accès"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Toute personne se trouvant dans un espace autorisé peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Toute personne de lespace %1$s peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membres de lespace"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Les Espaces ne sont pas encore supportés"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Vous aurez besoin dune adresse pour le rendre visible dans lannuaire public."</string>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string>
<string name="screen_edit_room_address_title">"Adresa miestnosti"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu miestnosti"</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Ktokoľvek môže požiadať o pripojenie do miestnosti, ale správca alebo moderátor bude musieť žiadosť prijať."</string>
<string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_edit_room_address_title">"Upraviť adresu"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu"</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Všetci musia požiadať o prístup."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Požiadať o pripojenie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Áno, povoliť šifrovanie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti.
@@ -13,17 +13,22 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_encryption_section_footer">"Po zapnutí už šifrovanie nie je možné vypnúť."</string>
<string name="screen_security_and_privacy_encryption_section_header">"Šifrovanie"</string>
<string name="screen_security_and_privacy_encryption_toggle_title">"Povoliť end-to-end šifrovanie"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Ktokoľvek môže nájsť a pripojiť sa"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Pripojiť sa môže ktokoľvek."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Ľudia sa môžu pripojiť len vtedy, ak sú pozvaní"</string>
<string name="screen_security_and_privacy_room_access_footer">"Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovať priestory"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Pripojiť sa môžu iba pozvaní ľudia."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Iba na pozvánku"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Prístup do miestnosti"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Prístup"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Ktokoľvek v povolených priestoroch sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Ktokoľvek v %1$s sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Členovia priestoru"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Priestory momentálne nie sú podporované"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string>
<string name="screen_security_and_privacy_room_address_section_header">"Adresa miestnosti"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_address_section_header">"Adresa"</string>
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s"</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné v adresári verejných miestností"</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Umožniť nájdenie vyhľadávaním vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné vo verejnom adresári"</string>
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_history_section_header">"Kto môže čítať históriu"</string>
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Len pre členov, odkedy boli pozvaní"</string>
@@ -31,6 +36,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými.
Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera."</string>
<string name="screen_security_and_privacy_room_publishing_section_header">"Zverejnenie miestnosti"</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť miestnosti"</string>
<string name="screen_security_and_privacy_room_visibility_section_footer">"Adresy sú spôsob, ako nájsť a získať prístup do miestností a priestorov. To tiež zabezpečuje, že ich môžete jednoducho zdieľať s ostatnými."</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť"</string>
<string name="screen_security_and_privacy_title">"Bezpečnosť a súkromie"</string>
</resources>
@@ -3,8 +3,10 @@
<string name="screen_edit_room_address_room_address_section_footer">"Youll need an address in order to make it visible in the public directory."</string>
<string name="screen_edit_room_address_title">"Edit address"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Add address"</string>
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Anyone in authorized spaces can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Everyone must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Ask to join"</string>
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Anyone in %1$s can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Yes, enable encryption"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.
No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.