Merge develop into feature/fga/image_loading

This commit is contained in:
ganfra 2023-05-05 12:51:00 +02:00
commit affb928801
602 changed files with 3434 additions and 1627 deletions

View file

@ -33,6 +33,8 @@ android {
implementation(libs.accompanist.systemui)
implementation(projects.libraries.elementresources)
implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
kspTest(libs.showkase.processor)
}
}

View file

@ -35,8 +35,8 @@ import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Text
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
@ -97,15 +97,10 @@ fun ClickableLinkText(
)
}
@Preview
@Preview(group = PreviewGroup.Text)
@Composable
internal fun ClickableLinkTextLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun ClickableLinkTextDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun ClickableLinkTextPreview() =
ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
@ -117,3 +112,4 @@ private fun ContentToPreview() {
interactionSource = MutableInteractionSource(),
)
}

View file

@ -23,8 +23,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.designsystem.theme.components.Text
@ -52,13 +52,9 @@ fun LabelledCheckbox(
}
}
@Preview
@Preview(group = PreviewGroup.Toggles)
@Composable
internal fun LabelledCheckboxLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun LabelledCheckboxDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun LabelledCheckboxPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -30,8 +30,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.components.dialogs.DialogPreview
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
@ -71,15 +72,43 @@ fun ProgressDialog(
}
}
@Preview
@Composable
internal fun ProgressDialogLightPreview() = ElementPreviewLight { ContentToPreview() }
private fun ProgressDialogContent(
modifier: Modifier = Modifier,
text: String? = null,
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(8.dp)
)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (!text.isNullOrBlank()) {
Text(
text = text,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(16.dp)
)
}
}
}
}
@Preview
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun ProgressDialogDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun ProgressDialogPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
ProgressDialog(text = "test dialog content")
DialogPreview {
ProgressDialogContent(text = "test dialog content")
}
}

View file

@ -34,8 +34,8 @@ import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import io.element.android.libraries.designsystem.AvatarGradientEnd
import io.element.android.libraries.designsystem.AvatarGradientStart
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar
import io.element.android.libraries.designsystem.theme.components.Text
import timber.log.Timber
@ -106,17 +106,7 @@ private fun InitialsAvatar(
}
}
@Preview
@Preview(group = PreviewGroup.Avatars)
@Composable
fun AvatarLightPreview(@PreviewParameter(AvatarDataProvider::class) avatarData: AvatarData) =
ElementPreviewLight { ContentToPreview(avatarData) }
@Preview
@Composable
fun AvatarDarkPreview(@PreviewParameter(AvatarDataProvider::class) avatarData: AvatarData) =
ElementPreviewDark { ContentToPreview(avatarData) }
@Composable
private fun ContentToPreview(avatarData: AvatarData) {
Avatar(avatarData)
}
fun AvatarPreview(@PreviewParameter(AvatarDataProvider::class) avatarData: AvatarData) =
ElementThemedPreview { Avatar(avatarData) }

View file

