From 079d89ace50c6f7c5a05b66ebe0a378b0fdd75ac Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 17 Aug 2023 14:16:22 +0200 Subject: [PATCH 01/37] Update UI for poll in the timeline --- .../event/TimelineItemContentPollFactory.kt | 8 +- .../model/event/TimelineItemPollContent.kt | 1 - .../event/TimelineItemPollContentProvider.kt | 3 +- features/poll/api/build.gradle.kts | 2 - .../poll/api/ActivePollContentView.kt | 102 +++++++++++------- .../features/poll/api/PollAnswerView.kt | 89 +++++++-------- .../libraries/matrix/api/poll/PollKind.kt | 5 +- 7 files changed, 109 insertions(+), 101 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 7c61466337..b7a39974d4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -23,7 +23,7 @@ import io.element.android.features.poll.api.PollAnswerItem import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.poll.PollKind +import io.element.android.libraries.matrix.api.poll.isDisclosed import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import javax.inject.Inject @@ -36,16 +36,15 @@ class TimelineItemContentPollFactory @Inject constructor( if (!featureFlagService.isFeatureEnabled(FeatureFlags.Polls)) return TimelineItemUnknownContent // Todo Move this computation to the matrix rust sdk - val showResults = content.kind == PollKind.Disclosed && matrixClient.sessionId in content.votes.flatMap { it.value } val pollVotesCount = content.votes.flatMap { it.value }.size val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys val answerItems = content.answers.map { answer -> val votesCount = content.votes[answer.id]?.size ?: 0 - val progress = if (pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f + val progress = if (content.kind.isDisclosed && pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f PollAnswerItem( answer = answer, isSelected = answer.id in userVotes, - isDisclosed = showResults, + isDisclosed = content.kind.isDisclosed, votesCount = votesCount, progress = progress, ) @@ -56,7 +55,6 @@ class TimelineItemContentPollFactory @Inject constructor( answerItems = answerItems, votes = content.votes, pollKind = content.kind, - isDisclosed = showResults ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt index b8a2fa8bca..fcbe81b8da 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt @@ -25,7 +25,6 @@ data class TimelineItemPollContent( val answerItems: List, val votes: Map>, val pollKind: PollKind, - val isDisclosed: Boolean, ) : TimelineItemEventContent { override val type: String = "TimelineItemPollContent" } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt index 665d507ead..01253f6e77 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt @@ -24,14 +24,13 @@ open class TimelineItemPollContentProvider : PreviewParameterProvider get() = sequenceOf( aTimelineItemPollContent(), - aTimelineItemPollContent().copy(isDisclosed = true), + aTimelineItemPollContent().copy(pollKind = PollKind.Undisclosed), ) } fun aTimelineItemPollContent(): TimelineItemPollContent { return TimelineItemPollContent( pollKind = PollKind.Disclosed, - isDisclosed = false, question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), votes = emptyMap(), diff --git a/features/poll/api/build.gradle.kts b/features/poll/api/build.gradle.kts index be198ba740..6d94fa1b2f 100644 --- a/features/poll/api/build.gradle.kts +++ b/features/poll/api/build.gradle.kts @@ -27,8 +27,6 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) - implementation(libs.androidx.constraintlayout) - implementation(libs.androidx.constraintlayout.compose) implementation(projects.libraries.matrix.api) ksp(libs.showkase.processor) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt index 587c3306b1..2f534957cd 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt @@ -18,12 +18,14 @@ package io.element.android.features.poll.api import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.BarChart +import androidx.compose.material.icons.outlined.Poll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -47,54 +49,80 @@ fun ActivePollContentView( onAnswerSelected: (PollAnswer) -> Unit, modifier: Modifier = Modifier, ) { - val showResults = answerItems.any { it.isSelected } Column( modifier = modifier .selectableGroup() .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - Icon(imageVector = Icons.Default.BarChart, contentDescription = null) - Text( - text = question, - style = ElementTheme.typography.fontBodyLgMedium - ) - } + PollTitle(title = question) - answerItems.forEach { answerItem -> - PollAnswerView( - answerItem = answerItem, - onClick = { onAnswerSelected(answerItem.answer) } - ) - } + PollAnswers(answerItems = answerItems, onAnswerSelected = onAnswerSelected) - val votesCount = answerItems.sumOf { it.votesCount } - when { - pollKind == PollKind.Undisclosed -> { - Text( - modifier = Modifier - .align(Alignment.Start) - .padding(start = 32.dp), - style = ElementTheme.typography.fontBodyXsRegular, - color = ElementTheme.colors.textSecondary, - text = stringResource(CommonStrings.common_poll_undisclosed_text), - ) - } - showResults -> { - Text( - modifier = Modifier.align(Alignment.End), - style = ElementTheme.typography.fontBodyXsRegular, - color = ElementTheme.colors.textSecondary, - text = stringResource(CommonStrings.common_poll_total_votes, votesCount), - ) - } + when (pollKind) { + PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems) + PollKind.Undisclosed -> UndisclosedPollBottomNotice() } } } +@Composable +internal fun PollTitle( + title: String, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon( + modifier = Modifier.size(22.dp), + imageVector = Icons.Outlined.Poll, + contentDescription = null + ) + Text( + text = title, + style = ElementTheme.typography.fontBodyLgMedium + ) + } +} + +@Composable +internal fun PollAnswers( + answerItems: ImmutableList, + onAnswerSelected: (PollAnswer) -> Unit, +) { + answerItems.forEach { answerItem -> + PollAnswerView( + answerItem = answerItem, + onClick = { onAnswerSelected(answerItem.answer) } + ) + } +} + +@Composable +internal fun ColumnScope.DisclosedPollBottomNotice( + answerItems: ImmutableList, +) { + val votesCount = answerItems.sumOf { it.votesCount } + Text( + modifier = Modifier.align(Alignment.End), + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + text = stringResource(CommonStrings.common_poll_total_votes, votesCount), + ) +} + +@Composable +fun ColumnScope.UndisclosedPollBottomNotice() { + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 34.dp), + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + text = stringResource(CommonStrings.common_poll_undisclosed_text), + ) +} + @DayNightPreviews @Composable internal fun ActivePollContentNoResultsPreview() = ElementPreview { diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt index 26fa6fbb71..d6f4c815f9 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt @@ -16,18 +16,21 @@ package io.element.android.features.poll.api +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.selectable import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import androidx.constraintlayout.compose.Visibility import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator @@ -36,16 +39,14 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonPlurals -@Suppress("DestructuringDeclarationWithTooManyEntries") // This is necessary to declare the constraints ids @Composable fun PollAnswerView( answerItem: PollAnswerItem, onClick: () -> Unit, modifier: Modifier = Modifier, ) { - ConstraintLayout( + Row( modifier - .wrapContentHeight() .fillMaxWidth() .selectable( selected = answerItem.isSelected, @@ -53,56 +54,38 @@ fun PollAnswerView( role = Role.RadioButton, ) ) { - val (radioButton, answerText, votesText, progressBar) = createRefs() RadioButton( - modifier = Modifier.constrainAs(radioButton) { - top.linkTo(answerText.top) - bottom.linkTo(answerText.bottom) - start.linkTo(parent.start) - end.linkTo(answerText.start) - }, + modifier = Modifier.size(22.dp), selected = answerItem.isSelected, onClick = null // null recommended for accessibility with screenreaders ) - Text( - modifier = Modifier.constrainAs(answerText) { - width = Dimension.fillToConstraints - top.linkTo(parent.top) - start.linkTo(radioButton.end, margin = 8.dp) - end.linkTo(votesText.start) - bottom.linkTo(progressBar.top) - }, - text = answerItem.answer.text, - ) - Text( - modifier = Modifier.constrainAs(votesText) { - start.linkTo(answerText.end) - end.linkTo(parent.end) - bottom.linkTo(answerText.bottom) - visibility = if (answerItem.isDisclosed) Visibility.Visible else Visibility.Gone - }, - text = pluralStringResource( - id = CommonPlurals.common_poll_votes_count, - count = answerItem.votesCount, - answerItem.votesCount - ), - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, - ) - LinearProgressIndicator( - progress = answerItem.progress, - modifier = Modifier - .constrainAs(progressBar) { - start.linkTo(answerText.start) - end.linkTo(votesText.end) - top.linkTo(answerText.bottom, margin = 10.dp) - bottom.linkTo(parent.bottom) - width = Dimension.fillToConstraints - visibility = if (answerItem.isDisclosed) Visibility.Visible else Visibility.Gone - - }, - strokeCap = StrokeCap.Round, - ) + Spacer(modifier = Modifier.width(12.dp)) + Column { + Row { + Text( + modifier = Modifier.weight(1f), + text = answerItem.answer.text + ) + if (answerItem.isDisclosed) { + Text( + modifier = Modifier.align(Alignment.Bottom), + text = pluralStringResource( + id = CommonPlurals.common_poll_votes_count, + count = answerItem.votesCount, + answerItem.votesCount + ), + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + Spacer(modifier = Modifier.height(10.dp)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = answerItem.progress, + strokeCap = StrokeCap.Round, + ) + } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt index 85bb7c0256..b78f00bc86 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt @@ -20,5 +20,8 @@ enum class PollKind { Disclosed, /** Results should be only revealed when the poll is ended. */ - Undisclosed + Undisclosed, } + +val PollKind.isDisclosed: Boolean + get() = this == PollKind.Disclosed From 81051295a4dde3575565ceaede4ed2c87c560b59 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 17 Aug 2023 17:30:38 +0200 Subject: [PATCH 02/37] Use custom RadioButton --- .../poll/api/ActivePollContentView.kt | 1 + .../features/poll/api/PollAnswerView.kt | 33 +++++++-- .../theme/components/IconToggleButton.kt | 72 +++++++++++++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt index 2f534957cd..c9aa822632 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt @@ -90,6 +90,7 @@ internal fun PollAnswers( answerItems: ImmutableList, onAnswerSelected: (PollAnswer) -> Unit, ) { + answerItems.forEach { answerItem -> PollAnswerView( answerItem = answerItem, diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt index d6f4c815f9..4d300eb2f3 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt @@ -24,6 +24,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.selectable +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.RadioButtonUnchecked +import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,8 +37,9 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconToggleButton import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator -import io.element.android.libraries.designsystem.theme.components.RadioButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonPlurals @@ -54,11 +59,25 @@ fun PollAnswerView( role = Role.RadioButton, ) ) { - RadioButton( + IconToggleButton( modifier = Modifier.size(22.dp), - selected = answerItem.isSelected, - onClick = null // null recommended for accessibility with screenreaders - ) + checked = answerItem.isSelected, + colors = IconButtonDefaults.iconToggleButtonColors( + contentColor = ElementTheme.colors.iconSecondary, + checkedContentColor = ElementTheme.colors.iconPrimary, + disabledContentColor = ElementTheme.colors.iconDisabled, + ), + onCheckedChange = { onClick() }, + ) { + Icon( + imageVector = if (answerItem.isSelected) { + Icons.Default.CheckCircle + } else { + Icons.Default.RadioButtonUnchecked + }, + contentDescription = null, + ) + } Spacer(modifier = Modifier.width(12.dp)) Column { Row { @@ -93,7 +112,7 @@ fun PollAnswerView( @Composable internal fun PollAnswerViewNoResultsPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(), + answerItem = aPollAnswerItem(isSelected = true), onClick = { }, ) } @@ -102,7 +121,7 @@ internal fun PollAnswerViewNoResultsPreview() = ElementPreview { @Composable internal fun PollAnswerViewWithResultPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = true), + answerItem = aPollAnswerItem(isDisclosed = false), onClick = { } ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt new file mode 100644 index 0000000000..6b0965ffb1 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.theme.components + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.RadioButtonUnchecked +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.IconToggleButtonColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup + +@Composable +fun IconToggleButton( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable () -> Unit +) { + androidx.compose.material3.IconToggleButton( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + colors = colors, + interactionSource = interactionSource, + content = content, + ) +} + +@Preview(group = PreviewGroup.Toggles) +@Composable +internal fun IconToggleButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + var checked by remember { mutableStateOf(false) } + IconToggleButton( + checked = checked, + onCheckedChange = { checked = !checked }, + ) { + Icon( + imageVector = if (checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, + contentDescription = "IconToggleButton" + ) + } +} From 886c6526e80d00e9f5c2603cb4cd714d31f76c49 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 17 Aug 2023 17:56:30 +0200 Subject: [PATCH 03/37] Improve previews --- .../theme/components/IconToggleButton.kt | 20 +++++++++------ .../theme/components/RadioButton.kt | 25 +++++++++---------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt index 6b0965ffb1..dbf1365bb2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.RadioButtonUnchecked @@ -55,18 +56,23 @@ fun IconToggleButton( @Preview(group = PreviewGroup.Toggles) @Composable -internal fun IconToggleButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } +internal fun IconToggleButtonCheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(true) } + +@Preview(group = PreviewGroup.Toggles) +@Composable +internal fun IconToggleButtonUncheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(false) } @Composable -private fun ContentToPreview() { - var checked by remember { mutableStateOf(false) } - IconToggleButton( - checked = checked, - onCheckedChange = { checked = !checked }, - ) { +private fun ContentToPreview(defaultCheck: Boolean) { + var checked by remember { mutableStateOf(defaultCheck) } + val icon: @Composable () -> Unit = { Icon( imageVector = if (checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, contentDescription = "IconToggleButton" ) } + Column { + IconToggleButton(checked = checked, enabled = true, onCheckedChange = { checked = !checked }, content = icon) + IconToggleButton(checked = checked, enabled = false, onCheckedChange = { checked = !checked }, content = icon) + } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt index a8b186a6b2..9d85cf8d87 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt @@ -17,16 +17,16 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.material3.RadioButtonColors import androidx.compose.material3.RadioButtonDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.theme.ElementTheme @@ -63,18 +63,17 @@ internal fun compoundRadioButtonColors(): RadioButtonColors { @Preview(group = PreviewGroup.Toggles) @Composable -internal fun RadioButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } +internal fun RadioButtonCheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(true) } + +@Preview(group = PreviewGroup.Toggles) +@Composable +internal fun RadioButtonUncheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(false) } @Composable -private fun ContentToPreview() { +private fun ContentToPreview(defaultCheck: Boolean) { + var checked by remember { mutableStateOf(defaultCheck) } Column { - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - RadioButton(selected = false, onClick = {}) - RadioButton(selected = false, enabled = false, onClick = {}) - } - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - RadioButton(selected = true, onClick = {}) - RadioButton(selected = true, enabled = false, onClick = {}) - } + RadioButton(selected = checked, enabled = true, onClick = { checked = !checked }) + RadioButton(selected = checked, enabled = false, onClick = { checked = !checked }) } } From df1be4f6b12bcbaec3118cd8c9997a91607d911c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 18 Aug 2023 09:46:44 +0200 Subject: [PATCH 04/37] Update option progress when selected --- .../factories/event/TimelineItemContentPollFactory.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index b7a39974d4..7b4f536932 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -40,10 +40,16 @@ class TimelineItemContentPollFactory @Inject constructor( val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys val answerItems = content.answers.map { answer -> val votesCount = content.votes[answer.id]?.size ?: 0 - val progress = if (content.kind.isDisclosed && pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f + val isSelected = answer.id in userVotes + val progress = when { + pollVotesCount == 0 -> 0f + content.kind.isDisclosed -> votesCount.toFloat() / pollVotesCount.toFloat() + isSelected -> 1f + else -> 0f + } PollAnswerItem( answer = answer, - isSelected = answer.id in userVotes, + isSelected = isSelected, isDisclosed = content.kind.isDisclosed, votesCount = votesCount, progress = progress, From d59349184f6ffcd02c4c13acb77e9a8f4546c1e5 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 18 Aug 2023 10:17:48 +0200 Subject: [PATCH 05/37] Rename progress variable to percentage --- .../factories/event/TimelineItemContentPollFactory.kt | 4 ++-- .../android/features/poll/api/PollAnswerItem.kt | 4 ++-- .../android/features/poll/api/PollAnswerView.kt | 2 +- .../features/poll/api/PollAnswerViewProvider.kt | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 7b4f536932..656cb0910f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -41,7 +41,7 @@ class TimelineItemContentPollFactory @Inject constructor( val answerItems = content.answers.map { answer -> val votesCount = content.votes[answer.id]?.size ?: 0 val isSelected = answer.id in userVotes - val progress = when { + val percentage = when { pollVotesCount == 0 -> 0f content.kind.isDisclosed -> votesCount.toFloat() / pollVotesCount.toFloat() isSelected -> 1f @@ -52,7 +52,7 @@ class TimelineItemContentPollFactory @Inject constructor( isSelected = isSelected, isDisclosed = content.kind.isDisclosed, votesCount = votesCount, - progress = progress, + percentage = percentage, ) } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt index 24db33ad1f..eb6e131068 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt @@ -25,12 +25,12 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer * @property isSelected whether the user has selected this answer. * @property isDisclosed whether the votes for this answer should be disclosed. * @property votesCount the number of votes for this answer. - * @property progress the percentage of votes for this answer. + * @property percentage the percentage of votes for this answer. */ data class PollAnswerItem( val answer: PollAnswer, val isSelected: Boolean, val isDisclosed: Boolean, val votesCount: Int, - val progress: Float, + val percentage: Float, ) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt index 4d300eb2f3..c87d95be3a 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt @@ -101,7 +101,7 @@ fun PollAnswerView( Spacer(modifier = Modifier.height(10.dp)) LinearProgressIndicator( modifier = Modifier.fillMaxWidth(), - progress = answerItem.progress, + progress = answerItem.percentage, strokeCap = StrokeCap.Round, ) } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt index 062d09fd88..271b6bef3a 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt @@ -24,20 +24,20 @@ fun aPollAnswerItemList(isDisclosed: Boolean = true) = persistentListOf( answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"), isDisclosed = isDisclosed, votesCount = 5, - progress = 0.5f + percentage = 0.5f ), aPollAnswerItem( answer = PollAnswer("option_2", "Chinese \uD83C\uDDE8\uD83C\uDDF3"), isDisclosed = isDisclosed, votesCount = 0, - progress = 0f + percentage = 0f ), aPollAnswerItem( answer = PollAnswer("option_3", "Brazilian \uD83C\uDDE7\uD83C\uDDF7"), isDisclosed = isDisclosed, isSelected = true, votesCount = 1, - progress = 0.1f + percentage = 0.1f ), aPollAnswerItem(isDisclosed = isDisclosed), ) @@ -50,11 +50,11 @@ fun aPollAnswerItem( isSelected: Boolean = false, isDisclosed: Boolean = true, votesCount: Int = 4, - progress: Float = 0.4f, + percentage: Float = 0.4f, ) = PollAnswerItem( answer = answer, isSelected = isSelected, isDisclosed = isDisclosed, votesCount = votesCount, - progress = progress + percentage = percentage ) From 246e9c50e69d1d0fba607670019166a5f27caecd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 18 Aug 2023 10:26:01 +0200 Subject: [PATCH 06/37] Do not handle poll end event --- .../event/TimelineItemContentFactory.kt | 3 -- .../TimelineItemContentPollEndFactory.kt | 29 ------------------- .../impl/timeline/groups/Groupability.kt | 2 -- .../messages/fixtures/timelineItemsFactory.kt | 2 -- .../impl/DefaultRoomLastMessageFormatter.kt | 3 +- .../impl/DefaultTimelineEventFormatter.kt | 2 -- .../api/timeline/item/event/EventContent.kt | 4 --- .../item/event/TimelineEventContentMapper.kt | 4 --- 8 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index b3b2c896c3..1de4ee7c86 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent @@ -38,7 +37,6 @@ class TimelineItemContentFactory @Inject constructor( private val redactedMessageFactory: TimelineItemContentRedactedFactory, private val stickerFactory: TimelineItemContentStickerFactory, private val pollFactory: TimelineItemContentPollFactory, - private val pollEndFactory: TimelineItemContentPollEndFactory, private val utdFactory: TimelineItemContentUTDFactory, private val roomMembershipFactory: TimelineItemContentRoomMembershipFactory, private val profileChangeFactory: TimelineItemContentProfileChangeFactory, @@ -58,7 +56,6 @@ class TimelineItemContentFactory @Inject constructor( is StateContent -> stateFactory.create(eventTimelineItem) is StickerContent -> stickerFactory.create(itemContent) is PollContent -> pollFactory.create(itemContent) - is PollEndContent -> pollEndFactory.create(itemContent) is UnableToDecryptContent -> utdFactory.create(itemContent) is UnknownContent -> TimelineItemUnknownContent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt deleted file mode 100644 index ff9eb837b6..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollEndFactory.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.features.messages.impl.timeline.factories.event - -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent -import javax.inject.Inject - -class TimelineItemContentPollEndFactory @Inject constructor() { - - fun create(@Suppress("UNUSED_PARAMETER") content: PollEndContent): TimelineItemEventContent { - return TimelineItemUnknownContent - } -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt index 1d2dec09b7..844942002a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent @@ -79,7 +78,6 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean { RedactedContent, is StickerContent, is PollContent, - is PollEndContent, is UnableToDecryptContent -> true is FailedToParseStateContent, is ProfileChangeContent, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt index 8fea1ae155..0d4dc98340 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt @@ -21,7 +21,6 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseStateFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentMessageFactory -import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentPollEndFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentPollFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentProfileChangeFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentRedactedFactory @@ -54,7 +53,6 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { redactedMessageFactory = TimelineItemContentRedactedFactory(), stickerFactory = TimelineItemContentStickerFactory(), pollFactory = TimelineItemContentPollFactory(matrixClient, FakeFeatureFlagService()), - pollEndFactory = TimelineItemContentPollEndFactory(), utdFactory = TimelineItemContentUTDFactory(), roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter), profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter), diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index 0736cf61ce..a0a0525fb5 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -38,7 +38,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageConten import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent @@ -96,7 +95,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( is StateContent -> { stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList) } - is PollContent, is PollEndContent, // TODO Polls: handle last message + is PollContent, // TODO Polls: handle last message is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom) } diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt index 42d8aae083..2b729fb0d5 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -27,7 +27,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent @@ -66,7 +65,6 @@ class DefaultTimelineEventFormatter @Inject constructor( RedactedContent, is StickerContent, is PollContent, - is PollEndContent, is UnableToDecryptContent, is MessageContent, is FailedToParseMessageLikeContent, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index 82c322668c..3316de64eb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -80,10 +80,6 @@ data class PollContent( val endTime: ULong? ) : EventContent -data class PollEndContent( - val startEventId: String -) : EventContent - data class UnableToDecryptContent( val data: Data ) : EventContent { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 7ee1d1490d..22f8a062f0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent @@ -106,9 +105,6 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap endTime = kind.endTime, ) } - is TimelineItemContentKind.PollEnd -> { - PollEndContent(startEventId = kind.startEventId) - } is TimelineItemContentKind.UnableToDecrypt -> { UnableToDecryptContent( data = kind.msg.map() From bb2f5a133082656dd824f28de67cbda21a93c4b8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 21 Aug 2023 14:10:21 +0200 Subject: [PATCH 07/37] Render ended poll with winning answers --- .../components/event/TimelineItemPollView.kt | 5 +- .../event/TimelineItemContentPollFactory.kt | 19 +++-- .../model/event/TimelineItemPollContent.kt | 1 + .../event/TimelineItemPollContentProvider.kt | 1 + .../features/poll/api/PollAnswerItem.kt | 4 + .../features/poll/api/PollAnswerView.kt | 78 ++++++++++++++++--- .../poll/api/PollAnswerViewProvider.kt | 14 +++- ...ePollContentView.kt => PollContentView.kt} | 31 ++++++-- 8 files changed, 122 insertions(+), 31 deletions(-) rename features/poll/api/src/main/kotlin/io/element/android/features/poll/api/{ActivePollContentView.kt => PollContentView.kt} (84%) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index db3503be37..3608593cde 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider -import io.element.android.features.poll.api.ActivePollContentView +import io.element.android.features.poll.api.PollContentView import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.matrix.api.poll.PollAnswer @@ -33,10 +33,11 @@ fun TimelineItemPollView( onAnswerSelected: (PollAnswer) -> Unit, modifier: Modifier = Modifier, ) { - ActivePollContentView( + PollContentView( question = content.question, answerItems = content.answerItems.toImmutableList(), pollKind = content.pollKind, + isPollEnded = content.isEnded, onAnswerSelected = onAnswerSelected, modifier = modifier, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 656cb0910f..df15d2868a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -38,19 +38,23 @@ class TimelineItemContentPollFactory @Inject constructor( // Todo Move this computation to the matrix rust sdk val pollVotesCount = content.votes.flatMap { it.value }.size val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys + val isEndedPoll = content.endTime != null + val winnerIds = content.answers.map { it.id } + .groupBy { content.votes[it]?.size ?: 0 } // Group by votes count + .maxBy { it.key } // Keep max voted answers + .takeIf { it.key > 0 } // Ignore if no option has been voted + ?.value.orEmpty() val answerItems = content.answers.map { answer -> val votesCount = content.votes[answer.id]?.size ?: 0 val isSelected = answer.id in userVotes - val percentage = when { - pollVotesCount == 0 -> 0f - content.kind.isDisclosed -> votesCount.toFloat() / pollVotesCount.toFloat() - isSelected -> 1f - else -> 0f - } + val isWinner = answer.id in winnerIds + val percentage = if (pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f PollAnswerItem( answer = answer, isSelected = isSelected, - isDisclosed = content.kind.isDisclosed, + isEnabled = isEndedPoll, + isWinner = isWinner, + isDisclosed = content.kind.isDisclosed || isEndedPoll, votesCount = votesCount, percentage = percentage, ) @@ -61,6 +65,7 @@ class TimelineItemContentPollFactory @Inject constructor( answerItems = answerItems, votes = content.votes, pollKind = content.kind, + isEnded = isEndedPoll, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt index fcbe81b8da..3c0e0edfd4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt @@ -25,6 +25,7 @@ data class TimelineItemPollContent( val answerItems: List, val votes: Map>, val pollKind: PollKind, + val isEnded: Boolean, ) : TimelineItemEventContent { override val type: String = "TimelineItemPollContent" } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt index 01253f6e77..49dfa58c8a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt @@ -33,6 +33,7 @@ fun aTimelineItemPollContent(): TimelineItemPollContent { pollKind = PollKind.Disclosed, question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), + isEnded = false, votes = emptyMap(), ) } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt index eb6e131068..1955701c5b 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt @@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer * * @property answer the poll answer. * @property isSelected whether the user has selected this answer. + * @property isEnabled whether the answer can be voted. + * @property isWinner whether this is the winner answer in the poll. * @property isDisclosed whether the votes for this answer should be disclosed. * @property votesCount the number of votes for this answer. * @property percentage the percentage of votes for this answer. @@ -30,6 +32,8 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer data class PollAnswerItem( val answer: PollAnswer, val isSelected: Boolean, + val isEnabled: Boolean, + val isWinner: Boolean, val isDisclosed: Boolean, val votesCount: Int, val percentage: Float, diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt index c87d95be3a..3e52cc5fc7 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt @@ -34,13 +34,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.DayNightPreviews -import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconToggleButton import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.toEnabledColor import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonPlurals @@ -55,6 +56,7 @@ fun PollAnswerView( .fillMaxWidth() .selectable( selected = answerItem.isSelected, + enabled = answerItem.isEnabled, onClick = onClick, role = Role.RadioButton, ) @@ -62,6 +64,7 @@ fun PollAnswerView( IconToggleButton( modifier = Modifier.size(22.dp), checked = answerItem.isSelected, + enabled = answerItem.isEnabled, colors = IconButtonDefaults.iconToggleButtonColors( contentColor = ElementTheme.colors.iconSecondary, checkedContentColor = ElementTheme.colors.iconPrimary, @@ -83,7 +86,8 @@ fun PollAnswerView( Row { Text( modifier = Modifier.weight(1f), - text = answerItem.answer.text + text = answerItem.answer.text, + style = if (answerItem.isWinner) ElementTheme.typography.fontBodyLgMedium else ElementTheme.typography.fontBodyLgRegular, ) if (answerItem.isDisclosed) { Text( @@ -93,35 +97,85 @@ fun PollAnswerView( count = answerItem.votesCount, answerItem.votesCount ), - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, + style = if (answerItem.isWinner) ElementTheme.typography.fontBodySmMedium else ElementTheme.typography.fontBodySmRegular, + color = if (answerItem.isWinner) ElementTheme.colors.textPrimary else ElementTheme.colors.textSecondary, ) } } Spacer(modifier = Modifier.height(10.dp)) LinearProgressIndicator( modifier = Modifier.fillMaxWidth(), - progress = answerItem.percentage, + color = if (answerItem.isWinner) ElementTheme.colors.textSuccessPrimary else answerItem.isEnabled.toEnabledColor(), + progress = when { + answerItem.isDisclosed -> answerItem.percentage + answerItem.isSelected -> 1f + else -> 0f + }, strokeCap = StrokeCap.Round, ) } } } -@DayNightPreviews +@Preview @Composable -internal fun PollAnswerViewNoResultsPreview() = ElementPreview { +internal fun PollAnswerDisclosedNotSelectedPreview() = ElementThemedPreview { PollAnswerView( - answerItem = aPollAnswerItem(isSelected = true), + answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false), onClick = { }, ) } -@DayNightPreviews +@Preview @Composable -internal fun PollAnswerViewWithResultPreview() = ElementPreview { +internal fun PollAnswerDisclosedSelectedPreview() = ElementThemedPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = false), + answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true), + onClick = { } + ) +} + +@Preview +@Composable +internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementThemedPreview { + PollAnswerView( + answerItem = aPollAnswerItem(isDisclosed = false, isSelected = false), + onClick = { }, + ) +} + +@Preview +@Composable +internal fun PollAnswerUndisclosedSelectedPreview() = ElementThemedPreview { + PollAnswerView( + answerItem = aPollAnswerItem(isDisclosed = false, isSelected = true), + onClick = { } + ) +} + +@Preview +@Composable +internal fun PollAnswerEndedWinnerNotSelectedPreview() = ElementThemedPreview { + PollAnswerView( + answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false, isEnabled = false, isWinner = true), + onClick = { } + ) +} + +@Preview +@Composable +internal fun PollAnswerEndedWinnerSelectedPreview() = ElementThemedPreview { + PollAnswerView( + answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = true), + onClick = { } + ) +} + +@Preview +@Composable +internal fun PollAnswerEndedSelectedPreview() = ElementThemedPreview { + PollAnswerView( + answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = false), onClick = { } ) } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt index 271b6bef3a..e94b5adeeb 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt @@ -19,27 +19,33 @@ package io.element.android.features.poll.api import io.element.android.libraries.matrix.api.poll.PollAnswer import kotlinx.collections.immutable.persistentListOf -fun aPollAnswerItemList(isDisclosed: Boolean = true) = persistentListOf( +fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = persistentListOf( aPollAnswerItem( answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"), isDisclosed = isDisclosed, + isEnabled = !isEnded, + isWinner = isEnded, votesCount = 5, percentage = 0.5f ), aPollAnswerItem( answer = PollAnswer("option_2", "Chinese \uD83C\uDDE8\uD83C\uDDF3"), isDisclosed = isDisclosed, + isEnabled = !isEnded, + isWinner = false, votesCount = 0, percentage = 0f ), aPollAnswerItem( answer = PollAnswer("option_3", "Brazilian \uD83C\uDDE7\uD83C\uDDF7"), isDisclosed = isDisclosed, + isEnabled = !isEnded, + isWinner = false, isSelected = true, votesCount = 1, percentage = 0.1f ), - aPollAnswerItem(isDisclosed = isDisclosed), + aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded), ) fun aPollAnswerItem( @@ -48,12 +54,16 @@ fun aPollAnswerItem( "French \uD83C\uDDEB\uD83C\uDDF7 But make it a very very very long option then this should just keep expanding" ), isSelected: Boolean = false, + isEnabled: Boolean = true, + isWinner: Boolean = false, isDisclosed: Boolean = true, votesCount: Int = 4, percentage: Float = 0.4f, ) = PollAnswerItem( answer = answer, isSelected = isSelected, + isEnabled = isEnabled, + isWinner = isWinner, isDisclosed = isDisclosed, votesCount = votesCount, percentage = percentage diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt similarity index 84% rename from features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt rename to features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index c9aa822632..f778f66bd1 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -42,10 +42,11 @@ import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @Composable -fun ActivePollContentView( +fun PollContentView( question: String, answerItems: ImmutableList, pollKind: PollKind, + isPollEnded: Boolean, onAnswerSelected: (PollAnswer) -> Unit, modifier: Modifier = Modifier, ) { @@ -59,9 +60,9 @@ fun ActivePollContentView( PollAnswers(answerItems = answerItems, onAnswerSelected = onAnswerSelected) - when (pollKind) { - PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems) - PollKind.Undisclosed -> UndisclosedPollBottomNotice() + when { + isPollEnded || pollKind == PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems) + pollKind == PollKind.Undisclosed -> UndisclosedPollBottomNotice() } } } @@ -126,22 +127,36 @@ fun ColumnScope.UndisclosedPollBottomNotice() { @DayNightPreviews @Composable -internal fun ActivePollContentNoResultsPreview() = ElementPreview { - ActivePollContentView( +internal fun PollContentNoResultsPreview() = ElementPreview { + PollContentView( question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(isDisclosed = false), pollKind = PollKind.Undisclosed, + isPollEnded = false, onAnswerSelected = { }, ) } @DayNightPreviews @Composable -internal fun ActivePollContentWithResultsPreview() = ElementPreview { - ActivePollContentView( +internal fun PollContentWithResultsPreview() = ElementPreview { + PollContentView( question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), pollKind = PollKind.Disclosed, + isPollEnded = false, + onAnswerSelected = { }, + ) +} + +@DayNightPreviews +@Composable +internal fun PollContentEndedPreview() = ElementPreview { + PollContentView( + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(isEnded = true), + pollKind = PollKind.Disclosed, + isPollEnded = false, onAnswerSelected = { }, ) } From 8657a5dfac7e3980e06bb34fef2b404e12d61ec3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 21 Aug 2023 14:27:24 +0200 Subject: [PATCH 08/37] Rename previews --- .../io/element/android/features/poll/api/PollContentView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index f778f66bd1..6a79bda543 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -127,7 +127,7 @@ fun ColumnScope.UndisclosedPollBottomNotice() { @DayNightPreviews @Composable -internal fun PollContentNoResultsPreview() = ElementPreview { +internal fun PollContentUndisclosedPreview() = ElementPreview { PollContentView( question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(isDisclosed = false), @@ -139,7 +139,7 @@ internal fun PollContentNoResultsPreview() = ElementPreview { @DayNightPreviews @Composable -internal fun PollContentWithResultsPreview() = ElementPreview { +internal fun PollContentDisclosedPreview() = ElementPreview { PollContentView( question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), From ed62286eb880161166b2649dc2d6ad1fbc8c0513 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 21 Aug 2023 16:13:01 +0200 Subject: [PATCH 09/37] Fix poll enabled condition --- .../timeline/factories/event/TimelineItemContentPollFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index df15d2868a..0354401cb7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -52,7 +52,7 @@ class TimelineItemContentPollFactory @Inject constructor( PollAnswerItem( answer = answer, isSelected = isSelected, - isEnabled = isEndedPoll, + isEnabled = !isEndedPoll, isWinner = isWinner, isDisclosed = content.kind.isDisclosed || isEndedPoll, votesCount = votesCount, From 5ac965c81f46730cf841362fc716f9d5aefbef4b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 22 Aug 2023 12:10:18 +0200 Subject: [PATCH 10/37] changelog --- changelog.d/1113.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1113.wip diff --git a/changelog.d/1113.wip b/changelog.d/1113.wip new file mode 100644 index 0000000000..f95dc6ba88 --- /dev/null +++ b/changelog.d/1113.wip @@ -0,0 +1 @@ +[Polls] Improve UI and render ended state From 44b4372d4958c9a41ccacddaccfbb615f14e9921 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 22 Aug 2023 10:19:26 +0000 Subject: [PATCH 11/37] Update screenshots --- ...ll_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...ll_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png | 4 ++-- ..._ActivePollContentNoResults-D-0_0_null,NEXUS_5,1.0,en].png | 3 --- ..._ActivePollContentNoResults-N-0_1_null,NEXUS_5,1.0,en].png | 3 --- ...ctivePollContentWithResults-D-1_1_null,NEXUS_5,1.0,en].png | 3 --- ...ctivePollContentWithResults-N-1_2_null,NEXUS_5,1.0,en].png | 3 --- ..._PollAnswerDisclosedNotSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...ull_PollAnswerDisclosedSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...pi_null_PollAnswerEndedSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...ollAnswerEndedWinnerNotSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...l_PollAnswerEndedWinnerSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...ollAnswerUndisclosedNotSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...l_PollAnswerUndisclosedSelected_0_null,NEXUS_5,1.0,en].png | 3 +++ ...ull_PollAnswerViewNoResults-D-2_2_null,NEXUS_5,1.0,en].png | 3 --- ...ull_PollAnswerViewNoResults-N-2_3_null,NEXUS_5,1.0,en].png | 3 --- ...ll_PollAnswerViewWithResult-D-3_3_null,NEXUS_5,1.0,en].png | 3 --- ...ll_PollAnswerViewWithResult-N-3_4_null,NEXUS_5,1.0,en].png | 3 --- ...i_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png | 3 +++ ...i_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png | 3 +++ ...l.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png | 3 +++ ...l.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png | 3 +++ ...null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png | 3 +++ ...null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png | 3 +++ ...Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png | 3 +++ ...ggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png | 3 +++ ...null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png | 3 +++ ...ll_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png | 3 +++ ...onents_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png | 3 --- 30 files changed, 59 insertions(+), 35 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-D-0_0_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-N-0_1_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-D-1_1_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-N-1_2_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedNotSelected_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedSelected_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedSelected_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerNotSelected_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerSelected_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedNotSelected_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedSelected_0_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-D-2_2_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-N-2_3_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-D-3_3_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-N-3_4_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png index 0134aee79e..9bd8087f0c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f704348b48d03ce3e788e7e37298a008116c66f3ad0a074d164df4ebbb05d9d8 -size 48964 +oid sha256:15d14bf99af3cd0433870ebd6032d9bf4a45196e2ef1df7184cc55859a704dee +size 49008 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png index 0134aee79e..170f3d997a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-D-13_14_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f704348b48d03ce3e788e7e37298a008116c66f3ad0a074d164df4ebbb05d9d8 -size 48964 +oid sha256:6e47fc219bbd63b76d01a5e50c3e4c6b1b0a8b4ec40b08b13271d0b2673d8d5e +size 50932 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png index f327dc9154..cb9d6334b0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:945d8f7b4226c451c8d33f51bf051c7f08a316c786259d49475b2e2cde407bd7 -size 46415 +oid sha256:cacb91ffca97f21bbc19b29525813f58fc8017a858aa28ccd4e620b70a8cd9ca +size 46119 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png index f327dc9154..ec2787d842 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollView-N-13_15_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:945d8f7b4226c451c8d33f51bf051c7f08a316c786259d49475b2e2cde407bd7 -size 46415 +oid sha256:3f1c14a23ee598ece6843a68d3ca0b1d1f725f53174bd493e27c6137de70c508 +size 48296 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-D-0_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-D-0_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 57ddb69c7a..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-D-0_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb9e4bbe341a84452206a7485a477d81725b535369b1dfad3cf430548dbb21e8 -size 46450 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-N-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-N-0_1_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 7a31eae39a..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentNoResults-N-0_1_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bfbbae8e27c4be7ea7fadeff2470773775cb476ef37b25c8f1bb8c35b5eddd9 -size 43082 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-D-1_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-D-1_1_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 0134aee79e..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-D-1_1_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f704348b48d03ce3e788e7e37298a008116c66f3ad0a074d164df4ebbb05d9d8 -size 48964 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-N-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-N-1_2_null,NEXUS_5,1.0,en].png deleted file mode 100644 index f327dc9154..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_ActivePollContentWithResults-N-1_2_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:945d8f7b4226c451c8d33f51bf051c7f08a316c786259d49475b2e2cde407bd7 -size 46415 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedNotSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedNotSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a3e90a69fe --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedNotSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74508ef7f77a9c8713c75586ae4d34a9daab2608dbbd2f20de3e4d4a9a9be7e9 +size 39225 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7be79c2135 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerDisclosedSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac9e0523fc99d472a1fe8f21719e64dbb7d1ec01059dd4183aa4a152f8ead55 +size 38673 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7ee39de1a4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed68d9ebe67d5dad938a3efcd8c2b680444c650e635bdc04cfd140ba694d9f1d +size 38928 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerNotSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerNotSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b2d901dc0d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerNotSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d5c9d2042dad75b48b61cdbae5b2425d7c935eb860df5d5c6fa3bcb327d13d1 +size 38842 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4998418006 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerEndedWinnerSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4ab648f5651b7457635be3eabb914be1c976154d12a163f1c1bb2cd92168824 +size 38730 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedNotSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedNotSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a2cd64d048 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedNotSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbb713d36f8b36ce6b55f38c10323e784a80f6187e6613ded91dc531b23a7cb7 +size 36444 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedSelected_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedSelected_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2d973414d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerUndisclosedSelected_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31735c42ea83974595544a0f03636a2337210f23e60d4b7f8e41d46ba21d483f +size 35920 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-D-2_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-D-2_2_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 1eef37d750..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-D-2_2_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4c5bc5f2086bd78a364e9996f0f41526d73d69857875594f5de7ea9998333d7 -size 23388 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-N-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-N-2_3_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 95c64c4548..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewNoResults-N-2_3_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42848910a949ec938ccc7a3547b19d1c2c81193f9a93b1db21ca77e4d9ed9663 -size 21761 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-D-3_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-D-3_3_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 1eef37d750..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-D-3_3_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4c5bc5f2086bd78a364e9996f0f41526d73d69857875594f5de7ea9998333d7 -size 23388 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-N-3_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-N-3_4_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 95c64c4548..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollAnswerViewWithResult-N-3_4_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42848910a949ec938ccc7a3547b19d1c2c81193f9a93b1db21ca77e4d9ed9663 -size 21761 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9bd8087f0c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-D-1_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15d14bf99af3cd0433870ebd6032d9bf4a45196e2ef1df7184cc55859a704dee +size 49008 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cb9d6334b0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentDisclosed-N-1_2_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cacb91ffca97f21bbc19b29525813f58fc8017a858aa28ccd4e620b70a8cd9ca +size 46119 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f148a727ae --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-D-2_2_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a9ccbbc0a208ac398f07f0070a43d0ca5ab67ef322b274b641270a9c21a4a60 +size 48972 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..df2a5c30b9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentEnded-N-2_3_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5736eb1d232b841d62f9d96070fc9b2993453652f5d7c448b6a819db9543615e +size 45756 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7319bf6694 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-D-0_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2264139761f7fe3977ece3c9a7d949ac51223dd209497b7b311987cba3e5a069 +size 47154 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3a129abb0b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentUndisclosed-N-0_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3ff35998ce8558b5a7af84e378bee039e36099084b357fffccf329a4983b035 +size 43551 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8ed5b53ea6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8dd204bfd2abda63626688fc31a70af5985a28793249a9e382326dd205ad7b15 +size 9994 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3d0cfdf7a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0857c3a7f01c90df0ba9b4cab7dfb523236dee74f7de0da4f7275741af89128a +size 10204 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..34f115b02e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9566b59af56b7dac1717880b732ea0c5a037e06c5200a395c298dd2e47ce030 +size 11187 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ac385411ef --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2f9b6ba324fccdb5db6b838481b4d7e3dac64a7e648563f48b5c3763c735404 +size 9818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 97f012d11b..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:88c7be1a8cff74b057ee0f7ba09922c814c8c0b6a3dbaa69ea58ec287c425fca -size 14357 From 962571bec19c92148ad54d28e48a4cbdcf4bf001 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 22 Aug 2023 14:44:05 +0200 Subject: [PATCH 12/37] Fix lint --- .../timeline/factories/event/TimelineItemContentPollFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 0354401cb7..4a52c9df6f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -43,7 +43,8 @@ class TimelineItemContentPollFactory @Inject constructor( .groupBy { content.votes[it]?.size ?: 0 } // Group by votes count .maxBy { it.key } // Keep max voted answers .takeIf { it.key > 0 } // Ignore if no option has been voted - ?.value.orEmpty() + ?.value + .orEmpty() val answerItems = content.answers.map { answer -> val votesCount = content.votes[answer.id]?.size ?: 0 val isSelected = answer.id in userVotes From dec41460edae3cb7bd2538d0c9cd01c860b0a902 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 22 Aug 2023 15:18:26 +0200 Subject: [PATCH 13/37] Fix winning answers detection --- .../event/TimelineItemContentPollFactory.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 4a52c9df6f..4a21874e1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -36,27 +36,32 @@ class TimelineItemContentPollFactory @Inject constructor( if (!featureFlagService.isFeatureEnabled(FeatureFlags.Polls)) return TimelineItemUnknownContent // Todo Move this computation to the matrix rust sdk - val pollVotesCount = content.votes.flatMap { it.value }.size - val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys + val totalVoteCount = content.votes.flatMap { it.value }.size + val myVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys val isEndedPoll = content.endTime != null - val winnerIds = content.answers.map { it.id } - .groupBy { content.votes[it]?.size ?: 0 } // Group by votes count - .maxBy { it.key } // Keep max voted answers - .takeIf { it.key > 0 } // Ignore if no option has been voted - ?.value - .orEmpty() + val winnerIds = if (!isEndedPoll) { + emptyList() + } else { + content.answers + .map { answer -> answer.id } + .groupBy { answerId -> content.votes[answerId]?.size ?: 0 } // Group by votes count + .maxByOrNull { (votes, _) -> votes } // Keep max voted answers + ?.takeIf { (votes, _) -> votes > 0 } // Ignore if no option has been voted + ?.value + .orEmpty() + } val answerItems = content.answers.map { answer -> - val votesCount = content.votes[answer.id]?.size ?: 0 - val isSelected = answer.id in userVotes + val answerVoteCount = content.votes[answer.id]?.size ?: 0 + val isSelected = answer.id in myVotes val isWinner = answer.id in winnerIds - val percentage = if (pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f + val percentage = if (totalVoteCount > 0) answerVoteCount.toFloat() / totalVoteCount.toFloat() else 0f PollAnswerItem( answer = answer, isSelected = isSelected, isEnabled = !isEndedPoll, isWinner = isWinner, isDisclosed = content.kind.isDisclosed || isEndedPoll, - votesCount = votesCount, + votesCount = answerVoteCount, percentage = percentage, ) } From 29cd8f287f14e0f66d2484bb90820264b607820b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 22 Aug 2023 16:11:57 +0200 Subject: [PATCH 14/37] Add missing modifier --- .../android/features/poll/api/PollContentView.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index 6a79bda543..419aa21204 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -70,8 +70,10 @@ fun PollContentView( @Composable internal fun PollTitle( title: String, + modifier: Modifier = Modifier ) { Row( + modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp), ) { Icon( @@ -90,10 +92,12 @@ internal fun PollTitle( internal fun PollAnswers( answerItems: ImmutableList, onAnswerSelected: (PollAnswer) -> Unit, + modifier: Modifier = Modifier, ) { answerItems.forEach { answerItem -> PollAnswerView( + modifier = modifier, answerItem = answerItem, onClick = { onAnswerSelected(answerItem.answer) } ) @@ -103,10 +107,11 @@ internal fun PollAnswers( @Composable internal fun ColumnScope.DisclosedPollBottomNotice( answerItems: ImmutableList, + modifier: Modifier = Modifier ) { val votesCount = answerItems.sumOf { it.votesCount } Text( - modifier = Modifier.align(Alignment.End), + modifier = modifier.align(Alignment.End), style = ElementTheme.typography.fontBodyXsRegular, color = ElementTheme.colors.textSecondary, text = stringResource(CommonStrings.common_poll_total_votes, votesCount), @@ -114,9 +119,9 @@ internal fun ColumnScope.DisclosedPollBottomNotice( } @Composable -fun ColumnScope.UndisclosedPollBottomNotice() { +fun ColumnScope.UndisclosedPollBottomNotice(modifier: Modifier = Modifier) { Text( - modifier = Modifier + modifier = modifier .align(Alignment.Start) .padding(start = 34.dp), style = ElementTheme.typography.fontBodyXsRegular, From 648e2c378d4bc302c14f03f3e300f130aed7bbd3 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 24 Aug 2023 09:41:58 +0200 Subject: [PATCH 15/37] Fix sent videos being cropped (#1124) --- changelog.d/862.bugfix | 1 + .../android/libraries/mediaupload/VideoCompressor.kt | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 changelog.d/862.bugfix diff --git a/changelog.d/862.bugfix b/changelog.d/862.bugfix new file mode 100644 index 0000000000..30715a76e3 --- /dev/null +++ b/changelog.d/862.bugfix @@ -0,0 +1 @@ +Videos sent from the app were cropped in some cases. diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt index e7e294cd7c..5587eedfa4 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt @@ -20,6 +20,8 @@ import android.content.Context import android.net.Uri import com.otaliastudios.transcoder.Transcoder import com.otaliastudios.transcoder.TranscoderListener +import com.otaliastudios.transcoder.resize.AtMostResizer +import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy import io.element.android.libraries.androidutils.file.createTmpFile import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.di.ApplicationContext @@ -35,6 +37,11 @@ class VideoCompressor @Inject constructor( fun compress(uri: Uri) = callbackFlow { val tmpFile = context.createTmpFile(extension = "mp4") val future = Transcoder.into(tmpFile.path) + .setVideoTrackStrategy( + DefaultVideoStrategy.Builder() + .addResizer(AtMostResizer(1920, 1080)) + .build() + ) .addDataSource(context, uri) .setListener(object : TranscoderListener { override fun onTranscodeProgress(progress: Double) { From 13ff443b4a3db771ace03365ee80610c372fee0a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 09:54:14 +0200 Subject: [PATCH 16/37] Move the file pull_request_template.md to the parent folder to make it the default template. --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From dd515423ccabf3425a6ae1bb4d776ee07fcec32e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 09:55:29 +0200 Subject: [PATCH 17/37] Element X --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d83826f9f1..11e6aeba19 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to Element Android +# Contributing to Element X Android From f2b7252a8848005d176f4b013e41f7bd6d0f123e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 10:07:10 +0200 Subject: [PATCH 18/37] Update pull request template for Element X. --- .github/pull_request_template.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 431c018fdd..aec678857c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - + ## Type of change @@ -17,13 +17,17 @@ ## Screenshots / GIFs - - -- [ ] Changes has been tested on an Android device or Android emulator with API 21 +- [ ] Changes has been tested on an Android device or Android emulator with API 23 - [ ] UI change has been tested on both light and dark themes -- [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#accessibility +- [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#accessibility - [ ] Pull request is based on the develop branch -- [ ] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog +- [ ] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#changelog - [ ] Pull request includes screenshots or videos if containing UI changes - [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off) - [ ] You've made a self review of your PR From 82c3b4896c24d243f667cad84d5af17dd2934bf4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 11:15:37 +0200 Subject: [PATCH 19/37] Fix grammar --- .github/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index aec678857c..7aabcb77aa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,9 +18,9 @@ ## Screenshots / GIFs -- [ ] Changes has been tested on an Android device or Android emulator with API 23 +- [ ] Changes have been tested on an Android device or Android emulator with API 23 - [ ] UI change has been tested on both light and dark themes - [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#accessibility - [ ] Pull request is based on the develop branch From 9fb02162723f38e926a07a843adc628fd0d9845b Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Thu, 24 Aug 2023 11:54:01 +0200 Subject: [PATCH 20/37] Surface send poll start API from rust sdk (#1140) Rust api added in: https://github.com/matrix-org/matrix-rust-sdk/pull/2391 Closes https://github.com/vector-im/element-meta/issues/2010 --- .../libraries/matrix/api/room/MatrixRoom.kt | 16 ++++++++++++ .../libraries/matrix/impl/poll/PollKind.kt | 5 ++++ .../matrix/impl/room/RustMatrixRoom.kt | 21 ++++++++++++++- .../matrix/test/room/FakeMatrixRoom.kt | 26 +++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index f2c51c357e..88d35c83d4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import kotlinx.coroutines.flow.StateFlow @@ -141,6 +142,21 @@ interface MatrixRoom : Closeable { assetType: AssetType? = null, ): Result + /** + * Create a poll in the room. + * + * @param question The question to ask. + * @param answers The list of answers. + * @param maxSelections The maximum number of answers that can be selected. + * @param pollKind The kind of poll to create. + */ + suspend fun createPoll( + question: String, + answers: List, + maxSelections: Int, + pollKind: PollKind, + ): Result + override fun close() = destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt index bde49464ad..d0682edf2c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt @@ -23,3 +23,8 @@ fun RustPollKind.map(): PollKind = when (this) { RustPollKind.DISCLOSED -> PollKind.Disclosed RustPollKind.UNDISCLOSED -> PollKind.Undisclosed } + +fun PollKind.toInner(): RustPollKind = when (this) { + PollKind.Disclosed -> RustPollKind.DISCLOSED + PollKind.Undisclosed -> RustPollKind.UNDISCLOSED +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 878aaa4473..ffe06379d3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType @@ -41,6 +42,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.impl.core.toProgressWatcher import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl import io.element.android.libraries.matrix.impl.media.map +import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.util.destroyAll @@ -378,7 +380,24 @@ class RustMatrixRoom( description = description, zoomLevel = zoomLevel?.toUByte(), assetType = assetType?.toInner(), - txnId = genTransactionId() + txnId = genTransactionId(), + ) + } + } + + override suspend fun createPoll( + question: String, + answers: List, + maxSelections: Int, + pollKind: PollKind, + ): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.createPoll( + question = question, + answers = answers, + maxSelections = maxSelections.toUByte(), + pollKind = pollKind.toInner(), + txnId = genTransactionId(), ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index a660c56a99..88f705162e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType @@ -83,6 +84,7 @@ class FakeMatrixRoom( private var forwardEventResult = Result.success(Unit) private var reportContentResult = Result.success(Unit) private var sendLocationResult = Result.success(Unit) + private var createPollResult = Result.success(Unit) private var progressCallbackValues = emptyList>() val editMessageCalls = mutableListOf() @@ -104,6 +106,9 @@ class FakeMatrixRoom( private val _sentLocations = mutableListOf() val sentLocations: List = _sentLocations + private val _createPollInvocations = mutableListOf() + val createPollInvocations: List = _createPollInvocations + var invitedUserId: UserId? = null private set @@ -305,6 +310,16 @@ class FakeMatrixRoom( return sendLocationResult } + override suspend fun createPoll( + question: String, + answers: List, + maxSelections: Int, + pollKind: PollKind + ): Result = simulateLongTask { + _createPollInvocations.add(CreatePollInvocation(question, answers, maxSelections, pollKind)) + return createPollResult + } + fun givenLeaveRoomError(throwable: Throwable?) { this.leaveRoomError = throwable } @@ -397,6 +412,10 @@ class FakeMatrixRoom( sendLocationResult = result } + fun givenCreatePollResult(result: Result) { + createPollResult = result + } + fun givenProgressCallbackValues(values: List>) { progressCallbackValues = values } @@ -409,3 +428,10 @@ data class SendLocationInvocation( val zoomLevel: Int?, val assetType: AssetType?, ) + +data class CreatePollInvocation( + val question: String, + val answers: List, + val maxSelections: Int, + val pollKind: PollKind, +) From c670fc9e9c07fcc69b9564e2f1254a5ceb63aa97 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 24 Aug 2023 13:43:36 +0200 Subject: [PATCH 21/37] Prevent verification while initial sync is in progress (#1138) * Prevent verification while initial sync is in progress * Add `canVerifySessionFlow` to simplify the check --- changelog.d/1131.bugfix | 1 + .../impl/root/PreferencesRootPresenter.kt | 11 +++-------- .../features/roomlist/impl/RoomListPresenter.kt | 5 ++--- .../roomlist/impl/RoomListPresenterTests.kt | 2 +- .../verification/SessionVerificationService.kt | 6 ++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 17 +++++++---------- .../RustSessionVerificationService.kt | 11 ++++++++++- .../FakeSessionVerificationService.kt | 11 ++++++++--- 8 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 changelog.d/1131.bugfix diff --git a/changelog.d/1131.bugfix b/changelog.d/1131.bugfix new file mode 100644 index 0000000000..23649a19d0 --- /dev/null +++ b/changelog.d/1131.bugfix @@ -0,0 +1 @@ +Only display verification prompt after initial sync is done. diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index b33940b21e..af82a3ab2a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -34,7 +33,6 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -62,11 +60,8 @@ class PreferencesRootPresenter @Inject constructor( val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() } - // Session verification status (unknown, not verified, verified) - val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() - val sessionIsNotVerified by remember { - derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified } - } + // We should display the 'complete verification' option if the current session can be verified + val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false) val accountManagementUrl: MutableState = remember { mutableStateOf(null) @@ -82,7 +77,7 @@ class PreferencesRootPresenter @Inject constructor( logoutState = logoutState, myUser = matrixUser.value, version = versionFormatter.get(), - showCompleteVerification = sessionIsNotVerified, + showCompleteVerification = showCompleteVerification, accountManagementUrl = accountManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, showDeveloperSettings = showDeveloperSettings, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index dbef9c799b..e069634d49 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -39,7 +39,6 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject @@ -73,11 +72,11 @@ class RoomListPresenter @Inject constructor( } // Session verification status (unknown, not verified, verified) - val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() + val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false) var verificationPromptDismissed by rememberSaveable { mutableStateOf(false) } // We combine both values to only display the prompt if the session is not verified and it wasn't dismissed val displayVerificationPrompt by remember { - derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified && !verificationPromptDismissed } + derivedStateOf { canVerifySession && !verificationPromptDismissed } } var displaySearchResults by rememberSaveable { mutableStateOf(false) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index eaa3801e12..d8524d5834 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -202,7 +202,7 @@ class RoomListPresenterTests { fun `present - handle DismissRequestVerificationPrompt`() = runTest { val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient( - roomListService = roomListService + roomListService = roomListService, ) val presenter = createRoomListPresenter( client = matrixClient, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index b2f79c0750..4cb1918ce1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.api.verification +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface SessionVerificationService { @@ -37,6 +38,11 @@ interface SessionVerificationService { */ val sessionVerifiedStatus: StateFlow + /** + * Returns whether the current session needs to be verified and the SDK is ready to start the verification. + */ + val canVerifySessionFlow: Flow + /** * Request verification of the current session. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index c68913b705..4b0afb0b4f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.SyncState 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 @@ -93,8 +92,8 @@ class RustMatrixClient constructor( private val innerRoomListService = syncService.roomListService() private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") - private val verificationService = RustSessionVerificationService() private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope) + private val verificationService = RustSessionVerificationService(rustSyncService) private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, @@ -149,13 +148,11 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) - rustSyncService.syncState - .onEach { syncState -> - if (syncState == SyncState.Running) { - onSlidingSyncUpdate() - } + roomListService.state.onEach { state -> + if (state == RoomListService.State.Running) { + setupVerificationControllerIfNeeded() } - .launchIn(sessionCoroutineScope) + }.launchIn(sessionCoroutineScope) } override suspend fun getRoom(roomId: RoomId): MatrixRoom? = withContext(sessionDispatcher) { @@ -338,8 +335,8 @@ class RustMatrixClient constructor( } } - private fun onSlidingSyncUpdate() { - if (!verificationService.isReady.value) { + private fun setupVerificationControllerIfNeeded() { + if (verificationService.verificationController == null) { try { verificationService.verificationController = client.getSessionVerificationController() } catch (e: Throwable) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index dc65bb74a6..29797ed3c4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -17,20 +17,25 @@ package io.element.android.libraries.matrix.impl.verification import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.impl.sync.RustSyncService import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface import org.matrix.rustcomponents.sdk.SessionVerificationEmoji import javax.inject.Inject -class RustSessionVerificationService @Inject constructor() : SessionVerificationService, SessionVerificationControllerDelegate { +class RustSessionVerificationService @Inject constructor( + private val syncService: RustSyncService, +) : SessionVerificationService, SessionVerificationControllerDelegate { var verificationController: SessionVerificationControllerInterface? = null set(value) { @@ -52,6 +57,10 @@ class RustSessionVerificationService @Inject constructor() : SessionVerification private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus.asStateFlow() + override val canVerifySessionFlow = combine(sessionVerifiedStatus, syncService.syncState) { verificationStatus, syncState -> + syncState == SyncState.Running && verificationStatus == SessionVerifiedStatus.NotVerified + } + override suspend fun requestVerification() = tryOrFail { verificationController?.requestVerification() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 2f7887c537..62b0b39adf 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -27,13 +28,13 @@ class FakeSessionVerificationService : SessionVerificationService { private val _isReady = MutableStateFlow(false) private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) private var _verificationFlowState = MutableStateFlow(VerificationFlowState.Initial) + private var _canVerifySessionFlow = MutableStateFlow(true) private var emojiList = emptyList() var shouldFail = false - override val verificationFlowState: StateFlow - get() = _verificationFlowState - + override val verificationFlowState: StateFlow =_verificationFlowState override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus + override val canVerifySessionFlow: Flow = _canVerifySessionFlow override val isReady: StateFlow = _isReady @@ -77,6 +78,10 @@ class FakeSessionVerificationService : SessionVerificationService { _verificationFlowState.value = state } + fun givenCanVerifySession(canVerify: Boolean) { + _canVerifySessionFlow.value = canVerify + } + fun givenIsReady(value: Boolean) { _isReady.value = value } From cbeab3111d15fb7a91fbb24ea01a73eda1ba8e0f Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Thu, 24 Aug 2023 14:14:59 +0200 Subject: [PATCH 22/37] Update rust sdk to 0.1.47 (#1142) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43c2a25bdb..434c014932 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -146,7 +146,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.46" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.47" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } From b326ca28cc0b9bd7dd2194677c84b7b8bcf09904 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 24 Aug 2023 15:11:05 +0200 Subject: [PATCH 23/37] Improve list items: add lists to dialogs, rework `ListItem` customizations (#1119) * Improve list items: - Create `ListItemContent`. - Create `ListItemStyle`. - Apply those to `ListItem` components. - Create helper list item components for checkboxes, switches, radio buttons. * Create single/multiple selection dialogs. * Create `SingleSelectionListItem` and `MultipleSelectionListItem` - Add `subtitle` to `AlertDialogContents`. - Fix paddings and margins inside dialogs. - Add `ListOption`. * Adds small delay before hiding the single selection dialog. --------- Co-authored-by: ElementBot --- .../components/dialogs/ListOption.kt | 33 ++++ .../dialogs/MultipleSelectionDialog.kt | 151 +++++++++++++++ .../dialogs/SingleSelectionDialog.kt | 131 +++++++++++++ .../components/list/CheckboxListItem.kt | 47 +++++ .../components/list/ListItemContent.kt | 126 +++++++++++++ .../list/MultipleSelectionListItem.kt | 159 ++++++++++++++++ .../components/list/RadioButtonListItem.kt | 47 +++++ .../list/SingleSelectionListItem.kt | 174 ++++++++++++++++++ .../components/list/SwitchListItem.kt | 46 +++++ .../theme/components/AlertDialogContent.kt | 90 +++++++-- .../designsystem/theme/components/Icon.kt | 16 ++ .../designsystem/theme/components/ListItem.kt | 130 ++++++++----- .../theme/components/ListSection.kt | 6 +- .../components/previews/DatePickerPreview.kt | 3 +- .../components/previews/TimePickerPreview.kt | 9 +- ...firmationDialog_0_null,NEXUS_5,1.0,en].png | 4 +- ...ogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png | 4 +- ...onDialogContent_0_null,NEXUS_5,1.0,en].png | 3 + ...ogs_RetryDialog_0_null,NEXUS_5,1.0,en].png | 4 +- ...onDialogContent_0_null,NEXUS_5,1.0,en].png | 3 + ...DialogContent-N_1_null,NEXUS_5,1.0,en].png | 3 + ...DialogContent-N_1_null,NEXUS_5,1.0,en].png | 3 + ...tem-noselection_0_null,NEXUS_5,1.0,en].png | 3 + ...nsupportingtext_0_null,NEXUS_5,1.0,en].png | 3 + ...trailingcontent_0_null,NEXUS_5,1.0,en].png | 3 + ...customformatter_0_null,NEXUS_5,1.0,en].png | 3 + ...,supportingtext_0_null,NEXUS_5,1.0,en].png | 3 + ...tem-noselection_0_null,NEXUS_5,1.0,en].png | 3 + ...nsupportingtext_0_null,NEXUS_5,1.0,en].png | 3 + ...trailingcontent_0_null,NEXUS_5,1.0,en].png | 3 + ..._ProgressDialog_0_null,NEXUS_5,1.0,en].png | 4 +- ...sageandokbutton_0_null,NEXUS_5,1.0,en].png | 4 +- ...iconandokbutton_0_null,NEXUS_5,1.0,en].png | 4 +- ...itleandokbutton_0_null,NEXUS_5,1.0,en].png | 4 +- ...xt-largepadding_0_null,NEXUS_5,1.0,en].png | 4 +- ...xt-smallpadding_0_null,NEXUS_5,1.0,en].png | 4 +- 36 files changed, 1155 insertions(+), 85 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt new file mode 100644 index 0000000000..cfc414862f --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt @@ -0,0 +1,33 @@ +/* + * 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 kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +/** + * Used to store the visual data for a list option. + */ +data class ListOption( + val title: String, + val subtitle: String? = null, +) + +/** Creates an immutable list of [ListOption]s from the given [values], using them as titles. */ +fun listOptionOf(vararg values: String): ImmutableList { + return values.map { ListOption(it) }.toImmutableList() +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt new file mode 100644 index 0000000000..af0bb4e1d1 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt @@ -0,0 +1,151 @@ +/* + * 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.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.airbnb.android.showkase.annotation.ShowkaseComposable +import io.element.android.libraries.designsystem.components.list.CheckboxListItem +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.DialogPreview +import io.element.android.libraries.designsystem.theme.components.ListSupportingText +import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MultipleSelectionDialog( + options: ImmutableList, + onConfirmClicked: (List) -> Unit, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + confirmButtonTitle: String = stringResource(CommonStrings.action_confirm), + dismissButtonTitle: String = stringResource(CommonStrings.action_cancel), + title: String? = null, + subtitle: String? = null, + initialSelection: ImmutableList = persistentListOf(), +) { + val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let { + @Composable { + ListSupportingText( + text = it, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + AlertDialog( + modifier = modifier, + onDismissRequest = onDismissRequest, + ) { + MultipleSelectionDialogContent( + title = title, + subtitle = decoratedSubtitle, + options = options, + confirmButtonTitle = confirmButtonTitle, + onConfirmClicked = onConfirmClicked, + dismissButtonTitle = dismissButtonTitle, + onDismissRequest = onDismissRequest, + initialSelected = initialSelection, + ) + } +} + +@Composable +internal fun MultipleSelectionDialogContent( + options: ImmutableList, + confirmButtonTitle: String, + onConfirmClicked: (List) -> Unit, + dismissButtonTitle: String, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + initialSelected: ImmutableList = persistentListOf(), + subtitle: @Composable (() -> Unit)? = null, +) { + val selectedOptionIndexes = remember { initialSelected.toMutableStateList() } + + fun isSelected(index: Int) = selectedOptionIndexes.any { it == index } + + SimpleAlertDialogContent( + title = title, + subtitle = subtitle, + modifier = modifier, + submitText = confirmButtonTitle, + onSubmitClicked = { + onConfirmClicked(selectedOptionIndexes.toList()) + }, + cancelText = dismissButtonTitle, + onCancelClicked = onDismissRequest, + applyPaddingToContents = false, + ) { + LazyColumn { + itemsIndexed(options) { index, option -> + CheckboxListItem( + headline = option.title, + checked = isSelected(index), + onChange = { + if (isSelected(index)) { + selectedOptionIndexes.remove(index) + } else { + selectedOptionIndexes.add(index) + } + }, + supportingText = option.subtitle, + compactLayout = true, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } +} + +@DayNightPreviews +@ShowkaseComposable(group = PreviewGroup.Dialogs) +@Composable +internal fun MultipleSelectionDialogContentPreview() { + ElementPreview(showBackground = false) { + DialogPreview { + val options = persistentListOf( + ListOption("Option 1", "Supporting line text lorem ipsum dolor sit amet, consectetur."), + ListOption("Option 2"), + ListOption("Option 3"), + ) + MultipleSelectionDialogContent( + title = "Dialog title", + options = options, + onConfirmClicked = {}, + onDismissRequest = {}, + confirmButtonTitle = "Save", + dismissButtonTitle = "Cancel", + initialSelected = persistentListOf(0), + ) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt new file mode 100644 index 0000000000..5b75318136 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt @@ -0,0 +1,131 @@ +/* + * 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.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.airbnb.android.showkase.annotation.ShowkaseComposable +import io.element.android.libraries.designsystem.components.list.RadioButtonListItem +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.DialogPreview +import io.element.android.libraries.designsystem.theme.components.ListSupportingText +import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SingleSelectionDialog( + options: ImmutableList, + onOptionSelected: (Int) -> Unit, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + subtitle: String? = null, + dismissButtonTitle: String = stringResource(CommonStrings.action_cancel), + initialSelection: Int? = null, +) { + val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let { + @Composable { + ListSupportingText( + text = it, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + AlertDialog( + modifier = modifier, + onDismissRequest = onDismissRequest, + ) { + SingleSelectionDialogContent( + title = title, + subtitle = decoratedSubtitle, + options = options, + onOptionSelected = onOptionSelected, + dismissButtonTitle = dismissButtonTitle, + onDismissRequest = onDismissRequest, + initialSelection = initialSelection, + ) + } +} + +@Composable +internal fun SingleSelectionDialogContent( + options: ImmutableList, + onOptionSelected: (Int) -> Unit, + onDismissRequest: () -> Unit, + dismissButtonTitle: String, + modifier: Modifier = Modifier, + title: String? = null, + initialSelection: Int? = null, + subtitle: @Composable (() -> Unit)? = null, +) { + SimpleAlertDialogContent( + title = title, + subtitle = subtitle, + modifier = modifier, + cancelText = dismissButtonTitle, + onCancelClicked = onDismissRequest, + applyPaddingToContents = false, + ) { + LazyColumn { + itemsIndexed(options) { index, option -> + RadioButtonListItem( + headline = option.title, + supportingText = option.subtitle, + selected = index == initialSelection, + onSelected = { onOptionSelected(index) }, + compactLayout = true, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } +} + +@DayNightPreviews +@ShowkaseComposable(group = PreviewGroup.Dialogs) +@Composable +internal fun SingleSelectionDialogContentPreview() { + ElementPreview(showBackground = false) { + DialogPreview { + val options = persistentListOf( + ListOption("Option 1"), + ListOption("Option 2"), + ListOption("Option 3"), + ) + SingleSelectionDialogContent( + title = "Dialog title", + options = options, + onOptionSelected = {}, + onDismissRequest = {}, + dismissButtonTitle = "Cancel", + initialSelection = 0 + ) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt new file mode 100644 index 0000000000..d2bc30262a --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt @@ -0,0 +1,47 @@ +/* + * 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.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun CheckboxListItem( + headline: String, + checked: Boolean, + onChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + trailingContent: ListItemContent? = null, + enabled: Boolean = true, + style: ListItemStyle = ListItemStyle.Default, + compactLayout: Boolean = false, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = ListItemContent.Checkbox(checked, null, enabled, compact = compactLayout), + trailingContent = trailingContent, + style = style, + enabled = enabled, + onClick = { onChange(!checked) }, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt new file mode 100644 index 0000000000..cac8b557ee --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt @@ -0,0 +1,126 @@ +/* + * 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.list + +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Checkbox as CheckboxComponent +import io.element.android.libraries.designsystem.theme.components.Icon as IconComponent +import io.element.android.libraries.designsystem.theme.components.RadioButton as RadioButtonComponent +import io.element.android.libraries.designsystem.theme.components.Switch as SwitchComponent +import io.element.android.libraries.designsystem.theme.components.Text as TextComponent + +/** + * This is a helper to set default leading and trailing content for [ListItem]s. + */ +sealed interface ListItemContent { + /** + * Default Switch content for [ListItem]. + * @param checked The current state of the switch. + * @param onChange Callback when the switch is toggled: it should only be set to override the default click behaviour in the [ListItem]. + * @param enabled Whether the switch is enabled or not. + */ + data class Switch( + val checked: Boolean, + val onChange: ((Boolean) -> Unit)? = null, + val enabled: Boolean = true + ) : ListItemContent + + /** + * Default Checkbox content for [ListItem]. + * @param checked The current state of the checkbox. + * @param onChange Callback when the checkbox is toggled: it should only be set to override the default click behaviour in the [ListItem]. + * @param enabled Whether the checkbox is enabled or not. + * @param compact Reduces the size of the component to make the wrapping [ListItem] smaller. + * This is especially useful when the [ListItem] is used inside a Dialog. `false` by default. + */ + data class Checkbox( + val checked: Boolean, + val onChange: ((Boolean) -> Unit)? = null, + val enabled: Boolean = true, + val compact: Boolean = false + ) : ListItemContent + + /** + * Default RadioButton content for [ListItem]. + * @param selected The current state of the radio button. + * @param onClick Callback when the radio button is toggled: it should only be set to override the default click behaviour in the [ListItem]. + * @param enabled Whether the radio button is enabled or not. + * @param compact Reduces the size of the component to make the wrapping [ListItem] smaller. + * This is especially useful when the [ListItem] is used inside a Dialog. `false` by default. + */ + data class RadioButton( + val selected: Boolean, + val onClick: (() -> Unit)? = null, + val enabled: Boolean = true, + val compact: Boolean = false + ) : ListItemContent + + /** + * Default Icon content for [ListItem]. Sets the Icon component to a predefined size. + * @param iconSource The icon to display, using [IconSource.getPainter]. + */ + data class Icon(val iconSource: IconSource) : ListItemContent + + /** + * Default Text content for [ListItem]. Sets the Text component to a max size and clips overflow. + * @param text The text to display. + */ + data class Text(val text: String) : ListItemContent + + /** Displays any custom content. */ + data class Custom(val content: @Composable () -> Unit) : ListItemContent + + @Composable + fun View() { + when (this) { + is Switch -> SwitchComponent( + checked = checked, + onCheckedChange = onChange, + enabled = enabled + ) + is Checkbox -> CheckboxComponent( + modifier = if (compact) Modifier.size(maxCompactSize) else Modifier, + checked = checked, + onCheckedChange = onChange, + enabled = enabled + ) + is RadioButton -> RadioButtonComponent( + modifier = if (compact) Modifier.size(maxCompactSize) else Modifier, + selected = selected, + onClick = onClick, + enabled = enabled + ) + is Icon -> IconComponent( + modifier = Modifier.size(maxCompactSize), + painter = iconSource.getPainter(), + contentDescription = iconSource.contentDescription + ) + is Text -> TextComponent(modifier = Modifier.widthIn(max = 128.dp), text = text, maxLines = 1, overflow = TextOverflow.Ellipsis) + is Custom -> content() + } + } +} + +private val maxCompactSize = DpSize(24.dp, 24.dp) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt new file mode 100644 index 0000000000..8a0bc1b4a9 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt @@ -0,0 +1,159 @@ +/* + * 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.list + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.element.android.libraries.designsystem.components.dialogs.ListOption +import io.element.android.libraries.designsystem.components.dialogs.MultipleSelectionDialog +import io.element.android.libraries.designsystem.components.dialogs.listOptionOf +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +@Composable +fun MultipleSelectionListItem( + headline: String, + options: ImmutableList, + onSelectionChanged: (List) -> Unit, + resultFormatter: (List) -> String?, + modifier: Modifier = Modifier, + supportingText: String? = null, + leadingContent: ListItemContent? = null, + selected: ImmutableList = persistentListOf(), + displayResultInTrailingContent: Boolean = false, +) { + val selectedIndexes = remember(selected) { selected.toMutableStateList() } + val selectedItemsText by remember { derivedStateOf { resultFormatter(selectedIndexes) } } + + val decoratedSupportedText: @Composable (() -> Unit)? = when { + !selectedItemsText.isNullOrBlank() && !displayResultInTrailingContent -> { + @Composable { + Text(selectedItemsText!!) + } + } + supportingText != null -> { + @Composable { + Text(supportingText) + } + } + else -> null + } + + val trailingContent: ListItemContent? = if (!selectedItemsText.isNullOrBlank() && displayResultInTrailingContent) { + ListItemContent.Text(selectedItemsText!!) + } else { + null + } + + var displaySelectionDialog by rememberSaveable { mutableStateOf(false) } + + ListItem( + modifier = modifier, + headlineContent = { Text(text = headline) }, + supportingContent = decoratedSupportedText, + leadingContent = leadingContent, + trailingContent = trailingContent, + onClick = { displaySelectionDialog = true } + ) + + if (displaySelectionDialog) { + MultipleSelectionDialog( + title = headline, + options = options, + onConfirmClicked = { newSelectedIndexes -> + if (newSelectedIndexes != selectedIndexes.toList()) { + onSelectionChanged(newSelectedIndexes) + selectedIndexes.clear() + selectedIndexes.addAll(newSelectedIndexes) + } + displaySelectionDialog = false + }, + onDismissRequest = { displaySelectionDialog = false }, + initialSelection = selectedIndexes.toImmutableList(), + ) + } +} + +@Preview("Multiple selection List item - no selection", group = PreviewGroup.ListItems) +@Composable +internal fun MutipleSelectionListItemPreview() { + ElementThemedPreview { + val options = listOptionOf("Option 1", "Option 2", "Option 3") + MultipleSelectionListItem( + headline = "Headline", + options = options, + onSelectionChanged = {}, + supportingText = "Supporting text", + resultFormatter = { result -> formatResult(result, options) }, + ) + } +} + +@Preview("Multiple selection List item - selection in supporting text", group = PreviewGroup.ListItems) +@Composable +internal fun MutipleSelectionListItemSelectedPreview() { + ElementThemedPreview { + val options = listOptionOf("Option 1", "Option 2", "Option 3") + val selected = persistentListOf(0, 2) + MultipleSelectionListItem( + headline = "Headline", + options = options, + onSelectionChanged = {}, + supportingText = "Supporting text", + resultFormatter = { + val selectedValues = formatResult(it, options) + "Selected: $selectedValues" + }, + selected = selected, + ) + } +} + +@Preview("Multiple selection List item - selection in trailing content", group = PreviewGroup.ListItems) +@Composable +internal fun MutipleSelectionListItemSelectedTrailingContentPreview() { + ElementThemedPreview { + val options = listOptionOf("Option 1", "Option 2", "Option 3") + val selected = persistentListOf(0, 2) + MultipleSelectionListItem( + headline = "Headline", + options = options, + onSelectionChanged = {}, + supportingText = "Supporting text", + resultFormatter = { selected.size.toString() }, + displayResultInTrailingContent = true, + selected = selected, + ) + } +} + +private fun formatResult(result: List, options: ImmutableList): String? { + return options.mapIndexedNotNull { index, value -> value.title.takeIf { result.contains(index) } }.joinToString(", ").takeIf { it.isNotEmpty() } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt new file mode 100644 index 0000000000..fff038121e --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt @@ -0,0 +1,47 @@ +/* + * 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.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun RadioButtonListItem( + headline: String, + selected: Boolean, + onSelected: () -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + trailingContent: ListItemContent? = null, + style: ListItemStyle = ListItemStyle.Default, + enabled: Boolean = true, + compactLayout: Boolean = false, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = ListItemContent.RadioButton(selected, null, enabled, compact = compactLayout), + trailingContent = trailingContent, + style = style, + enabled = enabled, + onClick = onSelected, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt new file mode 100644 index 0000000000..01dff71991 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt @@ -0,0 +1,174 @@ +/* + * 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.list + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.element.android.libraries.designsystem.components.dialogs.ListOption +import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog +import io.element.android.libraries.designsystem.components.dialogs.listOptionOf +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds + +@Composable +fun SingleSelectionListItem( + headline: String, + options: ImmutableList, + onSelectionChanged: (Int) -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + leadingContent: ListItemContent? = null, + resultFormatter: (Int) -> String? = { options.getOrNull(it)?.title }, + selected: Int? = null, + displayResultInTrailingContent: Boolean = false, +) { + val coroutineScope = rememberCoroutineScope() + + var selectedIndex by rememberSaveable(selected) { mutableStateOf(selected) } + val selectedItem by remember { derivedStateOf { selectedIndex?.let { resultFormatter(it) } } } + val decoratedSupportedText: @Composable (() -> Unit)? = if (!selectedItem.isNullOrBlank() && !displayResultInTrailingContent) { + @Composable { + Text(selectedItem!!) + } + } else { + supportingText?.let { + @Composable { + Text(it) + } + } + } + val trailingContent: ListItemContent? = if (!selectedItem.isNullOrBlank() && displayResultInTrailingContent) { + ListItemContent.Text(selectedItem!!) + } else { + null + } + + var displaySelectionDialog by rememberSaveable { mutableStateOf(false) } + + ListItem( + modifier = modifier, + headlineContent = { Text(text = headline) }, + supportingContent = decoratedSupportedText, + leadingContent = leadingContent, + trailingContent = trailingContent, + onClick = { displaySelectionDialog = true } + ) + + if (displaySelectionDialog) { + SingleSelectionDialog( + title = headline, + options = options, + onOptionSelected = { index -> + if (index != selectedIndex) { + onSelectionChanged(index) + selectedIndex = index + } + // Delay hiding the dialog for a bit so the new state is displayed in it before being dismissed + coroutineScope.launch { + delay(0.5.seconds) + displaySelectionDialog = false + } + }, + onDismissRequest = { displaySelectionDialog = false }, + initialSelection = selectedIndex, + ) + } +} + +@Preview("Single selection List item - no selection", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + onSelectionChanged = {}, + ) + } +} + +@Preview("Single selection List item - no selection, supporting text", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemUnselectedWithSupportingTextPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + ) + } +} + +@Preview("Single selection List item - selection in supporting text", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemSelectedInSupportingTextPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + selected = 1, + ) + } +} + +@Preview("Single selection List item - selection in trailing content", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemSelectedInTrailingContentPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + selected = 1, + displayResultInTrailingContent = true, + ) + } +} + +@Preview("Single selection List item - custom formatter", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemCustomFormattertPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + resultFormatter = { "Selected index: $it"}, + selected = 1, + displayResultInTrailingContent = true, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt new file mode 100644 index 0000000000..bbd7b11396 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt @@ -0,0 +1,46 @@ +/* + * 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.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun SwitchListItem( + headline: String, + value: Boolean, + onChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + leadingContent: ListItemContent? = null, + enabled: Boolean = true, + style: ListItemStyle = ListItemStyle.Default, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = leadingContent, + trailingContent = ListItemContent.Switch(value, null, enabled), + style = style, + enabled = enabled, + onClick = { onChange(!value) }, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt index a3c7274c45..c9bb2d9dd5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.designsystem.theme.components +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -55,11 +56,49 @@ internal fun SimpleAlertDialogContent( onCancelClicked: () -> Unit, modifier: Modifier = Modifier, title: String? = null, + subtitle: @Composable (() -> Unit)? = null, submitText: String? = null, onSubmitClicked: () -> Unit = {}, thirdButtonText: String? = null, onThirdButtonClicked: () -> Unit = {}, + applyPaddingToContents: Boolean = true, icon: @Composable (() -> Unit)? = null, +) { + SimpleAlertDialogContent( + content = { + Text( + text = content, + style = ElementTheme.materialTypography.bodyMedium, + ) + }, + cancelText = cancelText, + onCancelClicked = onCancelClicked, + modifier = modifier, + title = title, + subtitle = subtitle, + submitText = submitText, + onSubmitClicked = onSubmitClicked, + thirdButtonText = thirdButtonText, + onThirdButtonClicked = onThirdButtonClicked, + icon = icon, + applyPaddingToContents = applyPaddingToContents, + ) +} + +@Composable +internal fun SimpleAlertDialogContent( + cancelText: String, + onCancelClicked: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + subtitle: @Composable (() -> Unit)? = null, + submitText: String? = null, + onSubmitClicked: () -> Unit = {}, + thirdButtonText: String? = null, + onThirdButtonClicked: () -> Unit = {}, + applyPaddingToContents: Boolean = true, + icon: @Composable (() -> Unit)? = null, + content: @Composable () -> Unit, ) { AlertDialogContent( buttons = { @@ -99,12 +138,8 @@ internal fun SimpleAlertDialogContent( ) } }, - text = { - Text( - text = content, - style = ElementTheme.materialTypography.bodyMedium, - ) - }, + subtitle = subtitle, + content = content, shape = DialogContentDefaults.shape, containerColor = DialogContentDefaults.containerColor, iconContentColor = DialogContentDefaults.iconContentColor, @@ -117,6 +152,7 @@ internal fun SimpleAlertDialogContent( // TextButtons will not consume this provided content color value, and will used their // own defined or default colors. buttonContentColor = MaterialTheme.colorScheme.primary, + applyPaddingToContents = applyPaddingToContents, ) } @@ -128,7 +164,8 @@ internal fun AlertDialogContent( buttons: @Composable () -> Unit, icon: (@Composable () -> Unit)?, title: (@Composable () -> Unit)?, - text: @Composable (() -> Unit)?, + subtitle: @Composable (() -> Unit)?, + content: @Composable (() -> Unit)?, shape: Shape, containerColor: Color, tonalElevation: Dp, @@ -137,6 +174,7 @@ internal fun AlertDialogContent( titleContentColor: Color, textContentColor: Color, modifier: Modifier = Modifier, + applyPaddingToContents: Boolean = true, ) { Surface( modifier = modifier, @@ -145,12 +183,21 @@ internal fun AlertDialogContent( tonalElevation = tonalElevation, ) { Column( - modifier = Modifier.padding(DialogContentDefaults.externalPadding) + modifier = Modifier.padding( + if (applyPaddingToContents) { + // We can just apply the same padding to the whole dialog contents + DialogContentDefaults.externalPadding + } else { + // We should only apply vertical padding in this case, every component will apply the horizontal content individually + DialogContentDefaults.externalVerticalPadding + } + ) ) { icon?.let { CompositionLocalProvider(LocalContentColor provides iconContentColor) { Box( Modifier + .then(if (applyPaddingToContents) Modifier else Modifier.padding(DialogContentDefaults.externalHorizontalPadding)) .padding(DialogContentDefaults.iconPadding) .align(Alignment.CenterHorizontally) ) { @@ -165,6 +212,12 @@ internal fun AlertDialogContent( Box( // Align the title to the center when an icon is present. Modifier + .then( + if (applyPaddingToContents) + Modifier + else + Modifier.padding(DialogContentDefaults.externalHorizontalPadding) + ) .padding(DialogContentDefaults.titlePadding) .align( if (icon == null) { @@ -179,23 +232,28 @@ internal fun AlertDialogContent( } } } - text?.let { + subtitle?.invoke() + content?.let { CompositionLocalProvider(LocalContentColor provides textContentColor) { - val textStyle = - MaterialTheme.typography.bodyMedium + val textStyle = MaterialTheme.typography.bodyMedium ProvideTextStyle(textStyle) { Box( Modifier .weight(weight = 1f, fill = false) + // We don't apply padding here if it wasn't applied to the root component, this allows us to have a full width content .padding(DialogContentDefaults.textPadding) .align(Alignment.Start) ) { - text() + content() } } } } - Box(modifier = Modifier.align(Alignment.End)) { + Box( + modifier = Modifier + .then(if (applyPaddingToContents) Modifier else Modifier.padding(DialogContentDefaults.externalHorizontalPadding)) + .align(Alignment.End) + ) { CompositionLocalProvider(LocalContentColor provides buttonContentColor) { val textStyle = MaterialTheme.typography.labelLarge @@ -304,6 +362,7 @@ private fun AlertDialogFlowRow( internal fun DialogPreview(content: @Composable () -> Unit) { Box( modifier = Modifier + .background(ElementTheme.materialColors.onSurfaceVariant) .sizeIn(minWidth = DialogMinWidth, maxWidth = DialogMaxWidth) .padding(20.dp), propagateMinConstraints = true @@ -313,8 +372,11 @@ internal fun DialogPreview(content: @Composable () -> Unit) { } internal object DialogContentDefaults { + private val externalPaddingDp = 24.dp val shape = RoundedCornerShape(12.dp) - val externalPadding = PaddingValues(all = 24.dp) + val externalPadding = PaddingValues(all = externalPaddingDp) + val externalHorizontalPadding = PaddingValues(horizontal = externalPaddingDp) + val externalVerticalPadding = PaddingValues(vertical = externalPaddingDp) val titlePadding = PaddingValues(bottom = 16.dp) val iconPadding = PaddingValues(bottom = 8.dp) val textPadding = PaddingValues(bottom = 16.dp) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt index 24de433058..af63763113 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -123,6 +124,21 @@ fun Icon( ) } +@Composable +fun Icon( + painter: Painter, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current, +) { + androidx.compose.material3.Icon( + painter = painter, + contentDescription = contentDescription, + modifier = modifier, + tint = tint + ) +} + @Preview(group = PreviewGroup.Icons) @Composable internal fun IconImageVectorPreview() = diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt index e0be3486b7..6be9089d11 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.clickable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.ListItemColors import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle @@ -29,9 +30,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.theme.ElementTheme @@ -55,35 +58,27 @@ fun ListItem( headlineContent: @Composable () -> Unit, modifier: Modifier = Modifier, supportingContent: @Composable (() -> Unit)? = null, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, style: ListItemStyle = ListItemStyle.Default, enabled: Boolean = true, onClick: (() -> Unit)? = null, ) { - val headlineColor = if (enabled) when (style) { - ListItemStyle.Destructive -> ElementTheme.colors.textCriticalPrimary - else -> ElementTheme.colors.textPrimary - } else { - // We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132 - ElementTheme.colors.textDisabled - } + val colors = ListItemDefaults.colors( + containerColor = Color.Transparent, + headlineColor = style.headlineColor(), + leadingIconColor = style.leadingIconColor(), + trailingIconColor = style.trailingIconColor(), + supportingColor = style.supportingTextColor(), + disabledHeadlineColor = ListItemDefaultColors.headlineDisabled, + disabledLeadingIconColor = ListItemDefaultColors.iconDisabled, + disabledTrailingIconColor = ListItemDefaultColors.iconDisabled, + ) - val supportingContentColor = if (enabled) { - ElementTheme.materialColors.onSurfaceVariant - } else { - // We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132 - ElementTheme.colors.textDisabled - } - - val leadingTrailingContentColor = if (enabled) when (style) { - ListItemStyle.Primary -> ElementTheme.colors.iconPrimary - ListItemStyle.Destructive -> ElementTheme.colors.iconCriticalPrimary - else -> ElementTheme.colors.iconTertiary - } else { - // We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132 - ElementTheme.colors.iconDisabled - } + // We cannot just pass the disabled colors, they must be set manually: https://issuetracker.google.com/issues/280480132 + val headlineColor = if (enabled) colors.headlineColor else colors.disabledHeadlineColor + val leadingContentColor = if (enabled) colors.leadingIconColor else colors.disabledLeadingIconColor + val trailingContentColor = if (enabled) colors.trailingIconColor else colors.disabledTrailingIconColor val decoratedHeadlineContent: @Composable () -> Unit = { CompositionLocalProvider( @@ -97,7 +92,6 @@ fun ListItem( { CompositionLocalProvider( LocalTextStyle provides ElementTheme.materialTypography.bodyMedium, - LocalContentColor provides supportingContentColor, ) { content() } @@ -106,31 +100,31 @@ fun ListItem( val decoratedLeadingContent: (@Composable () -> Unit)? = leadingContent?.let { content -> { CompositionLocalProvider( - LocalContentColor provides leadingTrailingContentColor, + LocalContentColor provides leadingContentColor, ) { - content() + content.View() } } } val decoratedTrailingContent: (@Composable () -> Unit)? = trailingContent?.let { content -> { CompositionLocalProvider( - LocalContentColor provides leadingTrailingContentColor, LocalTextStyle provides ElementTheme.typography.fontBodyMdRegular, + LocalContentColor provides trailingContentColor, ) { - content() + content.View() } } } androidx.compose.material3.ListItem( headlineContent = decoratedHeadlineContent, - modifier = modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}), + modifier = Modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}).then(modifier), overlineContent = null, supportingContent = decoratedSupportingContent, leadingContent = decoratedLeadingContent, trailingContent = decoratedTrailingContent, - colors = ListItemDefaults.colors(), // These aren't really used since we need the workaround for the disabled state color + colors = colors, tonalElevation = 0.dp, shadowElevation = 0.dp, ) @@ -143,6 +137,47 @@ sealed interface ListItemStyle { object Default : ListItemStyle object Primary: ListItemStyle object Destructive : ListItemStyle + + @Composable fun headlineColor() = when (this) { + Default, Primary -> ListItemDefaultColors.headline + Destructive -> ElementTheme.colors.textCriticalPrimary + } + + @Composable fun supportingTextColor() = when (this) { + Default, Primary -> ListItemDefaultColors.supportingText + // FIXME once we have a defined color for this value + Destructive -> ElementTheme.colors.textCriticalPrimary.copy(alpha = 0.8f) + } + + @Composable fun leadingIconColor() = when (this) { + Default -> ListItemDefaultColors.icon + Primary -> ElementTheme.colors.iconPrimary + Destructive -> ElementTheme.colors.iconCriticalPrimary + } + + @Composable fun trailingIconColor() = when (this) { + Default -> ListItemDefaultColors.icon + Primary -> ElementTheme.colors.iconPrimary + Destructive -> ElementTheme.colors.iconCriticalPrimary + } +} + +object ListItemDefaultColors { + val headline: Color @Composable get() = ElementTheme.colors.textPrimary + val headlineDisabled: Color @Composable get() = ElementTheme.colors.textDisabled + val supportingText: Color @Composable get() = ElementTheme.materialColors.onSurfaceVariant + val icon: Color @Composable get() = ElementTheme.colors.iconTertiary + val iconDisabled: Color @Composable get() = ElementTheme.colors.iconDisabled + + val colors: ListItemColors @Composable get() = ListItemDefaults.colors( + headlineColor = headline, + supportingColor = supportingText, + leadingIconColor = icon, + trailingIconColor = icon, + disabledHeadlineColor = headlineDisabled, + disabledLeadingIconColor = iconDisabled, + disabledTrailingIconColor = iconDisabled, + ) } // region: Simple list item @@ -335,8 +370,9 @@ private object PreviewItems { @Composable fun ThreeLinesListItemPreview( modifier: Modifier = Modifier, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, + style: ListItemStyle = ListItemStyle.Default, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, ) { ElementThemedPreview { ListItem( @@ -344,6 +380,7 @@ private object PreviewItems { supportingContent = PreviewItems.text(), leadingContent = leadingContent, trailingContent = trailingContent, + style = style, modifier = modifier, ) } @@ -352,8 +389,9 @@ private object PreviewItems { @Composable fun TwoLinesListItemPreview( modifier: Modifier = Modifier, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, + style: ListItemStyle = ListItemStyle.Default, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, ) { ElementThemedPreview { ListItem( @@ -361,6 +399,7 @@ private object PreviewItems { supportingContent = PreviewItems.textSingleLine(), leadingContent = leadingContent, trailingContent = trailingContent, + style = style, modifier = modifier, ) } @@ -369,9 +408,9 @@ private object PreviewItems { @Composable fun OneLineListItemPreview( modifier: Modifier = Modifier, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, style: ListItemStyle = ListItemStyle.Default, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, enabled: Boolean = true, ) { ElementThemedPreview { @@ -402,25 +441,22 @@ private object PreviewItems { } @Composable - fun checkbox() = @Composable { + fun checkbox(): ListItemContent { var checked by remember { mutableStateOf(false) } - Checkbox(checked = checked, onCheckedChange = { checked = !checked }) + return ListItemContent.Checkbox(checked = checked, onChange = { checked = !checked }) } @Composable - fun radioButton() = @Composable { + fun radioButton(): ListItemContent { var checked by remember { mutableStateOf(false) } - RadioButton(selected = checked, onClick = { checked = !checked }) + return ListItemContent.RadioButton(selected = checked, onClick = { checked = !checked }) } @Composable - fun switch() = @Composable { + fun switch() : ListItemContent { var checked by remember { mutableStateOf(false) } - Switch(checked = checked, onCheckedChange = { checked = !checked }) + return ListItemContent.Switch(checked = checked, onChange = { checked = !checked }) } - @Composable - fun icon() = @Composable { - Icon(imageVector = Icons.Outlined.Share, contentDescription = null) - } + fun icon() = ListItemContent.Icon(iconSource = IconSource.Vector(Icons.Outlined.Share)) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt index 4e51795ccd..4269ff04f8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable @@ -32,6 +33,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.theme.ElementTheme @@ -249,7 +251,7 @@ internal fun ListSupportingTextDefaultPaddingPreview() { internal fun ListSupportingTextSmallPaddingPreview() { ElementThemedPreview { Column { - ListItem(headlineContent = { Text("A title") }, leadingContent = { Icon(Icons.Default.Share, null) }) + ListItem(headlineContent = { Text("A title") }, leadingContent = ListItemContent.Icon(IconSource.Vector(Icons.Outlined.Share))) ListSupportingText( text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more", contentPadding = ListSupportingTextDefaults.Padding.SmallLeadingContent, @@ -263,7 +265,7 @@ internal fun ListSupportingTextSmallPaddingPreview() { internal fun ListSupportingTextLargePaddingPreview() { ElementThemedPreview { Column { - ListItem(headlineContent = { Text("A title") }, leadingContent = { Switch(checked = true, onCheckedChange = null) }) + ListItem(headlineContent = { Text("A title") }, leadingContent = ListItemContent.Switch(checked = true, onChange = {})) ListSupportingText( text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more", contentPadding = ListSupportingTextDefaults.Padding.LargeLeadingContent, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt index 45b5eb39be..e0435ee30e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt @@ -50,7 +50,8 @@ private fun ContentToPreview() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { DatePicker(state = state, showModeToggle = true) }, + subtitle = null, + content = { DatePicker(state = state, showModeToggle = true) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt index 7aae42ed0e..d900dd6d8b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt @@ -39,7 +39,8 @@ internal fun TimePickerHorizontalPreview() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Horizontal) }, + subtitle = null, + content = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Horizontal) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, @@ -60,7 +61,8 @@ internal fun TimePickerVerticalPreviewLight() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Vertical) }, + subtitle = null, + content = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Vertical) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, @@ -85,7 +87,8 @@ internal fun TimePickerVerticalPreviewDark() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { TimePicker(state = pickerState, layoutType = TimePickerLayoutType.Vertical) }, + subtitle = null, + content = { TimePicker(state = pickerState, layoutType = TimePickerLayoutType.Vertical) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png index fec2a14e4d..4d9869c5eb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:461788974e3bee520d26dbec39b2721c8c2cc0b8b907a7850e8761784abc2792 -size 23763 +oid sha256:1e13461ce6fe7e873fa2796520cf107e6e041559b43c8d911ba78848de34b398 +size 24585 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png index 40868aec6e..0146de16cc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:449a17861383c164774ada4098e8bfd5cb364de211d25b83baeb930e0f40020d -size 17446 +oid sha256:eebe29b4f5b0aa38316bb54e439f9ab541185251deffbae07ba14342b453c5cb +size 17528 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d9cb1ead47 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76aadb1f17f02efa6b0dbbafec5e5a08a1bfdb08e540b494eeaa48825c7c130a +size 30155 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png index 84b542ad82..915f6f8539 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd3224a9292b9268874f355b681f28ae8a530341fdded66c85e36db9a14818a7 -size 23194 +oid sha256:a19f93d6d31a074b2e5c1d691505881e8424361f1d244366bc6ee5fbd5de15f6 +size 23225 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..98594b6108 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18f79874b2342ffe0e6cd759f381e452767c6c824069cc04441c49914b2860d8 +size 20757 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f92655c313 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f540b6b0f761949348fbf65596103d482b0f3c29eb8c99750756dce1c40e674e +size 28692 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..de25110b80 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa6c275983acd5902a73fecc03a3050f1dbf70c272557bf8240c7bc5ec6c1a45 +size 19809 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..49e9fd7ed5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb1e859e011c8359935b0254472be653d878146a3735196f9edee324a204b1c1 +size 14192 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..dbdcc65b12 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:504265795b72916040c091b767ffd278fe273a83d4f4a1683c06d805b3e3a16d +size 17818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ec20ff6d63 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a0eb132bfaf1c90c6507c12b25150fb29e260ae3bd0a5760f5fb2e9483fda26 +size 14814 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3d0c5e4a60 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:624a00c3098b821482dcdb9529db12cfcf46dc82e136d8b255561125e932862b +size 19467 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..49e9fd7ed5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb1e859e011c8359935b0254472be653d878146a3735196f9edee324a204b1c1 +size 14192 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..229ae01f43 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d86d982afc4add4d3d12574814ec686ad90f20f5098b965560a7a5e413b7f1dd +size 8441 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..26c7c0519f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c442cb40b7c6a26d6014005587cd618d213ccb003e8d107faf8d688f069395f3 +size 11844 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..41bc1be58d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2ffe9b836ee6dc753f7e6faf10814d7a52611cd1ff9d11a539cc045fdafc1db +size 17227 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png index e7b5185fbe..fd42ba21ef 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6695c2d908300730424b28de599a966a3a7ea44f056ac6de745c713915a6a0a5 -size 21745 +oid sha256:ee64fde90f43c95741d8f39e9bf4c770cf542dfaadaf611e98151e9419f1bf9d +size 22069 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png index 14cb782be4..32d5b45a6b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:033fc99c9948b786db535249490b123f9830b4c0c6b8d29d40188e917b8e1209 -size 51022 +oid sha256:a38a4a04c3527e9d1bb7efc3c4b91f6cfbd4eab93c84d316e68a4fc558e137cd +size 51533 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png index e8f354cd2c..30ca4d7948 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61c633685a0afc23e5f896bb5576658a7444b0a0fa7a1aefc4be9fae86fcd3e7 -size 57358 +oid sha256:0182810e251fe8a355d275bbe97a1f7c8f82577b7f70a769a852811f8e9ee715 +size 57958 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png index 450b1e3057..d090dc2fa8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92502759287057d36a3fe5532d48e911e24df080c8c1e8eab01c4e406bd95fc8 -size 55792 +oid sha256:9980d0479b405e3b3215628ae92227c02423b7839c2754bb4b230da549e82cbe +size 56305 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png index ae1a8f9573..f7b0bd2921 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ba37602f8c2519c1854776ea349fd53fb9fb6fd4b1d94a55d297b297854656b -size 27584 +oid sha256:3b37e1793e658812995411e954e060e983e48e7d450af28e1577b2b8f6f8e30f +size 27527 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png index 7b01286f2e..b6851470fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d2a776e9724abd16b1692f0fdac43e39dedcd8c003128d1ea354f1f8df245ac -size 26147 +oid sha256:acb1adfada4482915a2b42b86c9b73c5815fe992647e67b065f569b45ae69779 +size 26304 From 0abda97d6f4d798a8bc30d330fcb5f267f687c58 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Thu, 24 Aug 2023 17:11:13 +0200 Subject: [PATCH 24/37] ListItem: Don't apply clickable modifier if onClick is null. (#1146) If it is applied but disabled (like before this PR) it will hijack all touch input from the view and no other component in it can respond to clicks. --- .../android/libraries/designsystem/theme/components/ListItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt index 6be9089d11..f51414307a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt @@ -119,7 +119,7 @@ fun ListItem( androidx.compose.material3.ListItem( headlineContent = decoratedHeadlineContent, - modifier = Modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}).then(modifier), + modifier = if (onClick != null) Modifier.clickable(enabled = enabled, onClick = onClick).then(modifier) else modifier, overlineContent = null, supportingContent = decoratedSupportingContent, leadingContent = decoratedLeadingContent, From 1378a7cb9d282024855e59d6d3c73a53b2c19ae0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 08:11:45 +0200 Subject: [PATCH 25/37] Update dependency com.google.firebase:firebase-bom to v32.2.3 (#1147) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 434c014932..178bb3bf90 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,7 +65,7 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref android_desugar = "com.android.tools:desugar_jdk_libs:2.0.3" kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:32.2.2" +google_firebase_bom = "com.google.firebase:firebase-bom:32.2.3" # AndroidX androidx_material = { module = "com.google.android.material:material", version.ref = "material" } From 8f2647799ef61aa8d7c617d15129411fbecf639e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 25 Aug 2023 09:01:20 +0200 Subject: [PATCH 26/37] Use a single preview for radio buttons with checked & unchecked states --- .../theme/components/IconToggleButton.kt | 41 ++++++++++++------- .../theme/components/RadioButton.kt | 23 +++++++---- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt index dbf1365bb2..fd355d3860 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt @@ -17,7 +17,9 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.RadioButtonUnchecked @@ -30,6 +32,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup @@ -56,23 +59,31 @@ fun IconToggleButton( @Preview(group = PreviewGroup.Toggles) @Composable -internal fun IconToggleButtonCheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(true) } - -@Preview(group = PreviewGroup.Toggles) -@Composable -internal fun IconToggleButtonUncheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(false) } +internal fun IconToggleButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } @Composable -private fun ContentToPreview(defaultCheck: Boolean) { - var checked by remember { mutableStateOf(defaultCheck) } - val icon: @Composable () -> Unit = { - Icon( - imageVector = if (checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, - contentDescription = "IconToggleButton" - ) - } +private fun ContentToPreview() { + var checked by remember { mutableStateOf(false) } Column { - IconToggleButton(checked = checked, enabled = true, onCheckedChange = { checked = !checked }, content = icon) - IconToggleButton(checked = checked, enabled = false, onCheckedChange = { checked = !checked }, content = icon) + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + val icon: @Composable () -> Unit = { + Icon( + imageVector = if (checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, + contentDescription = "IconToggleButton" + ) + } + IconToggleButton(checked = checked, enabled = true, onCheckedChange = { checked = !checked }, content = icon) + IconToggleButton(checked = checked, enabled = false, onCheckedChange = { checked = !checked }, content = icon) + } + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + val icon: @Composable () -> Unit = { + Icon( + imageVector = if (!checked) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, + contentDescription = "IconToggleButton" + ) + } + IconToggleButton(checked = !checked, enabled = true, onCheckedChange = { checked = !checked }, content = icon) + IconToggleButton(checked = !checked, enabled = false, onCheckedChange = { checked = !checked }, content = icon) + } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt index 9d85cf8d87..9f373d1b71 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt @@ -17,7 +17,9 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.material3.RadioButtonColors import androidx.compose.material3.RadioButtonDefaults import androidx.compose.runtime.Composable @@ -27,6 +29,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.theme.ElementTheme @@ -63,17 +66,19 @@ internal fun compoundRadioButtonColors(): RadioButtonColors { @Preview(group = PreviewGroup.Toggles) @Composable -internal fun RadioButtonCheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(true) } - -@Preview(group = PreviewGroup.Toggles) -@Composable -internal fun RadioButtonUncheckedPreview() = ElementThemedPreview(vertical = false) { ContentToPreview(false) } +internal fun RadioButtonPreview() = ElementThemedPreview(vertical = false) { ContentToPreview() } @Composable -private fun ContentToPreview(defaultCheck: Boolean) { - var checked by remember { mutableStateOf(defaultCheck) } +private fun ContentToPreview() { + var checked by remember { mutableStateOf(false) } Column { - RadioButton(selected = checked, enabled = true, onClick = { checked = !checked }) - RadioButton(selected = checked, enabled = false, onClick = { checked = !checked }) + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + RadioButton(selected = checked, enabled = true, onClick = { checked = !checked }) + RadioButton(selected = checked, enabled = false, onClick = { checked = !checked }) + } + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + RadioButton(selected = !checked, enabled = true, onClick = { checked = !checked }) + RadioButton(selected = !checked, enabled = false, onClick = { checked = !checked }) + } } } From 2a29c67b965519b3c575690157261e601652f000 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 23 Aug 2023 17:46:55 +0200 Subject: [PATCH 27/37] Improve timestamp rendering for poll event content --- ...melineItemEventForTimestampViewProvider.kt | 1 + .../components/TimelineItemEventRow.kt | 167 ++++++++++-------- .../timeline/components/TimestampPosition.kt | 34 ++++ 3 files changed, 132 insertions(+), 70 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt index 7697ccf4a4..c52e49c512 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt @@ -35,5 +35,6 @@ class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider Unit, modifier: Modifier = Modifier ) { - val isMediaItem = event.content is TimelineItemImageContent - || event.content is TimelineItemVideoContent - || event.content is TimelineItemLocationContent + val timestampPosition = when (event.content) { + is TimelineItemImageContent, + is TimelineItemVideoContent, + is TimelineItemLocationContent -> TimestampPosition.Above + is TimelineItemPollContent -> TimestampPosition.Below + else -> TimestampPosition.Aligned + } val replyToDetails = event.inReplyTo as? InReplyTo.Ready // Long clicks are not not automatically propagated from a `clickable` @@ -384,96 +390,97 @@ private fun MessageEventBubbleContent( @Composable fun ContentAndTimestampView( - overlayTimestamp: Boolean, + timestampPosition: TimestampPosition, modifier: Modifier = Modifier, contentModifier: Modifier = Modifier, timestampModifier: Modifier = Modifier, ) { - if (overlayTimestamp) { - Box(modifier) { - ContentView(modifier = contentModifier) - TimelineEventTimestampView( - event = event, - onClick = onTimestampClicked, - onLongClick = ::onTimestampLongClick, - modifier = timestampModifier - .padding(horizontal = 4.dp, vertical = 4.dp) // Outer padding - .background(ElementTheme.colors.bgSubtleSecondary, RoundedCornerShape(10.0.dp)) - .align(Alignment.BottomEnd) - .padding(horizontal = 4.dp, vertical = 2.dp) // Inner padding - ) - } - } else { - Box(modifier) { - ContentView(modifier = contentModifier) - TimelineEventTimestampView( - event = event, - onClick = onTimestampClicked, - onLongClick = ::onTimestampLongClick, - modifier = timestampModifier - .align(Alignment.BottomEnd) - .padding(horizontal = 8.dp, vertical = 4.dp) - ) - } + when (timestampPosition) { + TimestampPosition.Above -> + Box(modifier) { + ContentView(modifier = contentModifier) + TimelineEventTimestampView( + event = event, + onClick = onTimestampClicked, + onLongClick = ::onTimestampLongClick, + modifier = timestampModifier + .padding(horizontal = 4.dp, vertical = 4.dp) // Outer padding + .background(ElementTheme.colors.bgSubtleSecondary, RoundedCornerShape(10.0.dp)) + .align(Alignment.BottomEnd) + .padding(horizontal = 4.dp, vertical = 2.dp) // Inner padding + ) + } + TimestampPosition.Aligned -> + Box(modifier) { + ContentView(modifier = contentModifier) + TimelineEventTimestampView( + event = event, + onClick = onTimestampClicked, + onLongClick = ::onTimestampLongClick, + modifier = timestampModifier + .align(Alignment.BottomEnd) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) + } + TimestampPosition.Below -> + Column(modifier) { + ContentView(modifier = contentModifier) + TimelineEventTimestampView( + event = event, + onClick = onTimestampClicked, + onLongClick = ::onTimestampLongClick, + modifier = timestampModifier + .align(Alignment.End) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) + } } } - /** Used only for media items, with no reply to metadata. It displays the contents with no paddings. */ - @Composable - fun SimpleMediaItemLayout(modifier: Modifier = Modifier) { - ContentAndTimestampView(overlayTimestamp = true, modifier = modifier) - } - - /** Used for every other type of message, groups the different components in a Column with some space between them. */ + /** Groups the different components in a Column with some space between them. */ @Composable fun CommonLayout( inReplyToDetails: InReplyTo.Ready?, modifier: Modifier = Modifier ) { + var modifierWithPadding: Modifier = Modifier + var contentModifier: Modifier = Modifier EqualWidthColumn(modifier = modifier, spacing = 8.dp) { - if (inReplyToDetails != null) { - val senderName = inReplyToDetails.senderDisplayName ?: inReplyToDetails.senderId.value - val attachmentThumbnailInfo = attachmentThumbnailInfoForInReplyTo(inReplyToDetails) - val text = textForInReplyTo(inReplyToDetails) - ReplyToContent( - senderName = senderName, - text = text, - attachmentThumbnailInfo = attachmentThumbnailInfo, - modifier = Modifier - .padding(top = 8.dp, start = 8.dp, end = 8.dp) - .clip(RoundedCornerShape(6.dp)) - .clickable(enabled = true, onClick = inReplyToClick), - ) - } - val modifierWithPadding = if (isMediaItem) { - Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) - } else { - Modifier - } - - val contentModifier = if (isMediaItem) { - Modifier.clip(RoundedCornerShape(12.dp)) - } else { - if (inReplyToDetails != null) { - Modifier.padding(start = 12.dp, end = 12.dp, top = 0.dp, bottom = 8.dp) - } else { - Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp) + when { + inReplyToDetails != null -> { + val senderName = inReplyToDetails.senderDisplayName ?: inReplyToDetails.senderId.value + val attachmentThumbnailInfo = attachmentThumbnailInfoForInReplyTo(inReplyToDetails) + val text = textForInReplyTo(inReplyToDetails) + ReplyToContent( + senderName = senderName, + text = text, + attachmentThumbnailInfo = attachmentThumbnailInfo, + modifier = Modifier + .padding(top = 8.dp, start = 8.dp, end = 8.dp) + .clip(RoundedCornerShape(6.dp)) + .clickable(enabled = true, onClick = inReplyToClick), + ) + if (timestampPosition == TimestampPosition.Above) { + modifierWithPadding = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) + contentModifier = Modifier.clip(RoundedCornerShape(12.dp)) + } else { + contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 0.dp, bottom = 8.dp) + } + } + timestampPosition != TimestampPosition.Above -> { + contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp) } } ContentAndTimestampView( - overlayTimestamp = isMediaItem, + timestampPosition = timestampPosition, contentModifier = contentModifier, modifier = modifierWithPadding, ) } } - if (isMediaItem && replyToDetails == null) { - SimpleMediaItemLayout() - } else { - CommonLayout(inReplyToDetails = replyToDetails, modifier = modifier) - } + CommonLayout(inReplyToDetails = replyToDetails, modifier = modifier) } @Composable @@ -810,3 +817,23 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { onTimestampClicked = {}, ) } + +// Note: no need for light/dark variant for this preview, we only look at the timestamp position +@Preview +@Composable +internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { + TimelineItemEventRow( + event = aTimelineItemEvent(content = aTimelineItemPollContent()), + isHighlighted = false, + canReply = true, + onClick = {}, + onLongClick = {}, + onUserDataClick = {}, + inReplyToClick = {}, + onReactionClick = { _, _ -> }, + onReactionLongClick = { _, _ -> }, + onMoreReactionsClick = {}, + onSwipeToReply = {}, + onTimestampClicked = {}, + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt new file mode 100644 index 0000000000..cb1a201f81 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components + +enum class TimestampPosition { + /** + * Timestamp should be rendered above the timeline event content (eg. image). + */ + Above, + + /** + * Timestamp should be aligned with the timeline event content if this is possible (eg. text). + */ + Aligned, + + /** + * Timestamp should always be rendered below the timeline event content (eg. poll). + */ + Below, +} From 20bd7764efb3a47a327b5eec7b000ac5223569c8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 24 Aug 2023 11:38:14 +0200 Subject: [PATCH 28/37] Add default timestamp position --- .../impl/timeline/components/TimelineItemEventRow.kt | 2 +- .../impl/timeline/components/TimestampPosition.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 2794c2b898..0fd05e4717 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -366,7 +366,7 @@ private fun MessageEventBubbleContent( is TimelineItemVideoContent, is TimelineItemLocationContent -> TimestampPosition.Above is TimelineItemPollContent -> TimestampPosition.Below - else -> TimestampPosition.Aligned + else -> TimestampPosition.Default } val replyToDetails = event.inReplyTo as? InReplyTo.Ready diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt index cb1a201f81..aaae767dbc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt @@ -30,5 +30,12 @@ enum class TimestampPosition { /** * Timestamp should always be rendered below the timeline event content (eg. poll). */ - Below, + Below; + + companion object { + /** + * Default timestamp position for timeline event contents. + */ + val Default: TimestampPosition = Aligned + } } From 0c9db257152975b32bfa3c8b929bb6220614b231 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 24 Aug 2023 10:13:05 +0000 Subject: [PATCH 29/37] Update screenshots --- ..._TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3db42b6e0c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5af81e5f2081b949673c5f08282b5165eb7df4bb970c1db485376c86543e1a96 +size 57313 From 7fc44e636992ed1c5009d58fb91e4b626d82ccef Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 25 Aug 2023 07:14:37 +0000 Subject: [PATCH 30/37] Update screenshots --- ..._Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png | 3 --- ...oggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png | 3 --- ...ts_null_Toggles_IconToggleButton_0_null,NEXUS_5,1.0,en].png | 3 +++ ..._null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png | 3 --- ...ull_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png | 3 --- ...ponents_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png | 3 +++ 6 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButton_0_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 8ed5b53ea6..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonChecked_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8dd204bfd2abda63626688fc31a70af5985a28793249a9e382326dd205ad7b15 -size 9994 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 3d0cfdf7a6..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButtonUnchecked_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0857c3a7f01c90df0ba9b4cab7dfb523236dee74f7de0da4f7275741af89128a -size 10204 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButton_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d27488dc29 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_IconToggleButton_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f9b51e2b099380112683be07ce85679436fdaa7765cc72c200cbaca1e0b843e +size 13557 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 34f115b02e..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonChecked_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9566b59af56b7dac1717880b732ea0c5a037e06c5200a395c298dd2e47ce030 -size 11187 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png deleted file mode 100644 index ac385411ef..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButtonUnchecked_0_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2f9b6ba324fccdb5db6b838481b4d7e3dac64a7e648563f48b5c3763c735404 -size 9818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..97f012d11b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Toggles_RadioButton_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88c7be1a8cff74b057ee0f7ba09922c814c8c0b6a3dbaa69ea58ec287c425fca +size 14357 From dab331b9bf902f7a2cfcbb3908233c1b74c0753d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 25 Aug 2023 09:30:05 +0200 Subject: [PATCH 31/37] Rename TimestampPosition.Above to Overlay --- .../impl/timeline/components/TimelineItemEventRow.kt | 8 ++++---- .../impl/timeline/components/TimestampPosition.kt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 0fd05e4717..90e286030d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -364,7 +364,7 @@ private fun MessageEventBubbleContent( val timestampPosition = when (event.content) { is TimelineItemImageContent, is TimelineItemVideoContent, - is TimelineItemLocationContent -> TimestampPosition.Above + is TimelineItemLocationContent -> TimestampPosition.Overlay is TimelineItemPollContent -> TimestampPosition.Below else -> TimestampPosition.Default } @@ -396,7 +396,7 @@ private fun MessageEventBubbleContent( timestampModifier: Modifier = Modifier, ) { when (timestampPosition) { - TimestampPosition.Above -> + TimestampPosition.Overlay -> Box(modifier) { ContentView(modifier = contentModifier) TimelineEventTimestampView( @@ -460,14 +460,14 @@ private fun MessageEventBubbleContent( .clip(RoundedCornerShape(6.dp)) .clickable(enabled = true, onClick = inReplyToClick), ) - if (timestampPosition == TimestampPosition.Above) { + if (timestampPosition == TimestampPosition.Overlay) { modifierWithPadding = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) contentModifier = Modifier.clip(RoundedCornerShape(12.dp)) } else { contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 0.dp, bottom = 8.dp) } } - timestampPosition != TimestampPosition.Above -> { + timestampPosition != TimestampPosition.Overlay -> { contentModifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt index aaae767dbc..7063e07635 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt @@ -18,9 +18,9 @@ package io.element.android.features.messages.impl.timeline.components enum class TimestampPosition { /** - * Timestamp should be rendered above the timeline event content (eg. image). + * Timestamp should overlay the timeline event content (eg. image). */ - Above, + Overlay, /** * Timestamp should be aligned with the timeline event content if this is possible (eg. text). From a9846c95e8afc6801ffeb208ff407360e491d431 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 25 Aug 2023 09:36:21 +0200 Subject: [PATCH 32/37] Rename modifier --- .../impl/timeline/components/TimelineItemEventRow.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 90e286030d..449b6ee104 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -351,6 +351,7 @@ private fun MessageSenderInformation( } } +@Suppress("") @Composable private fun MessageEventBubbleContent( event: TimelineItem.Event, @@ -359,7 +360,7 @@ private fun MessageEventBubbleContent( onMessageLongClick: () -> Unit, inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, - modifier: Modifier = Modifier + bubbleModifier: Modifier = Modifier ) { val timestampPosition = when (event.content) { is TimelineItemImageContent, @@ -480,7 +481,7 @@ private fun MessageEventBubbleContent( } } - CommonLayout(inReplyToDetails = replyToDetails, modifier = modifier) + CommonLayout(inReplyToDetails = replyToDetails, modifier = bubbleModifier) } @Composable From 2dc2f7c5210328c5623cbe64a877ad27e0a61728 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 25 Aug 2023 10:12:21 +0200 Subject: [PATCH 33/37] cleanup --- .../components/TimelineItemEventForTimestampViewProvider.kt | 1 - .../messages/impl/timeline/components/TimelineItemEventRow.kt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt index c52e49c512..7697ccf4a4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt @@ -35,6 +35,5 @@ class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider Unit, inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, - bubbleModifier: Modifier = Modifier + @SuppressLint("ModifierParameter") bubbleModifier: Modifier = Modifier, // need to rename this modifier to distinguish it from the following ones ) { val timestampPosition = when (event.content) { is TimelineItemImageContent, From 8415826a97331d40e56894ae2d22eaaf3c073027 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 12:15:06 +0200 Subject: [PATCH 34/37] Import strings with Localazy. --- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 9 +++ .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 2 +- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-fr/translations.xml | 1 - .../src/main/res/values-ro/translations.xml | 1 - .../src/main/res/values-ru/translations.xml | 1 - .../src/main/res/values-sk/translations.xml | 1 - .../impl/src/main/res/values/localazy.xml | 2 +- .../src/main/res/values-cs/translations.xml | 24 +++++++ .../src/main/res/values-de/translations.xml | 20 ++++++ .../src/main/res/values-ru/translations.xml | 7 ++ .../src/main/res/values-sk/translations.xml | 3 + .../src/main/res/values-cs/translations.xml | 2 +- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 5 ++ .../src/main/res/values-de/translations.xml | 13 ++-- .../src/main/res/values-cs/translations.xml | 4 +- .../src/main/res/values-de/translations.xml | 4 +- .../src/main/res/values-de/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 1 + .../src/main/res/values-de/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 43 +++++++++++- .../src/main/res/values-de/translations.xml | 70 ++++++++++++++----- .../src/main/res/values-ru/translations.xml | 11 +++ .../src/main/res/values-sk/translations.xml | 12 ++++ .../src/main/res/values/localazy.xml | 4 +- 28 files changed, 214 insertions(+), 37 deletions(-) create mode 100644 features/ftue/impl/src/main/res/values-cs/translations.xml diff --git a/features/analytics/impl/src/main/res/values-de/translations.xml b/features/analytics/impl/src/main/res/values-de/translations.xml index 44890927a4..7ef2ff2500 100644 --- a/features/analytics/impl/src/main/res/values-de/translations.xml +++ b/features/analytics/impl/src/main/res/values-de/translations.xml @@ -5,6 +5,6 @@ "Du kannst alle unsere Nutzerbedingungen %1$s lesen." "hier" "Du kannst dies jederzeit deaktivieren" - "Wir geben ""keine"" Informationen an Dritte weiter" + "Wir geben deine Daten nicht an Dritte weiter" "Hilf uns, %1$s zu verbessern" diff --git a/features/ftue/impl/src/main/res/values-cs/translations.xml b/features/ftue/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..38bbf4dacb --- /dev/null +++ b/features/ftue/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,9 @@ + + + "Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku." + "Historie zpráv šifrovaných místností nebude v této aktualizaci k dispozici." + "Rádi bychom se od vás dozvěděli, co si o tom myslíte, dejte nám vědět prostřednictvím stránky s nastavením." + "Jdeme na to!" + "Zde je to, co potřebujete vědět:" + "Vítá vás %1$s!" + diff --git a/features/ftue/impl/src/main/res/values-de/translations.xml b/features/ftue/impl/src/main/res/values-de/translations.xml index 0f2efc1c57..d0ebbc31e7 100644 --- a/features/ftue/impl/src/main/res/values-de/translations.xml +++ b/features/ftue/impl/src/main/res/values-de/translations.xml @@ -1,6 +1,6 @@ - "Anrufe, Standortfreigabe, Suche und mehr werden später in diesem Jahr hinzugefügt." + "Anrufe, Umfragen, Suche und mehr werden später in diesem Jahr hinzugefügt." "Der Nachrichtenverlauf für verschlüsselte Räume wird in diesem Update nicht verfügbar sein." "Wir würden uns freuen, wenn du uns über die Einstellungsseite deine Meinung mitteilst." "Los geht\'s!" diff --git a/features/login/impl/src/main/res/values-cs/translations.xml b/features/login/impl/src/main/res/values-cs/translations.xml index 02581401bf..5d697bf34a 100644 --- a/features/login/impl/src/main/res/values-cs/translations.xml +++ b/features/login/impl/src/main/res/values-cs/translations.xml @@ -9,7 +9,7 @@ "Chystáte se přihlásit do %s" "Zde budou uloženy vaše konverzace - podobně jako u poskytovatele e-mailových služeb uchováváte své e-maily." "Chystáte se vytvořit účet na %s" - "Matrix.org je otevřená síť pro bezpečnou, decentralizovanou komunikaci." + "Matrix.org je velký bezplatný server ve veřejné síti Matrix pro bezpečnou decentralizovanou komunikaci, který provozuje nadace Matrix.org." "Jiný" "Použijte jiného poskytovatele účtu, například vlastní soukromý server nebo pracovní účet." "Změnit poskytovatele účtu" diff --git a/features/login/impl/src/main/res/values-de/translations.xml b/features/login/impl/src/main/res/values-de/translations.xml index 099d00e83b..7464e17f8d 100644 --- a/features/login/impl/src/main/res/values-de/translations.xml +++ b/features/login/impl/src/main/res/values-de/translations.xml @@ -9,7 +9,7 @@ "Du bist dabei dich bei %s anzumelden" "Hier werden deine Konversationen stattfinden — genauso wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren." "Du bist dabei ein Konto auf %s zu erstellen" - "Matrix.org ist ein offenes Netzwerk für sichere, dezentralisierte Kommunikation." + "Matrix.org ist ein großer, kostenloser Server im öffentlichen Matrix-Netzwerk für sichere, dezentrale Kommunikation, der von der Matrix.org Foundation betrieben wird." "Andere" "Verwende einen anderen Kontoanbieter, z. B. deinen eigenen privaten Server oder ein Arbeitskonto." "Kontoanbieter ändern" diff --git a/features/login/impl/src/main/res/values-fr/translations.xml b/features/login/impl/src/main/res/values-fr/translations.xml index 77d171bdf6..74f23cc5c9 100644 --- a/features/login/impl/src/main/res/values-fr/translations.xml +++ b/features/login/impl/src/main/res/values-fr/translations.xml @@ -9,7 +9,6 @@ "Vous êtes sur le point de vous connecter à %s" "C\'est ici que vos conversations seront stockées - tout comme vous utiliseriez un fournisseur de messagerie pour conserver vos e-mails." "Vous êtes sur le point de créer un compte sur %s" - "Matrix.org est un réseau ouvert pour des communications sécurisées et décentralisées." "Autre" "Utilisez un autre fournisseur de compte, tel que votre propre serveur ou un compte professionnel." "Changer de fournisseur" diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml index 46596f2b95..0c58c45d03 100644 --- a/features/login/impl/src/main/res/values-ro/translations.xml +++ b/features/login/impl/src/main/res/values-ro/translations.xml @@ -9,7 +9,6 @@ "Sunteți pe cale să vă conectați la %s" "Aici vor trăi conversațiile - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile." "Sunteți pe cale să creați un cont pe %s" - "Matrix.org este o rețea deschisă pentru o comunicare sigură și descentralizată." "Altul" "Utilizați un alt furnizor de cont, cum ar fi propriul server privat sau un cont de serviciu." "Schimbați furnizorul contului" diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml index aa8b459f59..242ec4bb2c 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -9,7 +9,6 @@ "Вы собираетесь войти в %s" "Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем." "Вы собираетесь создать учетную запись на %s" - "Matrix.org — это открытая сеть для безопасной децентрализованной связи." "Другое" "Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись." "Сменить поставщика учетной записи" diff --git a/features/login/impl/src/main/res/values-sk/translations.xml b/features/login/impl/src/main/res/values-sk/translations.xml index a48fed0fd5..14a5407055 100644 --- a/features/login/impl/src/main/res/values-sk/translations.xml +++ b/features/login/impl/src/main/res/values-sk/translations.xml @@ -9,7 +9,6 @@ "Chystáte sa prihlásiť do %s" "Tu budú žiť vaše konverzácie — podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov." "Chystáte sa vytvoriť účet na %s" - "Matrix.org je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu." "Iný" "Použite iného poskytovateľa účtu, ako napríklad vlastný súkromný server alebo pracovný účet." "Zmeniť poskytovateľa účtu" diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 236de68212..e09f4fe693 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -9,7 +9,7 @@ "You’re about to sign in to %s" "This is where your conversations will live — just like you would use an email provider to keep your emails." "You’re about to create an account on %s" - "Matrix.org is an open network for secure, decentralized communication." + "Matrix.org is a large, free server on the public Matrix network for secure, decentralised communication, run by the Matrix.org Foundation." "Other" "Use a different account provider, such as your own private server or a work account." "Change account provider" diff --git a/features/messages/impl/src/main/res/values-cs/translations.xml b/features/messages/impl/src/main/res/values-cs/translations.xml index 8da8200bd5..7ca02ef21c 100644 --- a/features/messages/impl/src/main/res/values-cs/translations.xml +++ b/features/messages/impl/src/main/res/values-cs/translations.xml @@ -5,19 +5,43 @@ "%1$d změny místnosti" "%1$d změn místnosti" + + "%1$d další" + "%1$d další" + "%1$d dalších" + "Fotoaparát" "Vyfotit" "Natočit video" "Příloha" "Knihovna fotografií a videí" "Poloha" + "Hlasování" + "Historie zpráv je momentálně v této místnosti nedostupná" "Nepodařilo se načíst údaje o uživateli" "Chtěli byste je pozvat zpět?" "V tomto chatu jste sami" "Zpráva zkopírována" "Nemáte oprávnění zveřejňovat příspěvky v této místnosti" + "Povolit vlastní nastavení" + "Zapnutím této funkce přepíšete výchozí nastavení" + "Upozornit mě v tomto chatu na" + "Můžete změnit ve vašem %1$s." + "globální nastavení" + "Výchozí nastavení" + "Odebrat vlastní nastavení" + "Při načítání nastavení oznámení došlo k chybě." + "Obnovení výchozího režimu se nezdařilo, zkuste to prosím znovu." + "Nastavení režimu se nezdařilo, zkuste to prosím znovu." + "Všechny zprávy" + "Pouze zmínky a klíčová slova" + "V této místnosti mě upozornit na" + "Zobrazit méně" + "Zobrazit více" "Odeslat znovu" "Vaši zprávu se nepodařilo odeslat" + "Přidat emoji" + "Zobrazit méně" "Nahrání média se nezdařilo, zkuste to prosím znovu." "Odstranit" diff --git a/features/messages/impl/src/main/res/values-de/translations.xml b/features/messages/impl/src/main/res/values-de/translations.xml index 84c903844e..c24483a2f7 100644 --- a/features/messages/impl/src/main/res/values-de/translations.xml +++ b/features/messages/impl/src/main/res/values-de/translations.xml @@ -4,22 +4,42 @@ "%1$d Raumänderung" "%1$d Raumänderungen" + + + "%1$d weitere" + "Kamera" "Foto aufnehmen" "Video aufnehmen" "Anhang" "Foto- & Video-Bibliothek" "Standort" + "Umfrage" "Der Nachrichtenverlauf ist in diesem Raum derzeit nicht verfügbar" "Benutzerdetails konnten nicht abgerufen werden" "Möchtest du sie wieder einladen?" "Du bist allein in diesem Chat" "Nachricht kopiert" "Du bist keine Berechtigung, um in diesem Raum zu posten" + "Benutzerdefinierte Einstellung zulassen" + "Das Aktivieren dieser Option wird die Standardeinstellungen überschreiben." + "Benachrichtige mich in diesem Chat für" + "Du kannst es in deinem %1$s ändern." + "Globale Einstellungen" + "Standardeinstellung" + "Benutzerdefinierte Einstellung entfernen" + "Beim Laden der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." + "Wiederherstellung des Standardmodus fehlgeschlagen. Bitte versuche es erneut." + "Fehler beim Einstellen des Modus. Bitte versuche es erneut." + "Alle Nachrichten" + "Nur Erwähnungen und Schlüsselwörter" + "In diesem Raum, benachrichtige mich für" "Weniger anzeigen" "Mehr anzeigen" "Erneut senden" "Ihre Nachricht konnte nicht gesendet werden" + "Emoji hinzufügen" + "Weniger anzeigen" "Fehler bei der Verarbeitung von Medien zum Hochladen, bitte versuche es erneut." "Entfernen" diff --git a/features/messages/impl/src/main/res/values-ru/translations.xml b/features/messages/impl/src/main/res/values-ru/translations.xml index c3ed526564..33147f8ea2 100644 --- a/features/messages/impl/src/main/res/values-ru/translations.xml +++ b/features/messages/impl/src/main/res/values-ru/translations.xml @@ -5,6 +5,11 @@ "%1$d изменения в комнате" "%1$d изменений в комнате" + + "И ещё %1$d" + "И ещё %1$d" + "И ещё %1$d" + "Камера" "Сделать фото" "Записать видео" @@ -24,11 +29,13 @@ "Вы можете изменить его в своем %1$s." "Основные Настройки" "Настройка по умолчанию" + "Удалить пользовательскую настройку" "Произошла ошибка при загрузке настроек уведомлений." "Не удалось восстановить режим по умолчанию, попробуйте еще раз." "Не удалось настроить режим, попробуйте еще раз." "Все сообщения" "Только упоминания и ключевые слова" + "В этой комнате уведомить меня о" "Показать меньше" "Показать больше" "Отправить снова" diff --git a/features/messages/impl/src/main/res/values-sk/translations.xml b/features/messages/impl/src/main/res/values-sk/translations.xml index 07e62e9a4d..03a32f847f 100644 --- a/features/messages/impl/src/main/res/values-sk/translations.xml +++ b/features/messages/impl/src/main/res/values-sk/translations.xml @@ -16,6 +16,7 @@ "Príloha" "Knižnica fotografií a videí" "Poloha" + "Anketa" "História správ v tejto miestnosti nie je momentálne k dispozícii" "Nepodarilo sa získať údaje o používateľovi" "Chceli by ste ich pozvať späť?" @@ -28,11 +29,13 @@ "Môžete to zmeniť vo svojich %1$s." "všeobecných nastaveniach" "Predvolené nastavenie" + "Odstrániť vlastné nastavenie" "Pri načítavaní nastavení oznámení došlo k chybe." "Nepodarilo sa obnoviť predvolený režim, skúste to prosím znova." "Nepodarilo sa nastaviť režim, skúste to prosím znova." "Všetky správy" "Iba zmienky a kľúčové slová" + "V tejto miestnosti ma upozorniť na" "Zobraziť menej" "Zobraziť viac" "Odoslať znova" diff --git a/features/rageshake/impl/src/main/res/values-cs/translations.xml b/features/rageshake/impl/src/main/res/values-cs/translations.xml index d95752e91c..bfea484240 100644 --- a/features/rageshake/impl/src/main/res/values-cs/translations.xml +++ b/features/rageshake/impl/src/main/res/values-cs/translations.xml @@ -1,7 +1,7 @@ "Připojit snímek obrazovky" - "V případě dalších dotazů se na mě můžete obrátit" + "V případě dalších dotazů se na mě můžete obrátit." "Kontaktujte mě" "Upravit snímek obrazovky" "Popište prosím chybu. Co jste udělali? Co jste očekávali, že se stane? Co se ve skutečnosti stalo? Uveďte co nejvíce podrobností." diff --git a/features/rageshake/impl/src/main/res/values-de/translations.xml b/features/rageshake/impl/src/main/res/values-de/translations.xml index b316d8b45e..51b331a86f 100644 --- a/features/rageshake/impl/src/main/res/values-de/translations.xml +++ b/features/rageshake/impl/src/main/res/values-de/translations.xml @@ -10,6 +10,6 @@ "Absturzprotokolle senden" "Logs zulassen" "Bildschirmfoto senden" - "Um zu überprüfen, ob alles wie vorgesehen funktioniert, werden Protokolle mit deiner Nachricht gesendet. Diese werden privat sein. Um nur Ihre Nachricht zu senden, schalte diese Einstellung aus." + "Deiner Nachricht werden Protokolle beigefügt, um sicherzustellen, dass alles ordnungsgemäß funktioniert. Um deine Nachricht ohne Logs zu senden, deaktiviere diese Einstellung." "%1$s ist bei der letzten Verwendung abgestürzt. Möchtest du uns einen Absturzbericht senden?" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index 6e92891c70..90a9bb0190 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -13,7 +13,12 @@ "Nelze aktualizovat místnost" "Zprávy jsou zabezpečeny zámky. Pouze vy a příjemci máte jedinečné klíče k jejich odemčení." "Šifrování zpráv povoleno" + "Při načítání nastavení oznámení došlo k chybě." + "Ztišení této místnosti se nezdařilo, zkuste to prosím znovu." + "Nepodařilo se zrušit ztišení této místnosti, zkuste to prosím znovu." "Pozvat lidi" + "Vlastní" + "Výchozí" "Oznámení" "Název místnosti" "Sdílet místnost" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index 992ca94b85..8ae23e8152 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -1,18 +1,23 @@ - "1 Person" + "%1$d Person" "%1$d Personen" "Thema hinzufügen" "Bereits Mitglied" "Bereits eingeladen" "Raum bearbeiten" - "Wir konnten nicht alle Informationen für diesen Raum aktualisieren." + "Es gab einen unbekannten Fehler und die Informationen konnten nicht geändert werden." "Raum konnte nicht aktualisiert werden" "Nachrichten sind mit Schlössern gesichert. Nur du und der Empfänger haben die eindeutigen Schlüssel, um sie zu entsperren." "Nachrichtenverschlüsselung aktiviert" + "Beim Laden der Benachrichtigungseinstellungen ist ein Fehler aufgetreten." + "Das Stummschalten dieses Raums ist fehlgeschlagen. Bitte versuche es erneut." + "Die Stummschaltung dieses Raums konnte nicht aufgehoben werden. Bitte versuchen Sie es erneut." "Personen einladen" + "Benutzerdefiniert" + "Standard" "Benachrichtigungen" "Raumname" "Raum teilen" @@ -20,10 +25,10 @@ "Ausstehend" "Raummitglieder" "Blockieren" - "Blockierte Benutzer können dir keine Nachrichten senden und alle Nachrichten von ihnen werden ausgeblendet. Du kannst diese Aktion jederzeit rückgängig machen." + "Blockierte Benutzer können dir keine Nachrichten senden und alle ihre Nachrichten werden ausgeblendet. Du kannst sie jederzeit entsperren." "Nutzer blockieren" "Blockierung aufheben" - "Wenn du den Benutzer entsperrst, kannst du wieder alle Nachrichten von ihm sehen." + "Du wirst alle ihre Nachrichten wieder sehen." "Nutzer entblockieren" "Raum verlassen" "Personen" diff --git a/features/roomlist/impl/src/main/res/values-cs/translations.xml b/features/roomlist/impl/src/main/res/values-cs/translations.xml index d355d2c70c..cdf719e00f 100644 --- a/features/roomlist/impl/src/main/res/values-cs/translations.xml +++ b/features/roomlist/impl/src/main/res/values-cs/translations.xml @@ -1,7 +1,9 @@ "Vytvořte novou konverzaci nebo místnost" + "Začněte tím, že někomu pošnete zprávu." + "Zatím žádné konverzace." "Všechny chaty" "Zdá se, že používáte nové zařízení. Ověřte přihlášení, abyste měli přístup k zašifrovaným zprávám." - "Přístup k historii zpráv" + "Ověřte, že jste to vy" diff --git a/features/roomlist/impl/src/main/res/values-de/translations.xml b/features/roomlist/impl/src/main/res/values-de/translations.xml index 2ed1cd0263..49a400b138 100644 --- a/features/roomlist/impl/src/main/res/values-de/translations.xml +++ b/features/roomlist/impl/src/main/res/values-de/translations.xml @@ -1,7 +1,9 @@ "Ein neues Gespräch oder einen neuen Raum erstellen" + "Beginnen, indem du jemandem eine Nachricht sendest." + "Noch keine Chats." "Alle Chats" "Es sieht so aus, als ob du ein neues Gerät verwendest. Verifiziere, dass du es bist, um auf deine verschlüsselten Nachrichten zuzugreifen." - "Greife auf deine Nachrichten-Historie zu" + "Verifiziere, dass du es bist" diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml index a2ae094731..04c7521d9e 100644 --- a/libraries/push/impl/src/main/res/values-de/translations.xml +++ b/libraries/push/impl/src/main/res/values-de/translations.xml @@ -7,7 +7,7 @@ "** Senden fehlgeschlagen - bitte Raum öffnen" "Beitreten" "Ablehnen" - "Hat dich eingeladen" + "Hat dich zum Chatten eingeladen" "Neue Nachrichten" "Reagierte mit %1$s" "Als gelesen markieren" diff --git a/libraries/textcomposer/src/main/res/values-cs/translations.xml b/libraries/textcomposer/src/main/res/values-cs/translations.xml index 8e0524b69a..c37f419ec4 100644 --- a/libraries/textcomposer/src/main/res/values-cs/translations.xml +++ b/libraries/textcomposer/src/main/res/values-cs/translations.xml @@ -1,5 +1,6 @@ + "Přidat přílohu" "Přepnout seznam s odrážkami" "Přepnout blok kódu" "Zpráva…" diff --git a/libraries/textcomposer/src/main/res/values-de/translations.xml b/libraries/textcomposer/src/main/res/values-de/translations.xml index a28c784792..dea872d7c0 100644 --- a/libraries/textcomposer/src/main/res/values-de/translations.xml +++ b/libraries/textcomposer/src/main/res/values-de/translations.xml @@ -1,5 +1,6 @@ + "Anhang hinzufügen" "Aufzählungsliste ein-/ausschalten" "Codeblock umschalten" "Nachricht…" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 9d897db9d8..ee1b221ad7 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -24,7 +24,7 @@ "Upravit" "Povolit" "Zapomněli jste heslo?" - "Vpřed" + "Přeposlat" "Pozvat" "Pozvat přátele" "Pozvat přátele do %1$s" @@ -40,6 +40,7 @@ "Otevřít v aplikaci" "Rychlá odpověď" "Citovat" + "Reagovat" "Odstranit" "Odpovědět" "Nahlásit chybu" @@ -94,6 +95,9 @@ "Heslo" "Lidé" "Trvalý odkaz" + "Konečné hlasy: %1$s" + "Celkový počet hlasů: %1$s" + "Výsledky se zobrazí po skončení hlasování" "Zásady ochrany osobních údajů" "Reakce" "Obnovování…" @@ -140,7 +144,11 @@ "Cestování a místa" "Symboly" "Vytvoření trvalého odkazu se nezdařilo" + "%1$s nemohl načíst mapu. Zkuste to prosím později." "Načítání zpráv se nezdařilo" + "%1$s nemá přístup k vaší poloze. Zkuste to prosím později." + "%1$s nemá oprávnění k přístupu k vaší poloze. Přístup můžete povolit v Nastavení." + "%1$s nemá oprávnění k přístupu k vaší poloze. Povolit přístup níže." "Některé zprávy nebyly odeslány" "Omlouváme se, došlo k chybě" "🔐️ Připojte se ke mně na %1$s" @@ -154,6 +162,11 @@ "%1$d členové" "%1$d členů" + + "%d hlas" + "%d hlasy" + "%d hlasů" + "Zatřeste zařízením pro nahlášení chyby" "Zdá se, že jste frustrovaně třásli telefonem. Chcete otevřít obrazovku pro nahlášení chyby?" "Tato zpráva bude nahlášena správci vašeho domovského serveru. Nebude si moci přečíst žádné šifrované zprávy." @@ -167,7 +180,35 @@ "Nahrání média se nezdařilo, zkuste to prosím znovu." "Toto je jednorázový proces, děkujeme za čekání." "Nastavení vašeho účtu" + "Další nastavení" + "Halsové a video hovory" + "Neshoda konfigurace" + "Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností. + +Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní. + +Pokud budete pokračovat, některá nastavení se mohou změnit." + "Přímé zprávy" + "Vlastní nastavení pro chat" + "Při aktualizaci nastavení oznámení došlo k chybě." + "Všechny zprávy" + "Pouze zmínky a klíčová slova" + "V přímých zprávách mě upozornit na" + "Ve skupinových chatech mě upozornit na" + "Povolit oznámení na tomto zařízení" + "Konfigurace nebyla opravena, zkuste to prosím znovu." + "Skupinové chaty" + "Zmínky" + "Vše" + "Zmínky" + "Upozornit mě na" + "Upozornit mě na @room" + "Chcete-li dostávat oznámení, změňte prosím svůj %1$s." + "systémová nastavení" + "Systémová oznámení byla vypnuta" + "Oznámení" "Zaškrtněte, pokud chcete skrýt všechny aktuální a budoucí zprávy od tohoto uživatele" + "Účet a zařízení" "Sdílet polohu" "Sdílet moji polohu" "Otevřít v Mapách Apple" diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 0f5259e663..68223c8513 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -8,7 +8,7 @@ "Zurück" "Abbrechen" "Foto auswählen" - "Löschen" + "Zurücksetzen" "Schließen" "Verifizierung abschließen" "Bestätigen" @@ -37,9 +37,10 @@ "Nein" "Nicht jetzt" "OK" - "Öffne mit" + "Öffnen mit" "Schnellantwort" "Zitieren" + "Reagieren" "Entfernen" "Antworten" "Fehler melden" @@ -56,7 +57,7 @@ "Starten" "Chat starten" "Verifizierung starten" - "Tippe, um die Karte zu laden" + "Zum Karte laden tippen" "Foto aufnehmen" "Quelltext anzeigen" "Ja" @@ -80,25 +81,28 @@ "Nachricht weiterleiten" "GIF" "Bild" - "Wir können die Matrix-ID dieses Benutzers nicht validieren. Die Einladung wurde möglicherweise nicht empfangen." - "Raum verlassen" + "Diese Matrix-ID kann nicht gefunden werden, daher wird die Einladung möglicherweise nicht empfangen." + "Verlasse Raum" "Link in Zwischenablage kopiert" - "Wird geladen…" + "Lädt…" "Nachricht" "Nachrichtenlayout" - "Nachricht wurde entfernt" + "Nachricht entfernt" "Modern" "Stummschalten" "Keine Ergebnisse" "Offline" "Passwort" "Personen" - "Permalink" + "Dauerlink" + "Endgültige Stimmen: %1$s" + "Stimmen insgesamt: %1$s" + "Ergebnisse werden nach Ende der Umfrage angezeigt" "Datenschutz­erklärung" "Reaktionen" "Aktualisiere…" "Auf %1$s antworten" - "Melde einen Fehler" + "Einen Fehler melden" "Bericht gesendet" "Raumname" "z.B. dein Projektname" @@ -106,12 +110,12 @@ "Suchergebnisse" "Sicherheit" "Wählen deinen Server" - "Senden…" + "Sendet…" "Server wird nicht unterstützt" "Server-URL" "Einstellungen" "Geteilter Standort" - "Chat wird gestartet…" + "Starte Chat…" "Sticker" "Erfolg" "Vorschläge" @@ -120,7 +124,7 @@ "Thema" "Worum geht es in diesem Raum?" "Entschlüsselung nicht möglich" - "Wir konnten Einladungen nicht erfolgreich an einen oder mehrere Benutzer senden." + "Einladungen konnten nicht an einen oder mehrere Benutzer gesendet werden." "Einladung(en) können nicht gesendet werden" "Stummschaltung aufheben" "Nicht unterstütztes Ereignis" @@ -128,7 +132,7 @@ "Verifizierung abgebrochen" "Verifizierung abgeschlossen" "Video" - "Warten…" + "Warte…" "Bestätigung" "Warnung" "Aktivitäten" @@ -139,10 +143,12 @@ "Smileys & Personen" "Reisen & Orte" "Symbole" - "Fehler beim Erstellen des Permalinks" + "Fehler beim Erstellen des Dauerlinks" "%1$s konnte die Karte nicht laden. Bitte versuche es später erneut." "Fehler beim Laden der Nachrichten" "%1$s konnte nicht auf deinen Standort zugreifen. Bitte versuche es später erneut." + "%1$s hat keine Berechtigung, auf deinen Standort zuzugreifen. Du kannst den Zugriff in den Einstellungen aktivieren." + "%1$s hat keine Berechtigung, auf deinen Standort zuzugreifen. Aktiviere den Zugriff unten." "Einige Nachrichten wurden nicht gesendet" "Entschuldigung, ein Fehler ist aufgetreten." "🔐️ Besuche mich auf %1$s" @@ -155,9 +161,13 @@ "%1$d Mitglied" "%1$d Mitglieder" - "Rageshake zum Melden von Fehlern" + + "%d Stimme" + "%d Stimmen" + + "Schütteln zum Melden von Fehlern" "Du scheinst frustriert das Telefon zu schütteln. Möchtest du den Fehlerberichtsbildschirm öffnen?" - "Diese Nachricht wird an deinen Heimserver-Admin gemeldet werden. Er wird nicht in der Lage sein, verschlüsselte Nachrichten zu lesen." + "Diese Nachricht wird an deinen Heimserver-Admin gemeldet. Er wird nicht in der Lage sein, verschlüsselte Nachrichten zu lesen." "Grund für die Meldung dieses Inhalts" "Dies ist der Anfang von %1$s." "Dies ist der Beginn dieser Konversation." @@ -168,7 +178,35 @@ "Hochladen von Medien fehlgeschlagen, bitte versuchen Sie es erneut." "Dies ist ein einmaliger Vorgang, danke fürs Warten." "Dein Konto einrichten" + "Zusätzliche Einstellungen" + "Audio- und Videoanrufe" + "Konfigurationskonflikt" + "Wir haben die Benachrichtigungseinstellungen vereinfacht, damit Optionen leichter zu finden sind. + +Einige benutzerdefinierte Einstellungen, die du in der Vergangenheit ausgewählt hast, werden hier nicht angezeigt, sind aber immer noch aktiv. + +Wenn du fortfährst, ändern sich möglicherweise einige deine Einstellungen." + "Direkte Chats" + "Benutzerdefinierte Einstellung pro Chat" + "Beim Aktualisieren der Benachrichtigungseinstellung ist ein Fehler aufgetreten." + "Alle Nachrichten" + "Nur Erwähnungen und Schlüsselwörter" + "Bei direkten Chats, benachrichtigen mich für" + "Bei Gruppenchats, benachrichtigte mich für" + "Benachrichtigungen auf diesem Gerät aktivieren" + "Die Konfiguration wurde nicht korrigiert. Bitte versuche es erneut." + "Gruppenchats" + "Erwähnungen" + "Alle" + "Erwähnungen" + "Benachrichtige mich für" + "Benachrichtige mich bei @room" + "Um Benachrichtigungen zu erhalten, ändern bitte deine %1$s." + "Systemeinstellungen" + "Systembenachrichtigungen deaktiviert" + "Benachrichtigungen" "Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest" + "Konto und Geräte" "Standort teilen" "Meinen Standort teilen" "In Apple Maps öffnen" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index 77c2df4268..865eace823 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -182,11 +182,21 @@ "Настройка учетной записи." "Дополнительные параметры" "Аудио и видео звонки" + "Несоответствие конфигурации" + "Мы упростили настройки уведомлений, чтобы упростить поиск опций. + +Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны. + +Если вы продолжите, некоторые настройки могут быть изменены." "Прямые чаты" + "Индивидуальные настройки для каждого чата" "При обновлении настроек уведомления произошла ошибка." + "Все сообщения" + "Только упоминания и ключевые слова" "Уведомлять меня в личных чатах" "Уведомлять меня в групповых чатах" "Включить уведомления на данном устройстве" + "Конфигурация не была исправлена, попробуйте еще раз." "Групповые чаты" "Упоминания" "Все" @@ -198,6 +208,7 @@ "Системные уведомления выключены" "Уведомления" "Отметьте, хотите ли вы скрыть все текущие и будущие сообщения от этого пользователя" + "Учетная запись и устройства" "Поделиться местоположением" "Поделиться моим местоположением" "Открыть в Apple Maps" diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index 14dd5626c8..a070f874e9 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -182,10 +182,21 @@ "Nastavenie vášho účtu." "Ďalšie nastavenia" "Audio a video hovory" + "Nezhoda konfigurácie" + "Zjednodušili sme Nastavenia oznámení, aby ste ľahšie našli možnosti. + +Niektoré vlastné nastavenia, ktoré ste si nastavili v minulosti, sa tu nezobrazujú, ale sú stále aktívne. + +Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť." "Priame konverzácie" + "Vlastné nastavenie pre konverzácie" + "Pri aktualizácii nastavenia oznámenia došlo k chybe." + "Všetky správy" + "Iba zmienky a kľúčové slová" "Pri priamych rozhovoroch ma upozorniť na" "Pri skupinových rozhovoroch ma upozorniť na" "Povoliť oznámenia na tomto zariadení" + "Konfigurácia nebola opravená, skúste to prosím znova." "Skupinové rozhovory" "Zmienky" "Všetky" @@ -197,6 +208,7 @@ "Systémové oznámenia sú vypnuté" "Oznámenia" "Označte, či chcete skryť všetky aktuálne a budúce správy od tohto používateľa" + "Účet a zariadenia" "Zdieľať polohu" "Zdieľať moju polohu" "Otvoriť v Apple Maps" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index e38c4fc6b8..db58894472 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -181,9 +181,9 @@ "Additional settings" "Audio and video calls" "Configuration mismatch" - "We’ve simplified Notifications Settings to make options easier to find. + "We’ve simplified Notifications Settings to make options easier to find. -Some custom settings you’ve chosen in the past are not shown here, but they’re still active. +Some custom settings you’ve chosen in the past are not shown here, but they’re still active. If you proceed, some of your settings may change." "Direct chats" From cc604e8f9d99f99be044916d2858fe973a0e8ceb Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 25 Aug 2023 10:22:22 +0000 Subject: [PATCH 35/37] Update screenshots --- ...ChangeAccountProviderViewDark_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...hangeAccountProviderViewLight_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...SearchAccountProviderViewDark_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...earchAccountProviderViewLight_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewDark_0_null_0,NEXUS_5,1.0,en].png index ab3bc3ee7f..bc067f20c1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7aba18d2003a96a34e6eecf4fc6d0ee139fa8073549033da04eff3fa20b4c93 -size 40454 +oid sha256:a7f1d73aa1698bb02c03773ca9c3ec8494089d03c26d52b3b6f1aaa40a081528 +size 49266 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewLight_0_null_0,NEXUS_5,1.0,en].png index 87a527b090..3b94b23ab9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.changeaccountprovider_null_ChangeAccountProviderViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0422cc4e2bae580a88dacd711570e0c5ba105d5baae103313e42a07991823083 -size 42103 +oid sha256:f2154c7d196058a0a47aa46658ad9d2ea3edf6b89e5973396980178a2399179a +size 50726 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewDark_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewDark_0_null_1,NEXUS_5,1.0,en].png index e36afba0a8..3b68e95447 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewDark_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewDark_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64e67ea3130d0e70bdc8d0edc0bfa92763673c15b3235d4332f554a0e7a4840a -size 45553 +oid sha256:b6344d9976849f1e56782a463fcca625943a7c6adf74c598c7a58245809b9db5 +size 53642 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewLight_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewLight_0_null_1,NEXUS_5,1.0,en].png index f589e41a6d..2e35a8461c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewLight_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.login.impl.screens.searchaccountprovider_null_SearchAccountProviderViewLight_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f2fd6d2fccd68abec9d829ccd673b72a0f1277644a1affb09faed7be8b8a416 -size 47021 +oid sha256:fd7e6fc09011429e0fe19bff73d3c48124b1f3bed74d3cb12c9f3f0405f53980 +size 55728 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png index 3db42b6e0c..c1d740b8cb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components_null_TimelineItemEventTimestampBelow_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5af81e5f2081b949673c5f08282b5165eb7df4bb970c1db485376c86543e1a96 -size 57313 +oid sha256:f689d80043e7d5121d072b928c866a3bde0358b5d5df06e1d4f0ceeb9a11dfef +size 56344 From 89f1492233f0e6d650181b506823eea8dbe5e9d3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 12:50:53 +0200 Subject: [PATCH 36/37] Remove translation with issue. Will be synced later. --- .../messages/impl/src/main/res/values-de/translations.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/features/messages/impl/src/main/res/values-de/translations.xml b/features/messages/impl/src/main/res/values-de/translations.xml index c24483a2f7..482065c887 100644 --- a/features/messages/impl/src/main/res/values-de/translations.xml +++ b/features/messages/impl/src/main/res/values-de/translations.xml @@ -4,10 +4,6 @@ "%1$d Raumänderung" "%1$d Raumänderungen" - - - "%1$d weitere" - "Kamera" "Foto aufnehmen" "Video aufnehmen" From 1a5269e38abb08849c9edd55a26df42fb85ddb9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 09:37:23 +0200 Subject: [PATCH 37/37] Update org.maplibre.gl to v2.0.1 (#1151) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 178bb3bf90..118e6324c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -159,8 +159,8 @@ vanniktech_emoji = "com.vanniktech:emoji-google:0.16.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.0" maplibre = "org.maplibre.gl:android-sdk:10.2.0" -maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.0" -maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.0" +maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:2.0.1" +maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.1" # Analytics posthog = "com.posthog.android:posthog:2.0.3"