Merge branch 'develop' of https://github.com/vector-im/element-x-android into yostyle/notifications_global_settings

This commit is contained in:
David Langley 2023-08-30 15:02:59 +01:00
commit 5e2ec8b504
315 changed files with 3724 additions and 1216 deletions

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.androidutils.hash
import java.security.MessageDigest
import java.util.Locale
/**
* Compute a Hash of a String, using SHA-512 algorithm.
*/
fun String.hash() = try {
val digest = MessageDigest.getInstance("SHA-512")
digest.update(toByteArray())
digest.digest()
.joinToString("") { String.format(Locale.ROOT, "%02X", it) }
.lowercase(Locale.ROOT)
} catch (exc: Exception) {
// Should not happen, but just in case
hashCode().toString()
}

View file

@ -25,7 +25,7 @@ class FirstThrottler(private val minimumInterval: Long = 800) {
private var lastDate = 0L
sealed class CanHandleResult {
object Yes : CanHandleResult()
data object Yes : CanHandleResult()
data class No(val shouldWaitMillis: Long) : CanHandleResult()
fun waitMillis(): Long {

View file

@ -63,7 +63,7 @@ sealed interface Async<out T> {
/**
* Represents an uninitialized operation (i.e. yet to be run).
*/
object Uninitialized : Async<Nothing>
data object Uninitialized : Async<Nothing>
/**
* Returns the data returned by the operation, or null otherwise.

View file

@ -43,5 +43,11 @@ android {
ksp(libs.showkase.processor)
kspTest(libs.showkase.processor)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
}
}

View file

@ -105,7 +105,7 @@ sealed class ElementLogoAtomSize(
val shadowColorLight: Color,
val shadowRadius: Dp,
) {
object Medium : ElementLogoAtomSize(
data object Medium : ElementLogoAtomSize(
outerSize = 120.dp,
logoSize = 83.5.dp,
cornerRadius = 33.dp,
@ -115,7 +115,7 @@ sealed class ElementLogoAtomSize(
shadowRadius = 32.dp,
)
object Large : ElementLogoAtomSize(
data object Large : ElementLogoAtomSize(
outerSize = 158.dp,
logoSize = 110.dp,
cornerRadius = 44.dp,

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.atomic.pages
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAbsoluteAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.R
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.text.withColoredPeriod
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
@Composable
fun SunsetPage(
isLoading: Boolean,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
overallContent: @Composable () -> Unit,
) {
ElementTheme(
darkTheme = true
) {
Box(
modifier = modifier.fillMaxSize()
) {
SunsetBackground()
Box(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = BiasAbsoluteAlignment(
horizontalBias = 0f,
verticalBias = -0.05f
)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
strokeWidth = 2.dp,
color = ElementTheme.colors.iconPrimary
)
} else {
Spacer(modifier = Modifier.height(24.dp))
}
Spacer(modifier = Modifier.height(18.dp))
Text(
text = withColoredPeriod(title),
style = ElementTheme.typography.fontHeadingXlBold,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textPrimary,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
modifier = Modifier.widthIn(max = 360.dp),
text = subtitle,
style = ElementTheme.typography.fontBodyLgRegular,
textAlign = TextAlign.Center,
color = ElementTheme.colors.textPrimary,
)
}
}
overallContent()
}
}
}
}
@Composable
private fun SunsetBackground(
modifier: Modifier = Modifier,
) {
Column(modifier = modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(0.3f)
.background(Color.White)
)
Image(
modifier = Modifier
.fillMaxWidth(),
painter = painterResource(id = R.drawable.light_dark),
contentScale = ContentScale.Crop,
contentDescription = null,
)
Box(
modifier = Modifier
.fillMaxWidth()
.weight(0.7f)
.background(Color(0xFF121418))
)
}
}
@DayNightPreviews
@Composable
internal fun SunsetPagePreview() = ElementPreview {
SunsetPage(
isLoading = true,
title = "Title with a green period.",
subtitle = "Subtitle",
overallContent = {}
)
}

View file

@ -24,7 +24,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -79,8 +78,8 @@ fun ClickableLinkText(
@Composable
fun ClickableLinkText(
annotatedString: AnnotatedString,
interactionSource: MutableInteractionSource,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
linkify: Boolean = true,
linkAnnotationTag: String = LINK_TAG,
onClick: () -> Unit = {},
@ -136,7 +135,6 @@ fun ClickableLinkText(
layoutResult.value = it
},
inlineContent = inlineContent,
color = MaterialTheme.colorScheme.primary,
)
}

View file

@ -88,7 +88,7 @@ fun ProgressDialog(
@Immutable
sealed interface ProgressDialogType {
data class Determinate(val progress: Float) : ProgressDialogType
object Indeterminate : ProgressDialogType
data object Indeterminate : ProgressDialogType
}
@Composable

View file

@ -59,6 +59,7 @@ fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString {
* @param color the color to apply to the string
* @param underline whether to underline the string
* @param bold whether to bold the string
* @param tagAndLink an optional pair of tag and link to add to the styled part of the string, as StringAnnotation
*/
@Composable
fun buildAnnotatedStringWithStyledPart(
@ -67,6 +68,7 @@ fun buildAnnotatedStringWithStyledPart(
color: Color = LinkColor,
underline: Boolean = true,
bold: Boolean = false,
tagAndLink: Pair<String, String>? = null,
) = buildAnnotatedString {
val coloredPart = stringResource(coloredTextRes)
val fullText = stringResource(fullTextRes, coloredPart)
@ -81,4 +83,31 @@ fun buildAnnotatedStringWithStyledPart(
start = startIndex,
end = startIndex + coloredPart.length,
)
if (tagAndLink != null) {
addStringAnnotation(
tag = tagAndLink.first,
annotation = tagAndLink.second,
start = startIndex,
end = startIndex + coloredPart.length
)
}
}
/**
* Convert a string to an [AnnotatedString] with colored end period if present.
*/
fun withColoredPeriod(
text: String,
) = buildAnnotatedString {
append(text)
if (text.endsWith(".")) {
addStyle(
style = SpanStyle(
// Light.colorGreen700
color = Color(0xff0bc491),
),
start = text.length - 1,
end = text.length,
)
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.RadioButtonUnchecked
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconToggleButtonColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun IconToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
) {
androidx.compose.material3.IconToggleButton(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled,
colors = colors,
interactionSource = interactionSource,
content = content,
)
}
@Preview(group = PreviewGroup.Toggles)
@Composable
internal fun IconToggleButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() }
@Composable
private fun ContentToPreview() {
var checked by remember { mutableStateOf(false) }
Column {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
val icon: @Composable () -> Unit = {
Icon(
imageVector = if (checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked,
contentDescription = "IconToggleButton"
)
}
IconToggleButton(checked = checked, enabled = true, onCheckedChange = { checked = !checked }, content = icon)
IconToggleButton(checked = checked, enabled = false, onCheckedChange = { checked = !checked }, content = icon)
}
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
val icon: @Composable () -> Unit = {
Icon(
imageVector = if (!checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked,
contentDescription = "IconToggleButton"
)
}
IconToggleButton(checked = !checked, enabled = true, onCheckedChange = { checked = !checked }, content = icon)
IconToggleButton(checked = !checked, enabled = false, onCheckedChange = { checked = !checked }, content = icon)
}
}
}

View file

@ -119,7 +119,7 @@ fun ListItem(
androidx.compose.material3.ListItem(
headlineContent = decoratedHeadlineContent,
modifier = Modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}).then(modifier),
modifier = if (onClick != null) Modifier.clickable(enabled = enabled, onClick = onClick).then(modifier) else modifier,
overlineContent = null,
supportingContent = decoratedSupportingContent,
leadingContent = decoratedLeadingContent,
@ -134,9 +134,9 @@ fun ListItem(
* The style to use for a [ListItem].
*/
sealed interface ListItemStyle {
object Default : ListItemStyle
object Primary: ListItemStyle
object Destructive : ListItemStyle
data object Default : ListItemStyle
data object Primary: ListItemStyle
data object Destructive : ListItemStyle
@Composable fun headlineColor() = when (this) {
Default, Primary -> ListItemDefaultColors.headline

View file

@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
@ -30,9 +29,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@ -103,17 +104,21 @@ fun ListSupportingText(
* @param modifier The modifier to be applied to the text.
* @param contentPadding The padding to apply to the text. Default is [ListSupportingTextDefaults.Padding.Default].
*/
@OptIn(ExperimentalTextApi::class)
@Composable
fun ListSupportingText(
annotatedString: AnnotatedString,
modifier: Modifier = Modifier,
contentPadding: ListSupportingTextDefaults.Padding = ListSupportingTextDefaults.Padding.Default,
) {
Text(
text = annotatedString,
modifier = modifier.padding(contentPadding.paddingValues()),
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
val style = ElementTheme.typography.fontBodySmRegular
.copy(color = ElementTheme.colors.textSecondary)
val paddedModifier = modifier.padding(contentPadding.paddingValues())
ClickableLinkText(
annotatedString = annotatedString,
modifier = paddedModifier,
style = style,
linkify = false,
)
}
@ -122,13 +127,13 @@ object ListSupportingTextDefaults {
/** Specifies the padding to use for the supporting text. */
sealed interface Padding {
/** No padding. */
object None : Padding
data object None : Padding
/** Default padding, it will align fine with a [ListItem] with no leading content. */
object Default : Padding
data object Default : Padding
/** It will align to a [ListItem] with an [Icon] or [Checkbox] as leading content. */
object SmallLeadingContent : Padding
data object SmallLeadingContent : Padding
/** It will align to with a [ListItem] with a [Switch] as leading content. */
object LargeLeadingContent : Padding
data object LargeLeadingContent : Padding
/** It will align to with a [ListItem] with a custom start [padding]. */
data class Custom(val padding: Dp) : Padding

View file

@ -23,7 +23,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.material3.RadioButtonColors
import androidx.compose.material3.RadioButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -67,14 +70,15 @@ internal fun RadioButtonPreview() = ElementThemedPreview(vertical = false) { Con
@Composable
private fun ContentToPreview() {
var checked by remember { mutableStateOf(false) }
Column {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
RadioButton(selected = false, onClick = {})
RadioButton(selected = false, enabled = false, onClick = {})
RadioButton(selected = checked, enabled = true, onClick = { checked = !checked })
RadioButton(selected = checked, enabled = false, onClick = { checked = !checked })
}
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
RadioButton(selected = true, onClick = {})
RadioButton(selected = true, enabled = false, onClick = {})
RadioButton(selected = !checked, enabled = true, onClick = { checked = !checked })
RadioButton(selected = !checked, enabled = false, onClick = { checked = !checked })
}
}
}

View file

@ -34,33 +34,40 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.button.ButtonVisuals
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.Snackbar
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.atomic.AtomicBoolean
/**
* A global dispatcher of [SnackbarMessage] to be displayed in [Snackbar] via a [SnackbarHostState].
*/
class SnackbarDispatcher {
private val mutex = Mutex()
private val _snackbarMessage = MutableStateFlow<SnackbarMessage?>(null)
val snackbarMessage: Flow<SnackbarMessage?> = _snackbarMessage.asStateFlow()
suspend fun post(message: SnackbarMessage) {
mutex.withLock {
_snackbarMessage.update { message }
private val queueMutex = Mutex()
private val snackBarMessageQueue = ArrayDeque<SnackbarMessage>()
val snackbarMessage: Flow<SnackbarMessage?> = flow {
while (currentCoroutineContext().isActive) {
queueMutex.lock()
emit(snackBarMessageQueue.firstOrNull())
}
}
suspend fun clear() {
mutex.withLock {
_snackbarMessage.update { null }
suspend fun post(message: SnackbarMessage) {
if (snackBarMessageQueue.isEmpty()) {
snackBarMessageQueue.add(message)
if (queueMutex.isLocked) queueMutex.unlock()
} else {
snackBarMessageQueue.add(message)
}
}
fun clear() {
if (snackBarMessageQueue.isNotEmpty()) {
snackBarMessageQueue.removeFirstOrNull()
if (queueMutex.isLocked) queueMutex.unlock()
}
}
}
@ -87,31 +94,51 @@ fun SnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) {
}
}
/**
* Helper method to display a [SnackbarMessage] in a [SnackbarHostState] handling cancellations.
*/
@Composable
fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostState {
val snackbarHostState = remember { SnackbarHostState() }
val snackbarMessageText = snackbarMessage?.let {
stringResource(id = snackbarMessage.messageResId)
}
} ?: return snackbarHostState
val dispatcher = LocalSnackbarDispatcher.current
LaunchedEffect(snackbarMessage) {
if (snackbarMessageText == null) return@LaunchedEffect
launch {
snackbarHostState.showSnackbar(
message = snackbarMessageText,
duration = snackbarMessage.duration,
)
if (isActive) {
LaunchedEffect(snackbarMessageText) {
// If the message wasn't already displayed, do it now, and mark it as displayed
// This will prevent the message from appearing in any other active SnackbarHosts
if (snackbarMessage.isDisplayed.getAndSet(true) == false) {
try {
snackbarHostState.showSnackbar(
message = snackbarMessageText,
duration = snackbarMessage.duration,
)
// The snackbar item was displayed and dismissed, clear its message
dispatcher.clear()
} catch (e: CancellationException) {
// The snackbar was being displayed when the coroutine was cancelled,
// so we need to clear its message
dispatcher.clear()
throw e
}
}
}
return snackbarHostState
}
/**
* A message to be displayed in a [Snackbar].
* @param messageResId The message to be displayed.
* @param duration The duration of the message. The default value is [SnackbarDuration.Short].
* @param actionResId The action text to be displayed. The default value is `null`.
* @param isDisplayed Used to track if the current message is already displayed or not.
* @param action The action to be performed when the action is clicked.
*/
data class SnackbarMessage(
@StringRes val messageResId: Int,
val duration: SnackbarDuration = SnackbarDuration.Short,
@StringRes val actionResId: Int? = null,
val isDisplayed: AtomicBoolean = AtomicBoolean(false),
val action: () -> Unit = {},
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.utils
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
class SnackbarDispatcherTests {
@Test
fun `given an empty queue the flow emits a null item`() = runTest {
val snackbarDispatcher = SnackbarDispatcher()
snackbarDispatcher.snackbarMessage.test {
assertThat(awaitItem()).isNull()
}
}
@Test
fun `given an empty queue calling clear does nothing`() = runTest {
val snackbarDispatcher = SnackbarDispatcher()
snackbarDispatcher.snackbarMessage.test {
assertThat(awaitItem()).isNull()
snackbarDispatcher.clear()
expectNoEvents()
}
}
@Test
fun `given a non-empty queue the flow emits an item`() = runTest {
val snackbarDispatcher = SnackbarDispatcher()
snackbarDispatcher.snackbarMessage.test {
snackbarDispatcher.post(SnackbarMessage(0))
val result = expectMostRecentItem()
assertThat(result).isNotNull()
}
}
@Test
fun `given a call to clear, the current message is cleared`() = runTest {
val snackbarDispatcher = SnackbarDispatcher()
snackbarDispatcher.snackbarMessage.test {
snackbarDispatcher.post(SnackbarMessage(0))
val item = expectMostRecentItem()
assertThat(item).isNotNull()
snackbarDispatcher.clear()
assertThat(awaitItem()).isNull()
}
}
@Test
fun `given 2 message emissions, the next message is displayed only after a call to clear`() = runTest {
val snackbarDispatcher = SnackbarDispatcher()
snackbarDispatcher.snackbarMessage.test {
val messageA = SnackbarMessage(0)
val messageB = SnackbarMessage(1)
// Send message A - it is the most recent item
snackbarDispatcher.post(messageA)
assertThat(expectMostRecentItem()).isEqualTo(messageA)
// Send message B - message A is still the most recent item
snackbarDispatcher.post(messageB)
expectNoEvents()
// Clear the last message - message B is now the most recent item
snackbarDispatcher.clear()
assertThat(expectMostRecentItem()).isEqualTo(messageB)
// Clear again - the queue is empty
snackbarDispatcher.clear()
assertThat(awaitItem()).isNull()
}
}
}

View file

@ -95,7 +95,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
is StateContent -> {
stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList)
}
is PollContent,
is PollContent, // TODO Polls: handle last message
is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> {
prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom)
}

View file

@ -29,7 +29,7 @@ enum class FeatureFlags(
Polls(
key = "feature.polls",
title = "Polls",
description = "Render poll events in the timeline",
description = "Create poll and render poll events in the timeline",
defaultValue = false,
),
NotificationSettings(

View file

@ -18,7 +18,7 @@ plugins {
id("io.element.android-library")
id("kotlin-parcelize")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.9.0"
kotlin("plugin.serialization") version "1.9.10"
}
android {

View file

@ -60,7 +60,8 @@ interface MatrixClient : Closeable {
/**
* Logout the user.
* Returns an optional URL. When the URL is there, it should be presented to the user after logout for RP initiated logout on their account page.
* Returns an optional URL. When the URL is there, it should be presented to the user after logout for
* Relying Party (RP) initiated logout on their account page.
*/
suspend fun logout(): String?
suspend fun loadUserDisplayName(): Result<String>

View file

@ -40,54 +40,53 @@ data class NotificationData(
sealed interface NotificationContent {
sealed interface MessageLike : NotificationContent {
object CallAnswer : MessageLike
object CallInvite : MessageLike
object CallHangup : MessageLike
object CallCandidates : MessageLike
object KeyVerificationReady : MessageLike
object KeyVerificationStart : MessageLike
object KeyVerificationCancel : MessageLike
object KeyVerificationAccept : MessageLike
object KeyVerificationKey : MessageLike
object KeyVerificationMac : MessageLike
object KeyVerificationDone : MessageLike
data object CallAnswer : MessageLike
data object CallInvite : MessageLike
data object CallHangup : MessageLike
data object CallCandidates : MessageLike
data object KeyVerificationReady : MessageLike
data object KeyVerificationStart : MessageLike
data object KeyVerificationCancel : MessageLike
data object KeyVerificationAccept : MessageLike
data object KeyVerificationKey : MessageLike
data object KeyVerificationMac : MessageLike
data object KeyVerificationDone : MessageLike
data class ReactionContent(
val relatedEventId: String
) : MessageLike
object RoomEncrypted : MessageLike
data object RoomEncrypted : MessageLike
data class RoomMessage(
val senderId: UserId,
val messageType: MessageType
) : MessageLike
object RoomRedaction : MessageLike
object Sticker : MessageLike
data object RoomRedaction : MessageLike
data object Sticker : MessageLike
}
sealed interface StateEvent : NotificationContent {
object PolicyRuleRoom : StateEvent
object PolicyRuleServer : StateEvent
object PolicyRuleUser : StateEvent
object RoomAliases : StateEvent
object RoomAvatar : StateEvent
object RoomCanonicalAlias : StateEvent
object RoomCreate : StateEvent
object RoomEncryption : StateEvent
object RoomGuestAccess : StateEvent
object RoomHistoryVisibility : StateEvent
object RoomJoinRules : StateEvent
data object PolicyRuleRoom : StateEvent
data object PolicyRuleServer : StateEvent
data object PolicyRuleUser : StateEvent
data object RoomAliases : StateEvent
data object RoomAvatar : StateEvent
data object RoomCanonicalAlias : StateEvent
data object RoomCreate : StateEvent
data object RoomEncryption : StateEvent
data object RoomGuestAccess : StateEvent
data object RoomHistoryVisibility : StateEvent
data object RoomJoinRules : StateEvent
data class RoomMemberContent(
val userId: String,
val membershipState: RoomMembershipState
) : StateEvent
object RoomName : StateEvent
object RoomPinnedEvents : StateEvent
object RoomPowerLevels : StateEvent
object RoomServerAcl : StateEvent
object RoomThirdPartyInvite : StateEvent
object RoomTombstone : StateEvent
object RoomTopic : StateEvent
object SpaceChild : StateEvent
object SpaceParent : StateEvent
data object RoomName : StateEvent
data object RoomPinnedEvents : StateEvent
data object RoomPowerLevels : StateEvent
data object RoomServerAcl : StateEvent
data object RoomThirdPartyInvite : StateEvent
data object RoomTombstone : StateEvent
data object RoomTopic : StateEvent
data object SpaceChild : StateEvent
data object SpaceParent : StateEvent
}
}

View file

@ -84,7 +84,7 @@ object PermalinkBuilder {
}
sealed class PermalinkBuilderError : Throwable() {
object InvalidRoomAlias : PermalinkBuilderError()
object InvalidRoomId : PermalinkBuilderError()
object InvalidUserId : PermalinkBuilderError()
data object InvalidRoomAlias : PermalinkBuilderError()
data object InvalidRoomId : PermalinkBuilderError()
data object InvalidUserId : PermalinkBuilderError()
}

View file

@ -20,5 +20,8 @@ enum class PollKind {
Disclosed,
/** Results should be only revealed when the poll is ended. */
Undisclosed
Undisclosed,
}
val PollKind.isDisclosed: Boolean
get() = this == PollKind.Disclosed

View file

@ -17,7 +17,7 @@
package io.element.android.libraries.matrix.api.room
sealed interface MatrixRoomMembersState {
object Unknown : MatrixRoomMembersState
data object Unknown : MatrixRoomMembersState
data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
data class Error(val failure: Throwable, val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
data class Ready(val roomMembers: List<RoomMember>) : MatrixRoomMembersState

View file

@ -29,7 +29,7 @@ import kotlin.time.Duration
*/
interface RoomList {
sealed class LoadingState {
object NotLoaded : LoadingState()
data object NotLoaded : LoadingState()
data class Loaded(val numberOfRooms: Int) : LoadingState()
}

View file

@ -26,10 +26,10 @@ import kotlinx.coroutines.flow.StateFlow
interface RoomListService {
sealed class State {
object Idle : State()
object Running : State()
object Error : State()
object Terminated : State()
data object Idle : State()
data object Running : State()
data object Error : State()
data object Terminated : State()
}
/**

View file

@ -28,6 +28,6 @@ sealed interface MatrixTimelineItem {
}
data class Virtual(val uniqueId: Long, val virtual: VirtualTimelineItem) : MatrixTimelineItem
object Other : MatrixTimelineItem
data object Other : MatrixTimelineItem
}

View file

@ -17,5 +17,5 @@
package io.element.android.libraries.matrix.api.timeline
sealed class TimelineException : Exception() {
object CannotPaginate : TimelineException()
data object CannotPaginate : TimelineException()
}

View file

@ -35,13 +35,12 @@ data class MessageContent(
val type: MessageType?
) : EventContent
sealed interface InReplyTo {
/** The event details are not loaded yet. We can fetch them. */
data class NotLoaded(val eventId: EventId) : InReplyTo
/** The event details are pending to be fetched. We should **not** fetch them again. */
object Pending : InReplyTo
data object Pending : InReplyTo
/** The event details are available. */
data class Ready(
@ -60,7 +59,7 @@ sealed interface InReplyTo {
* If the reason for the failure is consistent on the server, we'd enter a loop
* where we keep trying to fetch the same event.
* */
object Error : InReplyTo
data object Error : InReplyTo
}
object RedactedContent : EventContent
@ -92,7 +91,7 @@ data class UnableToDecryptContent(
val sessionId: String
) : Data
object Unknown : Data
data object Unknown : Data
}
}
@ -205,55 +204,25 @@ enum class MembershipChange {
}
sealed interface OtherState {
object PolicyRuleRoom : OtherState
object PolicyRuleServer : OtherState
object PolicyRuleUser : OtherState
object RoomAliases : OtherState
data class RoomAvatar(
val url: String?
) : OtherState
object RoomCanonicalAlias : OtherState
object RoomCreate : OtherState
object RoomEncryption : OtherState
object RoomGuestAccess : OtherState
object RoomHistoryVisibility : OtherState
object RoomJoinRules : OtherState
data class RoomName(
val name: String?
) : OtherState
object RoomPinnedEvents : OtherState
object RoomPowerLevels : OtherState
object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(
val displayName: String?
) : OtherState
object RoomTombstone : OtherState
data class RoomTopic(
val topic: String?
) : OtherState
object SpaceChild : OtherState
object SpaceParent : OtherState
data class Custom(
val eventType: String
) : OtherState
data object PolicyRuleRoom : OtherState
data object PolicyRuleServer : OtherState
data object PolicyRuleUser : OtherState
data object RoomAliases : OtherState
data class RoomAvatar(val url: String?) : OtherState
data object RoomCanonicalAlias : OtherState
data object RoomCreate : OtherState
data object RoomEncryption : OtherState
data object RoomGuestAccess : OtherState
data object RoomHistoryVisibility : OtherState
data object RoomJoinRules : OtherState
data class RoomName(val name: String?) : OtherState
data object RoomPinnedEvents : OtherState
data object RoomPowerLevels : OtherState
data object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(val displayName: String?) : OtherState
data object RoomTombstone : OtherState
data class RoomTopic(val topic: String?) : OtherState
data object SpaceChild : OtherState
data object SpaceParent : OtherState
data class Custom(val eventType: String) : OtherState
}

View file

@ -19,8 +19,8 @@ package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
sealed interface LocalEventSendState {
object NotSentYet : LocalEventSendState
object Canceled : LocalEventSendState
data object NotSentYet : LocalEventSendState
data object Canceled : LocalEventSendState
data class SendingFailed(
val error: String

View file

@ -17,9 +17,9 @@
package io.element.android.libraries.matrix.api.timeline.item.event
sealed interface ProfileTimelineDetails {
object Unavailable : ProfileTimelineDetails
data object Unavailable : ProfileTimelineDetails
object Pending : ProfileTimelineDetails
data object Pending : ProfileTimelineDetails
data class Ready(
val displayName: String?,

View file

@ -22,7 +22,7 @@ sealed interface VirtualTimelineItem {
val timestamp: Long
) : VirtualTimelineItem
object ReadMarker : VirtualTimelineItem
data object ReadMarker : VirtualTimelineItem
object EncryptedHistoryBanner : VirtualTimelineItem
data object EncryptedHistoryBanner : VirtualTimelineItem
}

View file

@ -60,11 +60,11 @@ enum class Target(open val filter: String) {
}
sealed class LogLevel(val filter: String) {
object Warn : LogLevel("warn")
object Trace : LogLevel("trace")
object Info : LogLevel("info")
object Debug : LogLevel("debug")
object Error : LogLevel("error")
data object Warn : LogLevel("warn")
data object Trace : LogLevel("trace")
data object Info : LogLevel("info")
data object Debug : LogLevel("debug")
data object Error : LogLevel("error")
}
object TracingFilterConfigurations {

View file

@ -17,6 +17,6 @@
package io.element.android.libraries.matrix.api.tracing
sealed class WriteToFilesConfiguration {
object Disabled : WriteToFilesConfiguration()
data object Disabled : WriteToFilesConfiguration()
data class Enabled(val directory: String, val filenamePrefix: String) : WriteToFilesConfiguration()
}

View file

@ -77,35 +77,35 @@ interface SessionVerificationService {
/** Verification status of the current session. */
sealed interface SessionVerifiedStatus {
/** Unknown status, we couldn't read the actual value from the SDK. */
object Unknown : SessionVerifiedStatus
data object Unknown : SessionVerifiedStatus
/** Not verified session status. */
object NotVerified : SessionVerifiedStatus
data object NotVerified : SessionVerifiedStatus
/** Verified session status. */
object Verified : SessionVerifiedStatus
data object Verified : SessionVerifiedStatus
}
/** States produced by the [SessionVerificationService]. */
sealed interface VerificationFlowState {
/** Initial state. */
object Initial : VerificationFlowState
data object Initial : VerificationFlowState
/** Session verification request was accepted by another device. */
object AcceptedVerificationRequest : VerificationFlowState
data object AcceptedVerificationRequest : VerificationFlowState
/** Short Authentication String (SAS) verification started between the 2 devices. */
object StartedSasVerification : VerificationFlowState
data object StartedSasVerification : VerificationFlowState
/** Verification data for the SAS verification (emojis) received. */
data class ReceivedVerificationData(val emoji: List<VerificationEmoji>) : VerificationFlowState
/** Verification completed successfully. */
object Finished : VerificationFlowState
data object Finished : VerificationFlowState
/** Verification was cancelled by either device. */
object Canceled : VerificationFlowState
data object Canceled : VerificationFlowState
/** Verification failed with an error. */
object Failed : VerificationFlowState
data object Failed : VerificationFlowState
}

View file

@ -17,7 +17,7 @@
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.9.0"
kotlin("plugin.serialization") version "1.9.10"
}
android {

View file

@ -32,6 +32,14 @@ const val A_PASSWORD = "password"
val A_USER_ID = UserId("@alice:server.org")
val A_USER_ID_2 = UserId("@bob:server.org")
val A_USER_ID_3 = UserId("@carol:server.org")
val A_USER_ID_4 = UserId("@david:server.org")
val A_USER_ID_5 = UserId("@eve:server.org")
val A_USER_ID_6 = UserId("@justin:server.org")
val A_USER_ID_7 = UserId("@mallory:server.org")
val A_USER_ID_8 = UserId("@susie:server.org")
val A_USER_ID_9 = UserId("@victor:server.org")
val A_USER_ID_10 = UserId("@walter:server.org")
val A_SESSION_ID: SessionId = A_USER_ID
val A_SESSION_ID_2: SessionId = A_USER_ID_2
val A_SPACE_ID = SpaceId("!aSpaceId:domain")

View file

@ -31,7 +31,7 @@ sealed class AvatarAction(
val icon: ImageVector,
val destructive: Boolean = false,
) {
object TakePhoto : AvatarAction(titleResId = CommonStrings.action_take_photo, icon = Icons.Outlined.PhotoCamera)
object ChoosePhoto : AvatarAction(titleResId = CommonStrings.action_choose_photo, icon = Icons.Outlined.PhotoLibrary)
object Remove : AvatarAction(titleResId = CommonStrings.action_remove, icon = Icons.Outlined.Delete, destructive = true)
data object TakePhoto : AvatarAction(titleResId = CommonStrings.action_take_photo, icon = Icons.Outlined.PhotoCamera)
data object ChoosePhoto : AvatarAction(titleResId = CommonStrings.action_choose_photo, icon = Icons.Outlined.PhotoLibrary)
data object Remove : AvatarAction(titleResId = CommonStrings.action_remove, icon = Icons.Outlined.Delete, destructive = true)
}

View file

@ -35,7 +35,7 @@ data class MediaRequestData(
) {
sealed interface Kind {
object Content : Kind
data object Content : Kind
data class File(val body: String?, val mimeType: String) : Kind
data class Thumbnail(val width: Long, val height: Long) : Kind {
constructor(size: Long) : this(size, size)

View file

@ -26,14 +26,14 @@ sealed interface PickerType<Input, Output> {
fun getContract(): ActivityResultContract<Input, Output>
fun getDefaultRequest(): Input
object Image : PickerType<PickVisualMediaRequest, Uri?> {
data object Image : PickerType<PickVisualMediaRequest, Uri?> {
override fun getContract() = ActivityResultContracts.PickVisualMedia()
override fun getDefaultRequest(): PickVisualMediaRequest {
return PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
}
}
object ImageAndVideo : PickerType<PickVisualMediaRequest, Uri?> {
data object ImageAndVideo : PickerType<PickVisualMediaRequest, Uri?> {
override fun getContract() = ActivityResultContracts.PickVisualMedia()
override fun getDefaultRequest(): PickVisualMediaRequest {
return PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)

View file

@ -114,7 +114,7 @@ data class ImageCompressionResult(
)
sealed interface ResizeMode {
object None : ResizeMode
data object None : ResizeMode
data class Approximate(val desiredWidth: Int, val desiredHeight: Int) : ResizeMode
data class Strict(val maxWidth: Int, val maxHeight: Int) : ResizeMode
}

View file

@ -17,6 +17,6 @@
package io.element.android.libraries.permissions.api
sealed interface PermissionsEvents {
object OpenSystemDialog : PermissionsEvents
object CloseDialog : PermissionsEvents
data object OpenSystemDialog : PermissionsEvents
data object CloseDialog : PermissionsEvents
}

View file

@ -17,5 +17,5 @@
package io.element.android.libraries.push.api.gateway
sealed class PushGatewayFailure : Throwable(cause = null) {
object PusherRejected : PushGatewayFailure()
data object PusherRejected : PushGatewayFailure()
}

View file

@ -16,7 +16,7 @@
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.9.0"
kotlin("plugin.serialization") version "1.9.10"
}
android {

View file

@ -166,6 +166,6 @@ sealed interface OneShotNotification {
}
sealed interface SummaryNotification {
object Removed : SummaryNotification
data object Removed : SummaryNotification
data class Update(val notification: Notification) : SummaryNotification
}

View file

@ -7,7 +7,7 @@
<string name="notification_inline_reply_failed">"** Senden fehlgeschlagen - bitte Raum öffnen"</string>
<string name="notification_invitation_action_join">"Beitreten"</string>
<string name="notification_invitation_action_reject">"Ablehnen"</string>
<string name="notification_invite_body">"Hat dich eingeladen"</string>
<string name="notification_invite_body">"Hat dich zum Chatten eingeladen"</string>
<string name="notification_new_messages">"Neue Nachrichten"</string>
<string name="notification_reaction_body">"Reagierte mit %1$s"</string>
<string name="notification_room_action_mark_as_read">"Als gelesen markieren"</string>

View file

@ -16,7 +16,7 @@
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.9.0"
kotlin("plugin.serialization") version "1.9.10"
}
android {

View file

@ -31,9 +31,9 @@ class RegisterUnifiedPushUseCase @Inject constructor(
) {
sealed interface RegisterUnifiedPushResult {
object Success : RegisterUnifiedPushResult
object NeedToAskUserForDistributor : RegisterUnifiedPushResult
object Error : RegisterUnifiedPushResult
data object Success : RegisterUnifiedPushResult
data object NeedToAskUserForDistributor : RegisterUnifiedPushResult
data object Error : RegisterUnifiedPushResult
}
suspend fun execute(matrixClient: MatrixClient, distributor: Distributor, clientSecret: String): RegisterUnifiedPushResult {

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_a11y_add_attachment">"Přidat přílohu"</string>
<string name="rich_text_editor_bullet_list">"Přepnout seznam s odrážkami"</string>
<string name="rich_text_editor_code_block">"Přepnout blok kódu"</string>
<string name="rich_text_editor_composer_placeholder">"Zpráva…"</string>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_a11y_add_attachment">"Anhang hinzufügen"</string>
<string name="rich_text_editor_bullet_list">"Aufzählungsliste ein-/ausschalten"</string>
<string name="rich_text_editor_code_block">"Codeblock umschalten"</string>
<string name="rich_text_editor_composer_placeholder">"Nachricht…"</string>

View file

@ -24,7 +24,7 @@
<string name="action_edit">"Upravit"</string>
<string name="action_enable">"Povolit"</string>
<string name="action_forgot_password">"Zapomněli jste heslo?"</string>
<string name="action_forward">"Vpřed"</string>
<string name="action_forward">"Přeposlat"</string>
<string name="action_invite">"Pozvat"</string>
<string name="action_invite_friends">"Pozvat přátele"</string>
<string name="action_invite_friends_to_app">"Pozvat přátele do %1$s"</string>
@ -40,6 +40,7 @@
<string name="action_open_with">"Otevřít v aplikaci"</string>
<string name="action_quick_reply">"Rychlá odpověď"</string>
<string name="action_quote">"Citovat"</string>
<string name="action_react">"Reagovat"</string>
<string name="action_remove">"Odstranit"</string>
<string name="action_reply">"Odpovědět"</string>
<string name="action_report_bug">"Nahlásit chybu"</string>
@ -94,6 +95,9 @@
<string name="common_password">"Heslo"</string>
<string name="common_people">"Lidé"</string>
<string name="common_permalink">"Trvalý odkaz"</string>
<string name="common_poll_final_votes">"Konečné hlasy: %1$s"</string>
<string name="common_poll_total_votes">"Celkový počet hlasů: %1$s"</string>
<string name="common_poll_undisclosed_text">"Výsledky se zobrazí po skončení hlasování"</string>
<string name="common_privacy_policy">"Zásady ochrany osobních údajů"</string>
<string name="common_reactions">"Reakce"</string>
<string name="common_refreshing">"Obnovování…"</string>
@ -140,7 +144,11 @@
<string name="emoji_picker_category_places">"Cestování a místa"</string>
<string name="emoji_picker_category_symbols">"Symboly"</string>
<string name="error_failed_creating_the_permalink">"Vytvoření trvalého odkazu se nezdařilo"</string>
<string name="error_failed_loading_map">"%1$s nemohl načíst mapu. Zkuste to prosím později."</string>
<string name="error_failed_loading_messages">"Načítání zpráv se nezdařilo"</string>
<string name="error_failed_locating_user">"%1$s nemá přístup k vaší poloze. Zkuste to prosím později."</string>
<string name="error_missing_location_auth_android">"%1$s nemá oprávnění k přístupu k vaší poloze. Přístup můžete povolit v Nastavení."</string>
<string name="error_missing_location_rationale_android">"%1$s nemá oprávnění k přístupu k vaší poloze. Povolit přístup níže."</string>
<string name="error_some_messages_have_not_been_sent">"Některé zprávy nebyly odeslány"</string>
<string name="error_unknown">"Omlouváme se, došlo k chybě"</string>
<string name="invite_friends_rich_title">"🔐️ Připojte se ke mně na %1$s"</string>
@ -154,6 +162,11 @@
<item quantity="few">"%1$d členové"</item>
<item quantity="other">"%1$d členů"</item>
</plurals>
<plurals name="common_poll_votes_count">
<item quantity="one">"%d hlas"</item>
<item quantity="few">"%d hlasy"</item>
<item quantity="other">"%d hlasů"</item>
</plurals>
<string name="preference_rageshake">"Zatřeste zařízením pro nahlášení chyby"</string>
<string name="rageshake_dialog_content">"Zdá se, že jste frustrovaně třásli telefonem. Chcete otevřít obrazovku pro nahlášení chyby?"</string>
<string name="report_content_explanation">"Tato zpráva bude nahlášena správci vašeho domovského serveru. Nebude si moci přečíst žádné šifrované zprávy."</string>
@ -165,9 +178,35 @@
<string name="screen_media_picker_error_failed_selection">"Výběr média se nezdařil, zkuste to prosím znovu."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
<string name="screen_migration_message">"Toto je jednorázový proces, děkujeme za čekání."</string>
<string name="screen_migration_title">"Nastavení vašeho účtu"</string>
<string name="screen_notification_settings_additional_settings_section_title">"Další nastavení"</string>
<string name="screen_notification_settings_calls_label">"Halsové a video hovory"</string>
<string name="screen_notification_settings_configuration_mismatch">"Neshoda konfigurace"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností.
Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní.
Pokud budete pokračovat, některá nastavení se mohou změnit."</string>
<string name="screen_notification_settings_direct_chats">"Přímé zprávy"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Vlastní nastavení pro chat"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Při aktualizaci nastavení oznámení došlo k chybě."</string>
<string name="screen_notification_settings_edit_mode_all_messages">"Všechny zprávy"</string>
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Pouze zmínky a klíčová slova"</string>
<string name="screen_notification_settings_edit_screen_direct_section_header">"V přímých zprávách mě upozornit na"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Ve skupinových chatech mě upozornit na"</string>
<string name="screen_notification_settings_enable_notifications">"Povolit oznámení na tomto zařízení"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Konfigurace nebyla opravena, zkuste to prosím znovu."</string>
<string name="screen_notification_settings_group_chats">"Skupinové chaty"</string>
<string name="screen_notification_settings_mentions_section_title">"Zmínky"</string>
<string name="screen_notification_settings_mode_all">"Vše"</string>
<string name="screen_notification_settings_mode_mentions">"Zmínky"</string>
<string name="screen_notification_settings_notification_section_title">"Upozornit mě na"</string>
<string name="screen_notification_settings_room_mention_label">"Upozornit mě na @room"</string>
<string name="screen_notification_settings_system_notifications_action_required">"Chcete-li dostávat oznámení, změňte prosím svůj %1$s."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"systémová nastavení"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Systémová oznámení byla vypnuta"</string>
<string name="screen_notification_settings_title">"Oznámení"</string>
<string name="screen_report_content_block_user_hint">"Zaškrtněte, pokud chcete skrýt všechny aktuální a budoucí zprávy od tohoto uživatele"</string>
<string name="screen_settings_oidc_account">"Účet a zařízení"</string>
<string name="screen_share_location_title">"Sdílet polohu"</string>
<string name="screen_share_my_location_action">"Sdílet moji polohu"</string>
<string name="screen_share_open_apple_maps">"Otevřít v Mapách Apple"</string>

View file

@ -8,7 +8,7 @@
<string name="action_back">"Zurück"</string>
<string name="action_cancel">"Abbrechen"</string>
<string name="action_choose_photo">"Foto auswählen"</string>
<string name="action_clear">"Löschen"</string>
<string name="action_clear">"Zurücksetzen"</string>
<string name="action_close">"Schließen"</string>
<string name="action_complete_verification">"Verifizierung abschließen"</string>
<string name="action_confirm">"Bestätigen"</string>
@ -37,9 +37,10 @@
<string name="action_no">"Nein"</string>
<string name="action_not_now">"Nicht jetzt"</string>
<string name="action_ok">"OK"</string>
<string name="action_open_with">"Öffne mit"</string>
<string name="action_open_with">"Öffnen mit"</string>
<string name="action_quick_reply">"Schnellantwort"</string>
<string name="action_quote">"Zitieren"</string>
<string name="action_react">"Reagieren"</string>
<string name="action_remove">"Entfernen"</string>
<string name="action_reply">"Antworten"</string>
<string name="action_report_bug">"Fehler melden"</string>
@ -56,7 +57,7 @@
<string name="action_start">"Starten"</string>
<string name="action_start_chat">"Chat starten"</string>
<string name="action_start_verification">"Verifizierung starten"</string>
<string name="action_static_map_load">"Tippe, um die Karte zu laden"</string>
<string name="action_static_map_load">"Zum Karte laden tippen"</string>
<string name="action_take_photo">"Foto aufnehmen"</string>
<string name="action_view_source">"Quelltext anzeigen"</string>
<string name="action_yes">"Ja"</string>
@ -80,25 +81,28 @@
<string name="common_forward_message">"Nachricht weiterleiten"</string>
<string name="common_gif">"GIF"</string>
<string name="common_image">"Bild"</string>
<string name="common_invite_unknown_profile">"Wir können die Matrix-ID dieses Benutzers nicht validieren. Die Einladung wurde möglicherweise nicht empfangen."</string>
<string name="common_leaving_room">"Raum verlassen"</string>
<string name="common_invite_unknown_profile">"Diese Matrix-ID kann nicht gefunden werden, daher wird die Einladung möglicherweise nicht empfangen."</string>
<string name="common_leaving_room">"Verlasse Raum"</string>
<string name="common_link_copied_to_clipboard">"Link in Zwischenablage kopiert"</string>
<string name="common_loading">"Wird geladen…"</string>
<string name="common_loading">"Lädt…"</string>
<string name="common_message">"Nachricht"</string>
<string name="common_message_layout">"Nachrichtenlayout"</string>
<string name="common_message_removed">"Nachricht wurde entfernt"</string>
<string name="common_message_removed">"Nachricht entfernt"</string>
<string name="common_modern">"Modern"</string>
<string name="common_mute">"Stummschalten"</string>
<string name="common_no_results">"Keine Ergebnisse"</string>
<string name="common_offline">"Offline"</string>
<string name="common_password">"Passwort"</string>
<string name="common_people">"Personen"</string>
<string name="common_permalink">"Permalink"</string>
<string name="common_permalink">"Dauerlink"</string>
<string name="common_poll_final_votes">"Endgültige Stimmen: %1$s"</string>
<string name="common_poll_total_votes">"Stimmen insgesamt: %1$s"</string>
<string name="common_poll_undisclosed_text">"Ergebnisse werden nach Ende der Umfrage angezeigt"</string>
<string name="common_privacy_policy">"Datenschutz­erklärung"</string>
<string name="common_reactions">"Reaktionen"</string>
<string name="common_refreshing">"Aktualisiere…"</string>
<string name="common_replying_to">"Auf %1$s antworten"</string>
<string name="common_report_a_bug">"Melde einen Fehler"</string>
<string name="common_report_a_bug">"Einen Fehler melden"</string>
<string name="common_report_submitted">"Bericht gesendet"</string>
<string name="common_room_name">"Raumname"</string>
<string name="common_room_name_placeholder">"z.B. dein Projektname"</string>
@ -106,12 +110,12 @@
<string name="common_search_results">"Suchergebnisse"</string>
<string name="common_security">"Sicherheit"</string>
<string name="common_select_your_server">"Wählen deinen Server"</string>
<string name="common_sending">"Senden…"</string>
<string name="common_sending">"Sendet…"</string>
<string name="common_server_not_supported">"Server wird nicht unterstützt"</string>
<string name="common_server_url">"Server-URL"</string>
<string name="common_settings">"Einstellungen"</string>
<string name="common_shared_location">"Geteilter Standort"</string>
<string name="common_starting_chat">"Chat wird gestartet…"</string>
<string name="common_starting_chat">"Starte Chat…"</string>
<string name="common_sticker">"Sticker"</string>
<string name="common_success">"Erfolg"</string>
<string name="common_suggestions">"Vorschläge"</string>
@ -120,7 +124,7 @@
<string name="common_topic">"Thema"</string>
<string name="common_topic_placeholder">"Worum geht es in diesem Raum?"</string>
<string name="common_unable_to_decrypt">"Entschlüsselung nicht möglich"</string>
<string name="common_unable_to_invite_message">"Wir konnten Einladungen nicht erfolgreich an einen oder mehrere Benutzer senden."</string>
<string name="common_unable_to_invite_message">"Einladungen konnten nicht an einen oder mehrere Benutzer gesendet werden."</string>
<string name="common_unable_to_invite_title">"Einladung(en) können nicht gesendet werden"</string>
<string name="common_unmute">"Stummschaltung aufheben"</string>
<string name="common_unsupported_event">"Nicht unterstütztes Ereignis"</string>
@ -128,7 +132,7 @@
<string name="common_verification_cancelled">"Verifizierung abgebrochen"</string>
<string name="common_verification_complete">"Verifizierung abgeschlossen"</string>
<string name="common_video">"Video"</string>
<string name="common_waiting">"Warten…"</string>
<string name="common_waiting">"Warte…"</string>
<string name="dialog_title_confirmation">"Bestätigung"</string>
<string name="dialog_title_warning">"Warnung"</string>
<string name="emoji_picker_category_activity">"Aktivitäten"</string>
@ -139,10 +143,12 @@
<string name="emoji_picker_category_people">"Smileys &amp; Personen"</string>
<string name="emoji_picker_category_places">"Reisen &amp; Orte"</string>
<string name="emoji_picker_category_symbols">"Symbole"</string>
<string name="error_failed_creating_the_permalink">"Fehler beim Erstellen des Permalinks"</string>
<string name="error_failed_creating_the_permalink">"Fehler beim Erstellen des Dauerlinks"</string>
<string name="error_failed_loading_map">"%1$s konnte die Karte nicht laden. Bitte versuche es später erneut."</string>
<string name="error_failed_loading_messages">"Fehler beim Laden der Nachrichten"</string>
<string name="error_failed_locating_user">"%1$s konnte nicht auf deinen Standort zugreifen. Bitte versuche es später erneut."</string>
<string name="error_missing_location_auth_android">"%1$s hat keine Berechtigung, auf deinen Standort zuzugreifen. Du kannst den Zugriff in den Einstellungen aktivieren."</string>
<string name="error_missing_location_rationale_android">"%1$s hat keine Berechtigung, auf deinen Standort zuzugreifen. Aktiviere den Zugriff unten."</string>
<string name="error_some_messages_have_not_been_sent">"Einige Nachrichten wurden nicht gesendet"</string>
<string name="error_unknown">"Entschuldigung, ein Fehler ist aufgetreten."</string>
<string name="invite_friends_rich_title">"🔐️ Besuche mich auf %1$s"</string>
@ -155,9 +161,13 @@
<item quantity="one">"%1$d Mitglied"</item>
<item quantity="other">"%1$d Mitglieder"</item>
</plurals>
<string name="preference_rageshake">"Rageshake zum Melden von Fehlern"</string>
<plurals name="common_poll_votes_count">
<item quantity="one">"%d Stimme"</item>
<item quantity="other">"%d Stimmen"</item>
</plurals>
<string name="preference_rageshake">"Schütteln zum Melden von Fehlern"</string>
<string name="rageshake_dialog_content">"Du scheinst frustriert das Telefon zu schütteln. Möchtest du den Fehlerberichtsbildschirm öffnen?"</string>
<string name="report_content_explanation">"Diese Nachricht wird an deinen Heimserver-Admin gemeldet werden. Er wird nicht in der Lage sein, verschlüsselte Nachrichten zu lesen."</string>
<string name="report_content_explanation">"Diese Nachricht wird an deinen Heimserver-Admin gemeldet. Er wird nicht in der Lage sein, verschlüsselte Nachrichten zu lesen."</string>
<string name="report_content_hint">"Grund für die Meldung dieses Inhalts"</string>
<string name="room_timeline_beginning_of_room">"Dies ist der Anfang von %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Dies ist der Beginn dieser Konversation."</string>
@ -166,9 +176,35 @@
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuche es erneut."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Fehler bei der Verarbeitung von Medien zum Hochladen, bitte versuche es erneut."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Hochladen von Medien fehlgeschlagen, bitte versuchen Sie es erneut."</string>
<string name="screen_migration_message">"Dies ist ein einmaliger Vorgang, danke fürs Warten."</string>
<string name="screen_migration_title">"Dein Konto einrichten"</string>
<string name="screen_notification_settings_additional_settings_section_title">"Zusätzliche Einstellungen"</string>
<string name="screen_notification_settings_calls_label">"Audio- und Videoanrufe"</string>
<string name="screen_notification_settings_configuration_mismatch">"Konfigurationskonflikt"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Wir haben die Benachrichtigungseinstellungen vereinfacht, damit Optionen leichter zu finden sind.
Einige benutzerdefinierte Einstellungen, die du in der Vergangenheit ausgewählt hast, werden hier nicht angezeigt, sind aber immer noch aktiv.
Wenn du fortfährst, ändern sich möglicherweise einige deine Einstellungen."</string>
<string name="screen_notification_settings_direct_chats">"Direkte Chats"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Benutzerdefinierte Einstellung pro Chat"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Beim Aktualisieren der Benachrichtigungseinstellung ist ein Fehler aufgetreten."</string>
<string name="screen_notification_settings_edit_mode_all_messages">"Alle Nachrichten"</string>
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Nur Erwähnungen und Schlüsselwörter"</string>
<string name="screen_notification_settings_edit_screen_direct_section_header">"Bei direkten Chats, benachrichtigen mich für"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Bei Gruppenchats, benachrichtigte mich für"</string>
<string name="screen_notification_settings_enable_notifications">"Benachrichtigungen auf diesem Gerät aktivieren"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Die Konfiguration wurde nicht korrigiert. Bitte versuche es erneut."</string>
<string name="screen_notification_settings_group_chats">"Gruppenchats"</string>
<string name="screen_notification_settings_mentions_section_title">"Erwähnungen"</string>
<string name="screen_notification_settings_mode_all">"Alle"</string>
<string name="screen_notification_settings_mode_mentions">"Erwähnungen"</string>
<string name="screen_notification_settings_notification_section_title">"Benachrichtige mich für"</string>
<string name="screen_notification_settings_room_mention_label">"Benachrichtige mich bei @room"</string>
<string name="screen_notification_settings_system_notifications_action_required">"Um Benachrichtigungen zu erhalten, ändern bitte deine %1$s."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"Systemeinstellungen"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Systembenachrichtigungen deaktiviert"</string>
<string name="screen_notification_settings_title">"Benachrichtigungen"</string>
<string name="screen_report_content_block_user_hint">"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest"</string>
<string name="screen_settings_oidc_account">"Konto und Geräte"</string>
<string name="screen_share_location_title">"Standort teilen"</string>
<string name="screen_share_my_location_action">"Meinen Standort teilen"</string>
<string name="screen_share_open_apple_maps">"In Apple Maps öffnen"</string>

View file

@ -165,8 +165,6 @@
<string name="screen_media_picker_error_failed_selection">"Impossible de sélectionner un média, veuillez réessayer."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Échec du traitement du média avant son envoi, veuillez réessayer."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Impossible denvoyer le média, veuillez réessayer."</string>
<string name="screen_migration_message">"Ce processus na besoin dêtre fait quune seule fois, merci de patienter."</string>
<string name="screen_migration_title">"Configuration de votre compte."</string>
<string name="screen_notification_settings_enable_notifications">"Activer les notifications sur cet appareil"</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"paramètres système"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Notifications système désactivées"</string>

View file

@ -178,15 +178,23 @@
<string name="screen_media_picker_error_failed_selection">"Не удалось выбрать носитель, попробуйте еще раз."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string>
<string name="screen_migration_message">"Это одноразовый процесс, спасибо, что подождали."</string>
<string name="screen_migration_title">"Настройка учетной записи."</string>
<string name="screen_notification_settings_additional_settings_section_title">"Дополнительные параметры"</string>
<string name="screen_notification_settings_calls_label">"Аудио и видео звонки"</string>
<string name="screen_notification_settings_configuration_mismatch">"Несоответствие конфигурации"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Мы упростили настройки уведомлений, чтобы упростить поиск опций.
Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны.
Если вы продолжите, некоторые настройки могут быть изменены."</string>
<string name="screen_notification_settings_direct_chats">"Прямые чаты"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Индивидуальные настройки для каждого чата"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"При обновлении настроек уведомления произошла ошибка."</string>
<string name="screen_notification_settings_edit_mode_all_messages">"Все сообщения"</string>
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Только упоминания и ключевые слова"</string>
<string name="screen_notification_settings_edit_screen_direct_section_header">"Уведомлять меня в личных чатах"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Уведомлять меня в групповых чатах"</string>
<string name="screen_notification_settings_enable_notifications">"Включить уведомления на данном устройстве"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Конфигурация не была исправлена, попробуйте еще раз."</string>
<string name="screen_notification_settings_group_chats">"Групповые чаты"</string>
<string name="screen_notification_settings_mentions_section_title">"Упоминания"</string>
<string name="screen_notification_settings_mode_all">"Все"</string>
@ -198,6 +206,7 @@
<string name="screen_notification_settings_system_notifications_turned_off">"Системные уведомления выключены"</string>
<string name="screen_notification_settings_title">"Уведомления"</string>
<string name="screen_report_content_block_user_hint">"Отметьте, хотите ли вы скрыть все текущие и будущие сообщения от этого пользователя"</string>
<string name="screen_settings_oidc_account">"Учетная запись и устройства"</string>
<string name="screen_share_location_title">"Поделиться местоположением"</string>
<string name="screen_share_my_location_action">"Поделиться моим местоположением"</string>
<string name="screen_share_open_apple_maps">"Открыть в Apple Maps"</string>

View file

@ -178,14 +178,23 @@
<string name="screen_media_picker_error_failed_selection">"Nepodarilo sa vybrať médium, skúste to prosím znova."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Nepodarilo sa nahrať médiá, skúste to prosím znova."</string>
<string name="screen_migration_message">"Ide o jednorazový proces, ďakujeme za trpezlivosť."</string>
<string name="screen_migration_title">"Nastavenie vášho účtu."</string>
<string name="screen_notification_settings_additional_settings_section_title">"Ďalšie nastavenia"</string>
<string name="screen_notification_settings_calls_label">"Audio a video hovory"</string>
<string name="screen_notification_settings_configuration_mismatch">"Nezhoda konfigurácie"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Zjednodušili sme Nastavenia oznámení, aby ste ľahšie našli možnosti.
Niektoré vlastné nastavenia, ktoré ste si nastavili v minulosti, sa tu nezobrazujú, ale sú stále aktívne.
Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť."</string>
<string name="screen_notification_settings_direct_chats">"Priame konverzácie"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Vlastné nastavenie pre konverzácie"</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_edit_mode_all_messages">"Všetky správy"</string>
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Iba zmienky a kľúčové slová"</string>
<string name="screen_notification_settings_edit_screen_direct_section_header">"Pri priamych rozhovoroch ma upozorniť na"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Pri skupinových rozhovoroch ma upozorniť na"</string>
<string name="screen_notification_settings_enable_notifications">"Povoliť oznámenia na tomto zariadení"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Konfigurácia nebola opravená, skúste to prosím znova."</string>
<string name="screen_notification_settings_group_chats">"Skupinové rozhovory"</string>
<string name="screen_notification_settings_mentions_section_title">"Zmienky"</string>
<string name="screen_notification_settings_mode_all">"Všetky"</string>
@ -197,6 +206,7 @@
<string name="screen_notification_settings_system_notifications_turned_off">"Systémové oznámenia sú vypnuté"</string>
<string name="screen_notification_settings_title">"Oznámenia"</string>
<string name="screen_report_content_block_user_hint">"Označte, či chcete skryť všetky aktuálne a budúce správy od tohto používateľa"</string>
<string name="screen_settings_oidc_account">"Účet a zariadenia"</string>
<string name="screen_share_location_title">"Zdieľať polohu"</string>
<string name="screen_share_my_location_action">"Zdieľať moju polohu"</string>
<string name="screen_share_open_apple_maps">"Otvoriť v Apple Maps"</string>

View file

@ -144,7 +144,6 @@
<string name="room_timeline_read_marker_title">"新訊息"</string>
<string name="screen_analytics_settings_share_data">"分享分析數據"</string>
<string name="screen_media_upload_preview_error_failed_sending">"無法上傳媒體檔案,請稍後再試。"</string>
<string name="screen_migration_title">"設定您的帳號"</string>
<string name="screen_notification_settings_additional_settings_section_title">"其他設定"</string>
<string name="screen_notification_settings_direct_chats">"私訊"</string>
<string name="screen_notification_settings_enable_notifications">"在這個裝置上開啟通知"</string>

View file

@ -181,14 +181,12 @@
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
<string name="screen_migration_title">"Setting up your account."</string>
<string name="screen_notification_settings_additional_settings_section_title">"Additional settings"</string>
<string name="screen_notification_settings_calls_label">"Audio and video calls"</string>
<string name="screen_notification_settings_configuration_mismatch">"Configuration mismatch"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Weve simplified Notifications Settings to make options easier to find.
<string name="screen_notification_settings_configuration_mismatch_description">"Weve simplified Notifications Settings to make options easier to find.
Some custom settings youve chosen in the past are not shown here, but theyre still active.
Some custom settings youve chosen in the past are not shown here, but theyre still active.
If you proceed, some of your settings may change."</string>
<string name="screen_notification_settings_direct_chats">"Direct chats"</string>