@ -16,22 +16,16 @@
package io.element.android.libraries.designsystem.components.avatar
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Immutable
@Parcelize
data class AvatarData(
val id: String,
val name: String?,
val url: String? = null,
@IgnoredOnParcel
val size: AvatarSize = AvatarSize.MEDIUM
) : Parcelable {
) {
@IgnoredOnParcel
val initial by lazy {
(name?.takeIf { it.isNotBlank() } ?: id)
.let { dn ->

View file

@ -24,8 +24,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.ui.strings.R as StringR
@ -47,16 +47,9 @@ fun BackButton(
}
}
@Preview
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun BackButtonPreviewLight() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun BackButtonPreviewDark() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
internal fun BackButtonPreview() = ElementThemedPreview {
Column {
BackButton(onClick = { }, enabled = true, contentDescription = "Back")
BackButton(onClick = { }, enabled = false, contentDescription = "Back")

View file

@ -33,8 +33,8 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ElementButtonDefaults
@ -93,16 +93,9 @@ fun ButtonWithProgress(
}
}
@Preview
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun ButtonWithProgressLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun ButtonWithProgressDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
internal fun ButtonWithProgressPreview() = ElementThemedPreview {
ButtonWithProgress(
text = "Button with progress",
onClick = {},

View file

@ -0,0 +1,95 @@
/*
* 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.components.button
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
@Composable
fun MainActionButton(
title: String,
icon: ImageVector,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentDescription: String = title,
) {
val ripple = rememberRipple(bounded = false)
val interactionSource = MutableInteractionSource()
Column(
modifier.clickable(
enabled = enabled,
interactionSource = interactionSource,
onClick = onClick,
indication = ripple
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val tintColor = if (enabled) LocalContentColor.current else MaterialTheme.colorScheme.secondary
Icon(
icon,
contentDescription = contentDescription,
tint = tintColor,
)
Spacer(modifier = Modifier.height(14.dp))
Text(
title,
style = ElementTextStyles.Regular.bodyMD.copy(fontWeight = FontWeight.Medium),
color = tintColor,
)
}
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun MainActionButtonPreview() {
ElementThemedPreview {
ContentsToPreview()
}
}
@Composable
private fun ContentsToPreview() {
Row(Modifier.padding(10.dp)) {
MainActionButton(title = "Share", icon = Icons.Outlined.Share, onClick = { })
Spacer(modifier = Modifier.width(20.dp))
MainActionButton(title = "Share", icon = Icons.Outlined.Share, onClick = { }, enabled = false)
}
}

View file

@ -0,0 +1,320 @@
/*
* 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.components.dialogs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.theme.components.Text
import kotlin.math.max
@Composable
internal fun SimpleAlertDialogContent(
content: String,
cancelText: String,
onCancelClicked: () -> Unit,
modifier: Modifier = Modifier,
title: String? = null,
submitText: String? = null,
onSubmitClicked: () -> Unit = {},
thirdButtonText: String? = null,
onThirdButtonClicked: () -> Unit = {},
emphasizeSubmitButton: Boolean = false,
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
icon: @Composable (() -> Unit)? = null,
) {
AlertDialogContent(
buttons = {
AlertDialogFlowRow(
mainAxisSpacing = ButtonsMainAxisSpacing,
crossAxisSpacing = ButtonsCrossAxisSpacing
) {
if (thirdButtonText != null) {
// If there is a 3rd item it should be at the end of the dialog
// Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f
TextButton(onClick = onThirdButtonClicked) {
Text(thirdButtonText)
}
}
TextButton(onClick = onCancelClicked) {
Text(cancelText)
}
if (submitText != null) {
TextButton(
onClick = {
onSubmitClicked()
},
) {
Text(
submitText,
style = if (emphasizeSubmitButton) {
ElementTextStyles.Bold.subheadline
} else {
MaterialTheme.typography.labelLarge
}
)
}
}
}
},
modifier = modifier,
title = {
if (title != null) { Text(text = title) }
},
text = {
Text(content)
},
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
icon = icon,
// Note that a button content color is provided here from the dialog's token, but in
// most cases, TextButtons should be used for dismiss and confirm buttons.
// TextButtons will not consume this provided content color value, and will used their
// own defined or default colors.
buttonContentColor = MaterialTheme.colorScheme.primary,
)
}
@Composable
internal fun AlertDialogContent(
buttons: @Composable () -> Unit,
icon: (@Composable () -> Unit)?,
title: (@Composable () -> Unit)?,
text: @Composable (() -> Unit)?,
shape: Shape,
containerColor: Color,
tonalElevation: Dp,
buttonContentColor: Color,
iconContentColor: Color,
titleContentColor: Color,
textContentColor: Color,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier,
shape = shape,
color = containerColor,
tonalElevation = tonalElevation,
) {
Column(
modifier = Modifier.padding(DialogPadding)
) {
icon?.let {
CompositionLocalProvider(LocalContentColor provides iconContentColor) {
Box(
Modifier
.padding(IconPadding)
.align(Alignment.CenterHorizontally)
) {
icon()
}
}
}
title?.let {
CompositionLocalProvider(LocalContentColor provides titleContentColor) {
val textStyle = MaterialTheme.typography.headlineSmall
ProvideTextStyle(textStyle) {
Box(
// Align the title to the center when an icon is present.
Modifier
.padding(TitlePadding)
.align(
if (icon == null) {
Alignment.Start
} else {
Alignment.CenterHorizontally
}
)
) {
title()
}
}
}
}
text?.let {
CompositionLocalProvider(LocalContentColor provides textContentColor) {
val textStyle =
MaterialTheme.typography.bodyMedium
ProvideTextStyle(textStyle) {
Box(
Modifier
.weight(weight = 1f, fill = false)
.padding(TextPadding)
.align(Alignment.Start)
) {
text()
}
}
}
}
Box(modifier = Modifier.align(Alignment.End)) {
CompositionLocalProvider(LocalContentColor provides buttonContentColor) {
val textStyle =
MaterialTheme.typography.labelLarge
ProvideTextStyle(value = textStyle, content = buttons)
}
}
}
}
}
/**
* Simple clone of FlowRow that arranges its children in a horizontal flow with limited
* customization.
*/
@Composable
internal fun AlertDialogFlowRow(
mainAxisSpacing: Dp,
crossAxisSpacing: Dp,
content: @Composable () -> Unit
) {
Layout(content) { measurables, constraints ->
val sequences = mutableListOf<List<Placeable>>()
val crossAxisSizes = mutableListOf<Int>()
val crossAxisPositions = mutableListOf<Int>()
var mainAxisSpace = 0
var crossAxisSpace = 0
val currentSequence = mutableListOf<Placeable>()
var currentMainAxisSize = 0
var currentCrossAxisSize = 0
// Return whether the placeable can be added to the current sequence.
fun canAddToCurrentSequence(placeable: Placeable) =
currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() +
placeable.width <= constraints.maxWidth
// Store current sequence information and start a new sequence.
fun startNewSequence() {
if (sequences.isNotEmpty()) {
crossAxisSpace += crossAxisSpacing.roundToPx()
}
sequences += currentSequence.toList()
crossAxisSizes += currentCrossAxisSize
crossAxisPositions += crossAxisSpace
crossAxisSpace += currentCrossAxisSize
mainAxisSpace = max(mainAxisSpace, currentMainAxisSize)
currentSequence.clear()
currentMainAxisSize = 0
currentCrossAxisSize = 0
}
for (measurable in measurables) {
// Ask the child for its preferred size.
val placeable = measurable.measure(constraints)
// Start a new sequence if there is not enough space.
if (!canAddToCurrentSequence(placeable)) startNewSequence()
// Add the child to the current sequence.
if (currentSequence.isNotEmpty()) {
currentMainAxisSize += mainAxisSpacing.roundToPx()
}
currentSequence.add(placeable)
currentMainAxisSize += placeable.width
currentCrossAxisSize = max(currentCrossAxisSize, placeable.height)
}
if (currentSequence.isNotEmpty()) startNewSequence()
val mainAxisLayoutSize = max(mainAxisSpace, constraints.minWidth)
val crossAxisLayoutSize = max(crossAxisSpace, constraints.minHeight)
val layoutWidth = mainAxisLayoutSize
val layoutHeight = crossAxisLayoutSize
layout(layoutWidth, layoutHeight) {
sequences.forEachIndexed { i, placeables ->
val childrenMainAxisSizes = IntArray(placeables.size) { j ->
placeables[j].width +
if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
}
val arrangement = Arrangement.Bottom
// TODO(soboleva): rtl support
// Handle vertical direction
val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
with(arrangement) {
arrange(mainAxisLayoutSize, childrenMainAxisSizes, mainAxisPositions)
}
placeables.forEachIndexed { j, placeable ->
placeable.place(
x = mainAxisPositions[j],
y = crossAxisPositions[i]
)
}
}
}
}
}
@Composable
internal fun DialogPreview(content: @Composable () -> Unit) {
Box(
modifier = Modifier
.sizeIn(minWidth = DialogMinWidth, maxWidth = DialogMaxWidth)
.padding(20.dp),
propagateMinConstraints = true
) {
content()
}
}
// Paddings for each of the dialog's parts.
private val DialogPadding = PaddingValues(all = 24.dp)
private val IconPadding = PaddingValues(bottom = 16.dp)
private val TitlePadding = PaddingValues(bottom = 16.dp)
private val TextPadding = PaddingValues(bottom = 24.dp)
internal val ButtonsMainAxisSpacing = 8.dp
internal val ButtonsCrossAxisSpacing = 12.dp
internal val DialogMinWidth = 280.dp
internal val DialogMaxWidth = 560.dp

View file

@ -18,8 +18,8 @@ package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -28,13 +28,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.BooleanProvider
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConfirmationDialog(
content: String,
@ -56,70 +55,82 @@ fun ConfirmationDialog(
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
AlertDialog(
AlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
ConfirmationDialogContent(
title = title,
content = content,
submitText = submitText,
cancelText = cancelText,
thirdButtonText = thirdButtonText,
onSubmitClicked = onSubmitClicked,
onCancelClicked = onCancelClicked,
onThirdButtonClicked = onThirdButtonClicked,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
emphasizeSubmitButton = emphasizeSubmitButton,
)
}
}
@Composable
private fun ConfirmationDialogContent(
content: String,
submitText: String,
cancelText: String,
onSubmitClicked: () -> Unit,
onCancelClicked: () -> Unit,
modifier: Modifier = Modifier,
title: String? = null,
thirdButtonText: String? = null,
onThirdButtonClicked: () -> Unit = {},
emphasizeSubmitButton: Boolean = false,
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
icon: @Composable (() -> Unit)? = null,
) {
SimpleAlertDialogContent(
modifier = modifier,
onDismissRequest = onDismiss,
title = {
if (title != null) { Text(text = title) }
},
text = {
Text(content)
},
dismissButton = {
if (thirdButtonText != null) {
// If there is a 3rd item it should be at the end of the dialog
// Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f
TextButton(onClick = onThirdButtonClicked) {
Text(thirdButtonText)
}
}
TextButton(onClick = onCancelClicked) {
Text(cancelText)
}
},
confirmButton = {
TextButton(
onClick = {
onSubmitClicked()
},
) {
Text(
submitText,
style = if (emphasizeSubmitButton) {
ElementTextStyles.Bold.subheadline
} else {
MaterialTheme.typography.labelLarge
}
)
}
},
title = title,
content = content,
submitText = submitText,
onSubmitClicked = onSubmitClicked,
cancelText = cancelText,
onCancelClicked = onCancelClicked,
thirdButtonText = thirdButtonText,
onThirdButtonClicked = onThirdButtonClicked,
emphasizeSubmitButton = emphasizeSubmitButton,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
icon = icon,
)
}
@Preview
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun ConfirmationDialogLightPreview(@PreviewParameter(BooleanProvider::class) emphasizeSubmitButton: Boolean) =
ElementPreviewLight { ContentToPreview(emphasizeSubmitButton) }
@Preview
@Composable
internal fun ConfirmationDialogDarkPreview(@PreviewParameter(BooleanProvider::class) emphasizeSubmitButton: Boolean) =
ElementPreviewDark { ContentToPreview(emphasizeSubmitButton) }
@Composable
private fun ContentToPreview(emphasizeSubmitButton: Boolean) {
ConfirmationDialog(
title = "Title",
content = "Content",
thirdButtonText = "Disable",
onSubmitClicked = {},
onDismiss = {},
emphasizeSubmitButton = emphasizeSubmitButton,
)
}
internal fun ConfirmationDialogPreview(@PreviewParameter(BooleanProvider::class) emphasizeSubmitButton: Boolean) =
ElementThemedPreview {
DialogPreview {
ConfirmationDialogContent(
content = "Content",
title = "Title",
submitText = "OK",
cancelText = "Cancel",
thirdButtonText = "Disable",
onSubmitClicked = {},
onCancelClicked = {},
emphasizeSubmitButton = emphasizeSubmitButton,
)
}
}

View file

@ -18,7 +18,7 @@ package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.TextButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -26,11 +26,11 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ErrorDialog(
content: String,
@ -45,20 +45,42 @@ fun ErrorDialog(
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
AlertDialog(
AlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
ErrorDialogContent(
title = title,
content = content,
submitText = submitText,
onSubmitText = onDismiss,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
}
@Composable
private fun ErrorDialogContent(
content: String,
modifier: Modifier = Modifier,
title: String = ErrorDialogDefaults.title,
submitText: String = ErrorDialogDefaults.submitText,
onSubmitText: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
SimpleAlertDialogContent(
modifier = modifier,
onDismissRequest = onDismiss,
title = {
Text(title)
},
text = {
Text(content)
},
confirmButton = {
TextButton(onClick = onDismiss) {
Text(submitText)
}
},
title = title,
content = content,
cancelText = submitText,
onCancelClicked = onSubmitText,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
@ -73,17 +95,14 @@ object ErrorDialogDefaults {
val submitText: String @Composable get() = stringResource(id = StringR.string.action_ok)
}
@Preview
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun ErrorDialogLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun ErrorDialogDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
ErrorDialog(
content = "Content",
)
internal fun ErrorDialogPreview() {
ElementThemedPreview {
DialogPreview {
ErrorDialogContent(
content = "Content",
)
}
}
}

View file

@ -26,8 +26,8 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.R as StringR
@ -75,23 +75,54 @@ fun RetryDialog(
)
}
@Composable
private fun RetryDialogContent(
content: String,
modifier: Modifier = Modifier,
title: String = RetryDialogDefaults.title,
retryText: String = RetryDialogDefaults.retryText,
dismissText: String = RetryDialogDefaults.dismissText,
onRetry: () -> Unit = {},
onDismiss: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
SimpleAlertDialogContent(
modifier = modifier,
title = title,
content = content,
submitText = retryText,
onSubmitClicked = onRetry,
cancelText = dismissText,
onCancelClicked = onDismiss,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
object RetryDialogDefaults {
val title: String @Composable get() = stringResource(id = StringR.string.dialog_title_error)
val retryText: String @Composable get() = stringResource(id = StringR.string.action_retry)
val dismissText: String @Composable get() = stringResource(id = StringR.string.action_cancel)
}
@Preview
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun RetryDialogLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun RetryDialogDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun RetryDialogPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
RetryDialog(
content = "Content",
)
DialogPreview {
RetryDialogContent(
content = "Content",
)
}
}

View file

@ -28,9 +28,8 @@ import androidx.compose.runtime.Composable
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.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Text
@Composable
@ -64,13 +63,9 @@ fun PreferenceCategoryTitle(title: String, modifier: Modifier = Modifier) {
)
}
@Preview
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceCategoryLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun PreferenceCategoryDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun PreferenceCategoryPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -16,12 +16,31 @@
package io.element.android.libraries.designsystem.components.preferences
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.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
import io.element.android.libraries.designsystem.theme.components.Divider
@Composable
fun PreferenceDivider(modifier: Modifier = Modifier) {
Divider(modifier, thickness = 0.5.dp)
}
@Preview(group = PreviewGroup.Dividers)
@Composable
internal fun PreferenceDividerPreview() {
ElementThemedPreview {
Box(Modifier.padding(vertical = 10.dp), contentAlignment = Alignment.Center) {
PreferenceDivider()
}
}
}

View file

@ -31,8 +31,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Slider
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.toEnabledColor
@ -87,13 +87,9 @@ fun PreferenceSlide(
}
}
@Preview
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceSlideLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun PreferenceSlideDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun PreferenceSlidePreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -31,8 +31,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.toEnabledColor
@ -60,13 +60,17 @@ fun PreferenceSwitch(
enabled = enabled
)
Text(
modifier = Modifier.weight(1f).padding(vertical = preferencePaddingVertical),
modifier = Modifier
.weight(1f)
.padding(vertical = preferencePaddingVertical),
style = MaterialTheme.typography.bodyLarge,
color = enabled.toEnabledColor(),
text = title
)
Checkbox(
modifier = Modifier.padding(end = preferencePaddingHorizontal).align(Alignment.CenterVertically),
modifier = Modifier
.padding(end = preferencePaddingHorizontal)
.align(Alignment.CenterVertically),
checked = isChecked,
enabled = enabled,
onCheckedChange = onCheckedChange
@ -75,13 +79,9 @@ fun PreferenceSwitch(
}
}
@Preview
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceSwitchLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun PreferenceSwitchDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun PreferenceSwitchPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -39,8 +39,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
@ -95,7 +95,9 @@ fun PreferenceText(
Text(currentValue, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary)
Spacer(Modifier.width(16.dp))
} else if (loadingCurrentValue) {
CircularProgressIndicator(modifier = Modifier.progressSemantics().size(20.dp), strokeWidth = 2.dp)
CircularProgressIndicator(modifier = Modifier
.progressSemantics()
.size(20.dp), strokeWidth = 2.dp)
Spacer(Modifier.width(16.dp))
}
@ -103,13 +105,9 @@ fun PreferenceText(
}
}
@Preview
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceTextLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun PreferenceTextDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun PreferenceTextPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -24,7 +24,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class ImageVectorProvider : PreviewParameterProvider<ImageVector?> {
override val values: Sequence<ImageVector?>
get() = sequenceOf(
null,
Icons.Default.BugReport,
null,
)
}

View file

@ -27,8 +27,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
@ -57,15 +57,10 @@ fun PreferenceIcon(
}
}
@Preview
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceIconLightPreview(@PreviewParameter(ImageVectorProvider::class) content: ImageVector?) =
ElementPreviewLight { ContentToPreview(content) }
@Preview
@Composable
internal fun PreferenceIconDarkPreview(@PreviewParameter(ImageVectorProvider::class) content: ImageVector?) =
ElementPreviewDark { ContentToPreview(content) }
internal fun PreferenceIconPreview(@PreviewParameter(ImageVectorProvider::class) content: ImageVector?) =
ElementThemedPreview { ContentToPreview(content) }
@Composable
private fun ContentToPreview(content: ImageVector?) {

View file

@ -16,7 +16,18 @@
package io.element.android.libraries.designsystem.preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.Surface
@ -45,6 +56,45 @@ fun ElementPreviewDark(
}
@Composable
@Suppress("ModifierMissing")
fun ElementThemedPreview(
showBackground: Boolean = true,
vertical: Boolean = true,
content: @Composable () -> Unit,
) {
Box(modifier = Modifier
.background(Color.Gray)
.padding(4.dp)) {
if (vertical) {
Column {
ElementPreviewLight(
showBackground = showBackground,
content = content,
)
Spacer(modifier = Modifier.height(4.dp))
ElementPreviewDark(
showBackground = showBackground,
content = content
)
}
} else {
Row {
ElementPreviewLight(
showBackground = showBackground,
content = content,
)
Spacer(modifier = Modifier.width(4.dp))
ElementPreviewDark(
showBackground = showBackground,
content = content
)
}
}
}
}
@Composable
@Suppress("ModifierMissing")
private fun ElementPreview(
darkTheme: Boolean,
showBackground: Boolean,

View file

@ -0,0 +1,26 @@
/*
* 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.preview
import androidx.compose.ui.tooling.preview.Preview
/**
* Our Paparazzi tests will check components with non-null `heightDp` and use a custom rendering for them,
* adding extra vertical space so long scrolling components can be displayed. This is a helper for that functionality.
*/
@Preview(heightDp = 1000)
annotation class LargeHeightPreview

View file

@ -0,0 +1,35 @@
/*
* 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.preview
object PreviewGroup {
const val AppBars = "App Bars"
const val Avatars = "Avatars"
const val BottomSheets = "Bottom Sheets"
const val Buttons = "Buttons"
const val Dialogs = "Dialogs"
const val Dividers = "Dividers"
const val FABs = "Floating Action Buttons"
const val Icons = "Icons"
const val Preferences = "Preferences"
const val Progress = "Progress Indicators"
const val Search = "Search views"
const val Sliders = "Sliders"
const val Text = "Text"
const val TextFields = "TextFields"
const val Toggles = "Toggles"
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2022 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.showkase
import com.airbnb.android.showkase.annotation.ShowkaseRoot
import com.airbnb.android.showkase.annotation.ShowkaseRootModule
@ShowkaseRoot
class DesignSystemShowkaseRootModule : ShowkaseRootModule

View file

@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun Button(
@ -71,16 +71,9 @@ object ElementButtonDefaults {
}
@Preview
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun ButtonsLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun ButtonsDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
internal fun ButtonPreview() = ElementThemedPreview {
Column {
Button(onClick = {}, enabled = true) {
Text(text = "Click me! - Enabled")

View file

@ -27,8 +27,8 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -52,16 +52,12 @@ fun CenterAlignedTopAppBar(
)
}
@Preview
@Preview(group = PreviewGroup.AppBars)
@Composable
internal fun CenterAlignedTopAppBarLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun CenterAlignedTopAppBarDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun CenterAlignedTopAppBarPreview() =
ElementThemedPreview { ContentToPreview() }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ContentToPreview() {
CenterAlignedTopAppBar(title = { Text(text = "Title") })

View file

@ -24,8 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun Checkbox(
@ -46,13 +46,9 @@ fun Checkbox(
)
}
@Preview
@Preview(group = PreviewGroup.Toggles)
@Composable
internal fun CheckboxesLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun CheckboxesDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun CheckboxesPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -25,8 +25,8 @@ import androidx.compose.ui.graphics.Color
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.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun CircularProgressIndicator(
@ -56,13 +56,9 @@ fun CircularProgressIndicator(
)
}
@Preview
@Preview(group = PreviewGroup.Progress)
@Composable
internal fun CircularProgressIndicatorLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun CircularProgressIndicatorDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun CircularProgressIndicatorPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -16,14 +16,19 @@
package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DividerDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun Divider(
@ -38,13 +43,13 @@ fun Divider(
)
}
@Preview
@Preview(group = PreviewGroup.Dividers)
@Composable
internal fun DividerLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun DividerDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun DividerPreview() = ElementThemedPreview {
Box(Modifier.padding(vertical = 10.dp), contentAlignment = Alignment.Center) {
ContentToPreview()
}
}
@Composable
private fun ContentToPreview() {

View file

@ -31,8 +31,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun FloatingActionButton(
@ -57,15 +57,10 @@ fun FloatingActionButton(
)
}
@Preview
@Preview(group = PreviewGroup.FABs)
@Composable
internal fun FloatingActionButtonLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun FloatingActionButtonDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun FloatingActionButtonPreview() =
ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -27,8 +27,8 @@ import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun Icon(
@ -75,15 +75,10 @@ fun Icon(
)
}
@Preview
@Preview(group = PreviewGroup.Icons)
@Composable
internal fun IconImageVectorLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun IconImageVectorDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun IconImageVectorPreview() =
ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -24,8 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun IconButton(
@ -45,15 +45,10 @@ fun IconButton(
)
}
@Preview
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun IconButtonLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun IconButtonDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun IconButtonPreview() =
ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -27,8 +27,8 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -52,15 +52,10 @@ fun MediumTopAppBar(
)
}
@Preview
@Preview(group = PreviewGroup.AppBars)
@Composable
internal fun MediumTopAppBarLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun MediumTopAppBarDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun MediumTopAppBarPreview() =
ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -19,7 +19,17 @@
package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetDefaults
import androidx.compose.material.ModalBottomSheetState
@ -28,13 +38,17 @@ import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
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.modifiers.applyIf
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewGroup
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -42,15 +56,36 @@ fun ModalBottomSheetLayout(
sheetContent: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
sheetState: ModalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
sheetShape: Shape = MaterialTheme.shapes.large,
sheetShape: Shape = MaterialTheme.shapes.large.copy(bottomStart = CornerSize(0.dp), bottomEnd = CornerSize(0.dp)),
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
displayHandle: Boolean = false,
useSystemPadding: Boolean = true,
content: @Composable () -> Unit = {}
) {
androidx.compose.material.ModalBottomSheetLayout(
sheetContent = sheetContent,
sheetContent = {
Column(
Modifier.fillMaxWidth()
.applyIf(useSystemPadding, ifTrue = {
navigationBarsPadding()
})
) {
if (displayHandle) {
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.onSurfaceVariant, RoundedCornerShape(2.dp))
.size(width = 32.dp, height = 4.dp)
.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(24.dp))
}
sheetContent()
}
},
modifier = modifier,
sheetState = sheetState,
sheetShape = sheetShape,
@ -62,12 +97,12 @@ fun ModalBottomSheetLayout(
)
}
@Preview
@Preview(group = PreviewGroup.BottomSheets)
@Composable
internal fun ModalBottomSheetLayoutLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Preview(group = PreviewGroup.BottomSheets)
@Composable
internal fun ModalBottomSheetLayoutDarkPreview() =
ElementPreviewDark { ContentToPreview() }
@ -75,9 +110,13 @@ internal fun ModalBottomSheetLayoutDarkPreview() =
@Composable
private fun ContentToPreview() {
ModalBottomSheetLayout(
modifier = Modifier.height(140.dp),
displayHandle = true,
sheetState = ModalBottomSheetState(ModalBottomSheetValue.Expanded),
sheetContent = {
Text(text = "Sheet Content", modifier = Modifier.background(color = Color.Green))
Text(text = "Sheet Content", modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 20.dp)
.background(color = Color.Green))
}
) {
Text(text = "Content", modifier = Modifier.background(color = Color.Red))

View file

@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun OutlinedButton(
@ -73,13 +73,9 @@ object ElementOutlinedButtonDefaults {
}
@Preview
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun OutlinedButtonsLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun OutlinedButtonsDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun OutlinedButtonsPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -39,13 +39,13 @@ import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.allBooleans
import io.element.android.libraries.designsystem.utils.asInt
@ -109,11 +109,11 @@ fun Modifier.onTabOrEnterKeyFocusNext(focusManager: FocusManager): Modifier = on
}
}
@Preview
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun OutlinedTextFieldsLightPreview() = ElementPreviewLight { ContentToPreview() }
internal fun OutlinedTextFieldsPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun OutlinedTextFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() }

View file

@ -24,8 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun RadioButton(
@ -46,13 +46,9 @@ fun RadioButton(
)
}
@Preview
@Preview(group = PreviewGroup.Toggles)
@Composable
internal fun RadioButtonLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun RadioButtonDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun RadioButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -73,13 +73,9 @@ fun SearchBar(
)
}
@Preview
@Preview(group = PreviewGroup.Search)
@Composable
internal fun DockedSearchBarLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun DockedSearchBarDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun DockedSearchBarPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -24,8 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun Slider(
@ -53,13 +53,9 @@ fun Slider(
)
}
@Preview
@Preview(group = PreviewGroup.Sliders)
@Composable
internal fun SlidersLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun SlidersDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun SlidersPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -31,6 +31,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
@Composable
fun Surface(
@ -57,13 +58,8 @@ fun Surface(
@Preview
@Composable
internal fun SurfaceLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun SurfaceDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun SurfacePreview() =
ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -46,6 +46,7 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.toHrf
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
@ -130,11 +131,11 @@ fun Text(
)
}
@Preview
@Preview(group = PreviewGroup.Text)
@Composable
internal fun TextLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Preview(group = PreviewGroup.Text)
@Composable
internal fun TextDarkPreview() = ElementPreviewDark { ContentToPreview() }

View file

@ -29,8 +29,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun TextButton(
@ -59,13 +59,9 @@ fun TextButton(
)
}
@Preview
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun TextButtonLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun TextButtonDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun TextButtonPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {

View file

@ -46,6 +46,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.allBooleans
import io.element.android.libraries.designsystem.utils.asInt
@ -97,12 +98,12 @@ fun TextField(
)
}
@Preview
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun TextFieldLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Preview(group = PreviewGroup.TextFields)
@Composable
internal fun TextFieldDarkPreview() =
ElementPreviewDark { ContentToPreview() }

View file

@ -27,8 +27,8 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -52,16 +52,12 @@ fun TopAppBar(
)
}
@Preview
@Preview(group = PreviewGroup.AppBars)
@Composable
internal fun TopAppBarLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun TopAppBarDarkPreview() =
ElementPreviewDark { ContentToPreview() }
internal fun TopAppBarPreview() =
ElementThemedPreview { ContentToPreview() }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ContentToPreview() {
TopAppBar(title = { Text(text = "Title") })

View file

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

View file

@ -26,6 +26,8 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import java.io.Closeable
@ -40,6 +42,7 @@ interface MatrixClient : Closeable {
suspend fun unignoreUser(userId: UserId): Result<Unit>
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
suspend fun createDM(userId: UserId): Result<RoomId>
suspend fun getProfile(userId: UserId): Result<MatrixUser>
fun startSync()
fun stopSync()
fun sessionVerificationService(): SessionVerificationService
@ -52,4 +55,6 @@ interface MatrixClient : Closeable {
fun onSlidingSyncUpdate()
fun roomMembershipObserver(): RoomMembershipObserver
suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults>
}

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
data class RoomMember(
val userId: UserId,
@ -29,6 +30,12 @@ data class RoomMember(
val isIgnored: Boolean,
)
fun RoomMember.toMatrixUser() = MatrixUser(
userId = userId,
displayName = displayName,
avatarUrl = avatarUrl,
)
enum class RoomMembershipState {
BAN, INVITE, JOIN, KNOCK, LEAVE
}

View file

@ -0,0 +1,22 @@
/*
* 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.matrix.api.user
data class MatrixSearchUserResults(
val results: List<MatrixUser>,
val limited: Boolean,
)

View file

@ -0,0 +1,28 @@
/*
* 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.matrix.api.user
import android.os.Parcelable
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
@Parcelize
data class MatrixUser(
val userId: UserId,
val displayName: String? = null,
val avatarUrl: String? = null
) : Parcelable

View file

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

View file

@ -29,6 +29,8 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
@ -36,9 +38,12 @@ import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
@ -268,6 +273,12 @@ class RustMatrixClient constructor(
return createRoom(createRoomParams)
}
override suspend fun getProfile(userId: UserId): Result<MatrixUser> = withContext(Dispatchers.IO) {
runCatching {
client.getProfile(userId.value).let(UserProfileMapper::map)
}
}
override fun sessionVerificationService(): SessionVerificationService = verificationService
override fun pushersService(): PushersService = pushersService
@ -334,6 +345,13 @@ class RustMatrixClient constructor(
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> =
withContext(dispatchers.io) {
runCatching {
client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map)
}
}
private fun File.deleteSessionDirectory(userID: String): Boolean {
// Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_")

View file

@ -0,0 +1,30 @@
/*
* 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.matrix.impl.usersearch
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import org.matrix.rustcomponents.sdk.UserProfile
object UserProfileMapper {
fun map(userProfile: UserProfile): MatrixUser =
MatrixUser(
userId = UserId(userProfile.userId),
displayName = userProfile.displayName,
avatarUrl = userProfile.avatarUrl,
)
}

View file

@ -0,0 +1,30 @@
/*
* 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.matrix.impl.usersearch
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import org.matrix.rustcomponents.sdk.SearchUsersResults
object UserSearchResultMapper {
fun map(result: SearchUsersResults): MatrixSearchUserResults {
return MatrixSearchUserResults(
results = result.results.map(UserProfileMapper::map),
limited = result.limited,
)
}
}

View file

@ -27,6 +27,8 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
@ -56,6 +58,8 @@ class FakeMatrixClient(
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
private var logoutFailure: Throwable? = null
private val getRoomResults = mutableMapOf<RoomId, MatrixRoom>()
private val searchUserResults = mutableMapOf<String, Result<MatrixSearchUserResults>>()
private val getProfileResults = mutableMapOf<UserId, Result<MatrixUser>>()
override fun getRoom(roomId: RoomId): MatrixRoom? {
return getRoomResults[roomId]
@ -84,6 +88,10 @@ class FakeMatrixClient(
return createDmResult
}
override suspend fun getProfile(userId: UserId): Result<MatrixUser> {
return getProfileResults[userId] ?: Result.failure(IllegalStateException("No profile found for $userId"))
}
override fun startSync() = Unit
override fun stopSync() = Unit
@ -115,6 +123,10 @@ class FakeMatrixClient(
return RoomMembershipObserver()
}
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> {
return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm"))
}
// Mocks
fun givenLogoutError(failure: Throwable?) {
@ -148,4 +160,12 @@ class FakeMatrixClient(
fun givenGetRoomResult(roomId: RoomId, result: MatrixRoom) {
getRoomResults[roomId] = result
}
fun givenSearchUsersResult(searchTerm: String, result: Result<MatrixSearchUserResults>) {
searchUserResults[searchTerm] = result
}
fun givenGetProfileResult(userId: UserId, result: Result<MatrixUser>) {
getProfileResults[userId] = result
}
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2022 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.matrix.ui
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import javax.inject.Inject
class MatrixItemHelper @Inject constructor(
private val client: MatrixClient
) {
/**
* TODO Make username and avatar live...
*/
@OptIn(FlowPreview::class)
fun getCurrentUserData(avatarSize: AvatarSize): Flow<MatrixUser> {
return suspend {
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData =
AvatarData(
client.sessionId.value,
userDisplayName,
userAvatarUrl,
avatarSize
)
MatrixUser(
id = client.sessionId,
username = userDisplayName,
avatarData = avatarData,
)
}.asFlow()
}
}

View file

@ -30,14 +30,14 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
@Composable
fun CheckableMatrixUserRow(
checked: Boolean,
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
avatarSize: AvatarSize = matrixUser.avatarData.size,
avatarSize: AvatarSize = AvatarSize.MEDIUM,
onCheckedChange: (Boolean) -> Unit = {},
enabled: Boolean = true,
) {

View file

@ -38,7 +38,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
@Composable
@ -56,7 +57,7 @@ fun MatrixUserHeader(
horizontalAlignment = Alignment.CenterHorizontally
) {
Avatar(
matrixUser.avatarData.copy(size = AvatarSize.HUGE),
matrixUser.getAvatarData(size = AvatarSize.HUGE),
)
Spacer(modifier = Modifier.height(16.dp))
// Name
@ -69,10 +70,10 @@ fun MatrixUserHeader(
color = MaterialTheme.colorScheme.primary,
)
// Id
if (matrixUser.username.isNullOrEmpty().not()) {
if (matrixUser.displayName.isNullOrEmpty().not()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = matrixUser.id.value,
text = matrixUser.userId.value,
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,

View file

@ -17,25 +17,20 @@
package io.element.android.libraries.matrix.ui.components
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
override val values: Sequence<MatrixUser>
get() = sequenceOf(
aMatrixUser(),
aMatrixUser().copy(
username = null,
avatarData = anAvatarData().copy(name = null)
),
aMatrixUser().copy(displayName = null),
)
}
fun aMatrixUser(id: String = "@id_of_alice:server.org", userName: String = "Alice") = MatrixUser(
id = UserId(id),
username = userName,
avatarData = anAvatarData(id, userName)
fun aMatrixUser(id: String = "@id_of_alice:server.org", displayName: String = "Alice") = MatrixUser(
userId = UserId(id),
displayName = displayName,
)
fun aMatrixUserList() = listOf(
@ -55,10 +50,7 @@ open class MatrixUserWithNullProvider : PreviewParameterProvider<MatrixUser?> {
override val values: Sequence<MatrixUser?>
get() = sequenceOf(
aMatrixUser(),
aMatrixUser().copy(
username = null,
avatarData = anAvatarData().copy(name = null)
),
aMatrixUser().copy(displayName = null),
null,
)
}

View file

@ -37,14 +37,15 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
@Composable
fun MatrixUserRow(
matrixUser: MatrixUser,
modifier: Modifier = Modifier,
avatarSize: AvatarSize = matrixUser.avatarData.size,
avatarSize: AvatarSize = AvatarSize.MEDIUM,
) {
Row(
modifier = modifier
@ -54,7 +55,7 @@ fun MatrixUserRow(
verticalAlignment = Alignment.CenterVertically
) {
Avatar(
matrixUser.avatarData.copy(size = avatarSize),
matrixUser.getAvatarData(size = avatarSize),
)
Column(
modifier = Modifier
@ -70,9 +71,9 @@ fun MatrixUserRow(
color = MaterialTheme.colorScheme.primary,
)
// Id
if (matrixUser.username.isNullOrEmpty().not()) {
if (matrixUser.displayName.isNullOrEmpty().not()) {
Text(
text = matrixUser.id.value,
text = matrixUser.userId.value,
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,

View file

@ -16,20 +16,17 @@
package io.element.android.libraries.matrix.ui.model
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.user.MatrixUser
@Parcelize
@Immutable
data class MatrixUser(
val id: UserId,
val username: String? = null,
val avatarData: AvatarData = AvatarData(id.value, username),
) : Parcelable
fun MatrixUser.getAvatarData(size: AvatarSize = AvatarSize.MEDIUM) = AvatarData(
id = userId.value,
name = displayName,
url = avatarUrl,
size = size,
)
fun MatrixUser.getBestName(): String {
return username?.takeIf { it.isNotEmpty() } ?: id.value
return displayName?.takeIf { it.isNotEmpty() } ?: userId.value
}

View file

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

View file

@ -7,6 +7,7 @@
<string name="notification_room_action_mark_as_read">"Als gelesen markieren"</string>
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
<string name="notification_unread_notified_messages_and_invitation">"%1$s und %2$s"</string>
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$s in %2$s und %3$s"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s: %2$d Nachricht"</item>

View file

@ -18,32 +18,26 @@
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$s în %2$s și %3$s"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s: %2$d mesaj"</item>
<item quantity="few"></item>
<item quantity="other">"%1$s: %2$d mesaje"</item>
</plurals>
<plurals name="notification_compat_summary_title">
<item quantity="one">"%d notificare"</item>
<item quantity="few"></item>
<item quantity="other">"%d notificări"</item>
</plurals>
<plurals name="notification_invitations">
<item quantity="one">"%d invitație"</item>
<item quantity="few"></item>
<item quantity="other">"%d invitații"</item>
</plurals>
<plurals name="notification_new_messages_for_room">
<item quantity="one">"%d mesaj nou"</item>
<item quantity="few"></item>
<item quantity="other">"%d mesaje noi"</item>
</plurals>
<plurals name="notification_unread_notified_messages">
<item quantity="one">"%d mesaj notificat necitit"</item>
<item quantity="few"></item>
<item quantity="other">"%d mesaje notificate necitite"</item>
</plurals>
<plurals name="notification_unread_notified_messages_in_room_rooms">
<item quantity="one">"%d cameră"</item>
<item quantity="few"></item>
<item quantity="other">"%d camere"</item>
</plurals>
<string name="push_choose_distributor_dialog_title_android">"Alegeți modul de primire a notificărilor"</string>
@ -51,4 +45,4 @@
<string name="push_distributor_firebase_android">"Servicii Google"</string>
<string name="push_no_valid_google_play_services_apk_android">"Nu au fost găsite servicii Google Play valide. Este posibil ca notificările să nu funcționeze corect."</string>
<string name="notification_room_action_quick_reply">"Raspuns rapid"</string>
</resources>
</resources>

View file

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

View file

@ -31,17 +31,19 @@ class UnifiedPushGatewayResolver @Inject constructor(
suspend fun getGateway(endpoint: String): String? {
val gateway = UnifiedPushConfig.default_push_gateway_http_url
val url = URL(endpoint)
val custom = "${url.protocol}://${url.host}/_matrix/push/v1/notify"
Timber.i("Testing $custom")
val port = if (url.port != -1) { ":${url.port}" } else { "" }
val customBase = "${url.protocol}://${url.host}${port}"
val customUrl = "$customBase/_matrix/push/v1/notify"
Timber.i("Testing $customUrl")
try {
return withContext(coroutineDispatchers.io) {
val api = retrofitFactory.create("${url.protocol}://${url.host}")
val api = retrofitFactory.create(customBase)
.create(UnifiedPushApi::class.java)
try {
val discoveryResponse = api.discover()
if (discoveryResponse.unifiedpush.gateway == "matrix") {
Timber.d("Using custom gateway")
return@withContext custom
return@withContext customUrl
}
} catch (throwable: Throwable) {
Timber.tag("UnifiedPushHelper").e(throwable)

View file

@ -25,6 +25,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -55,8 +57,9 @@ fun TextComposer(
if (LocalInspectionMode.current) {
FakeComposer(modifier)
} else {
val focusRequester = FocusRequester()
AndroidView(
modifier = modifier,
modifier = modifier.focusRequester(focusRequester),
factory = { context ->
RichTextComposerLayout(context).apply {
// Sets up listeners for View -> Compose communication

View file

@ -93,14 +93,12 @@
<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_read_marker_title">"Neu"</string>
<string name="screen_room_member_details_block_alert_action">"Blockieren"</string>
<string name="screen_room_member_details_block_user">"Nutzer blockieren"</string>
<string name="screen_room_member_details_unblock_alert_action">"Blockierung aufheben"</string>
<string name="screen_room_member_details_unblock_user">"Nutzer entblockieren"</string>
<string name="screen_analytics_settings_share_data">"Teile Analyse-Daten"</string>
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuche es erneut."</string>
<string name="settings_rageshake_detection_threshold">"Erkennungsschwelle"</string>
<string name="settings_version_number">"Version: %1$s (%2$s)"</string>
<string name="test_language_identifier">"de"</string>
<string name="dialog_title_error">"Fehler"</string>
<string name="dialog_title_success">"Erfolg"</string>
<string name="screen_report_content_block_user">"Nutzer blockieren"</string>
</resources>
</resources>

View file

@ -128,12 +128,6 @@
<string name="room_timeline_beginning_of_room_no_name">"Este es el principio de esta conversación."</string>
<string name="room_timeline_read_marker_title">"Nuevos"</string>
<string name="screen_report_content_block_user_hint">"Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario"</string>
<string name="screen_room_member_details_block_alert_action">"Bloquear"</string>
<string name="screen_room_member_details_block_alert_description">"Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puede revertir esta acción en cualquier momento."</string>
<string name="screen_room_member_details_block_user">"Bloquear usuario"</string>
<string name="screen_room_member_details_unblock_alert_action">"Desbloquear"</string>
<string name="screen_room_member_details_unblock_alert_description">"Al desbloquear al usuario, podrás volver a ver todos sus mensajes."</string>
<string name="screen_room_member_details_unblock_user">"Desbloquear usuario"</string>
<string name="settings_rageshake">"Agitar con fuerza"</string>
<string name="settings_rageshake_detection_threshold">"Umbral de detección"</string>
<string name="settings_title_general">"General"</string>
@ -142,4 +136,4 @@
<string name="dialog_title_error">"Error"</string>
<string name="dialog_title_success">"Terminado"</string>
<string name="screen_report_content_block_user">"Bloquear usuario"</string>
</resources>
</resources>

View file

@ -128,12 +128,6 @@
<string name="room_timeline_beginning_of_room_no_name">"Questo è l\'inizio della conversazione."</string>
<string name="room_timeline_read_marker_title">"Nuovo"</string>
<string name="screen_report_content_block_user_hint">"Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente"</string>
<string name="screen_room_member_details_block_alert_action">"Blocca"</string>
<string name="screen_room_member_details_block_alert_description">"Gli utenti bloccati non saranno in grado di inviarti nuovi messaggi e tutti quelli già esistenti saranno nascosti. Potrai annullare questa azione in qualsiasi momento."</string>
<string name="screen_room_member_details_block_user">"Blocca utente"</string>
<string name="screen_room_member_details_unblock_alert_action">"Sblocca"</string>
<string name="screen_room_member_details_unblock_alert_description">"Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."</string>
<string name="screen_room_member_details_unblock_user">"Sblocca utente"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Soglia di rilevamento"</string>
<string name="settings_title_general">"Generali"</string>
@ -142,4 +136,4 @@
<string name="dialog_title_error">"Errore"</string>
<string name="dialog_title_success">"Operazione riuscita"</string>
<string name="screen_report_content_block_user">"Blocca utente"</string>
</resources>
</resources>

View file

@ -122,12 +122,10 @@
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="common_member_count">
<item quantity="one">"%1$d membru"</item>
<item quantity="few"></item>
<item quantity="other">"%1$d membri"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d schimbare a camerii"</item>
<item quantity="few"></item>
<item quantity="other">"%1$d schimbări ale camerei"</item>
</plurals>
<string name="preference_rageshake">"Rageshake pentru a raporta erori"</string>
@ -137,7 +135,6 @@
<string name="room_timeline_beginning_of_room">"Acesta este începutul conversației %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Acesta este începutul acestei conversații."</string>
<string name="room_timeline_read_marker_title">"Nou"</string>
<string name="screen_analytics_help_us_improve">"Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime."</string>
<string name="screen_analytics_prompt_data_usage"><b>"Nu"</b>" înregistrăm sau profilăm datele contului"</string>
<string name="screen_analytics_prompt_help_us_improve">"Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime."</string>
<string name="screen_analytics_prompt_read_terms">"Puteți citi toate condițiile noastre %1$s."</string>
@ -145,16 +142,7 @@
<string name="screen_analytics_prompt_settings">"Puteți dezactiva această opțiune oricând din setări"</string>
<string name="screen_analytics_prompt_third_party_sharing"><b>"Nu"</b>" împărtășim informații cu terți"</string>
<string name="screen_analytics_prompt_title">"Ajutați la îmbunătățirea %1$s"</string>
<string name="screen_analytics_read_terms">"Puteți citi toate condițiile noastre %1$s."</string>
<string name="screen_analytics_read_terms_content_link">"aici"</string>
<string name="screen_analytics_share_data">"Partajați datele analitice"</string>
<string name="screen_report_content_block_user_hint">"Confirmați că doriți să ascundeți toate mesajele curente și viitoare de la acest utilizator"</string>
<string name="screen_room_member_details_block_alert_action">"Blocați"</string>
<string name="screen_room_member_details_block_alert_description">"Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând."</string>
<string name="screen_room_member_details_block_user">"Blocați utilizatorul"</string>
<string name="screen_room_member_details_unblock_alert_action">"Deblocați"</string>
<string name="screen_room_member_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
<string name="screen_room_member_details_unblock_user">"Deblocați utilizatorul"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Prag de detecție"</string>
<string name="settings_title_general">"General"</string>
@ -162,5 +150,8 @@
<string name="test_language_identifier">"ro"</string>
<string name="dialog_title_error">"Eroare"</string>
<string name="dialog_title_success">"Succes"</string>
<string name="screen_analytics_settings_help_us_improve">"Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime."</string>
<string name="screen_analytics_settings_read_terms">"Puteți citi toate condițiile noastre %1$s."</string>
<string name="screen_analytics_settings_read_terms_content_link">"aici"</string>
<string name="screen_report_content_block_user">"Blocați utilizatorul"</string>
</resources>
</resources>

View file

@ -135,7 +135,6 @@
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
<string name="room_timeline_read_marker_title">"New"</string>
<string name="screen_analytics_help_us_improve">"Help us identify issues and improve %1$s by sharing anonymous usage data."</string>
<string name="screen_analytics_prompt_data_usage">"We "<b>"don\'t"</b>" record or profile any account data"</string>
<string name="screen_analytics_prompt_help_us_improve">"Help us identify issues and improve %1$s by sharing anonymous usage data."</string>
<string name="screen_analytics_prompt_read_terms">"You can read all our terms %1$s."</string>
@ -143,16 +142,11 @@
<string name="screen_analytics_prompt_settings">"You can turn this off anytime in settings"</string>
<string name="screen_analytics_prompt_third_party_sharing">"We "<b>"don\'t"</b>" share information with third parties"</string>
<string name="screen_analytics_prompt_title">"Help improve %1$s"</string>
<string name="screen_analytics_read_terms">"You can read all our terms %1$s."</string>
<string name="screen_analytics_read_terms_content_link">"here"</string>
<string name="screen_analytics_share_data">"Share analytics data"</string>
<string name="screen_analytics_settings_share_data">"Share analytics data"</string>
<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_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
<string name="screen_room_member_details_block_alert_action">"Block"</string>
<string name="screen_room_member_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
<string name="screen_room_member_details_block_user">"Block user"</string>
<string name="screen_room_member_details_unblock_alert_action">"Unblock"</string>
<string name="screen_room_member_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
<string name="screen_room_member_details_unblock_user">"Unblock user"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
<string name="settings_title_general">"General"</string>
@ -161,5 +155,8 @@
<string name="test_untranslated_default_language_identifier">"en"</string>
<string name="dialog_title_error">"Error"</string>
<string name="dialog_title_success">"Success"</string>
<string name="screen_analytics_settings_help_us_improve">"Help us identify issues and improve %1$s by sharing anonymous usage data."</string>
<string name="screen_analytics_settings_read_terms">"You can read all our terms %1$s."</string>
<string name="screen_analytics_settings_read_terms_content_link">"here"</string>
<string name="screen_report_content_block_user">"Block user"</string>
</resources>