From 079d89ace50c6f7c5a05b66ebe0a378b0fdd75ac Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 17 Aug 2023 14:16:22 +0200 Subject: [PATCH 01/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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 c84b1747e7de14d9144faf090a670346420bbde8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 17:23:12 +0200 Subject: [PATCH 15/86] Handle remarks from PR #1127 --- .../ConfirmAccountProviderPresenter.kt | 9 ++++----- .../features/logout/api/LogoutPreferenceScreen.kt | 7 +++++-- .../preferences/impl/root/PreferencesRootView.kt | 2 +- .../element/android/libraries/matrix/api/MatrixClient.kt | 3 ++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 61ef4d724b..d5412cb139 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -68,9 +68,9 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( } LaunchedEffect(Unit) { - launch { - defaultOidcActionFlow.collect { - onOidcAction(it, loginFlowAction) + defaultOidcActionFlow.collect { oidcAction -> + if (oidcAction != null) { + onOidcAction(oidcAction, loginFlowAction) } } } @@ -113,10 +113,9 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( } private suspend fun onOidcAction( - oidcAction: OidcAction?, + oidcAction: OidcAction, loginFlowAction: MutableState>, ) { - oidcAction ?: return loginFlowAction.value = Async.Loading() when (oidcAction) { OidcAction.GoBack -> { diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt index f9f23ac9c4..7be1c977f2 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt @@ -34,7 +34,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight @Composable fun LogoutPreferenceView( state: LogoutPreferenceState, - onSuccessLogout: (String?) -> Unit = {} + onSuccessLogout: (logoutUrlResult: String?) -> Unit ) { val eventSink = state.eventSink if (state.logoutAction is Async.Success) { @@ -96,5 +96,8 @@ internal fun LogoutPreferenceViewDarkPreview() = ElementPreviewDark { ContentToP @Composable private fun ContentToPreview() { - LogoutPreferenceView(aLogoutPreferenceState()) + LogoutPreferenceView( + aLogoutPreferenceState(), + onSuccessLogout = {} + ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index c24a2ec875..df108c03a3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -57,7 +57,7 @@ fun PreferencesRootView( onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, onOpenDeveloperSettings: () -> Unit, - onSuccessLogout: (String?) -> Unit, + onSuccessLogout: (logoutUrlResult: String?) -> Unit, modifier: Modifier = Modifier, ) { val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index be64a3371f..2ca4420145 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -58,7 +58,8 @@ interface MatrixClient : Closeable { /** * Logout the user. - * Returns an optional URL. When the URL is there, it should be presented to the user after logout for RP initiated logout on their account page. + * Returns an optional URL. When the URL is there, it should be presented to the user after logout for + * Relying Party (RP) initiated logout on their account page. */ suspend fun logout(): String? suspend fun loadUserDisplayName(): Result From 26e5da94d6f9a3cbf4f19e4a04dc9cc6c5f19f39 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 18:11:08 +0200 Subject: [PATCH 16/86] Add a workaround to detect the Chrome Custom Tab closing (when there is no redirection). --- .../features/login/impl/LoginFlowNode.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index fe47fb1b67..e545bf7b86 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -22,7 +22,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -33,6 +35,8 @@ import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.api.oidc.OidcAction +import io.element.android.features.login.api.oidc.OidcActionFlow import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler @@ -51,6 +55,8 @@ import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.theme.ElementTheme +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @ContributesNode(AppScope::class) @@ -61,6 +67,7 @@ class LoginFlowNode @AssistedInject constructor( private val customTabHandler: CustomTabHandler, private val accountProviderDataSource: AccountProviderDataSource, private val defaultLoginUserStory: DefaultLoginUserStory, + private val oidcActionFlow: OidcActionFlow, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.ConfirmAccountProvider, @@ -78,9 +85,26 @@ class LoginFlowNode @AssistedInject constructor( private val inputs: Inputs = inputs() + private var customChromeTabStarted = false + override fun onBuilt() { super.onBuilt() defaultLoginUserStory.setLoginFlowIsDone(false) + lifecycle.subscribe( + onResume = { + if (customChromeTabStarted) { + customChromeTabStarted = false + // Workaround to detect that the Custom Chrome Tab has been closed + // If there is no coming OidcAction (that would end this Node), + // consider that the user has cancelled the login + // by pressing back or by closing the Custom Chrome Tab. + lifecycleScope.launch { + delay(5000) + oidcActionFlow.post(OidcAction.GoBack) + } + } + } + ) } sealed interface NavTarget : Parcelable { @@ -113,7 +137,10 @@ class LoginFlowNode @AssistedInject constructor( override fun onOidcDetails(oidcDetails: OidcDetails) { if (customTabAvailabilityChecker.supportCustomTab()) { // In this case open a Chrome Custom tab - activity?.let { customTabHandler.open(it, darkTheme, oidcDetails.url) } + activity?.let { + customChromeTabStarted = true + customTabHandler.open(it, darkTheme, oidcDetails.url) + } } else { // Fallback to WebView mode backstack.push(NavTarget.OidcView(oidcDetails)) From 7083abcf09b1a58bb0c864e8bcea4850e9921273 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 09:32:44 +0200 Subject: [PATCH 17/86] migrate `object` to `data object` (#1135) --- .../android/appnav/LoggedInFlowNode.kt | 14 ++-- .../android/appnav/NotLoggedInFlowNode.kt | 2 +- .../io/element/android/appnav/RootFlowNode.kt | 6 +- .../android/appnav/loggedin/LoggedInEvents.kt | 2 +- .../android/appnav/room/LoadingRoomState.kt | 4 +- .../android/appnav/room/RoomFlowNode.kt | 4 +- .../android/appnav/room/RoomLoadedFlowNode.kt | 4 +- .../createroom/impl/ConfigureRoomFlowNode.kt | 4 +- .../createroom/impl/CreateRoomFlowNode.kt | 4 +- .../impl/configureroom/ConfigureRoomEvents.kt | 2 +- .../impl/root/CreateRoomRootEvents.kt | 2 +- .../features/ftue/impl/FtueFlowNode.kt | 6 +- .../ftue/impl/state/DefaultFtueState.kt | 4 +- .../invitelist/impl/InviteListEvents.kt | 10 +-- .../invitelist/impl/InviteListState.kt | 2 +- .../features/leaveroom/api/LeaveRoomEvent.kt | 4 +- .../features/leaveroom/api/LeaveRoomState.kt | 10 +-- .../common/permissions/PermissionsEvents.kt | 2 +- .../common/permissions/PermissionsState.kt | 6 +- .../location/impl/send/SendLocationEvents.kt | 14 ++-- .../location/impl/send/SendLocationState.kt | 10 +-- .../location/impl/show/ShowLocationEvents.kt | 10 +-- .../location/impl/show/ShowLocationState.kt | 6 +- .../features/login/api/oidc/OidcAction.kt | 2 +- .../features/login/impl/LoginFlowNode.kt | 8 +- .../impl/changeserver/ChangeServerEvents.kt | 2 +- .../login/impl/error/ChangeServerError.kt | 2 +- .../login/impl/oidc/webview/OidcEvents.kt | 4 +- .../ConfirmAccountProviderEvents.kt | 4 +- .../ConfirmAccountProviderState.kt | 2 +- .../loginpassword/LoginPasswordEvents.kt | 4 +- .../screens/waitlistscreen/WaitListEvents.kt | 6 +- .../logout/api/LogoutPreferenceEvents.kt | 2 +- .../features/messages/impl/MessagesEvents.kt | 2 +- .../messages/impl/MessagesFlowNode.kt | 4 +- .../impl/actionlist/ActionListEvents.kt | 2 +- .../impl/actionlist/ActionListState.kt | 2 +- .../actionlist/model/TimelineItemAction.kt | 14 ++-- .../preview/AttachmentsPreviewEvents.kt | 4 +- .../preview/AttachmentsPreviewState.kt | 6 +- .../impl/forward/ForwardMessagesEvents.kt | 8 +- .../impl/media/viewer/MediaViewerEvents.kt | 10 +-- .../messagecomposer/MessageComposerEvents.kt | 20 ++--- .../messagecomposer/MessageComposerState.kt | 2 +- .../impl/report/ReportMessageEvents.kt | 6 +- .../messages/impl/timeline/TimelineEvents.kt | 2 +- .../reactionsummary/ReactionSummaryEvents.kt | 2 +- .../retrysendmenu/RetrySendMenuEvents.kt | 6 +- .../model/TimelineItemGroupPosition.kt | 8 +- .../features/poll/impl/PollFlowNode.kt | 2 +- .../preferences/impl/PreferencesFlowNode.kt | 8 +- .../preferences/impl/about/ElementLegal.kt | 6 +- .../impl/developer/DeveloperSettingsEvents.kt | 2 +- .../api/crash/CrashDetectionEvents.kt | 4 +- .../api/detection/RageshakeDetectionEvents.kt | 8 +- .../impl/bugreport/BugReportEvents.kt | 6 +- .../roomdetails/api/RoomDetailsEntryPoint.kt | 2 +- .../roomdetails/impl/RoomDetailsAction.kt | 5 +- .../roomdetails/impl/RoomDetailsEvent.kt | 2 +- .../roomdetails/impl/RoomDetailsFlowNode.kt | 8 +- .../roomdetails/impl/RoomDetailsState.kt | 6 +- .../impl/edit/RoomDetailsEditEvents.kt | 4 +- .../details/RoomMemberDetailsEvents.kt | 4 +- .../features/roomlist/impl/RoomListEvents.kt | 6 +- .../features/roomlist/impl/RoomListState.kt | 2 +- .../impl/VerifySelfSessionState.kt | 10 +-- .../impl/VerifySelfSessionStateMachine.kt | 38 ++++----- .../impl/VerifySelfSessionViewEvents.kt | 12 +-- .../androidutils/throttler/FirstThrottler.kt | 2 +- .../android/libraries/architecture/Async.kt | 2 +- .../atomic/atoms/ElementLogoAtom.kt | 4 +- .../designsystem/components/ProgressDialog.kt | 2 +- .../designsystem/theme/components/ListItem.kt | 6 +- .../theme/components/ListSection.kt | 8 +- .../api/notification/NotificationData.kt | 69 ++++++++-------- .../matrix/api/permalink/PermalinkBuilder.kt | 6 +- .../matrix/api/room/MatrixRoomMembersState.kt | 2 +- .../libraries/matrix/api/roomlist/RoomList.kt | 2 +- .../matrix/api/roomlist/RoomListService.kt | 8 +- .../matrix/api/timeline/MatrixTimelineItem.kt | 2 +- .../matrix/api/timeline/TimelineException.kt | 2 +- .../api/timeline/item/event/EventContent.kt | 79 ++++++------------- .../item/event/LocalEventSendState.kt | 4 +- .../item/event/ProfileTimelineDetails.kt | 4 +- .../item/virtual/VirtualTimelineItem.kt | 4 +- .../api/tracing/TracingFilterConfiguration.kt | 10 +-- .../api/tracing/WriteToFilesConfiguration.kt | 2 +- .../SessionVerificationService.kt | 18 ++--- .../libraries/matrix/ui/media/AvatarAction.kt | 6 +- .../matrix/ui/media/MediaRequestData.kt | 2 +- .../libraries/mediapickers/api/PickerType.kt | 4 +- .../libraries/mediaupload/ImageCompressor.kt | 2 +- .../permissions/api/PermissionsEvents.kt | 4 +- .../push/api/gateway/PushGatewayFailure.kt | 2 +- .../impl/notifications/NotificationFactory.kt | 2 +- .../unifiedpush/RegisterUnifiedPushUseCase.kt | 6 +- .../services/apperror/api/AppErrorState.kt | 4 +- .../appnavstate/api/NavigationState.kt | 2 +- 98 files changed, 314 insertions(+), 357 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 7943151a5e..cd5ac726a2 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -178,10 +178,10 @@ class LoggedInFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Permanent : NavTarget + data object Permanent : NavTarget @Parcelize - object RoomList : NavTarget + data object RoomList : NavTarget @Parcelize data class Room( @@ -190,19 +190,19 @@ class LoggedInFlowNode @AssistedInject constructor( ) : NavTarget @Parcelize - object Settings : NavTarget + data object Settings : NavTarget @Parcelize - object CreateRoom : NavTarget + data object CreateRoom : NavTarget @Parcelize - object VerifySession : NavTarget + data object VerifySession : NavTarget @Parcelize - object InviteList : NavTarget + data object InviteList : NavTarget @Parcelize - object Ftue : NavTarget + data object Ftue : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index 1ed1aec678..17f3a44eb8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -64,7 +64,7 @@ class NotLoggedInFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object OnBoarding : NavTarget + data object OnBoarding : NavTarget @Parcelize data class LoginFlow( diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 089e956c61..4b6d2395ce 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -170,10 +170,10 @@ class RootFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object SplashScreen : NavTarget + data object SplashScreen : NavTarget @Parcelize - object NotLoggedInFlow : NavTarget + data object NotLoggedInFlow : NavTarget @Parcelize data class LoggedInFlow( @@ -182,7 +182,7 @@ class RootFlowNode @AssistedInject constructor( ) : NavTarget @Parcelize - object BugReport : NavTarget + data object BugReport : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt index 664ec1f663..be784ea7c9 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt @@ -17,5 +17,5 @@ package io.element.android.appnav.loggedin // sealed interface LoggedInEvents { -// object MyEvent : LoggedInEvents +// data object MyEvent : LoggedInEvents // } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt index db4627c3b4..3836fbff74 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomState.kt @@ -32,8 +32,8 @@ import kotlinx.coroutines.flow.stateIn import javax.inject.Inject sealed interface LoadingRoomState { - object Loading : LoadingRoomState - object Error : LoadingRoomState + data object Loading : LoadingRoomState + data object Error : LoadingRoomState data class Loaded(val room: MatrixRoom) : LoadingRoomState } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 661d3c5433..f8fa7e629f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -77,10 +77,10 @@ class RoomFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Loading : NavTarget + data object Loading : NavTarget @Parcelize - object Loaded : NavTarget + data object Loaded : NavTarget } override fun onBuilt() { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index d00c4791f7..8230e62119 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -152,10 +152,10 @@ class RoomLoadedFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Messages : NavTarget + data object Messages : NavTarget @Parcelize - object RoomDetails : NavTarget + data object RoomDetails : NavTarget @Parcelize data class RoomMemberDetails(val userId: UserId) : NavTarget diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt index a5a78e54d5..3b96ac3edd 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt @@ -63,10 +63,10 @@ class ConfigureRoomFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Root : NavTarget + data object Root : NavTarget @Parcelize - object ConfigureRoom : NavTarget + data object ConfigureRoom : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 6f447e6bc9..207ab73e66 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -54,10 +54,10 @@ class CreateRoomFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Root : NavTarget + data object Root : NavTarget @Parcelize - object NewRoom : NavTarget + data object NewRoom : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt index a020b387cb..f5dcfd8451 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt @@ -27,5 +27,5 @@ sealed interface ConfigureRoomEvents { data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents - object CancelCreateRoom : ConfigureRoomEvents + data object CancelCreateRoom : ConfigureRoomEvents } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt index 7d8211aea5..b22489dd2d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt @@ -20,5 +20,5 @@ import io.element.android.libraries.matrix.api.user.MatrixUser sealed interface CreateRoomRootEvents { data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents - object CancelStartDM : CreateRoomRootEvents + data object CancelStartDM : CreateRoomRootEvents } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 0ff9c80d46..8866154fff 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -69,13 +69,13 @@ class FtueFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Placeholder : NavTarget + data object Placeholder : NavTarget @Parcelize - object WelcomeScreen : NavTarget + data object WelcomeScreen : NavTarget @Parcelize - object AnalyticsOptIn : NavTarget + data object AnalyticsOptIn : NavTarget } private val callback = plugins.filterIsInstance().firstOrNull() diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt index 52c8d90254..f67c25a21d 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt @@ -89,6 +89,6 @@ class DefaultFtueState @Inject constructor( } sealed interface FtueStep { - object WelcomeScreen : FtueStep - object AnalyticsOptIn : FtueStep + data object WelcomeScreen : FtueStep + data object AnalyticsOptIn : FtueStep } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt index 0b8f03b45a..38055b7090 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListEvents.kt @@ -19,14 +19,12 @@ package io.element.android.features.invitelist.impl import io.element.android.features.invitelist.impl.model.InviteListInviteSummary sealed interface InviteListEvents { - data class AcceptInvite(val invite: InviteListInviteSummary) : InviteListEvents data class DeclineInvite(val invite: InviteListInviteSummary) : InviteListEvents - object ConfirmDeclineInvite: InviteListEvents - object CancelDeclineInvite: InviteListEvents - - object DismissAcceptError: InviteListEvents - object DismissDeclineError: InviteListEvents + data object ConfirmDeclineInvite: InviteListEvents + data object CancelDeclineInvite: InviteListEvents + data object DismissAcceptError: InviteListEvents + data object DismissDeclineError: InviteListEvents } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt index 5a7761ebc0..c1e00727f9 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListState.kt @@ -32,6 +32,6 @@ data class InviteListState( ) sealed interface InviteDeclineConfirmationDialog { - object Hidden : InviteDeclineConfirmationDialog + data object Hidden : InviteDeclineConfirmationDialog data class Visible(val isDirect: Boolean, val name: String) : InviteDeclineConfirmationDialog } diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt index d1a3369ac6..9a9eb80997 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt @@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.RoomId sealed interface LeaveRoomEvent { data class ShowConfirmation(val roomId: RoomId) : LeaveRoomEvent - object HideConfirmation : LeaveRoomEvent + data object HideConfirmation : LeaveRoomEvent data class LeaveRoom(val roomId: RoomId) : LeaveRoomEvent - object HideError : LeaveRoomEvent + data object HideError : LeaveRoomEvent } diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt index 7cb9926677..3f14833cf0 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt @@ -25,19 +25,19 @@ data class LeaveRoomState( val eventSink: (LeaveRoomEvent) -> Unit = {}, ) { sealed interface Confirmation { - object Hidden : Confirmation + data object Hidden : Confirmation data class Generic(val roomId: RoomId) : Confirmation data class PrivateRoom(val roomId: RoomId) : Confirmation data class LastUserInRoom(val roomId: RoomId) : Confirmation } sealed interface Progress { - object Hidden : Progress - object Shown : Progress + data object Hidden : Progress + data object Shown : Progress } sealed interface Error { - object Hidden : Error - object Shown : Error + data object Hidden : Error + data object Shown : Error } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt index fc18ec6ede..f4282bc59c 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt @@ -17,5 +17,5 @@ package io.element.android.features.location.impl.common.permissions sealed interface PermissionsEvents { - object RequestPermissions : PermissionsEvents + data object RequestPermissions : PermissionsEvents } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt index 76b786c638..d58361a82f 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt @@ -22,9 +22,9 @@ data class PermissionsState( val eventSink: (PermissionsEvents) -> Unit = {}, ) { sealed interface Permissions { - object AllGranted : Permissions - object SomeGranted : Permissions - object NoneGranted : Permissions + data object AllGranted : Permissions + data object SomeGranted : Permissions + data object NoneGranted : Permissions } val isAnyGranted: Boolean diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt index 2f0686da27..d39be47b40 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt @@ -30,13 +30,9 @@ sealed interface SendLocationEvents { ) } - object SwitchToMyLocationMode : SendLocationEvents - - object SwitchToPinLocationMode : SendLocationEvents - - object DismissDialog : SendLocationEvents - - object RequestPermissions : SendLocationEvents - - object OpenAppSettings : SendLocationEvents + data object SwitchToMyLocationMode : SendLocationEvents + data object SwitchToPinLocationMode : SendLocationEvents + data object DismissDialog : SendLocationEvents + data object RequestPermissions : SendLocationEvents + data object OpenAppSettings : SendLocationEvents } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt index 3aeec5f046..5dae23c998 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt @@ -24,13 +24,13 @@ data class SendLocationState( val eventSink: (SendLocationEvents) -> Unit = {}, ) { sealed interface Mode { - object SenderLocation : Mode - object PinLocation : Mode + data object SenderLocation : Mode + data object PinLocation : Mode } sealed interface Dialog { - object None : Dialog - object PermissionRationale : Dialog - object PermissionDenied : Dialog + data object None : Dialog + data object PermissionRationale : Dialog + data object PermissionDenied : Dialog } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt index f6c3c12528..21eed4a22d 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt @@ -17,11 +17,9 @@ package io.element.android.features.location.impl.show sealed interface ShowLocationEvents { - object Share : ShowLocationEvents + data object Share : ShowLocationEvents data class TrackMyLocation(val enabled: Boolean) : ShowLocationEvents - object DismissDialog : ShowLocationEvents - - object RequestPermissions : ShowLocationEvents - - object OpenAppSettings : ShowLocationEvents + data object DismissDialog : ShowLocationEvents + data object RequestPermissions : ShowLocationEvents + data object OpenAppSettings : ShowLocationEvents } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt index 0be6938ef6..67bcfa382e 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt @@ -28,8 +28,8 @@ data class ShowLocationState( val eventSink: (ShowLocationEvents) -> Unit, ) { sealed interface Dialog { - object None : Dialog - object PermissionRationale : Dialog - object PermissionDenied : Dialog + data object None : Dialog + data object PermissionRationale : Dialog + data object PermissionDenied : Dialog } } diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/oidc/OidcAction.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/oidc/OidcAction.kt index 6e90a390c4..6d87872879 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/oidc/OidcAction.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/oidc/OidcAction.kt @@ -17,6 +17,6 @@ package io.element.android.features.login.api.oidc sealed interface OidcAction { - object GoBack : OidcAction + data object GoBack : OidcAction data class Success(val url: String) : OidcAction } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index fe47fb1b67..bc34536a2c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -85,16 +85,16 @@ class LoginFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object ConfirmAccountProvider : NavTarget + data object ConfirmAccountProvider : NavTarget @Parcelize - object ChangeAccountProvider : NavTarget + data object ChangeAccountProvider : NavTarget @Parcelize - object SearchAccountProvider : NavTarget + data object SearchAccountProvider : NavTarget @Parcelize - object LoginPassword : NavTarget + data object LoginPassword : NavTarget @Parcelize data class WaitList(val loginFormState: LoginFormState) : NavTarget diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt index cd1cb7b4ce..3a1945da9d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt @@ -20,5 +20,5 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider sealed interface ChangeServerEvents { data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents - object ClearError : ChangeServerEvents + data object ClearError : ChangeServerEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt index 444ea3d3f2..f9289898b6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt @@ -27,7 +27,7 @@ sealed class ChangeServerError : Throwable() { @Composable fun message(): String = stringResource(messageId) } - object SlidingSyncAlert : ChangeServerError() + data object SlidingSyncAlert : ChangeServerError() companion object { fun from(error: Throwable): ChangeServerError = when (error) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt index 6265cfc85a..ae0a912ba6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcEvents.kt @@ -19,7 +19,7 @@ package io.element.android.features.login.impl.oidc.webview import io.element.android.features.login.api.oidc.OidcAction sealed interface OidcEvents { - object Cancel : OidcEvents + data object Cancel : OidcEvents data class OidcActionEvent(val oidcAction: OidcAction): OidcEvents - object ClearError : OidcEvents + data object ClearError : OidcEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt index 1ba3cc3028..6003c0a716 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt @@ -17,6 +17,6 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider sealed interface ConfirmAccountProviderEvents { - object Continue : ConfirmAccountProviderEvents - object ClearError : ConfirmAccountProviderEvents + data object Continue : ConfirmAccountProviderEvents + data object ClearError : ConfirmAccountProviderEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt index a870b88c58..c2c98101a5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt @@ -31,6 +31,6 @@ data class ConfirmAccountProviderState( } sealed interface LoginFlow { - object PasswordLogin : LoginFlow + data object PasswordLogin : LoginFlow data class OidcFlow(val oidcDetails: OidcDetails) : LoginFlow } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt index e6f23ca418..818fb97860 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt @@ -19,6 +19,6 @@ package io.element.android.features.login.impl.screens.loginpassword sealed interface LoginPasswordEvents { data class SetLogin(val login: String) : LoginPasswordEvents data class SetPassword(val password: String) : LoginPasswordEvents - object Submit : LoginPasswordEvents - object ClearError : LoginPasswordEvents + data object Submit : LoginPasswordEvents + data object ClearError : LoginPasswordEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt index 5ceee99f91..d5722e66d6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.screens.waitlistscreen sealed interface WaitListEvents { - object AttemptLogin : WaitListEvents - object ClearError : WaitListEvents - object Continue : WaitListEvents + data object AttemptLogin : WaitListEvents + data object ClearError : WaitListEvents + data object Continue : WaitListEvents } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceEvents.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceEvents.kt index 2dad1623ab..50dad213fd 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceEvents.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceEvents.kt @@ -17,5 +17,5 @@ package io.element.android.features.logout.api sealed interface LogoutPreferenceEvents { - object Logout : LogoutPreferenceEvents + data object Logout : LogoutPreferenceEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt index d475b5bc8c..b901f7e130 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt @@ -24,7 +24,7 @@ sealed interface MessagesEvents { data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents data class ToggleReaction(val emoji: String, val eventId: EventId) : MessagesEvents data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents - object Dismiss : MessagesEvents + data object Dismiss : MessagesEvents } enum class InviteDialogAction { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index da10171d0a..eee68768be 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -75,7 +75,7 @@ class MessagesFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Messages : NavTarget + data object Messages : NavTarget @Parcelize data class MediaViewer( @@ -100,7 +100,7 @@ class MessagesFlowNode @AssistedInject constructor( data class ReportMessage(val eventId: EventId, val senderId: UserId) : NavTarget @Parcelize - object SendLocation : NavTarget + data object SendLocation : NavTarget } private val callback = plugins().firstOrNull() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt index 3c796036e7..c5e6618736 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt @@ -19,6 +19,6 @@ package io.element.android.features.messages.impl.actionlist import io.element.android.features.messages.impl.timeline.model.TimelineItem sealed interface ActionListEvents { - object Clear : ActionListEvents + data object Clear : ActionListEvents data class ComputeForMessage(val event: TimelineItem.Event, val canRedact: Boolean) : ActionListEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt index aac3469218..a8fbf81486 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt @@ -28,7 +28,7 @@ data class ActionListState( val eventSink: (ActionListEvents) -> Unit, ) { sealed interface Target { - object None : Target + data object None : Target data class Loading(val event: TimelineItem.Event) : Target data class Success( val event: TimelineItem.Event, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index 8b2922e1d6..b6141218eb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -28,11 +28,11 @@ sealed class TimelineItemAction( @DrawableRes val icon: Int, val destructive: Boolean = false ) { - object Forward : TimelineItemAction(CommonStrings.action_forward, VectorIcons.Forward) - object Copy : TimelineItemAction(CommonStrings.action_copy, VectorIcons.Copy) - object Redact : TimelineItemAction(CommonStrings.action_remove, VectorIcons.Delete, destructive = true) - object Reply : TimelineItemAction(CommonStrings.action_reply, VectorIcons.Reply) - object Edit : TimelineItemAction(CommonStrings.action_edit, VectorIcons.Edit) - object Developer : TimelineItemAction(CommonStrings.action_view_source, VectorIcons.DeveloperMode) - object ReportContent : TimelineItemAction(CommonStrings.action_report_content, VectorIcons.ReportContent, destructive = true) + data object Forward : TimelineItemAction(CommonStrings.action_forward, VectorIcons.Forward) + data object Copy : TimelineItemAction(CommonStrings.action_copy, VectorIcons.Copy) + data object Redact : TimelineItemAction(CommonStrings.action_remove, VectorIcons.Delete, destructive = true) + data object Reply : TimelineItemAction(CommonStrings.action_reply, VectorIcons.Reply) + data object Edit : TimelineItemAction(CommonStrings.action_edit, VectorIcons.Edit) + data object Developer : TimelineItemAction(CommonStrings.action_view_source, VectorIcons.DeveloperMode) + data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, VectorIcons.ReportContent, destructive = true) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt index 14a6a3fb2d..6ce9348fcb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt @@ -20,6 +20,6 @@ import androidx.compose.runtime.Immutable @Immutable sealed interface AttachmentsPreviewEvents { - object SendAttachment : AttachmentsPreviewEvents - object ClearSendState : AttachmentsPreviewEvents + data object SendAttachment : AttachmentsPreviewEvents + data object ClearSendState : AttachmentsPreviewEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt index e41f43040f..183e1ea590 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt @@ -25,13 +25,13 @@ data class AttachmentsPreviewState( ) sealed interface SendActionState { - object Idle : SendActionState + data object Idle : SendActionState sealed interface Sending : SendActionState { - object Processing : Sending + data object Processing : Sending data class Uploading(val progress: Float) : Sending } data class Failure(val error: Throwable) : SendActionState - object Done : SendActionState + data object Done : SendActionState } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt index 0ae406efff..f7058e95b3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt @@ -21,9 +21,9 @@ import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails sealed interface ForwardMessagesEvents { data class SetSelectedRoom(val room: RoomSummaryDetails) : ForwardMessagesEvents // TODO remove to restore multi-selection - object RemoveSelectedRoom : ForwardMessagesEvents - object ToggleSearchActive : ForwardMessagesEvents + data object RemoveSelectedRoom : ForwardMessagesEvents + data object ToggleSearchActive : ForwardMessagesEvents data class UpdateQuery(val query: String) : ForwardMessagesEvents - object ForwardEvent : ForwardMessagesEvents - object ClearError : ForwardMessagesEvents + data object ForwardEvent : ForwardMessagesEvents + data object ClearError : ForwardMessagesEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt index b680ee58c9..a3d9632f18 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt @@ -17,9 +17,9 @@ package io.element.android.features.messages.impl.media.viewer sealed interface MediaViewerEvents { - object SaveOnDisk: MediaViewerEvents - object Share: MediaViewerEvents - object OpenWith: MediaViewerEvents - object RetryLoading : MediaViewerEvents - object ClearLoadingError : MediaViewerEvents + data object SaveOnDisk: MediaViewerEvents + data object Share: MediaViewerEvents + data object OpenWith: MediaViewerEvents + data object RetryLoading : MediaViewerEvents + data object ClearLoadingError : MediaViewerEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt index d040c503b1..a39fe45ea8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt @@ -21,20 +21,20 @@ import io.element.android.libraries.textcomposer.MessageComposerMode @Immutable sealed interface MessageComposerEvents { - object ToggleFullScreenState : MessageComposerEvents + data object ToggleFullScreenState : MessageComposerEvents data class FocusChanged(val hasFocus: Boolean) : MessageComposerEvents data class SendMessage(val message: String) : MessageComposerEvents - object CloseSpecialMode : MessageComposerEvents + data object CloseSpecialMode : MessageComposerEvents data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvents data class UpdateText(val text: String) : MessageComposerEvents - object AddAttachment : MessageComposerEvents - object DismissAttachmentMenu : MessageComposerEvents + data object AddAttachment : MessageComposerEvents + data object DismissAttachmentMenu : MessageComposerEvents sealed interface PickAttachmentSource : MessageComposerEvents { - object FromGallery : PickAttachmentSource - object FromFiles : PickAttachmentSource - object PhotoFromCamera : PickAttachmentSource - object VideoFromCamera : PickAttachmentSource - object Location : PickAttachmentSource + data object FromGallery : PickAttachmentSource + data object FromFiles : PickAttachmentSource + data object PhotoFromCamera : PickAttachmentSource + data object VideoFromCamera : PickAttachmentSource + data object Location : PickAttachmentSource } - object CancelSendAttachment : MessageComposerEvents + data object CancelSendAttachment : MessageComposerEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index 32faaf9d81..1b5bf3fe82 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -37,7 +37,7 @@ data class MessageComposerState( @Immutable sealed interface AttachmentsState { - object None : AttachmentsState + data object None : AttachmentsState data class Previewing(val attachments: ImmutableList) : AttachmentsState sealed interface Sending : AttachmentsState { data class Processing(val attachments: ImmutableList) : Sending diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt index ed5ee029e7..32007c0206 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt @@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.report sealed interface ReportMessageEvents { data class UpdateReason(val reason: String) : ReportMessageEvents - object ToggleBlockUser : ReportMessageEvents - object Report : ReportMessageEvents - object ClearError : ReportMessageEvents + data object ToggleBlockUser : ReportMessageEvents + data object Report : ReportMessageEvents + data object ClearError : ReportMessageEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index 2bfed45470..30f9aade79 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -19,7 +19,7 @@ package io.element.android.features.messages.impl.timeline import io.element.android.libraries.matrix.api.core.EventId sealed interface TimelineEvents { - object LoadMore : TimelineEvents + data object LoadMore : TimelineEvents data class SetHighlightedEvent(val eventId: EventId?) : TimelineEvents data class OnScrollFinished(val firstIndex: Int) : TimelineEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt index fdf94f52ce..24583783b9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt @@ -20,6 +20,6 @@ import io.element.android.features.messages.impl.timeline.model.AggregatedReacti import io.element.android.libraries.matrix.api.core.EventId sealed interface ReactionSummaryEvents { - object Clear : ReactionSummaryEvents + data object Clear : ReactionSummaryEvents data class ShowReactionSummary(val eventId: EventId, val reactions: List, val selectedKey: String) : ReactionSummaryEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt index ab6e32f078..97ef92ceb8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt @@ -20,7 +20,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem sealed interface RetrySendMenuEvents { data class EventSelected(val event: TimelineItem.Event) : RetrySendMenuEvents - object RetrySend : RetrySendMenuEvents - object RemoveFailed : RetrySendMenuEvents - object Dismiss: RetrySendMenuEvents + data object RetrySend : RetrySendMenuEvents + data object RemoveFailed : RetrySendMenuEvents + data object Dismiss: RetrySendMenuEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt index 5a93e87e73..556493bd84 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt @@ -40,22 +40,22 @@ sealed interface TimelineItemGroupPosition { /** * The event is part of a group of events from the same sender and is the first sent Event. */ - object First : TimelineItemGroupPosition + data object First : TimelineItemGroupPosition /** * The event is part of a group of events from the same sender and is neither the first nor the last sent Event. */ - object Middle : TimelineItemGroupPosition + data object Middle : TimelineItemGroupPosition /** * The event is part of a group of events from the same sender and is the last sent Event. */ - object Last : TimelineItemGroupPosition + data object Last : TimelineItemGroupPosition /** * The event is not part of a group of events. Sender of previous event is different, and sender of next event is different. */ - object None : TimelineItemGroupPosition + data object None : TimelineItemGroupPosition /** * Return true if the previous sender of the event is a different sender. diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt index 9dfeebc692..f983025236 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt @@ -48,7 +48,7 @@ class PollFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Root : NavTarget + data object Root : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index e5b8254488..7e95e0035c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -55,16 +55,16 @@ class PreferencesFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object Root : NavTarget + data object Root : NavTarget @Parcelize - object DeveloperSettings : NavTarget + data object DeveloperSettings : NavTarget @Parcelize - object AnalyticsSettings : NavTarget + data object AnalyticsSettings : NavTarget @Parcelize - object About : NavTarget + data object About : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt index e09e0df8f8..e54b7b9674 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt @@ -27,9 +27,9 @@ sealed class ElementLegal( @StringRes val titleRes: Int, val url: String, ) { - object Copyright : ElementLegal(CommonStrings.common_copyright, COPYRIGHT_URL) - object AcceptableUsePolicy : ElementLegal(CommonStrings.common_acceptable_use_policy, USE_POLICY_URL) - object PrivacyPolicy : ElementLegal(CommonStrings.common_privacy_policy, PRIVACY_URL) + data object Copyright : ElementLegal(CommonStrings.common_copyright, COPYRIGHT_URL) + data object AcceptableUsePolicy : ElementLegal(CommonStrings.common_acceptable_use_policy, USE_POLICY_URL) + data object PrivacyPolicy : ElementLegal(CommonStrings.common_privacy_policy, PRIVACY_URL) } fun getAllLegals(): List { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt index bb3879b129..ce67916178 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt @@ -20,5 +20,5 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents - object ClearCache: DeveloperSettingsEvents + data object ClearCache: DeveloperSettingsEvents } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt index 055a8339f6..8320d801b0 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt @@ -17,6 +17,6 @@ package io.element.android.features.rageshake.api.crash sealed interface CrashDetectionEvents { - object ResetAllCrashData : CrashDetectionEvents - object ResetAppHasCrashed : CrashDetectionEvents + data object ResetAllCrashData : CrashDetectionEvents + data object ResetAppHasCrashed : CrashDetectionEvents } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt index bfba87a01a..ff587293d5 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt @@ -19,9 +19,9 @@ package io.element.android.features.rageshake.api.detection import io.element.android.features.rageshake.api.screenshot.ImageResult sealed interface RageshakeDetectionEvents { - object Dismiss : RageshakeDetectionEvents - object Disable : RageshakeDetectionEvents - object StartDetection : RageshakeDetectionEvents - object StopDetection : RageshakeDetectionEvents + data object Dismiss : RageshakeDetectionEvents + data object Disable : RageshakeDetectionEvents + data object StartDetection : RageshakeDetectionEvents + data object StopDetection : RageshakeDetectionEvents data class ProcessScreenshot(val imageResult: ImageResult) : RageshakeDetectionEvents } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt index 9765f83da0..cde53ade5a 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt @@ -17,9 +17,9 @@ package io.element.android.features.rageshake.impl.bugreport sealed interface BugReportEvents { - object SendBugReport : BugReportEvents - object ResetAll : BugReportEvents - object ClearError : BugReportEvents + data object SendBugReport : BugReportEvents + data object ResetAll : BugReportEvents + data object ClearError : BugReportEvents data class SetDescription(val description: String) : BugReportEvents data class SetSendLog(val sendLog: Boolean) : BugReportEvents diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index e73d63f38c..4fa5c18b2e 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -29,7 +29,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { sealed interface InitialTarget : Parcelable { @Parcelize - object RoomDetails : InitialTarget + data object RoomDetails : InitialTarget @Parcelize data class RoomMemberDetails(val roomMemberId: UserId) : InitialTarget diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt index 61b3da21f9..bdd92fb589 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt @@ -17,7 +17,6 @@ package io.element.android.features.roomdetails.impl sealed interface RoomDetailsAction { - object Edit : RoomDetailsAction - - object AddTopic : RoomDetailsAction + data object Edit : RoomDetailsAction + data object AddTopic : RoomDetailsAction } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index b7bb31757e..7b18d398df 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -17,5 +17,5 @@ package io.element.android.features.roomdetails.impl sealed interface RoomDetailsEvent { - object LeaveRoom : RoomDetailsEvent + data object LeaveRoom : RoomDetailsEvent } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 7298e0eda6..b456df9f02 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -55,16 +55,16 @@ class RoomDetailsFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object RoomDetails : NavTarget + data object RoomDetails : NavTarget @Parcelize - object RoomMemberList : NavTarget + data object RoomMemberList : NavTarget @Parcelize - object RoomDetailsEdit : NavTarget + data object RoomDetailsEdit : NavTarget @Parcelize - object InviteMembers : NavTarget + data object InviteMembers : NavTarget @Parcelize data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index f146181bb6..49aa7f6ce0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -37,12 +37,12 @@ data class RoomDetailsState( ) sealed interface RoomDetailsType { - object Room : RoomDetailsType + data object Room : RoomDetailsType data class Dm(val roomMember: RoomMember) : RoomDetailsType } sealed interface RoomTopicState { - object Hidden : RoomTopicState - object CanAddTopic : RoomTopicState + data object Hidden : RoomTopicState + data object CanAddTopic : RoomTopicState data class ExistingTopic(val topic: String) : RoomTopicState } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt index b4bc348b8a..567e8927f8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt @@ -22,6 +22,6 @@ sealed interface RoomDetailsEditEvents { data class HandleAvatarAction(val action: AvatarAction) : RoomDetailsEditEvents data class UpdateRoomName(val name: String) : RoomDetailsEditEvents data class UpdateRoomTopic(val topic: String) : RoomDetailsEditEvents - object Save : RoomDetailsEditEvents - object CancelSaveChanges : RoomDetailsEditEvents + data object Save : RoomDetailsEditEvents + data object CancelSaveChanges : RoomDetailsEditEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt index c09d9a1f70..05688c6cf7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt @@ -19,6 +19,6 @@ package io.element.android.features.roomdetails.impl.members.details sealed interface RoomMemberDetailsEvents { data class BlockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents data class UnblockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents - object ClearBlockUserError : RoomMemberDetailsEvents - object ClearConfirmationDialog : RoomMemberDetailsEvents + data object ClearBlockUserError : RoomMemberDetailsEvents + data object ClearConfirmationDialog : RoomMemberDetailsEvents } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index e95b5bd60d..e377764942 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -22,9 +22,9 @@ import io.element.android.libraries.matrix.api.core.RoomId sealed interface RoomListEvents { data class UpdateFilter(val newFilter: String) : RoomListEvents data class UpdateVisibleRange(val range: IntRange) : RoomListEvents - object DismissRequestVerificationPrompt : RoomListEvents - object ToggleSearchResults : RoomListEvents + data object DismissRequestVerificationPrompt : RoomListEvents + data object ToggleSearchResults : RoomListEvents data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents - object HideContextMenu : RoomListEvents + data object HideContextMenu : RoomListEvents data class LeaveRoom(val roomId: RoomId) : RoomListEvents } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 7905b5bc61..c555afeca7 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -40,7 +40,7 @@ data class RoomListState( val eventSink: (RoomListEvents) -> Unit, ) { sealed interface ContextMenu { - object Hidden : ContextMenu + data object Hidden : ContextMenu data class Shown( val roomId: RoomId, val roomName: String, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt index 752cf942c1..248e3aec10 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt @@ -29,11 +29,11 @@ data class VerifySelfSessionState( @Stable sealed interface VerificationStep { - object Initial : VerificationStep - object Canceled : VerificationStep - object AwaitingOtherDeviceResponse : VerificationStep - object Ready : VerificationStep + data object Initial : VerificationStep + data object Canceled : VerificationStep + data object AwaitingOtherDeviceResponse : VerificationStep + data object Ready : VerificationStep data class Verifying(val emojiList: List, val state: Async) : VerificationStep - object Completed : VerificationStep + data object Completed : VerificationStep } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt index 29818197e0..ad48294e92 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt @@ -125,19 +125,19 @@ class VerifySelfSessionStateMachine @Inject constructor( sealed interface State { /** The initial state, before verification started. */ - object Initial : State + data object Initial : State /** Waiting for verification acceptance. */ - object RequestingVerification : State + data object RequestingVerification : State /** Verification request accepted. Waiting for start. */ - object VerificationRequestAccepted : State + data object VerificationRequestAccepted : State /** Waiting for SaS verification start. */ - object StartingSasVerification : State + data object StartingSasVerification : State /** A SaS verification flow has been started. */ - object SasVerificationStarted : State + data object SasVerificationStarted : State sealed class Verifying(open val emojis: List) : State { /** Verification accepted and emojis received. */ @@ -148,50 +148,50 @@ class VerifySelfSessionStateMachine @Inject constructor( } /** The verification is being canceled. */ - object Canceling : State + data object Canceling : State /** The verification has been canceled, remotely or locally. */ - object Canceled : State + data object Canceled : State /** Verification successful. */ - object Completed : State + data object Completed : State } sealed interface Event { /** Request verification. */ - object RequestVerification : Event + data object RequestVerification : Event /** The current verification request has been accepted. */ - object DidAcceptVerificationRequest : Event + data object DidAcceptVerificationRequest : Event /** Start a SaS verification flow. */ - object StartSasVerification : Event + data object StartSasVerification : Event /** Started a SaS verification flow. */ - object DidStartSasVerification : Event + data object DidStartSasVerification : Event /** Has received emojis. */ data class DidReceiveChallenge(val emojis: List) : Event /** Emojis match. */ - object AcceptChallenge : Event + data object AcceptChallenge : Event /** Emojis do not match. */ - object DeclineChallenge : Event + data object DeclineChallenge : Event /** Remote accepted challenge. */ - object DidAcceptChallenge : Event + data object DidAcceptChallenge : Event /** Request cancellation. */ - object Cancel : Event + data object Cancel : Event /** Verification cancelled. */ - object DidCancel : Event + data object DidCancel : Event /** Request failed. */ - object DidFail : Event + data object DidFail : Event /** Restart the verification flow. */ - object Restart : Event + data object Restart : Event } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt index 9c0fedada4..10e95bc09a 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt @@ -17,10 +17,10 @@ package io.element.android.features.verifysession.impl sealed interface VerifySelfSessionViewEvents { - object RequestVerification: VerifySelfSessionViewEvents - object StartSasVerification: VerifySelfSessionViewEvents - object Restart: VerifySelfSessionViewEvents - object ConfirmVerification: VerifySelfSessionViewEvents - object DeclineVerification: VerifySelfSessionViewEvents - object CancelAndClose: VerifySelfSessionViewEvents + data object RequestVerification: VerifySelfSessionViewEvents + data object StartSasVerification: VerifySelfSessionViewEvents + data object Restart: VerifySelfSessionViewEvents + data object ConfirmVerification: VerifySelfSessionViewEvents + data object DeclineVerification: VerifySelfSessionViewEvents + data object CancelAndClose: VerifySelfSessionViewEvents } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt index fba6066a64..f537ddcd4b 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt @@ -25,7 +25,7 @@ class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L sealed class CanHandleResult { - object Yes : CanHandleResult() + data object Yes : CanHandleResult() data class No(val shouldWaitMillis: Long) : CanHandleResult() fun waitMillis(): Long { diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt index fe728562e9..fb7bc2836b 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Async.kt @@ -63,7 +63,7 @@ sealed interface Async { /** * Represents an uninitialized operation (i.e. yet to be run). */ - object Uninitialized : Async + data object Uninitialized : Async /** * Returns the data returned by the operation, or null otherwise. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt index dcd1ea11bc..5edc527821 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt @@ -105,7 +105,7 @@ sealed class ElementLogoAtomSize( val shadowColorLight: Color, val shadowRadius: Dp, ) { - object Medium : ElementLogoAtomSize( + data object Medium : ElementLogoAtomSize( outerSize = 120.dp, logoSize = 83.5.dp, cornerRadius = 33.dp, @@ -115,7 +115,7 @@ sealed class ElementLogoAtomSize( shadowRadius = 32.dp, ) - object Large : ElementLogoAtomSize( + data object Large : ElementLogoAtomSize( outerSize = 158.dp, logoSize = 110.dp, cornerRadius = 44.dp, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt index 20589c89ee..542697b5f0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt @@ -88,7 +88,7 @@ fun ProgressDialog( @Immutable sealed interface ProgressDialogType { data class Determinate(val progress: Float) : ProgressDialogType - object Indeterminate : ProgressDialogType + data object Indeterminate : ProgressDialogType } @Composable 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..a7e240ce7f 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 @@ -140,9 +140,9 @@ fun ListItem( * The style to use for a [ListItem]. */ sealed interface ListItemStyle { - object Default : ListItemStyle - object Primary: ListItemStyle - object Destructive : ListItemStyle + data object Default : ListItemStyle + data object Primary: ListItemStyle + data object Destructive : ListItemStyle } // region: Simple list item 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..d465192ec1 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 @@ -120,13 +120,13 @@ object ListSupportingTextDefaults { /** Specifies the padding to use for the supporting text. */ sealed interface Padding { /** No padding. */ - object None : Padding + data object None : Padding /** Default padding, it will align fine with a [ListItem] with no leading content. */ - object Default : Padding + data object Default : Padding /** It will align to a [ListItem] with an [Icon] or [Checkbox] as leading content. */ - object SmallLeadingContent : Padding + data object SmallLeadingContent : Padding /** It will align to with a [ListItem] with a [Switch] as leading content. */ - object LargeLeadingContent : Padding + data object LargeLeadingContent : Padding /** It will align to with a [ListItem] with a custom start [padding]. */ data class Custom(val padding: Dp) : Padding diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 639509a15a..8565e4c747 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -40,54 +40,53 @@ data class NotificationData( sealed interface NotificationContent { sealed interface MessageLike : NotificationContent { - object CallAnswer : MessageLike - object CallInvite : MessageLike - object CallHangup : MessageLike - object CallCandidates : MessageLike - object KeyVerificationReady : MessageLike - object KeyVerificationStart : MessageLike - object KeyVerificationCancel : MessageLike - object KeyVerificationAccept : MessageLike - object KeyVerificationKey : MessageLike - object KeyVerificationMac : MessageLike - object KeyVerificationDone : MessageLike + data object CallAnswer : MessageLike + data object CallInvite : MessageLike + data object CallHangup : MessageLike + data object CallCandidates : MessageLike + data object KeyVerificationReady : MessageLike + data object KeyVerificationStart : MessageLike + data object KeyVerificationCancel : MessageLike + data object KeyVerificationAccept : MessageLike + data object KeyVerificationKey : MessageLike + data object KeyVerificationMac : MessageLike + data object KeyVerificationDone : MessageLike data class ReactionContent( val relatedEventId: String ) : MessageLike - object RoomEncrypted : MessageLike + data object RoomEncrypted : MessageLike data class RoomMessage( val senderId: UserId, val messageType: MessageType ) : MessageLike - object RoomRedaction : MessageLike - object Sticker : MessageLike + data object RoomRedaction : MessageLike + data object Sticker : MessageLike } sealed interface StateEvent : NotificationContent { - object PolicyRuleRoom : StateEvent - object PolicyRuleServer : StateEvent - object PolicyRuleUser : StateEvent - object RoomAliases : StateEvent - object RoomAvatar : StateEvent - object RoomCanonicalAlias : StateEvent - object RoomCreate : StateEvent - object RoomEncryption : StateEvent - object RoomGuestAccess : StateEvent - object RoomHistoryVisibility : StateEvent - object RoomJoinRules : StateEvent + data object PolicyRuleRoom : StateEvent + data object PolicyRuleServer : StateEvent + data object PolicyRuleUser : StateEvent + data object RoomAliases : StateEvent + data object RoomAvatar : StateEvent + data object RoomCanonicalAlias : StateEvent + data object RoomCreate : StateEvent + data object RoomEncryption : StateEvent + data object RoomGuestAccess : StateEvent + data object RoomHistoryVisibility : StateEvent + data object RoomJoinRules : StateEvent data class RoomMemberContent( val userId: String, val membershipState: RoomMembershipState ) : StateEvent - object RoomName : StateEvent - object RoomPinnedEvents : StateEvent - object RoomPowerLevels : StateEvent - object RoomServerAcl : StateEvent - object RoomThirdPartyInvite : StateEvent - object RoomTombstone : StateEvent - object RoomTopic : StateEvent - object SpaceChild : StateEvent - object SpaceParent : StateEvent + data object RoomName : StateEvent + data object RoomPinnedEvents : StateEvent + data object RoomPowerLevels : StateEvent + data object RoomServerAcl : StateEvent + data object RoomThirdPartyInvite : StateEvent + data object RoomTombstone : StateEvent + data object RoomTopic : StateEvent + data object SpaceChild : StateEvent + data object SpaceParent : StateEvent } - } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt index 31e28a40db..c79ab36a7b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt @@ -84,7 +84,7 @@ object PermalinkBuilder { } sealed class PermalinkBuilderError : Throwable() { - object InvalidRoomAlias : PermalinkBuilderError() - object InvalidRoomId : PermalinkBuilderError() - object InvalidUserId : PermalinkBuilderError() + data object InvalidRoomAlias : PermalinkBuilderError() + data object InvalidRoomId : PermalinkBuilderError() + data object InvalidUserId : PermalinkBuilderError() } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt index 4e41fd43ba..38ce7a03d3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt @@ -17,7 +17,7 @@ package io.element.android.libraries.matrix.api.room sealed interface MatrixRoomMembersState { - object Unknown : MatrixRoomMembersState + data object Unknown : MatrixRoomMembersState data class Pending(val prevRoomMembers: List? = null) : MatrixRoomMembersState data class Error(val failure: Throwable, val prevRoomMembers: List? = null) : MatrixRoomMembersState data class Ready(val roomMembers: List) : MatrixRoomMembersState diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt index 8714bc2c5c..c3dd6330b5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt @@ -29,7 +29,7 @@ import kotlin.time.Duration */ interface RoomList { sealed class LoadingState { - object NotLoaded : LoadingState() + data object NotLoaded : LoadingState() data class Loaded(val numberOfRooms: Int) : LoadingState() } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 99381d0e74..9ae6c22e7d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -26,10 +26,10 @@ import kotlinx.coroutines.flow.StateFlow interface RoomListService { sealed class State { - object Idle : State() - object Running : State() - object Error : State() - object Terminated : State() + data object Idle : State() + data object Running : State() + data object Error : State() + data object Terminated : State() } /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt index 38974b4002..fe328a57d2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt @@ -28,6 +28,6 @@ sealed interface MatrixTimelineItem { } data class Virtual(val uniqueId: Long, val virtual: VirtualTimelineItem) : MatrixTimelineItem - object Other : MatrixTimelineItem + data object Other : MatrixTimelineItem } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt index b7c155a5aa..e3970619cd 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt @@ -17,5 +17,5 @@ package io.element.android.libraries.matrix.api.timeline sealed class TimelineException : Exception() { - object CannotPaginate : TimelineException() + data object CannotPaginate : TimelineException() } 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 3316de64eb..b16e8d2694 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 @@ -35,13 +35,12 @@ data class MessageContent( val type: MessageType? ) : EventContent - sealed interface InReplyTo { /** The event details are not loaded yet. We can fetch them. */ data class NotLoaded(val eventId: EventId) : InReplyTo /** The event details are pending to be fetched. We should **not** fetch them again. */ - object Pending : InReplyTo + data object Pending : InReplyTo /** The event details are available. */ data class Ready( @@ -60,7 +59,7 @@ sealed interface InReplyTo { * If the reason for the failure is consistent on the server, we'd enter a loop * where we keep trying to fetch the same event. * */ - object Error : InReplyTo + data object Error : InReplyTo } object RedactedContent : EventContent @@ -92,7 +91,7 @@ data class UnableToDecryptContent( val sessionId: String ) : Data - object Unknown : Data + data object Unknown : Data } } @@ -205,55 +204,25 @@ enum class MembershipChange { } sealed interface OtherState { - object PolicyRuleRoom : OtherState - - object PolicyRuleServer : OtherState - - object PolicyRuleUser : OtherState - - object RoomAliases : OtherState - - data class RoomAvatar( - val url: String? - ) : OtherState - - object RoomCanonicalAlias : OtherState - - object RoomCreate : OtherState - - object RoomEncryption : OtherState - - object RoomGuestAccess : OtherState - - object RoomHistoryVisibility : OtherState - - object RoomJoinRules : OtherState - - data class RoomName( - val name: String? - ) : OtherState - - object RoomPinnedEvents : OtherState - - object RoomPowerLevels : OtherState - - object RoomServerAcl : OtherState - - data class RoomThirdPartyInvite( - val displayName: String? - ) : OtherState - - object RoomTombstone : OtherState - - data class RoomTopic( - val topic: String? - ) : OtherState - - object SpaceChild : OtherState - - object SpaceParent : OtherState - - data class Custom( - val eventType: String - ) : OtherState + data object PolicyRuleRoom : OtherState + data object PolicyRuleServer : OtherState + data object PolicyRuleUser : OtherState + data object RoomAliases : OtherState + data class RoomAvatar(val url: String?) : OtherState + data object RoomCanonicalAlias : OtherState + data object RoomCreate : OtherState + data object RoomEncryption : OtherState + data object RoomGuestAccess : OtherState + data object RoomHistoryVisibility : OtherState + data object RoomJoinRules : OtherState + data class RoomName(val name: String?) : OtherState + data object RoomPinnedEvents : OtherState + data object RoomPowerLevels : OtherState + data object RoomServerAcl : OtherState + data class RoomThirdPartyInvite(val displayName: String?) : OtherState + data object RoomTombstone : OtherState + data class RoomTopic(val topic: String?) : OtherState + data object SpaceChild : OtherState + data object SpaceParent : OtherState + data class Custom(val eventType: String) : OtherState } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt index 3e1ee55318..265be8af79 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt @@ -19,8 +19,8 @@ package io.element.android.libraries.matrix.api.timeline.item.event import io.element.android.libraries.matrix.api.core.EventId sealed interface LocalEventSendState { - object NotSentYet : LocalEventSendState - object Canceled : LocalEventSendState + data object NotSentYet : LocalEventSendState + data object Canceled : LocalEventSendState data class SendingFailed( val error: String diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt index fa22d3cf54..eddb9eb169 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt @@ -17,9 +17,9 @@ package io.element.android.libraries.matrix.api.timeline.item.event sealed interface ProfileTimelineDetails { - object Unavailable : ProfileTimelineDetails + data object Unavailable : ProfileTimelineDetails - object Pending : ProfileTimelineDetails + data object Pending : ProfileTimelineDetails data class Ready( val displayName: String?, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt index 11fd8b9c63..ae1b24c902 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt @@ -22,7 +22,7 @@ sealed interface VirtualTimelineItem { val timestamp: Long ) : VirtualTimelineItem - object ReadMarker : VirtualTimelineItem + data object ReadMarker : VirtualTimelineItem - object EncryptedHistoryBanner : VirtualTimelineItem + data object EncryptedHistoryBanner : VirtualTimelineItem } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt index 21c6954c2a..596b611296 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt @@ -60,11 +60,11 @@ enum class Target(open val filter: String) { } sealed class LogLevel(val filter: String) { - object Warn : LogLevel("warn") - object Trace : LogLevel("trace") - object Info : LogLevel("info") - object Debug : LogLevel("debug") - object Error : LogLevel("error") + data object Warn : LogLevel("warn") + data object Trace : LogLevel("trace") + data object Info : LogLevel("info") + data object Debug : LogLevel("debug") + data object Error : LogLevel("error") } object TracingFilterConfigurations { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt index cafa375a6a..01aeb208ca 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt @@ -17,6 +17,6 @@ package io.element.android.libraries.matrix.api.tracing sealed class WriteToFilesConfiguration { - object Disabled : WriteToFilesConfiguration() + data object Disabled : WriteToFilesConfiguration() data class Enabled(val directory: String, val filenamePrefix: String) : WriteToFilesConfiguration() } 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..d94a4e6817 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 @@ -71,35 +71,35 @@ interface SessionVerificationService { /** Verification status of the current session. */ sealed interface SessionVerifiedStatus { /** Unknown status, we couldn't read the actual value from the SDK. */ - object Unknown : SessionVerifiedStatus + data object Unknown : SessionVerifiedStatus /** Not verified session status. */ - object NotVerified : SessionVerifiedStatus + data object NotVerified : SessionVerifiedStatus /** Verified session status. */ - object Verified : SessionVerifiedStatus + data object Verified : SessionVerifiedStatus } /** States produced by the [SessionVerificationService]. */ sealed interface VerificationFlowState { /** Initial state. */ - object Initial : VerificationFlowState + data object Initial : VerificationFlowState /** Session verification request was accepted by another device. */ - object AcceptedVerificationRequest : VerificationFlowState + data object AcceptedVerificationRequest : VerificationFlowState /** Short Authentication String (SAS) verification started between the 2 devices. */ - object StartedSasVerification : VerificationFlowState + data object StartedSasVerification : VerificationFlowState /** Verification data for the SAS verification (emojis) received. */ data class ReceivedVerificationData(val emoji: List) : VerificationFlowState /** Verification completed successfully. */ - object Finished : VerificationFlowState + data object Finished : VerificationFlowState /** Verification was cancelled by either device. */ - object Canceled : VerificationFlowState + data object Canceled : VerificationFlowState /** Verification failed with an error. */ - object Failed : VerificationFlowState + data object Failed : VerificationFlowState } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt index 0a178f2c25..5fbc1cf44d 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt @@ -31,7 +31,7 @@ sealed class AvatarAction( val icon: ImageVector, val destructive: Boolean = false, ) { - object TakePhoto : AvatarAction(titleResId = CommonStrings.action_take_photo, icon = Icons.Outlined.PhotoCamera) - object ChoosePhoto : AvatarAction(titleResId = CommonStrings.action_choose_photo, icon = Icons.Outlined.PhotoLibrary) - object Remove : AvatarAction(titleResId = CommonStrings.action_remove, icon = Icons.Outlined.Delete, destructive = true) + data object TakePhoto : AvatarAction(titleResId = CommonStrings.action_take_photo, icon = Icons.Outlined.PhotoCamera) + data object ChoosePhoto : AvatarAction(titleResId = CommonStrings.action_choose_photo, icon = Icons.Outlined.PhotoLibrary) + data object Remove : AvatarAction(titleResId = CommonStrings.action_remove, icon = Icons.Outlined.Delete, destructive = true) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt index f2593766bc..979d42b826 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt @@ -35,7 +35,7 @@ data class MediaRequestData( ) { sealed interface Kind { - object Content : Kind + data object Content : Kind data class File(val body: String?, val mimeType: String) : Kind data class Thumbnail(val width: Long, val height: Long) : Kind { constructor(size: Long) : this(size, size) diff --git a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt index 7c86009d34..de07450eec 100644 --- a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt +++ b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt @@ -26,14 +26,14 @@ sealed interface PickerType { fun getContract(): ActivityResultContract fun getDefaultRequest(): Input - object Image : PickerType { + data object Image : PickerType { override fun getContract() = ActivityResultContracts.PickVisualMedia() override fun getDefaultRequest(): PickVisualMediaRequest { return PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) } } - object ImageAndVideo : PickerType { + data object ImageAndVideo : PickerType { override fun getContract() = ActivityResultContracts.PickVisualMedia() override fun getDefaultRequest(): PickVisualMediaRequest { return PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt index 938072433a..ab30f67b65 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt @@ -114,7 +114,7 @@ data class ImageCompressionResult( ) sealed interface ResizeMode { - object None : ResizeMode + data object None : ResizeMode data class Approximate(val desiredWidth: Int, val desiredHeight: Int) : ResizeMode data class Strict(val maxWidth: Int, val maxHeight: Int) : ResizeMode } diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt index a0b2411459..45232a51db 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt @@ -17,6 +17,6 @@ package io.element.android.libraries.permissions.api sealed interface PermissionsEvents { - object OpenSystemDialog : PermissionsEvents - object CloseDialog : PermissionsEvents + data object OpenSystemDialog : PermissionsEvents + data object CloseDialog : PermissionsEvents } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt index 9e8acc4d8f..c7814a1796 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt @@ -17,5 +17,5 @@ package io.element.android.libraries.push.api.gateway sealed class PushGatewayFailure : Throwable(cause = null) { - object PusherRejected : PushGatewayFailure() + data object PusherRejected : PushGatewayFailure() } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt index 9dd6c7d4cd..859bff17cf 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt @@ -166,6 +166,6 @@ sealed interface OneShotNotification { } sealed interface SummaryNotification { - object Removed : SummaryNotification + data object Removed : SummaryNotification data class Update(val notification: Notification) : SummaryNotification } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt index d42405ef9c..4877eff555 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt @@ -31,9 +31,9 @@ class RegisterUnifiedPushUseCase @Inject constructor( ) { sealed interface RegisterUnifiedPushResult { - object Success : RegisterUnifiedPushResult - object NeedToAskUserForDistributor : RegisterUnifiedPushResult - object Error : RegisterUnifiedPushResult + data object Success : RegisterUnifiedPushResult + data object NeedToAskUserForDistributor : RegisterUnifiedPushResult + data object Error : RegisterUnifiedPushResult } suspend fun execute(matrixClient: MatrixClient, distributor: Distributor, clientSecret: String): RegisterUnifiedPushResult { diff --git a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt index c808ebe503..fb5fb9fd76 100644 --- a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt +++ b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt @@ -17,13 +17,11 @@ package io.element.android.services.apperror.api sealed interface AppErrorState { - - object NoError : AppErrorState + data object NoError : AppErrorState data class Error( val title: String, val body: String, val dismiss: () -> Unit, ) : AppErrorState - } diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt index 12cd07f05e..fc51ffc038 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt @@ -30,7 +30,7 @@ import io.element.android.libraries.matrix.api.core.ThreadId * So we assume if we don't get the same owner, we can skip the onLeaving action as we already replaced it. */ sealed class NavigationState(open val owner: String) { - object Root : NavigationState("ROOT") + data object Root : NavigationState("ROOT") data class Session( override val owner: String, From 4cf242a5e14b75f3653407b6b7e6b8ef490b7890 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 09:37:08 +0200 Subject: [PATCH 18/86] changelog --- changelog.d/1135.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1135.misc diff --git a/changelog.d/1135.misc b/changelog.d/1135.misc new file mode 100644 index 0000000000..6672148591 --- /dev/null +++ b/changelog.d/1135.misc @@ -0,0 +1 @@ + Migrate `object` to `data object` in sealed interface / class #1135 From 0abda97d6f4d798a8bc30d330fcb5f267f687c58 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Thu, 24 Aug 2023 17:11:13 +0200 Subject: [PATCH 19/86] 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 20/86] 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 21/86] 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 22/86] 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 23/86] 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 24/86] 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 25/86] 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 26/86] 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 27/86] 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 28/86] 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 29/86] 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 30/86] 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 31/86] 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 0c5675e3073d044ac4589f200a39907dd90b7a78 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 16:21:33 +0200 Subject: [PATCH 32/86] Extract Composable to new SunsetPage. --- .../screens/waitlistscreen/WaitListView.kt | 194 +++++------------- .../designsystem/atomic/pages/SunsetPage.kt | 134 ++++++++++++ .../designsystem/text/AnnotatedStrings.kt | 19 ++ .../src/main/res/drawable/light_dark.png | Bin 4 files changed, 206 insertions(+), 141 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt rename {features/login/impl => libraries/designsystem}/src/main/res/drawable/light_dark.png (100%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt index 1bce4b39a9..6eb1b37d73 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -16,32 +16,17 @@ package io.element.android.features.login.impl.screens.waitlistscreen -import androidx.annotation.StringRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment -import androidx.compose.ui.BiasAbsoluteAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -50,15 +35,13 @@ import io.element.android.features.login.impl.R import io.element.android.features.login.impl.error.isWaitListError import io.element.android.features.login.impl.error.loginError import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.atomic.pages.SunsetPage import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Button -import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator -import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.utils.OnLifecycleEvent -import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings // Ref: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=6761-148425 @@ -75,12 +58,7 @@ fun WaitListView( else -> Unit } } - - Box(modifier = modifier) { - WaitListBackground() - WaitListContent(state, onCancelClicked) - WaitListError(state) - } + WaitListContent(state, onCancelClicked, modifier) } @Composable @@ -101,136 +79,70 @@ private fun WaitListError(state: WaitListState) { } } -@Composable -private fun WaitListBackground( - modifier: Modifier = Modifier, -) { - Column(modifier = modifier.fillMaxSize()) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(0.3f) - .background(Color.White) - ) - Image( - modifier = Modifier - .fillMaxWidth(), - painter = painterResource(id = R.drawable.light_dark), - contentScale = ContentScale.Crop, - contentDescription = null, - ) - Box( - modifier = Modifier - .fillMaxWidth() - .weight(0.7f) - .background(Color(0xFF121418)) - ) - } -} - @Composable private fun WaitListContent( state: WaitListState, onCancelClicked: () -> Unit, modifier: Modifier = Modifier, ) { - ElementTheme( - darkTheme = true + Box( + modifier = modifier.fillMaxSize(), ) { - Box( - modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .padding(horizontal = 16.dp, vertical = 16.dp) - ) { - if (state.loginAction !is Async.Success) { - CompositionLocalProvider(LocalContentColor provides Color.Black) { - TextButton( - text = stringResource(CommonStrings.action_cancel), - onClick = onCancelClicked, - ) - } - } - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = BiasAbsoluteAlignment( - horizontalBias = 0f, - verticalBias = -0.05f - ) - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (state.loginAction.isLoading()) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - strokeWidth = 2.dp, - color = ElementTheme.colors.iconPrimary - ) - } else { - Spacer(modifier = Modifier.height(24.dp)) - } - Spacer(modifier = Modifier.height(18.dp)) - val titleRes = when (state.loginAction) { - is Async.Success -> R.string.screen_waitlist_title_success - else -> R.string.screen_waitlist_title - } - Text( - text = withColoredPeriod(titleRes), - style = ElementTheme.typography.fontHeadingXlBold, - textAlign = TextAlign.Center, - color = ElementTheme.colors.textPrimary, - ) - Spacer(modifier = Modifier.height(8.dp)) - val subtitle = when (state.loginAction) { - is Async.Success -> stringResource( - id = R.string.screen_waitlist_message_success, - state.appName, - ) - else -> stringResource( - id = R.string.screen_waitlist_message, - state.appName, - state.serverName, - ) - } - Text( - modifier = Modifier.widthIn(max = 360.dp), - text = subtitle, - style = ElementTheme.typography.fontBodyLgRegular, - textAlign = TextAlign.Center, - color = ElementTheme.colors.textPrimary, - ) - } - } - if (state.loginAction is Async.Success) { - Button( - text = stringResource(id = CommonStrings.action_continue), - onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .padding(bottom = 8.dp), - ) + val title = stringResource( + when (state.loginAction) { + is Async.Success -> R.string.screen_waitlist_title_success + else -> R.string.screen_waitlist_title } + ) + val subtitle = when (state.loginAction) { + is Async.Success -> stringResource( + id = R.string.screen_waitlist_message_success, + state.appName, + ) + else -> stringResource( + id = R.string.screen_waitlist_message, + state.appName, + state.serverName, + ) } + SunsetPage( + modifier = modifier, + isLoading = state.loginAction !is Async.Success, + title = title, + subtitle = subtitle, + ) { + OverallContent(state, onCancelClicked) + } + WaitListError(state) } } @Composable -private fun withColoredPeriod( - @StringRes textRes: Int, -) = buildAnnotatedString { - val text = stringResource(textRes) - append(text) - if (text.endsWith(".")) { - addStyle( - style = SpanStyle( - // Light.colorGreen700 - color = Color(0xff0bc491), - ), - start = text.length - 1, - end = text.length, - ) +private fun OverallContent( + state: WaitListState, + onCancelClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.fillMaxSize()) { + if (state.loginAction !is Async.Success) { + CompositionLocalProvider(LocalContentColor provides Color.Black) { + TextButton( + text = stringResource(CommonStrings.action_cancel), + onClick = onCancelClicked, + ) + } + } + if (state.loginAction is Async.Success) { + Button( + text = stringResource(id = CommonStrings.action_continue), + onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(bottom = 8.dp), + ) + } + } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt new file mode 100644 index 0000000000..17cca583f6 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.atomic.pages + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAbsoluteAlignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.R +import io.element.android.libraries.designsystem.text.withColoredPeriod +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.theme.ElementTheme + +@Composable +fun SunsetPage( + isLoading: Boolean, + title: String, + subtitle: String, + modifier: Modifier = Modifier, + overallContent: @Composable () -> Unit, +) { + ElementTheme( + darkTheme = true + ) { + Box( + modifier = modifier.fillMaxSize() + ) { + SunsetBackground() + Box( + modifier = modifier + .fillMaxSize() + .systemBarsPadding() + .padding(horizontal = 16.dp, vertical = 16.dp) + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = BiasAbsoluteAlignment( + horizontalBias = 0f, + verticalBias = -0.05f + ) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + strokeWidth = 2.dp, + color = ElementTheme.colors.iconPrimary + ) + } else { + Spacer(modifier = Modifier.height(24.dp)) + } + Spacer(modifier = Modifier.height(18.dp)) + Text( + text = withColoredPeriod(title), + style = ElementTheme.typography.fontHeadingXlBold, + textAlign = TextAlign.Center, + color = ElementTheme.colors.textPrimary, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + modifier = Modifier.widthIn(max = 360.dp), + text = subtitle, + style = ElementTheme.typography.fontBodyLgRegular, + textAlign = TextAlign.Center, + color = ElementTheme.colors.textPrimary, + ) + } + } + overallContent() + } + } + } +} + +@Composable +private fun SunsetBackground( + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.3f) + .background(Color.White) + ) + Image( + modifier = Modifier + .fillMaxWidth(), + painter = painterResource(id = R.drawable.light_dark), + contentScale = ContentScale.Crop, + contentDescription = null, + ) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.7f) + .background(Color(0xFF121418)) + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt index 6b16ff96e7..779b7e7053 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt @@ -82,3 +82,22 @@ fun buildAnnotatedStringWithStyledPart( end = startIndex + coloredPart.length, ) } + +/** + * Convert a string to an [AnnotatedString] with colored end period if present. + */ +fun withColoredPeriod( + text: String, +) = buildAnnotatedString { + append(text) + if (text.endsWith(".")) { + addStyle( + style = SpanStyle( + // Light.colorGreen700 + color = Color(0xff0bc491), + ), + start = text.length - 1, + end = text.length, + ) + } +} diff --git a/features/login/impl/src/main/res/drawable/light_dark.png b/libraries/designsystem/src/main/res/drawable/light_dark.png similarity index 100% rename from features/login/impl/src/main/res/drawable/light_dark.png rename to libraries/designsystem/src/main/res/drawable/light_dark.png From 0ef0accdf6e129837e103c17d7fa2d98caf761b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 17:34:08 +0200 Subject: [PATCH 33/86] Localazy: move string `screen_migration_` to ftue/impl module and sync the strings. --- features/ftue/impl/src/main/res/values-cs/translations.xml | 2 ++ features/ftue/impl/src/main/res/values-de/translations.xml | 2 ++ features/ftue/impl/src/main/res/values-fr/translations.xml | 2 ++ features/ftue/impl/src/main/res/values-ru/translations.xml | 2 ++ features/ftue/impl/src/main/res/values-sk/translations.xml | 2 ++ features/ftue/impl/src/main/res/values-zh-rTW/translations.xml | 1 + features/ftue/impl/src/main/res/values/localazy.xml | 2 ++ libraries/ui-strings/src/main/res/values-cs/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-de/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-fr/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-ru/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-sk/translations.xml | 2 -- .../ui-strings/src/main/res/values-zh-rTW/translations.xml | 1 - libraries/ui-strings/src/main/res/values/localazy.xml | 2 -- tools/localazy/config.json | 3 ++- 15 files changed, 15 insertions(+), 14 deletions(-) diff --git a/features/ftue/impl/src/main/res/values-cs/translations.xml b/features/ftue/impl/src/main/res/values-cs/translations.xml index 38bbf4dacb..f1734c9c75 100644 --- a/features/ftue/impl/src/main/res/values-cs/translations.xml +++ b/features/ftue/impl/src/main/res/values-cs/translations.xml @@ -1,5 +1,7 @@ + "Toto je jednorázový proces, děkujeme za čekání." + "Nastavení vašeho účtu" "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." 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 d0ebbc31e7..19b445bd50 100644 --- a/features/ftue/impl/src/main/res/values-de/translations.xml +++ b/features/ftue/impl/src/main/res/values-de/translations.xml @@ -1,5 +1,7 @@ + "Dies ist ein einmaliger Vorgang, danke fürs Warten." + "Dein Konto einrichten" "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." diff --git a/features/ftue/impl/src/main/res/values-fr/translations.xml b/features/ftue/impl/src/main/res/values-fr/translations.xml index 313a6a533f..9f431f545d 100644 --- a/features/ftue/impl/src/main/res/values-fr/translations.xml +++ b/features/ftue/impl/src/main/res/values-fr/translations.xml @@ -1,5 +1,7 @@ + "Ce processus n’a besoin d’être fait qu’une seule fois, merci de patienter." + "Configuration de votre compte." "L’historique des messages pour les salons chiffrés ne sera pas disponible dans cette mise à jour." "Nous serions ravis d’avoir votre avis, n’hésitez pas à nous le partager via la page des paramètres." "C’est parti !" diff --git a/features/ftue/impl/src/main/res/values-ru/translations.xml b/features/ftue/impl/src/main/res/values-ru/translations.xml index db4fcd21fc..d72497bf98 100644 --- a/features/ftue/impl/src/main/res/values-ru/translations.xml +++ b/features/ftue/impl/src/main/res/values-ru/translations.xml @@ -1,5 +1,7 @@ + "Это одноразовый процесс, спасибо, что подождали." + "Настройка учетной записи." "Звонки, опросы, поиск и многое другое будут добавлены позже в этом году." "История сообщений для зашифрованных комнат в этом обновлении будет недоступна." "Мы будем рады услышать ваше мнение, сообщите нам об этом через страницу настроек." diff --git a/features/ftue/impl/src/main/res/values-sk/translations.xml b/features/ftue/impl/src/main/res/values-sk/translations.xml index 2438518334..5bbd2d386d 100644 --- a/features/ftue/impl/src/main/res/values-sk/translations.xml +++ b/features/ftue/impl/src/main/res/values-sk/translations.xml @@ -1,5 +1,7 @@ + "Ide o jednorazový proces, ďakujeme za trpezlivosť." + "Nastavenie vášho účtu." "Hovory, ankety, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku." "História správ pre zašifrované miestnosti nebude v tejto aktualizácii k dispozícii." "Radi by sme od vás počuli, dajte nám vedieť, čo si myslíte, prostredníctvom stránky nastavení." diff --git a/features/ftue/impl/src/main/res/values-zh-rTW/translations.xml b/features/ftue/impl/src/main/res/values-zh-rTW/translations.xml index 6c5d482cb8..b8b510aac5 100644 --- a/features/ftue/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/ftue/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,4 +1,5 @@ + "設定您的帳號" "開始吧!" diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 05cc72034e..aee8470751 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -1,5 +1,7 @@ + "This is a one time process, thanks for waiting." + "Setting up your account." "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms won’t be available in this update." "We’d love to hear from you, let us know what you think via the settings page." 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 ee1b221ad7..1ace446132 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -178,8 +178,6 @@ "Výběr média se nezdařil, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "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" 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 68223c8513..00979596a9 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -176,8 +176,6 @@ "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Fehler bei der Verarbeitung von Medien zum Hochladen, bitte versuche es erneut." "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" diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index e3fe11dfdc..e7020bc47b 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -165,8 +165,6 @@ "Impossible de sélectionner un média, veuillez réessayer." "Échec du traitement du média avant son envoi, veuillez réessayer." "Impossible d’envoyer le média, veuillez réessayer." - "Ce processus n’a besoin d’être fait qu’une seule fois, merci de patienter." - "Configuration de votre compte." "Activer les notifications sur cet appareil" "paramètres système" "Notifications système désactivées" 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 865eace823..23df74fff5 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -178,8 +178,6 @@ "Не удалось выбрать носитель, попробуйте еще раз." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось загрузить медиафайлы, попробуйте еще раз." - "Это одноразовый процесс, спасибо, что подождали." - "Настройка учетной записи." "Дополнительные параметры" "Аудио и видео звонки" "Несоответствие конфигурации" 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 a070f874e9..b8d919faa0 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -178,8 +178,6 @@ "Nepodarilo sa vybrať médium, skúste to prosím znova." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa nahrať médiá, skúste to prosím znova." - "Ide o jednorazový proces, ďakujeme za trpezlivosť." - "Nastavenie vášho účtu." "Ďalšie nastavenia" "Audio a video hovory" "Nezhoda konfigurácie" diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index 27230b0e9b..32f8a23086 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -144,7 +144,6 @@ "新訊息" "分享分析數據" "無法上傳媒體檔案,請稍後再試。" - "設定您的帳號" "其他設定" "私訊" "在這個裝置上開啟通知" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index db58894472..4f25e8f07b 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -176,8 +176,6 @@ "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." - "This is a one time process, thanks for waiting." - "Setting up your account." "Additional settings" "Audio and video calls" "Configuration mismatch" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index fce6b317b5..97acdf2ab5 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -117,7 +117,8 @@ { "name": ":features:ftue:impl", "includeRegex": [ - "screen_welcome_.*" + "screen_welcome_.*", + "screen_migration_.*" ] } ] From ff749947f8f6cda23309962a44d02a5ead0d826a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 18:02:04 +0200 Subject: [PATCH 34/86] Add the Migrate session screen (#1145) --- features/ftue/impl/build.gradle.kts | 1 + .../features/ftue/impl/FtueFlowNode.kt | 24 ++++++-- .../impl/migration/MigrationScreenNode.kt | 53 +++++++++++++++++ .../migration/MigrationScreenPresenter.kt | 44 ++++++++++++++ .../impl/migration/MigrationScreenState.kt | 21 +++++++ .../impl/migration/MigrationScreenStore.kt | 25 ++++++++ .../impl/migration/MigrationScreenView.kt | 54 +++++++++++++++++ .../SharedPrefsMigrationScreenStore.kt | 59 +++++++++++++++++++ .../ftue/impl/state/DefaultFtueState.kt | 20 ++++++- .../libraries/androidutils/hash/Hash.kt | 34 +++++++++++ 10 files changed, 328 insertions(+), 7 deletions(-) create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenter.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenState.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenStore.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index 0dee792464..5154d68be3 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) api(projects.features.ftue.api) + implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 0ff9c80d46..b23fa9016f 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -34,6 +34,7 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.ftue.api.FtueEntryPoint +import io.element.android.features.ftue.impl.migration.MigrationScreenNode import io.element.android.features.ftue.impl.state.DefaultFtueState import io.element.android.features.ftue.impl.state.FtueStep import io.element.android.features.ftue.impl.welcome.WelcomeNode @@ -41,6 +42,7 @@ import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SessionScope import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -50,7 +52,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize -@ContributesNode(AppScope::class) +@ContributesNode(SessionScope::class) class FtueFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, @@ -71,6 +73,9 @@ class FtueFlowNode @AssistedInject constructor( @Parcelize object Placeholder : NavTarget + @Parcelize + data object MigrationScreen : NavTarget + @Parcelize object WelcomeScreen : NavTarget @@ -102,6 +107,14 @@ class FtueFlowNode @AssistedInject constructor( NavTarget.Placeholder -> { createNode(buildContext) } + NavTarget.MigrationScreen -> { + val callback = object : MigrationScreenNode.Callback { + override fun onMigrationFinished() { + lifecycleScope.launch { moveToNextStep() } + } + } + createNode(buildContext, listOf(callback)) + } NavTarget.WelcomeScreen -> { val callback = object : WelcomeNode.Callback { override fun onContinueClicked() { @@ -117,12 +130,15 @@ class FtueFlowNode @AssistedInject constructor( } } - private suspend fun moveToNextStep() { + private fun moveToNextStep() { when (ftueState.getNextStep()) { - is FtueStep.WelcomeScreen -> { + FtueStep.MigrationScreen -> { + backstack.newRoot(NavTarget.MigrationScreen) + } + FtueStep.WelcomeScreen -> { backstack.newRoot(NavTarget.WelcomeScreen) } - is FtueStep.AnalyticsOptIn -> { + FtueStep.AnalyticsOptIn -> { backstack.replace(NavTarget.AnalyticsOptIn) } null -> callback?.onFtueFlowFinished() diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt new file mode 100644 index 0000000000..a4c5d16bc4 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenNode.kt @@ -0,0 +1,53 @@ +/* + * 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.ftue.impl.migration + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class MigrationScreenNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: MigrationScreenPresenter, +) : Node(buildContext, plugins = plugins) { + + interface Callback : Plugin { + fun onMigrationFinished() + } + + private fun onMigrationFinished() { + plugins.filterIsInstance().forEach { it.onMigrationFinished() } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + MigrationScreenView( + state, + onMigrationFinished = ::onMigrationFinished, + modifier = modifier + ) + } +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenter.kt new file mode 100644 index 0000000000..6507130383 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenter.kt @@ -0,0 +1,44 @@ +/* + * 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.ftue.impl.migration + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import javax.inject.Inject + +class MigrationScreenPresenter @Inject constructor( + private val matrixClient: MatrixClient, + private val migrationScreenStore: MigrationScreenStore, +) : Presenter { + @Composable + override fun present(): MigrationScreenState { + val roomListState by matrixClient.roomListService.state.collectAsState() + if (roomListState == RoomListService.State.Running) { + LaunchedEffect(Unit) { + migrationScreenStore.setMigrationScreenShown(matrixClient.sessionId) + } + } + return MigrationScreenState( + isMigrating = roomListState != RoomListService.State.Running + ) + } +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenState.kt new file mode 100644 index 0000000000..fa718990e4 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenState.kt @@ -0,0 +1,21 @@ +/* + * 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.ftue.impl.migration + +data class MigrationScreenState( + val isMigrating: Boolean +) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenStore.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenStore.kt new file mode 100644 index 0000000000..42eeab673a --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenStore.kt @@ -0,0 +1,25 @@ +/* + * 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.ftue.impl.migration + +import io.element.android.libraries.matrix.api.core.SessionId + +interface MigrationScreenStore { + fun isMigrationScreenNeeded(sessionId: SessionId): Boolean + fun setMigrationScreenShown(sessionId: SessionId) + fun reset() +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt new file mode 100644 index 0000000000..a8f20713e4 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenView.kt @@ -0,0 +1,54 @@ +/* + * 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.ftue.impl.migration + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.features.ftue.impl.R +import io.element.android.libraries.designsystem.atomic.pages.SunsetPage +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview + +@Composable +fun MigrationScreenView( + migrationState: MigrationScreenState, + onMigrationFinished: () -> Unit, + modifier: Modifier = Modifier, +) { + if (migrationState.isMigrating.not()) { + LaunchedEffect(Unit) { + onMigrationFinished() + } + } + SunsetPage( + modifier = modifier, + isLoading = true, + title = stringResource(id = R.string.screen_migration_title), + subtitle = stringResource(id = R.string.screen_migration_message), + overallContent = {} + ) +} + +@DayNightPreviews +@Composable +internal fun MigrationViewPreview() = ElementPreview { + MigrationScreenView( + migrationState = MigrationScreenState(isMigrating = true), + onMigrationFinished = {}) +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt new file mode 100644 index 0000000000..64bb27cf92 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt @@ -0,0 +1,59 @@ +/* + * 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.ftue.impl.migration + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.hash.md5 +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.DefaultPreferences +import io.element.android.libraries.matrix.api.core.SessionId +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class SharedPrefsMigrationScreenStore @Inject constructor( + @DefaultPreferences private val sharedPreferences: SharedPreferences, +) : MigrationScreenStore { + + override fun isMigrationScreenNeeded(sessionId: SessionId): Boolean { + return sharedPreferences.getBoolean(sessionId.toKey(), false).not() + } + + override fun setMigrationScreenShown(sessionId: SessionId) { + sharedPreferences.edit().putBoolean(sessionId.toKey(), true).apply() + } + + override fun reset() { + sharedPreferences.edit { + sharedPreferences.all.keys + .filter { it.startsWith(IS_MIGRATION_SCREEN_SHOWN_PREFIX) } + .forEach { + remove(it) + } + } + } + + private fun SessionId.toKey(): String { + return IS_MIGRATION_SCREEN_SHOWN_PREFIX + value.md5() + } + + companion object { + private const val IS_MIGRATION_SCREEN_SHOWN_PREFIX = "is_migration_screen_shown_" + } +} + diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt index 52c8d90254..f643c93335 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt @@ -19,8 +19,10 @@ package io.element.android.features.ftue.impl.state import androidx.annotation.VisibleForTesting import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.ftue.api.state.FtueState +import io.element.android.features.ftue.impl.migration.MigrationScreenStore import io.element.android.features.ftue.impl.welcome.state.WelcomeScreenState -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -30,11 +32,13 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import javax.inject.Inject -@ContributesBinding(AppScope::class) +@ContributesBinding(SessionScope::class) class DefaultFtueState @Inject constructor( private val coroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, private val welcomeScreenState: WelcomeScreenState, + private val migrationScreenStore: MigrationScreenStore, + private val matrixClient: MatrixClient, ) : FtueState { override val shouldDisplayFlow = MutableStateFlow(isAnyStepIncomplete()) @@ -42,6 +46,7 @@ class DefaultFtueState @Inject constructor( override suspend fun reset() { welcomeScreenState.reset() analyticsService.reset() + migrationScreenStore.reset() } init { @@ -52,7 +57,10 @@ class DefaultFtueState @Inject constructor( fun getNextStep(currentStep: FtueStep? = null): FtueStep? = when (currentStep) { - null -> if (shouldDisplayWelcomeScreen()) FtueStep.WelcomeScreen else getNextStep( + null -> if (shouldDisplayMigrationScreen()) FtueStep.MigrationScreen else getNextStep( + FtueStep.MigrationScreen + ) + FtueStep.MigrationScreen -> if (shouldDisplayWelcomeScreen()) FtueStep.WelcomeScreen else getNextStep( FtueStep.WelcomeScreen ) FtueStep.WelcomeScreen -> if (needsAnalyticsOptIn()) FtueStep.AnalyticsOptIn else getNextStep( @@ -63,11 +71,16 @@ class DefaultFtueState @Inject constructor( private fun isAnyStepIncomplete(): Boolean { return listOf( + shouldDisplayMigrationScreen(), shouldDisplayWelcomeScreen(), needsAnalyticsOptIn() ).any { it } } + private fun shouldDisplayMigrationScreen(): Boolean { + return migrationScreenStore.isMigrationScreenNeeded(matrixClient.sessionId) + } + private fun needsAnalyticsOptIn(): Boolean { // We need this function to not be suspend, so we need to load the value through runBlocking return runBlocking { analyticsService.didAskUserConsent().first().not() } @@ -89,6 +102,7 @@ class DefaultFtueState @Inject constructor( } sealed interface FtueStep { + data object MigrationScreen : FtueStep object WelcomeScreen : FtueStep object AnalyticsOptIn : FtueStep } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt new file mode 100644 index 0000000000..8c11c426af --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.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.libraries.androidutils.hash + +import java.security.MessageDigest +import java.util.Locale + +/** + * Compute a Hash of a String, using md5 algorithm. + */ +fun String.md5() = try { + val digest = MessageDigest.getInstance("md5") + digest.update(toByteArray()) + digest.digest() + .joinToString("") { String.format("%02X", it) } + .lowercase(Locale.ROOT) +} catch (exc: Exception) { + // Should not happen, but just in case + hashCode().toString() +} From d8459093a51d8d80feaaaf5d461e93e0e391fb5a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 12:00:14 +0200 Subject: [PATCH 35/86] Introduce `LoggedInAppScopeFlowNode`. It's a Node just responsible to set up the Dagger SessionScope. This allow to inject objects with SessionScope in the node `LoggedInFlowNode` and all it's dependency (in my case `FtueState` and `MatrixClient`) --- .../kotlin/io/element/android/x/MainNode.kt | 4 +- .../appnav/LoggedInAppScopeFlowNode.kt | 119 ++++++++++++++++++ .../android/appnav/LoggedInFlowNode.kt | 34 ++--- .../io/element/android/appnav/RootFlowNode.kt | 26 ++-- 4 files changed, 141 insertions(+), 42 deletions(-) create mode 100644 appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt diff --git a/app/src/main/kotlin/io/element/android/x/MainNode.kt b/app/src/main/kotlin/io/element/android/x/MainNode.kt index 8e7d0f194d..44006a6da5 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -27,7 +27,7 @@ import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin -import io.element.android.appnav.LoggedInFlowNode +import io.element.android.appnav.LoggedInAppScopeFlowNode import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.appnav.RootFlowNode import io.element.android.libraries.architecture.bindings @@ -56,7 +56,7 @@ class MainNode( ), DaggerComponentOwner by mainDaggerComponentOwner { - private val loggedInFlowNodeCallback = object : LoggedInFlowNode.LifecycleCallback { + private val loggedInFlowNodeCallback = object : LoggedInAppScopeFlowNode.LifecycleCallback { override fun onFlowCreated(identifier: String, client: MatrixClient) { val component = bindings().sessionComponentBuilder().client(client).build() mainDaggerComponentOwner.addComponent(identifier, component) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt new file mode 100644 index 0000000000..501f1dbc3f --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -0,0 +1,119 @@ +/* + * 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.appnav + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import coil.Coil +import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.lifecycle.subscribe +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import com.bumble.appyx.navmodel.backstack.BackStack +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.BackstackNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler +import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.waitForChildAttached +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.ui.di.MatrixUIBindings +import kotlinx.parcelize.Parcelize + +@ContributesNode(AppScope::class) +class LoggedInAppScopeFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BackstackNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins +) { + interface Callback : Plugin { + fun onOpenBugReport() + } + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + } + + interface LifecycleCallback : NodeLifecycleCallback { + fun onFlowCreated(identifier: String, client: MatrixClient) + + fun onFlowReleased(identifier: String, client: MatrixClient) + } + + data class Inputs( + val matrixClient: MatrixClient + ) : NodeInputs + + private val inputs: Inputs = inputs() + + override fun onBuilt() { + super.onBuilt() + lifecycle.subscribe( + onCreate = { + plugins().forEach { it.onFlowCreated(id, inputs.matrixClient) } + val imageLoaderFactory = bindings().loggedInImageLoaderFactory() + Coil.setImageLoader(imageLoaderFactory) + }, + onDestroy = { + plugins().forEach { it.onFlowReleased(id, inputs.matrixClient) } + } + ) + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Root -> { + val callback = object : LoggedInFlowNode.Callback { + override fun onOpenBugReport() { + plugins().forEach { it.onOpenBugReport() } + } + } + createNode(buildContext, listOf(callback)) + } + } + } + + suspend fun attachSession(): LoggedInFlowNode { + return waitForChildAttached { navTarget -> + navTarget is NavTarget.Root + } + } + + @Composable + override fun View(modifier: Modifier) { + Children( + navModel = backstack, + modifier = modifier, + transitionHandler = rememberDefaultTransitionHandler(), + ) + } +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 7943151a5e..e2da3afa3d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import coil.Coil import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext @@ -53,19 +52,15 @@ import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackNode -import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler -import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.architecture.inputs import io.element.android.libraries.deeplink.DeeplinkData import io.element.android.libraries.designsystem.utils.SnackbarDispatcher -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.sync.SyncState -import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope @@ -76,7 +71,7 @@ import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import timber.log.Timber -@ContributesNode(AppScope::class) +@ContributesNode(SessionScope::class) class LoggedInFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, @@ -91,6 +86,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val networkMonitor: NetworkMonitor, private val notificationDrawerManager: NotificationDrawerManager, private val ftueState: FtueState, + private val matrixClient: MatrixClient, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( backstack = BackStack( @@ -105,32 +101,18 @@ class LoggedInFlowNode @AssistedInject constructor( fun onOpenBugReport() } - interface LifecycleCallback : NodeLifecycleCallback { - fun onFlowCreated(identifier: String, client: MatrixClient) - - fun onFlowReleased(identifier: String, client: MatrixClient) - } - - data class Inputs( - val matrixClient: MatrixClient - ) : NodeInputs - - private val inputs: Inputs = inputs() - private val syncService = inputs.matrixClient.syncService() + private val syncService = matrixClient.syncService() private val loggedInFlowProcessor = LoggedInEventProcessor( snackbarDispatcher, - inputs.matrixClient.roomMembershipObserver(), - inputs.matrixClient.sessionVerificationService(), + matrixClient.roomMembershipObserver(), + matrixClient.sessionVerificationService(), ) override fun onBuilt() { super.onBuilt() lifecycle.subscribe( onCreate = { - plugins().forEach { it.onFlowCreated(id, inputs.matrixClient) } - val imageLoaderFactory = bindings().loggedInImageLoaderFactory() - Coil.setImageLoader(imageLoaderFactory) - appNavigationStateService.onNavigateToSession(id, inputs.matrixClient.sessionId) + appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId) // TODO We do not support Space yet, so directly navigate to main space appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE) loggedInFlowProcessor.observeEvents(coroutineScope) @@ -146,7 +128,6 @@ class LoggedInFlowNode @AssistedInject constructor( } }, onDestroy = { - plugins().forEach { it.onFlowReleased(id, inputs.matrixClient) } appNavigationStateService.onLeavingSpace(id) appNavigationStateService.onLeavingSession(id) loggedInFlowProcessor.stopObserving() @@ -351,4 +332,3 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.InviteList) } } - diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 089e956c61..0841137f90 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -72,15 +72,14 @@ class RootFlowNode @AssistedInject constructor( private val bugReportEntryPoint: BugReportEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, -) : - BackstackNode( - backstack = BackStack( - initialElement = NavTarget.SplashScreen, - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins - ) { +) : BackstackNode( + backstack = BackStack( + initialElement = NavTarget.SplashScreen, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins +) { override fun onBuilt() { matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap) @@ -191,14 +190,14 @@ class RootFlowNode @AssistedInject constructor( val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also { Timber.w("Couldn't find any session, go through SplashScreen") } - val inputs = LoggedInFlowNode.Inputs(matrixClient) - val callback = object : LoggedInFlowNode.Callback { + val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) + val callback = object : LoggedInAppScopeFlowNode.Callback { override fun onOpenBugReport() { backstack.push(NavTarget.BugReport) } } val nodeLifecycleCallbacks = plugins() - createNode(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) + createNode(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) } NavTarget.NotLoggedInFlow -> createNode(buildContext) NavTarget.SplashScreen -> splashNode(buildContext) @@ -233,6 +232,7 @@ class RootFlowNode @AssistedInject constructor( private suspend fun navigateTo(deeplinkData: DeeplinkData) { Timber.d("Navigating to $deeplinkData") attachSession(deeplinkData.sessionId) + .attachSession() .apply { when (deeplinkData) { is DeeplinkData.Root -> attachRoot() @@ -246,7 +246,7 @@ class RootFlowNode @AssistedInject constructor( oidcActionFlow.post(oidcAction) } - private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { + private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode { //TODO handle multi-session return waitForChildAttached { navTarget -> navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId From 3a8eefc927e4dd25ebfac74ecdecc800c030d873 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 14:37:55 +0200 Subject: [PATCH 36/86] Fix warning. --- .../io/element/android/libraries/androidutils/hash/Hash.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt index 8c11c426af..720fffdaa1 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt @@ -26,7 +26,7 @@ fun String.md5() = try { val digest = MessageDigest.getInstance("md5") digest.update(toByteArray()) digest.digest() - .joinToString("") { String.format("%02X", it) } + .joinToString("") { String.format(Locale.ROOT, "%02X", it) } .lowercase(Locale.ROOT) } catch (exc: Exception) { // Should not happen, but just in case From 7055fea895f3209930f114de9b6bb1bc9d1401e6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 14:38:34 +0200 Subject: [PATCH 37/86] Add preview for SunsetPage and fix warnings about Modifiers. --- .../impl/screens/waitlistscreen/WaitListView.kt | 1 - .../designsystem/atomic/pages/SunsetPage.kt | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt index 6eb1b37d73..38f493b214 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -106,7 +106,6 @@ private fun WaitListContent( ) } SunsetPage( - modifier = modifier, isLoading = state.loginAction !is Async.Success, title = title, subtitle = subtitle, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt index 17cca583f6..10a6f529af 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt @@ -38,6 +38,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.R +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.text.withColoredPeriod import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text @@ -59,7 +61,7 @@ fun SunsetPage( ) { SunsetBackground() Box( - modifier = modifier + modifier = Modifier .fillMaxSize() .systemBarsPadding() .padding(horizontal = 16.dp, vertical = 16.dp) @@ -132,3 +134,14 @@ private fun SunsetBackground( ) } } + +@DayNightPreviews +@Composable +internal fun SunsetPagePreview() = ElementPreview { + SunsetPage( + isLoading = true, + title = "Title with a green period.", + subtitle = "Subtitle", + overallContent = {} + ) +} From 13082285b2a6825f0fc28c70b31bc88046e4c499 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 14:51:19 +0200 Subject: [PATCH 38/86] Upgrade DefaultFtueStateTests regarding the new FTUE step MigrationScreen. --- .../ftue/impl/DefaultFtueStateTests.kt | 45 +++++++++++++++---- .../migration/InMemoryMigrationScreenStore.kt | 36 +++++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/InMemoryMigrationScreenStore.kt diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt index cfd489ea2a..908683227f 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt @@ -18,9 +18,14 @@ package io.element.android.features.ftue.impl import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.test.FakeAnalyticsService +import io.element.android.features.ftue.impl.migration.InMemoryMigrationScreenStore +import io.element.android.features.ftue.impl.migration.MigrationScreenStore import io.element.android.features.ftue.impl.state.DefaultFtueState import io.element.android.features.ftue.impl.state.FtueStep import io.element.android.features.ftue.impl.welcome.state.FakeWelcomeState +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -45,12 +50,14 @@ class DefaultFtueStateTests { fun `given all checks being true, should display flow is false`() = runTest { val welcomeState = FakeWelcomeState() val analyticsService = FakeAnalyticsService() + val migrationScreenStore = InMemoryMigrationScreenStore() val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) - val state = createState(coroutineScope, welcomeState, analyticsService) + val state = createState(coroutineScope, welcomeState, analyticsService, migrationScreenStore) welcomeState.setWelcomeScreenShown() analyticsService.setDidAskUserConsent() + migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) state.updateState() assertThat(state.shouldDisplayFlow.value).isFalse() @@ -63,16 +70,21 @@ class DefaultFtueStateTests { fun `traverse flow`() = runTest { val welcomeState = FakeWelcomeState() val analyticsService = FakeAnalyticsService() + val migrationScreenStore = InMemoryMigrationScreenStore() val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) - val state = createState(coroutineScope, welcomeState, analyticsService) + val state = createState(coroutineScope, welcomeState, analyticsService, migrationScreenStore) val steps = mutableListOf() - // First step, welcome screen + // First step, migration screen + steps.add(state.getNextStep(steps.lastOrNull())) + migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) + + // Second step, welcome screen steps.add(state.getNextStep(steps.lastOrNull())) welcomeState.setWelcomeScreenShown() - // Second step, analytics opt in + // Third step, analytics opt in steps.add(state.getNextStep(steps.lastOrNull())) analyticsService.setDidAskUserConsent() @@ -80,6 +92,7 @@ class DefaultFtueStateTests { steps.add(state.getNextStep(steps.lastOrNull())) assertThat(steps).containsExactly( + FtueStep.MigrationScreen, FtueStep.WelcomeScreen, FtueStep.AnalyticsOptIn, null, // Final state @@ -93,7 +106,16 @@ class DefaultFtueStateTests { fun `if a check for a step is true, start from the next one`() = runTest { val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) val analyticsService = FakeAnalyticsService() - val state = createState(coroutineScope = coroutineScope, analyticsService = analyticsService) + val migrationScreenStore = InMemoryMigrationScreenStore() + + val state = createState( + coroutineScope = coroutineScope, + analyticsService = analyticsService, + migrationScreenStore = migrationScreenStore, + ) + + migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) + assertThat(state.getNextStep()).isEqualTo(FtueStep.WelcomeScreen) state.setWelcomeScreenShown() assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn) @@ -108,7 +130,14 @@ class DefaultFtueStateTests { private fun createState( coroutineScope: CoroutineScope, welcomeState: FakeWelcomeState = FakeWelcomeState(), - analyticsService: AnalyticsService = FakeAnalyticsService() - ) = DefaultFtueState(coroutineScope, analyticsService, welcomeState) - + analyticsService: AnalyticsService = FakeAnalyticsService(), + migrationScreenStore: MigrationScreenStore = InMemoryMigrationScreenStore(), + matrixClient: MatrixClient = FakeMatrixClient(), + ) = DefaultFtueState( + coroutineScope = coroutineScope, + analyticsService = analyticsService, + welcomeScreenState = welcomeState, + migrationScreenStore = migrationScreenStore, + matrixClient = matrixClient, + ) } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/InMemoryMigrationScreenStore.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/InMemoryMigrationScreenStore.kt new file mode 100644 index 0000000000..a77a4d001a --- /dev/null +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/InMemoryMigrationScreenStore.kt @@ -0,0 +1,36 @@ +/* + * 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.ftue.impl.migration + +import io.element.android.libraries.matrix.api.core.SessionId + +class InMemoryMigrationScreenStore : MigrationScreenStore { + private val store = mutableMapOf() + + override fun isMigrationScreenNeeded(sessionId: SessionId): Boolean { + // If store does not have key return true, else return the opposite of the value + return store[sessionId]?.not() ?: true + } + + override fun setMigrationScreenShown(sessionId: SessionId) { + store[sessionId] = true + } + + override fun reset() { + store.clear() + } +} From 756af63edb80e2e4a0f7d62c1de5d47987be5083 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 14:55:52 +0200 Subject: [PATCH 39/86] Fix regression on WaitListView. --- .../features/login/impl/screens/waitlistscreen/WaitListView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt index 38f493b214..5ac53e851e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -106,7 +106,7 @@ private fun WaitListContent( ) } SunsetPage( - isLoading = state.loginAction !is Async.Success, + isLoading = state.loginAction.isLoading(), title = title, subtitle = subtitle, ) { From 0fd81089d74e652ea86dbde666d62da637f225c2 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 25 Aug 2023 13:08:05 +0000 Subject: [PATCH 40/86] Update screenshots --- ...migration_null_MigrationView-D-0_1_null,NEXUS_5,1.0,en].png | 3 +++ ...migration_null_MigrationView-N-0_2_null,NEXUS_5,1.0,en].png | 3 +++ ...pl.welcome_null_WelcomeView-D-1_2_null,NEXUS_5,1.0,en].png} | 0 ...pl.welcome_null_WelcomeView-N-1_3_null,NEXUS_5,1.0,en].png} | 0 ...m.atomic.pages_null_SunsetPage-D_0_null,NEXUS_5,1.0,en].png | 3 +++ ...m.atomic.pages_null_SunsetPage-N_1_null,NEXUS_5,1.0,en].png | 3 +++ 6 files changed, 12 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-D-0_1_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-N-0_2_null,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-0_1_null,NEXUS_5,1.0,en].png => ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-1_2_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-0_2_null,NEXUS_5,1.0,en].png => ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-1_3_null,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-D_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-N_1_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-D-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-D-0_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..99a9e03973 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-D-0_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c9d9aa75b2b01e9b0106377fbcd88a92fb8b4d6a34323b18264fed8f9a48cab +size 133347 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-N-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-N-0_2_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..99a9e03973 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.migration_null_MigrationView-N-0_2_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c9d9aa75b2b01e9b0106377fbcd88a92fb8b4d6a34323b18264fed8f9a48cab +size 133347 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-1_2_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-0_1_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-D-1_2_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-1_3_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-0_2_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_null_WelcomeView-N-1_3_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-D_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-D_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9a5371804e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-D_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50ca2afda159bdd7f57e78b04fbe3c022a9aeee1b68f72cf4d4269b3e503f0e5 +size 127335 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9a5371804e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.pages_null_SunsetPage-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50ca2afda159bdd7f57e78b04fbe3c022a9aeee1b68f72cf4d4269b3e503f0e5 +size 127335 From 61323a89ad99c9707a98b5f81a162be8360b293b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 15:38:41 +0200 Subject: [PATCH 41/86] Add test for MigrationScreenPresenter. --- .../migration/MigrationScreenPresenterTest.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt new file mode 100644 index 0000000000..6e19879b86 --- /dev/null +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/migration/MigrationScreenPresenterTest.kt @@ -0,0 +1,69 @@ +/* + * 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.ftue.impl.migration + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MigrationScreenPresenterTest { + @Test + fun `present - initial`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isMigrating).isTrue() + } + } + + @Test + fun `present - migration end`() = runTest { + val matrixClient = FakeMatrixClient() + val migrationScreenStore = InMemoryMigrationScreenStore() + val presenter = createPresenter(matrixClient, migrationScreenStore) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isMigrating).isTrue() + assertThat(migrationScreenStore.isMigrationScreenNeeded(A_SESSION_ID)).isTrue() + // Simulate room list loaded + (matrixClient.roomListService as FakeRoomListService).postState(RoomListService.State.Running) + val nextState = awaitItem() + assertThat(nextState.isMigrating).isFalse() + assertThat(migrationScreenStore.isMigrationScreenNeeded(A_SESSION_ID)).isFalse() + } + } + + private fun createPresenter( + matrixClient: MatrixClient = FakeMatrixClient(), + migrationScreenStore: MigrationScreenStore = InMemoryMigrationScreenStore(), + ) = MigrationScreenPresenter( + matrixClient, + migrationScreenStore, + ) +} From 64a7fc5f52dae88756d516ec31122f71b91a1b7b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 15:49:17 +0200 Subject: [PATCH 42/86] Use a safer algorithm, and limit the size of the hashed string. Fix an issue reported by Sonar. --- .../ftue/impl/migration/SharedPrefsMigrationScreenStore.kt | 6 ++++-- .../io/element/android/libraries/androidutils/hash/Hash.kt | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt index 64bb27cf92..8ac957f406 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt @@ -19,7 +19,7 @@ package io.element.android.features.ftue.impl.migration import android.content.SharedPreferences import androidx.core.content.edit import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.androidutils.hash.md5 +import io.element.android.libraries.androidutils.hash.hash import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.DefaultPreferences import io.element.android.libraries.matrix.api.core.SessionId @@ -49,7 +49,9 @@ class SharedPrefsMigrationScreenStore @Inject constructor( } private fun SessionId.toKey(): String { - return IS_MIGRATION_SCREEN_SHOWN_PREFIX + value.md5() + // Hash the sessionId to get ride of exotic char and take only the first 16 chars, + // The risk of collision is not high. + return IS_MIGRATION_SCREEN_SHOWN_PREFIX + value.hash().take(16) } companion object { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt index 720fffdaa1..6d4cc321a0 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt @@ -20,10 +20,10 @@ import java.security.MessageDigest import java.util.Locale /** - * Compute a Hash of a String, using md5 algorithm. + * Compute a Hash of a String, using SHA-512 algorithm. */ -fun String.md5() = try { - val digest = MessageDigest.getInstance("md5") +fun String.hash() = try { + val digest = MessageDigest.getInstance("SHA-512") digest.update(toByteArray()) digest.digest() .joinToString("") { String.format(Locale.ROOT, "%02X", it) } From 9a467c91ae03dc4a99af1937254b798569867f2b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 25 Aug 2023 15:51:28 +0200 Subject: [PATCH 43/86] changelog --- changelog.d/1149.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1149.feature diff --git a/changelog.d/1149.feature b/changelog.d/1149.feature new file mode 100644 index 0000000000..7f08bbc4e4 --- /dev/null +++ b/changelog.d/1149.feature @@ -0,0 +1 @@ +Add a "Setting up account" screen, displayed the first time the user logs in to the app (per account). 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 44/86] 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" From 51d92ae6a8b1f14cecd4dffbdabee2b50bc18a63 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 13:03:04 +0200 Subject: [PATCH 45/86] Fix typo --- .../ftue/impl/migration/SharedPrefsMigrationScreenStore.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt index 8ac957f406..c39a535e7d 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/migration/SharedPrefsMigrationScreenStore.kt @@ -49,7 +49,7 @@ class SharedPrefsMigrationScreenStore @Inject constructor( } private fun SessionId.toKey(): String { - // Hash the sessionId to get ride of exotic char and take only the first 16 chars, + // Hash the sessionId to get rid of exotic char and take only the first 16 chars, // The risk of collision is not high. return IS_MIGRATION_SCREEN_SHOWN_PREFIX + value.hash().take(16) } From e47d137c6a4b3ba54166345bb99f46953c51c09b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 13:07:02 +0200 Subject: [PATCH 46/86] Add documentation to LoggedInAppScopeFlowNode. --- .../io/element/android/appnav/LoggedInAppScopeFlowNode.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt index 501f1dbc3f..c94967939e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -42,6 +42,11 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import kotlinx.parcelize.Parcelize +/** + * `LoggedInAppScopeFlowNode` is a Node responsible to set up the Dagger + * [io.element.android.libraries.di.SessionScope]. It has only one child: [LoggedInFlowNode]. + * This allow to inject objects with SessionScope in the constructor of [LoggedInFlowNode]. + */ @ContributesNode(AppScope::class) class LoggedInAppScopeFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, From 5f7c1e9545dd4575d24d79b12038d126425c9bbf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 12:31:25 +0200 Subject: [PATCH 47/86] Move module `features.analytics.test` to `services.analytics.test`. `FakeAnalyticsService` was not implemented at the correct location. --- features/analytics/impl/build.gradle.kts | 2 +- .../features/analytics/impl/AnalyticsOptInPresenterTest.kt | 2 +- .../impl/preferences/AnalyticsPreferencesPresenterTest.kt | 2 +- features/createroom/impl/build.gradle.kts | 2 +- .../impl/configureroom/ConfigureRoomPresenterTests.kt | 2 +- .../createroom/impl/root/CreateRoomRootPresenterTests.kt | 2 +- features/ftue/impl/build.gradle.kts | 2 +- .../android/features/ftue/impl/DefaultFtueStateTests.kt | 2 +- features/invitelist/impl/build.gradle.kts | 2 +- .../features/invitelist/impl/InviteListPresenterTests.kt | 2 +- features/location/impl/build.gradle.kts | 2 +- .../features/location/impl/send/SendLocationPresenterTest.kt | 2 +- features/messages/impl/build.gradle.kts | 2 +- .../element/android/features/messages/MessagesPresenterTest.kt | 2 +- .../messages/textcomposer/MessageComposerPresenterTest.kt | 3 ++- features/preferences/impl/build.gradle.kts | 2 +- .../impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt | 2 +- .../preferences/impl/root/PreferencesRootPresenterTest.kt | 2 +- {features => services}/analytics/test/build.gradle.kts | 2 +- .../android/services}/analytics/test/FakeAnalyticsService.kt | 2 +- 20 files changed, 21 insertions(+), 20 deletions(-) rename {features => services}/analytics/test/build.gradle.kts (93%) rename {features/analytics/test/src/main/kotlin/io/element/android/features => services/analytics/test/src/main/kotlin/io/element/android/services}/analytics/test/FakeAnalyticsService.kt (97%) diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts index 8356bb38bb..0ff81ac1cf 100644 --- a/features/analytics/impl/build.gradle.kts +++ b/features/analytics/impl/build.gradle.kts @@ -50,6 +50,6 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(libs.test.mockk) testImplementation(projects.libraries.matrix.test) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.features.analytics.impl) } diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt index 541e5858bd..b9cb4d95ff 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -21,8 +21,8 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.api.AnalyticsOptInEvents -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt index d0914932bb..843e5b5532 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -21,8 +21,8 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.api.AnalyticsOptInEvents -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index fe2970dd80..0ef46a57ec 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(libs.test.robolectric) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediapickers.test) testImplementation(projects.libraries.mediaupload.test) diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt index 1703cf4142..864d85423d 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt @@ -22,7 +22,6 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CreatedRoom -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.createroom.impl.userlist.UserListDataStore @@ -38,6 +37,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.services.analytics.test.FakeAnalyticsService import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index 002e8c77fd..0f88280c84 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -21,7 +21,6 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CreatedRoom -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.createroom.impl.userlist.FakeUserListPresenter import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory import io.element.android.features.createroom.impl.userlist.UserListDataStore @@ -35,6 +34,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.usersearch.test.FakeUserRepository +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Before diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index 5154d68be3..8b12767b20 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -50,7 +50,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) ksp(libs.showkase.processor) } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt index 908683227f..f93f761994 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt @@ -17,7 +17,6 @@ package io.element.android.features.ftue.impl import com.google.common.truth.Truth.assertThat -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.ftue.impl.migration.InMemoryMigrationScreenStore import io.element.android.features.ftue.impl.migration.MigrationScreenStore import io.element.android.features.ftue.impl.state.DefaultFtueState @@ -27,6 +26,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel diff --git a/features/invitelist/impl/build.gradle.kts b/features/invitelist/impl/build.gradle.kts index 3f8f1a44ed..cd008472b5 100644 --- a/features/invitelist/impl/build.gradle.kts +++ b/features/invitelist/impl/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.push.test) testImplementation(projects.features.invitelist.test) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) ksp(libs.showkase.processor) } diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index f3eef2784c..adb2042b62 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -20,7 +20,6 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.invitelist.api.SeenInvitesStore import io.element.android.features.invitelist.test.FakeSeenInvitesStore import io.element.android.libraries.architecture.Async @@ -44,6 +43,7 @@ import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index e808eed11c..325003b110 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -55,6 +55,6 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(libs.test.truth) testImplementation(projects.libraries.matrix.test) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.features.messages.test) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt index 34962081e2..21766a2cf0 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt @@ -21,7 +21,6 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth import im.vector.app.features.analytics.plan.Composer -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.location.api.Location import io.element.android.features.location.impl.common.actions.FakeLocationActions import io.element.android.features.location.impl.common.permissions.PermissionsEvents @@ -34,6 +33,7 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.SendLocationInvocation import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 4746cff1de..71030c3ca0 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -71,7 +71,7 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.features.networkmonitor.test) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.tests.testutils) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.mediaupload.test) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 5fcd06a980..2c542e0054 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -21,7 +21,6 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.InviteDialogAction @@ -69,6 +68,7 @@ import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.testCoroutineDispatchers diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index 25b506eb7f..799db7f274 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -24,7 +24,6 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.messages.impl.messagecomposer.AttachmentsState import io.element.android.features.messages.impl.messagecomposer.MessageComposerContextImpl import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents @@ -55,6 +54,7 @@ import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.services.analytics.test.FakeAnalyticsService import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -551,5 +551,6 @@ fun anEditMode( message: String = A_MESSAGE, transactionId: TransactionId? = null, ) = MessageComposerMode.Edit(eventId, message, transactionId) + fun aReplyMode() = MessageComposerMode.Reply(A_USER_NAME, null, AN_EVENT_ID, A_MESSAGE) fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE) diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 4ffca9d239..2773379ccc 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -66,7 +66,7 @@ dependencies { testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) testImplementation(projects.features.logout.impl) - testImplementation(projects.features.analytics.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.features.analytics.impl) testImplementation(projects.tests.testutils) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt index 29cc25e5be..787658a71f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt @@ -21,8 +21,8 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 5d508912a8..9ad5db3d1b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -20,7 +20,6 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.logout.impl.DefaultLogoutPreferencePresenter import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.meta.BuildType @@ -30,6 +29,7 @@ import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/analytics/test/build.gradle.kts b/services/analytics/test/build.gradle.kts similarity index 93% rename from features/analytics/test/build.gradle.kts rename to services/analytics/test/build.gradle.kts index 9f1796b156..521af86a61 100644 --- a/features/analytics/test/build.gradle.kts +++ b/services/analytics/test/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } android { - namespace = "io.element.android.features.analytics.test" + namespace = "io.element.android.services.analytics.test" } dependencies { diff --git a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt similarity index 97% rename from features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt rename to services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index 9e3fe8bb59..f3b3a7fab2 100644 --- a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.analytics.test +package io.element.android.services.analytics.test import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen From 35835a876d291cc4171ab8d255f1746e93411a7f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 12:30:19 +0200 Subject: [PATCH 48/86] Fix typo in test class name. --- ...ttingsPresenterTest.kt => AnalyticsSettingsPresenterTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/{AnalyticsAnalyticsSettingsPresenterTest.kt => AnalyticsSettingsPresenterTest.kt} (97%) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt similarity index 97% rename from features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt rename to features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt index 787658a71f..2a7fdba258 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt @@ -26,7 +26,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test -class AnalyticsAnalyticsSettingsPresenterTest { +class AnalyticsSettingsPresenterTest { @Test fun `present - initial state`() = runTest { val analyticsPresenter = DefaultAnalyticsPreferencesPresenter(FakeAnalyticsService(), aBuildMeta()) From 0752c18f81bf0daa06b332d7aceace814671d515 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 12:34:04 +0200 Subject: [PATCH 49/86] Remove useless dependency. --- features/analytics/impl/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts index 0ff81ac1cf..3ce2bab507 100644 --- a/features/analytics/impl/build.gradle.kts +++ b/features/analytics/impl/build.gradle.kts @@ -51,5 +51,4 @@ dependencies { testImplementation(libs.test.mockk) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) - testImplementation(projects.features.analytics.impl) } From 53e581320a48142300a5deab8bb93dfa01ccabcf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 15:33:18 +0200 Subject: [PATCH 50/86] Changelog for version 0.1.4 --- CHANGES.md | 33 +++++++++++++++++++++++++++++++++ changelog.d/1033.bugfix | 1 - changelog.d/1064.wip | 1 - changelog.d/1077.bugfix | 1 - changelog.d/1079.bugfix | 1 - changelog.d/1082.bugfix | 1 - changelog.d/1090.bugfix | 1 - changelog.d/1101.bugfix | 1 - changelog.d/1111.bugfix | 1 - changelog.d/1113.wip | 1 - changelog.d/1125.bugfix | 1 - changelog.d/1127.feature | 1 - changelog.d/1131.bugfix | 1 - changelog.d/1135.misc | 1 - changelog.d/1149.feature | 1 - changelog.d/769.feature | 1 - changelog.d/862.bugfix | 1 - changelog.d/990.misc | 1 - 18 files changed, 33 insertions(+), 17 deletions(-) delete mode 100644 changelog.d/1033.bugfix delete mode 100644 changelog.d/1064.wip delete mode 100644 changelog.d/1077.bugfix delete mode 100644 changelog.d/1079.bugfix delete mode 100644 changelog.d/1082.bugfix delete mode 100644 changelog.d/1090.bugfix delete mode 100644 changelog.d/1101.bugfix delete mode 100644 changelog.d/1111.bugfix delete mode 100644 changelog.d/1113.wip delete mode 100644 changelog.d/1125.bugfix delete mode 100644 changelog.d/1127.feature delete mode 100644 changelog.d/1131.bugfix delete mode 100644 changelog.d/1135.misc delete mode 100644 changelog.d/1149.feature delete mode 100644 changelog.d/769.feature delete mode 100644 changelog.d/862.bugfix delete mode 100644 changelog.d/990.misc diff --git a/CHANGES.md b/CHANGES.md index 03766ed66b..3e6c3d9b3c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,36 @@ +Changes in Element X v0.1.4 (2023-08-28) +======================================== + +Features ✨ +---------- + - Allow cancelling media upload ([#769](https://github.com/vector-im/element-x-android/issues/769)) + - Enable OIDC support. ([#1127](https://github.com/vector-im/element-x-android/issues/1127)) + - Add a "Setting up account" screen, displayed the first time the user logs in to the app (per account). ([#1149](https://github.com/vector-im/element-x-android/issues/1149)) + +Bugfixes 🐛 +---------- + - Videos sent from the app were cropped in some cases. ([#862](https://github.com/vector-im/element-x-android/issues/862)) + - Timeline: sender names are now displayed in one single line. ([#1033](https://github.com/vector-im/element-x-android/issues/1033)) + - Fix `TextButtons` being displayed in black. ([#1077](https://github.com/vector-im/element-x-android/issues/1077)) + - Linkify links in HTML contents. ([#1079](https://github.com/vector-im/element-x-android/issues/1079)) + - Fix bug reporter failing after not finding some log files. ([#1082](https://github.com/vector-im/element-x-android/issues/1082)) + - Fix rendering of inline elements in list items. ([#1090](https://github.com/vector-im/element-x-android/issues/1090)) + - Fix crash RuntimeException "No matching key found for the ciphertext in the stream" ([#1101](https://github.com/vector-im/element-x-android/issues/1101)) + - Make links in messages clickable again. ([#1111](https://github.com/vector-im/element-x-android/issues/1111)) + - When event has no id, just cancel parsing the latest room message for a room. ([#1125](https://github.com/vector-im/element-x-android/issues/1125)) + - Only display verification prompt after initial sync is done. ([#1131](https://github.com/vector-im/element-x-android/issues/1131)) + +In development 🚧 +---------------- + - [Poll] Add feature flag in developer options ([#1064](https://github.com/vector-im/element-x-android/issues/1064)) + - [Polls] Improve UI and render ended state ([#1113](https://github.com/vector-im/element-x-android/issues/1113)) + +Other changes +------------- + - Compound: add `ListItem` and `ListSectionHeader` components. ([#990](https://github.com/vector-im/element-x-android/issues/990)) + - Migrate `object` to `data object` in sealed interface / class #1135 ([#1135](https://github.com/vector-im/element-x-android/issues/1135)) + + Changes in Element X v0.1.2 (2023-08-16) ======================================== diff --git a/changelog.d/1033.bugfix b/changelog.d/1033.bugfix deleted file mode 100644 index db4397449b..0000000000 --- a/changelog.d/1033.bugfix +++ /dev/null @@ -1 +0,0 @@ -Timeline: sender names are now displayed in one single line. diff --git a/changelog.d/1064.wip b/changelog.d/1064.wip deleted file mode 100644 index f3d8af5133..0000000000 --- a/changelog.d/1064.wip +++ /dev/null @@ -1 +0,0 @@ -[Poll] Add feature flag in developer options diff --git a/changelog.d/1077.bugfix b/changelog.d/1077.bugfix deleted file mode 100644 index f76a12e5a2..0000000000 --- a/changelog.d/1077.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix `TextButtons` being displayed in black. diff --git a/changelog.d/1079.bugfix b/changelog.d/1079.bugfix deleted file mode 100644 index 6fcaa759c3..0000000000 --- a/changelog.d/1079.bugfix +++ /dev/null @@ -1 +0,0 @@ -Linkify links in HTML contents. diff --git a/changelog.d/1082.bugfix b/changelog.d/1082.bugfix deleted file mode 100644 index c279e09af2..0000000000 --- a/changelog.d/1082.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix bug reporter failing after not finding some log files. diff --git a/changelog.d/1090.bugfix b/changelog.d/1090.bugfix deleted file mode 100644 index 7c93ed6879..0000000000 --- a/changelog.d/1090.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix rendering of inline elements in list items. diff --git a/changelog.d/1101.bugfix b/changelog.d/1101.bugfix deleted file mode 100644 index 79c25ffaf6..0000000000 --- a/changelog.d/1101.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash RuntimeException "No matching key found for the ciphertext in the stream" diff --git a/changelog.d/1111.bugfix b/changelog.d/1111.bugfix deleted file mode 100644 index 4d1baf87bf..0000000000 --- a/changelog.d/1111.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make links in messages clickable again. diff --git a/changelog.d/1113.wip b/changelog.d/1113.wip deleted file mode 100644 index f95dc6ba88..0000000000 --- a/changelog.d/1113.wip +++ /dev/null @@ -1 +0,0 @@ -[Polls] Improve UI and render ended state diff --git a/changelog.d/1125.bugfix b/changelog.d/1125.bugfix deleted file mode 100644 index 85ebb87033..0000000000 --- a/changelog.d/1125.bugfix +++ /dev/null @@ -1 +0,0 @@ -When event has no id, just cancel parsing the latest room message for a room. diff --git a/changelog.d/1127.feature b/changelog.d/1127.feature deleted file mode 100644 index da2c6b3cf1..0000000000 --- a/changelog.d/1127.feature +++ /dev/null @@ -1 +0,0 @@ -Enable OIDC support. diff --git a/changelog.d/1131.bugfix b/changelog.d/1131.bugfix deleted file mode 100644 index 23649a19d0..0000000000 --- a/changelog.d/1131.bugfix +++ /dev/null @@ -1 +0,0 @@ -Only display verification prompt after initial sync is done. diff --git a/changelog.d/1135.misc b/changelog.d/1135.misc deleted file mode 100644 index 6672148591..0000000000 --- a/changelog.d/1135.misc +++ /dev/null @@ -1 +0,0 @@ - Migrate `object` to `data object` in sealed interface / class #1135 diff --git a/changelog.d/1149.feature b/changelog.d/1149.feature deleted file mode 100644 index 7f08bbc4e4..0000000000 --- a/changelog.d/1149.feature +++ /dev/null @@ -1 +0,0 @@ -Add a "Setting up account" screen, displayed the first time the user logs in to the app (per account). diff --git a/changelog.d/769.feature b/changelog.d/769.feature deleted file mode 100644 index 8df765c27c..0000000000 --- a/changelog.d/769.feature +++ /dev/null @@ -1 +0,0 @@ -Allow cancelling media upload diff --git a/changelog.d/862.bugfix b/changelog.d/862.bugfix deleted file mode 100644 index 30715a76e3..0000000000 --- a/changelog.d/862.bugfix +++ /dev/null @@ -1 +0,0 @@ -Videos sent from the app were cropped in some cases. diff --git a/changelog.d/990.misc b/changelog.d/990.misc deleted file mode 100644 index 0deaf51c89..0000000000 --- a/changelog.d/990.misc +++ /dev/null @@ -1 +0,0 @@ -Compound: add `ListItem` and `ListSectionHeader` components. From 571689c1fd7a5778bfeb846928bdec3de53dad65 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 15:34:45 +0200 Subject: [PATCH 51/86] Adding fastlane file for version 0.1.4 --- fastlane/metadata/android/en-US/changelogs/40001040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40001040.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40001040.txt b/fastlane/metadata/android/en-US/changelogs/40001040.txt new file mode 100644 index 0000000000..2593704785 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40001040.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and add OIDC support. +Full changelog: https://github.com/vector-im/element-x-android/releases From 29465f49aeb156c4efd2e8f7e01ae13d79e57d9c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 15:36:04 +0200 Subject: [PATCH 52/86] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 658603cb56..0e9d807014 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 1 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 4 +private const val versionPatch = 6 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 8c2cc41f9790d25c448a8ef9cffa622e0df6233a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 16:54:08 +0200 Subject: [PATCH 53/86] Add missing plugin forward between `LoggedInAppScopeFlowNode` and `LoggedInFlowNode`. The RoomComponent was not created. Fix crash when opening a room. --- .../io/element/android/appnav/LoggedInAppScopeFlowNode.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt index c94967939e..6fbe63c1e8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -102,7 +102,8 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( plugins().forEach { it.onOpenBugReport() } } } - createNode(buildContext, listOf(callback)) + val nodeLifecycleCallbacks = plugins() + createNode(buildContext, nodeLifecycleCallbacks + callback) } } } From a700f114d2930303f98497287eb06fb484e8c166 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 16:59:31 +0200 Subject: [PATCH 54/86] Changelog --- changelog.d/1160.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1160.bugfix diff --git a/changelog.d/1160.bugfix b/changelog.d/1160.bugfix new file mode 100644 index 0000000000..de994b406e --- /dev/null +++ b/changelog.d/1160.bugfix @@ -0,0 +1 @@ +Fix crash when opening any room. From f696fcde353aab2459f6ba97737d1f87ba7e472e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 17:17:49 +0200 Subject: [PATCH 55/86] Setting version for the release 0.1.5 --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 0e9d807014..ebccf62e9c 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 1 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 6 +private const val versionPatch = 5 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 56af73234b55a59f6b0bf6a13473fa7b55a1c6c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 17:18:39 +0200 Subject: [PATCH 56/86] Changelog for version 0.1.5 --- CHANGES.md | 8 ++++++++ changelog.d/1160.bugfix | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) delete mode 100644 changelog.d/1160.bugfix diff --git a/CHANGES.md b/CHANGES.md index 3e6c3d9b3c..c9e3ad2d33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +Changes in Element X v0.1.5 (2023-08-28) +======================================== + +Bugfixes 🐛 +---------- + - Fix crash when opening any room. ([#1160](https://github.com/vector-im/element-x-android/issues/1160)) + + Changes in Element X v0.1.4 (2023-08-28) ======================================== diff --git a/changelog.d/1160.bugfix b/changelog.d/1160.bugfix deleted file mode 100644 index de994b406e..0000000000 --- a/changelog.d/1160.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when opening any room. From a48fad655b544a389ff6921b6f9dd308d129cae5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 17:18:55 +0200 Subject: [PATCH 57/86] Adding fastlane file for version 0.1.5 --- fastlane/metadata/android/en-US/changelogs/40001050.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40001050.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40001050.txt b/fastlane/metadata/android/en-US/changelogs/40001050.txt new file mode 100644 index 0000000000..2593704785 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40001050.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and add OIDC support. +Full changelog: https://github.com/vector-im/element-x-android/releases From 5e5063981519ff1fcf39072d85bd6bb5ded714fd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Aug 2023 17:20:50 +0200 Subject: [PATCH 58/86] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index ebccf62e9c..0e9d807014 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 1 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 5 +private const val versionPatch = 6 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 6596f5ec483ea800b900ddeb99c701874c4dd056 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:24:00 +0000 Subject: [PATCH 59/86] Update gradle/gradle-build-action action to v2.8.0 --- .github/workflows/build.yml | 2 +- .github/workflows/nightlyReports.yml | 2 +- .github/workflows/quality.yml | 2 +- .github/workflows/recordScreenshots.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/sonar.yml | 2 +- .github/workflows/tests.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9b8549fc42..1df2119bf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Assemble debug APK diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index e85fdc31d9..65f3cbb53c 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -62,7 +62,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Dependency analysis diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 80ab156480..9c0aac7aef 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -40,7 +40,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Run code quality check suite diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index 63b72b3318..e9ea93619c 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -24,7 +24,7 @@ jobs: java-version: '17' # Add gradle cache, this should speed up the process - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Record screenshots diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 225dc4905e..7e535a1467 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 - name: Create app bundle env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 1cbbfb639e..42846c2cd5 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -32,7 +32,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: 🔊 Publish results to Sonar diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0094f34e4c..460e57b4e3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.7.1 + uses: gradle/gradle-build-action@v2.8.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} From c280b43ecc6e7bf46af7cf72f44de168f6ee9457 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 29 Aug 2023 09:59:01 +0200 Subject: [PATCH 60/86] Bump telephoto to 0.6.0-SNAPSHOT to diagnose crash (#1164) --- gradle/libs.versions.toml | 2 +- settings.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 118e6324c5..84bc4c1774 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ dependencycheck = "8.4.0" dependencyanalysis = "1.21.0" stem = "2.3.0" sqldelight = "1.5.5" -telephoto = "0.5.0" +telephoto = "0.6.0-SNAPSHOT" # DI dagger = "2.47" diff --git a/settings.gradle.kts b/settings.gradle.kts index 751c65d388..e1894c16d7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,6 +29,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url = URI("https://oss.sonatype.org/content/repositories/snapshots/") } maven { url = URI("https://www.jitpack.io") content { From f8417bda21cc0af7f26cc9ca1a1677800aa02354 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 10:05:59 +0200 Subject: [PATCH 61/86] Format file. --- .../kotlin/io/element/android/x/MainNode.kt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/io/element/android/x/MainNode.kt b/app/src/main/kotlin/io/element/android/x/MainNode.kt index 44006a6da5..ce894ec2d6 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -28,8 +28,8 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin import io.element.android.appnav.LoggedInAppScopeFlowNode -import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.appnav.RootFlowNode +import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.DaggerComponentOwner @@ -45,15 +45,14 @@ class MainNode( buildContext: BuildContext, private val mainDaggerComponentOwner: MainDaggerComponentsOwner, plugins: List, -) : - ParentNode( - navModel = PermanentNavModel( - navTargets = setOf(RootNavTarget), - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins, +) : ParentNode( + navModel = PermanentNavModel( + navTargets = setOf(RootNavTarget), + savedStateMap = buildContext.savedStateMap, ), + buildContext = buildContext, + plugins = plugins, +), DaggerComponentOwner by mainDaggerComponentOwner { private val loggedInFlowNodeCallback = object : LoggedInAppScopeFlowNode.LifecycleCallback { From 79d4a4c1be22048ba1f79dbea7b104105a607d9a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 10:08:59 +0200 Subject: [PATCH 62/86] `LoggedInAppScopeFlowNode` is just having one permanent child: `LoggedInFlowNode`. So no need to have a Backstack here, a ParentNode with PermanentNavModel is enough. --- .../appnav/LoggedInAppScopeFlowNode.kt | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt index 6fbe63c1e8..6a0e60aaaf 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -23,16 +23,15 @@ import coil.Coil import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins -import com.bumble.appyx.navmodel.backstack.BackStack import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.NodeInputs -import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs @@ -51,9 +50,9 @@ import kotlinx.parcelize.Parcelize class LoggedInAppScopeFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, -) : BackstackNode( - backstack = BackStack( - initialElement = NavTarget.Root, +) : ParentNode( + navModel = PermanentNavModel( + navTargets = setOf(NavTarget), savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -63,10 +62,8 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( fun onOpenBugReport() } - sealed interface NavTarget : Parcelable { - @Parcelize - data object Root : NavTarget - } + @Parcelize + object NavTarget : Parcelable interface LifecycleCallback : NodeLifecycleCallback { fun onFlowCreated(identifier: String, client: MatrixClient) @@ -95,31 +92,24 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { - return when (navTarget) { - NavTarget.Root -> { - val callback = object : LoggedInFlowNode.Callback { - override fun onOpenBugReport() { - plugins().forEach { it.onOpenBugReport() } - } - } - val nodeLifecycleCallbacks = plugins() - createNode(buildContext, nodeLifecycleCallbacks + callback) + val callback = object : LoggedInFlowNode.Callback { + override fun onOpenBugReport() { + plugins().forEach { it.onOpenBugReport() } } } + val nodeLifecycleCallbacks = plugins() + return createNode(buildContext, nodeLifecycleCallbacks + callback) } suspend fun attachSession(): LoggedInFlowNode { - return waitForChildAttached { navTarget -> - navTarget is NavTarget.Root - } + return waitForChildAttached { _ -> true } } @Composable override fun View(modifier: Modifier) { Children( - navModel = backstack, + navModel = navModel, modifier = modifier, - transitionHandler = rememberDefaultTransitionHandler(), ) } } From 9c6a5bed540993c06bf700c12f762ef4b9a570bb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 28 Aug 2023 18:22:26 +0200 Subject: [PATCH 63/86] Add unit tests for TimelineItemContentPollFactory --- .../TimelineItemContentPollFactoryTest.kt | 288 ++++++++++++++++++ .../android/libraries/matrix/test/TestData.kt | 8 + 2 files changed, 296 insertions(+) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt new file mode 100644 index 0000000000..b7d13784f8 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt @@ -0,0 +1,288 @@ +/* + * 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.timeline.factories.event + +import com.google.common.truth.Truth +import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentPollFactory +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent +import io.element.android.features.poll.api.PollAnswerItem +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.poll.PollAnswer +import io.element.android.libraries.matrix.api.poll.PollKind +import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_ID_10 +import io.element.android.libraries.matrix.test.A_USER_ID_2 +import io.element.android.libraries.matrix.test.A_USER_ID_3 +import io.element.android.libraries.matrix.test.A_USER_ID_4 +import io.element.android.libraries.matrix.test.A_USER_ID_5 +import io.element.android.libraries.matrix.test.A_USER_ID_6 +import io.element.android.libraries.matrix.test.A_USER_ID_7 +import io.element.android.libraries.matrix.test.A_USER_ID_8 +import io.element.android.libraries.matrix.test.A_USER_ID_9 +import io.element.android.libraries.matrix.test.FakeMatrixClient +import kotlinx.coroutines.test.runTest +import org.junit.Test + +private const val A_POLL_QUESTION = "What is your favorite food?" +private val A_POLL_ANSWER_1 = PollAnswer("id_1", "Pizza") +private val A_POLL_ANSWER_2 = PollAnswer("id_2", "Pasta") +private val A_POLL_ANSWER_3 = PollAnswer("id_3", "French Fries") +private val A_POLL_ANSWER_4 = PollAnswer("id_4", "Hamburger") + +internal class TimelineItemContentPollFactoryTest { + + private val factory = TimelineItemContentPollFactory( + matrixClient = FakeMatrixClient(), + featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.Polls.key to true)), + ) + + @Test + fun `Disclosed poll - not ended states`() = runTest { + // No votes + Truth.assertThat( + factory.create(aPollContent(PollKind.Disclosed)) + ).isEqualTo(aTimelineItemPollContent(PollKind.Disclosed)) + + // Some votes, according one from current user + val votes = mapOf( + A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), + A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), + A_POLL_ANSWER_3.id to emptyList(), + A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), + ) + Truth.assertThat( + factory.create(aPollContent(PollKind.Disclosed).copy(votes = votes)) + ) + .isEqualTo( + aTimelineItemPollContent(PollKind.Disclosed).copy( + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1).copy(votesCount = 3, percentage = 0.3f), + aPollAnswerItem(A_POLL_ANSWER_2).copy(isSelected = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(A_POLL_ANSWER_3).copy(votesCount = 0, percentage = 0f), + aPollAnswerItem(A_POLL_ANSWER_4).copy(votesCount = 1, percentage = 0.1f), + ), + votes = votes, + ) + ) + } + + @Test + fun `Disclosed poll - ended states`() = runTest { + // No votes, no winner + Truth.assertThat( + factory.create(aPollContent(PollKind.Disclosed).copy(endTime = 1UL)) + ).isEqualTo( + aTimelineItemPollContent(PollKind.Disclosed).let { + it.copy( + answerItems = it.answerItems.map { answerItem -> + answerItem.copy(isEnabled = false, isWinner = false) + }, + isEnded = true, + ) + } + ) + + // Some votes, according one from current user (winner) + var votes = mapOf( + A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), + A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), + A_POLL_ANSWER_3.id to emptyList(), + A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), + ) + Truth.assertThat( + factory.create(aPollContent(PollKind.Disclosed).copy(votes = votes, endTime = 1UL)) + ) + .isEqualTo( + aTimelineItemPollContent(PollKind.Disclosed).copy( + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1).copy(isEnabled = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(A_POLL_ANSWER_2).copy(isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(A_POLL_ANSWER_3).copy(isEnabled = false, votesCount = 0, percentage = 0f), + aPollAnswerItem(A_POLL_ANSWER_4).copy(isEnabled = false, votesCount = 1, percentage = 0.1f), + ), + votes = votes, + isEnded = true, + ) + ) + + // Some votes, according one from current user (not winner) and two winning votes + votes = mapOf( + A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), + A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_6), + A_POLL_ANSWER_3.id to emptyList(), + A_POLL_ANSWER_4.id to listOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), + ) + Truth.assertThat( + factory.create(aPollContent(PollKind.Disclosed).copy(votes = votes, endTime = 1UL)) + ) + .isEqualTo( + aTimelineItemPollContent(PollKind.Disclosed).copy( + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1).copy(isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + aPollAnswerItem(A_POLL_ANSWER_2).copy(isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), + aPollAnswerItem(A_POLL_ANSWER_3).copy(isEnabled = false, votesCount = 0, percentage = 0f), + aPollAnswerItem(A_POLL_ANSWER_4).copy(isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + ), + votes = votes, + isEnded = true, + ) + ) + } + + @Test + fun `Undisclosed poll - not ended states`() = runTest { + // No votes + Truth.assertThat( + factory.create(aPollContent(PollKind.Undisclosed).copy()) + ).isEqualTo( + aTimelineItemPollContent(PollKind.Undisclosed).let { + it.copy(answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = false) }) + } + ) + + // Some votes, according one from current user + val votes = mapOf( + A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), + A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), + A_POLL_ANSWER_3.id to emptyList(), + A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), + ) + Truth.assertThat( + factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes)) + ) + .isEqualTo( + aTimelineItemPollContent(PollKind.Undisclosed).copy( + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1).copy(isDisclosed = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(A_POLL_ANSWER_2).copy(isDisclosed = false, isSelected = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(A_POLL_ANSWER_3).copy(isDisclosed = false, votesCount = 0, percentage = 0f), + aPollAnswerItem(A_POLL_ANSWER_4).copy(isDisclosed = false, votesCount = 1, percentage = 0.1f), + ), + votes = votes, + ) + ) + } + + @Test + fun `Undisclosed poll - ended states`() = runTest { + // No votes, no winner + Truth.assertThat( + factory.create(aPollContent(PollKind.Undisclosed).copy(endTime = 1UL)) + ).isEqualTo( + aTimelineItemPollContent(PollKind.Undisclosed).let { + it.copy( + answerItems = it.answerItems.map { answerItem -> + answerItem.copy(isDisclosed = true, isEnabled = false, isWinner = false) + }, + isEnded = true, + ) + } + ) + + // Some votes, according one from current user (winner) + var votes = mapOf( + A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), + A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), + A_POLL_ANSWER_3.id to emptyList(), + A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), + ) + Truth.assertThat( + factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL)) + ) + .isEqualTo( + aTimelineItemPollContent(PollKind.Undisclosed).copy( + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1).copy(isDisclosed = true, isEnabled = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(A_POLL_ANSWER_2).copy( + isDisclosed = true, + isSelected = true, + isEnabled = false, + isWinner = true, + votesCount = 6, + percentage = 0.6f + ), + aPollAnswerItem(A_POLL_ANSWER_3).copy(isDisclosed = true, isEnabled = false, votesCount = 0, percentage = 0f), + aPollAnswerItem(A_POLL_ANSWER_4).copy(isDisclosed = true, isEnabled = false, votesCount = 1, percentage = 0.1f), + ), + votes = votes, + isEnded = true, + ) + ) + + // Some votes, according one from current user (not winner) and two winning votes + votes = mapOf( + A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), + A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_6), + A_POLL_ANSWER_3.id to emptyList(), + A_POLL_ANSWER_4.id to listOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), + ) + Truth.assertThat( + factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL)) + ) + .isEqualTo( + aTimelineItemPollContent(PollKind.Undisclosed).copy( + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1).copy(isDisclosed = true, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + aPollAnswerItem(A_POLL_ANSWER_2).copy(isDisclosed = true, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), + aPollAnswerItem(A_POLL_ANSWER_3).copy(isDisclosed = true, isEnabled = false, votesCount = 0, percentage = 0f), + aPollAnswerItem(A_POLL_ANSWER_4).copy(isDisclosed = true, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + ), + votes = votes, + isEnded = true, + ) + ) + } + + private fun aPollContent(pollKind: PollKind): PollContent = PollContent( + question = A_POLL_QUESTION, + kind = pollKind, + maxSelections = 1UL, + answers = listOf( + A_POLL_ANSWER_1, + A_POLL_ANSWER_2, + A_POLL_ANSWER_3, + A_POLL_ANSWER_4, + ), + votes = emptyMap(), + endTime = null, + ) + + private fun aTimelineItemPollContent(pollKind: PollKind) = TimelineItemPollContent( + question = A_POLL_QUESTION, + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1), + aPollAnswerItem(A_POLL_ANSWER_2), + aPollAnswerItem(A_POLL_ANSWER_3), + aPollAnswerItem(A_POLL_ANSWER_4), + ), + votes = emptyMap(), + pollKind = pollKind, + isEnded = false, + ) + + private fun aPollAnswerItem(answer: PollAnswer) = PollAnswerItem( + answer = answer, + isSelected = false, + isEnabled = true, + isWinner = false, + isDisclosed = true, + votesCount = 0, + percentage = 0f, + ) +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 677774afe4..f6b6e1645f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -30,6 +30,14 @@ const val A_PASSWORD = "password" val A_USER_ID = UserId("@alice:server.org") val A_USER_ID_2 = UserId("@bob:server.org") +val A_USER_ID_3 = UserId("@carol:server.org") +val A_USER_ID_4 = UserId("@david:server.org") +val A_USER_ID_5 = UserId("@eve:server.org") +val A_USER_ID_6 = UserId("@justin:server.org") +val A_USER_ID_7 = UserId("@mallory:server.org") +val A_USER_ID_8 = UserId("@susie:server.org") +val A_USER_ID_9 = UserId("@victor:server.org") +val A_USER_ID_10 = UserId("@walter:server.org") val A_SESSION_ID: SessionId = A_USER_ID val A_SESSION_ID_2: SessionId = A_USER_ID_2 val A_SPACE_ID = SpaceId("!aSpaceId:domain") From 992050c8328d1c4e9420d606a2471e530eb6e502 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 29 Aug 2023 15:58:46 +0200 Subject: [PATCH 64/86] Factorize code and remove unused field in TimelineItemPollContent --- .../event/TimelineItemContentPollFactory.kt | 1 - .../model/event/TimelineItemPollContent.kt | 2 - .../event/TimelineItemPollContentProvider.kt | 1 - .../TimelineItemContentPollFactoryTest.kt | 299 +++++++++--------- 4 files changed, 145 insertions(+), 158 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 4a21874e1c..9c06b17056 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 @@ -69,7 +69,6 @@ class TimelineItemContentPollFactory @Inject constructor( return TimelineItemPollContent( question = content.question, 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 3c0e0edfd4..0f94b97776 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 @@ -17,13 +17,11 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.features.poll.api.PollAnswerItem -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.poll.PollKind data class TimelineItemPollContent( val question: String, val answerItems: List, - val votes: Map>, val pollKind: PollKind, val isEnded: Boolean, ) : TimelineItemEventContent { 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 49dfa58c8a..247d450ae7 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 @@ -34,6 +34,5 @@ fun aTimelineItemPollContent(): TimelineItemPollContent { question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), isEnded = false, - votes = emptyMap(), ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt index b7d13784f8..a1411293e9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt @@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.poll.api.PollAnswerItem import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.timeline.item.event.PollContent @@ -39,12 +40,6 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import kotlinx.coroutines.test.runTest import org.junit.Test -private const val A_POLL_QUESTION = "What is your favorite food?" -private val A_POLL_ANSWER_1 = PollAnswer("id_1", "Pizza") -private val A_POLL_ANSWER_2 = PollAnswer("id_2", "Pasta") -private val A_POLL_ANSWER_3 = PollAnswer("id_3", "French Fries") -private val A_POLL_ANSWER_4 = PollAnswer("id_4", "Hamburger") - internal class TimelineItemContentPollFactoryTest { private val factory = TimelineItemContentPollFactory( @@ -53,101 +48,82 @@ internal class TimelineItemContentPollFactoryTest { ) @Test - fun `Disclosed poll - not ended states`() = runTest { - // No votes - Truth.assertThat( - factory.create(aPollContent(PollKind.Disclosed)) - ).isEqualTo(aTimelineItemPollContent(PollKind.Disclosed)) + fun `Disclosed poll - not ended, no votes`() = runTest { + Truth.assertThat(factory.create(aPollContent())).isEqualTo(aTimelineItemPollContent()) + } - // Some votes, according one from current user - val votes = mapOf( - A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), - A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), - A_POLL_ANSWER_3.id to emptyList(), - A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), - ) + @Test + fun `Disclosed poll - not ended, some votes, including one from current user`() = runTest { + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(PollKind.Disclosed).copy(votes = votes)) + factory.create(aPollContent(votes = votes)) ) .isEqualTo( - aTimelineItemPollContent(PollKind.Disclosed).copy( + aTimelineItemPollContent( answerItems = listOf( - aPollAnswerItem(A_POLL_ANSWER_1).copy(votesCount = 3, percentage = 0.3f), - aPollAnswerItem(A_POLL_ANSWER_2).copy(isSelected = true, votesCount = 6, percentage = 0.6f), - aPollAnswerItem(A_POLL_ANSWER_3).copy(votesCount = 0, percentage = 0f), - aPollAnswerItem(A_POLL_ANSWER_4).copy(votesCount = 1, percentage = 0.1f), + aPollAnswerItem(answer = A_POLL_ANSWER_1, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(answer = A_POLL_ANSWER_3), + aPollAnswerItem(answer = A_POLL_ANSWER_4, votesCount = 1, percentage = 0.1f), ), - votes = votes, ) ) } @Test - fun `Disclosed poll - ended states`() = runTest { - // No votes, no winner + fun `Disclosed poll - ended, no votes, no winner`() = runTest { Truth.assertThat( - factory.create(aPollContent(PollKind.Disclosed).copy(endTime = 1UL)) + factory.create(aPollContent(endTime = 1UL)) ).isEqualTo( - aTimelineItemPollContent(PollKind.Disclosed).let { + aTimelineItemPollContent().let { it.copy( - answerItems = it.answerItems.map { answerItem -> - answerItem.copy(isEnabled = false, isWinner = false) - }, + answerItems = it.answerItems.map { answerItem -> answerItem.copy(isEnabled = false) }, isEnded = true, ) } ) + } - // Some votes, according one from current user (winner) - var votes = mapOf( - A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), - A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), - A_POLL_ANSWER_3.id to emptyList(), - A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), - ) + @Test + fun `Disclosed poll - ended, some votes, including one from current user (winner)`() = runTest { + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(PollKind.Disclosed).copy(votes = votes, endTime = 1UL)) + factory.create(aPollContent(votes = votes, endTime = 1UL)) ) .isEqualTo( - aTimelineItemPollContent(PollKind.Disclosed).copy( + aTimelineItemPollContent( answerItems = listOf( - aPollAnswerItem(A_POLL_ANSWER_1).copy(isEnabled = false, votesCount = 3, percentage = 0.3f), - aPollAnswerItem(A_POLL_ANSWER_2).copy(isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), - aPollAnswerItem(A_POLL_ANSWER_3).copy(isEnabled = false, votesCount = 0, percentage = 0f), - aPollAnswerItem(A_POLL_ANSWER_4).copy(isEnabled = false, votesCount = 1, percentage = 0.1f), + aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), + aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, votesCount = 1, percentage = 0.1f), ), - votes = votes, - isEnded = true, - ) - ) - - // Some votes, according one from current user (not winner) and two winning votes - votes = mapOf( - A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), - A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_6), - A_POLL_ANSWER_3.id to emptyList(), - A_POLL_ANSWER_4.id to listOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), - ) - Truth.assertThat( - factory.create(aPollContent(PollKind.Disclosed).copy(votes = votes, endTime = 1UL)) - ) - .isEqualTo( - aTimelineItemPollContent(PollKind.Disclosed).copy( - answerItems = listOf( - aPollAnswerItem(A_POLL_ANSWER_1).copy(isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), - aPollAnswerItem(A_POLL_ANSWER_2).copy(isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), - aPollAnswerItem(A_POLL_ANSWER_3).copy(isEnabled = false, votesCount = 0, percentage = 0f), - aPollAnswerItem(A_POLL_ANSWER_4).copy(isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), - ), - votes = votes, isEnded = true, ) ) } @Test - fun `Undisclosed poll - not ended states`() = runTest { - // No votes + fun `Disclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { + val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id } + Truth.assertThat( + factory.create(aPollContent(votes = votes, endTime = 1UL)) + ) + .isEqualTo( + aTimelineItemPollContent( + answerItems = listOf( + aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), + aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), + aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + ), + isEnded = true, + ) + ) + } + + @Test + fun `Undisclosed poll - not ended, no votes`() = runTest { Truth.assertThat( factory.create(aPollContent(PollKind.Undisclosed).copy()) ).isEqualTo( @@ -155,38 +131,35 @@ internal class TimelineItemContentPollFactoryTest { it.copy(answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = false) }) } ) + } - // Some votes, according one from current user - val votes = mapOf( - A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), - A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), - A_POLL_ANSWER_3.id to emptyList(), - A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), - ) + @Test + fun `Undisclosed poll - not ended, some votes, including one from current user`() = runTest { + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes)) + factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes)) ) .isEqualTo( - aTimelineItemPollContent(PollKind.Undisclosed).copy( + aTimelineItemPollContent( + pollKind = PollKind.Undisclosed, answerItems = listOf( - aPollAnswerItem(A_POLL_ANSWER_1).copy(isDisclosed = false, votesCount = 3, percentage = 0.3f), - aPollAnswerItem(A_POLL_ANSWER_2).copy(isDisclosed = false, isSelected = true, votesCount = 6, percentage = 0.6f), - aPollAnswerItem(A_POLL_ANSWER_3).copy(isDisclosed = false, votesCount = 0, percentage = 0f), - aPollAnswerItem(A_POLL_ANSWER_4).copy(isDisclosed = false, votesCount = 1, percentage = 0.1f), + aPollAnswerItem(answer = A_POLL_ANSWER_1, isDisclosed = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(answer = A_POLL_ANSWER_2, isDisclosed = false, isSelected = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(answer = A_POLL_ANSWER_3, isDisclosed = false), + aPollAnswerItem(answer = A_POLL_ANSWER_4, isDisclosed = false, votesCount = 1, percentage = 0.1f), ), - votes = votes, ) ) } @Test - fun `Undisclosed poll - ended states`() = runTest { - // No votes, no winner + fun `Undisclosed poll - ended, no votes, no winner`() = runTest { Truth.assertThat( - factory.create(aPollContent(PollKind.Undisclosed).copy(endTime = 1UL)) + factory.create(aPollContent(pollKind = PollKind.Undisclosed, endTime = 1UL)) ).isEqualTo( - aTimelineItemPollContent(PollKind.Undisclosed).let { + aTimelineItemPollContent().let { it.copy( + pollKind = PollKind.Undisclosed, answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = true, isEnabled = false, isWinner = false) }, @@ -194,95 +167,113 @@ internal class TimelineItemContentPollFactoryTest { ) } ) + } - // Some votes, according one from current user (winner) - var votes = mapOf( - A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), - A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), - A_POLL_ANSWER_3.id to emptyList(), - A_POLL_ANSWER_4.id to listOf(A_USER_ID_10), - ) + @Test + fun `Undisclosed poll - ended, some votes, including one from current user (winner)`() = runTest { + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL)) + factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes, endTime = 1UL)) ) .isEqualTo( - aTimelineItemPollContent(PollKind.Undisclosed).copy( + aTimelineItemPollContent( + pollKind = PollKind.Undisclosed, answerItems = listOf( - aPollAnswerItem(A_POLL_ANSWER_1).copy(isDisclosed = true, isEnabled = false, votesCount = 3, percentage = 0.3f), - aPollAnswerItem(A_POLL_ANSWER_2).copy( - isDisclosed = true, - isSelected = true, - isEnabled = false, - isWinner = true, - votesCount = 6, - percentage = 0.6f - ), - aPollAnswerItem(A_POLL_ANSWER_3).copy(isDisclosed = true, isEnabled = false, votesCount = 0, percentage = 0f), - aPollAnswerItem(A_POLL_ANSWER_4).copy(isDisclosed = true, isEnabled = false, votesCount = 1, percentage = 0.1f), + aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), + aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, votesCount = 1, percentage = 0.1f), ), - votes = votes, - isEnded = true, - ) - ) - - // Some votes, according one from current user (not winner) and two winning votes - votes = mapOf( - A_POLL_ANSWER_1.id to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), - A_POLL_ANSWER_2.id to listOf(A_USER_ID, A_USER_ID_6), - A_POLL_ANSWER_3.id to emptyList(), - A_POLL_ANSWER_4.id to listOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), - ) - Truth.assertThat( - factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL)) - ) - .isEqualTo( - aTimelineItemPollContent(PollKind.Undisclosed).copy( - answerItems = listOf( - aPollAnswerItem(A_POLL_ANSWER_1).copy(isDisclosed = true, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), - aPollAnswerItem(A_POLL_ANSWER_2).copy(isDisclosed = true, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), - aPollAnswerItem(A_POLL_ANSWER_3).copy(isDisclosed = true, isEnabled = false, votesCount = 0, percentage = 0f), - aPollAnswerItem(A_POLL_ANSWER_4).copy(isDisclosed = true, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), - ), - votes = votes, isEnded = true, ) ) } - private fun aPollContent(pollKind: PollKind): PollContent = PollContent( + @Test + fun `Undisclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { + val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id } + Truth.assertThat( + factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL)) + ) + .isEqualTo( + aTimelineItemPollContent( + pollKind = PollKind.Undisclosed, + answerItems = listOf( + aPollAnswerItem(A_POLL_ANSWER_1, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + aPollAnswerItem(A_POLL_ANSWER_2, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), + aPollAnswerItem(A_POLL_ANSWER_3, isEnabled = false), + aPollAnswerItem(A_POLL_ANSWER_4, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), + ), + isEnded = true, + ) + ) + } + + private fun aPollContent( + pollKind: PollKind = PollKind.Disclosed, + votes: Map> = emptyMap(), + endTime: ULong? = null, + ): PollContent = PollContent( question = A_POLL_QUESTION, kind = pollKind, maxSelections = 1UL, - answers = listOf( - A_POLL_ANSWER_1, - A_POLL_ANSWER_2, - A_POLL_ANSWER_3, - A_POLL_ANSWER_4, - ), - votes = emptyMap(), - endTime = null, + answers = listOf(A_POLL_ANSWER_1, A_POLL_ANSWER_2, A_POLL_ANSWER_3, A_POLL_ANSWER_4), + votes = votes, + endTime = endTime, ) - private fun aTimelineItemPollContent(pollKind: PollKind) = TimelineItemPollContent( - question = A_POLL_QUESTION, - answerItems = listOf( + private fun aTimelineItemPollContent( + pollKind: PollKind = PollKind.Disclosed, + answerItems: List = listOf( aPollAnswerItem(A_POLL_ANSWER_1), aPollAnswerItem(A_POLL_ANSWER_2), aPollAnswerItem(A_POLL_ANSWER_3), aPollAnswerItem(A_POLL_ANSWER_4), ), - votes = emptyMap(), + isEnded: Boolean = false, + ) = TimelineItemPollContent( + question = A_POLL_QUESTION, + answerItems = answerItems, pollKind = pollKind, - isEnded = false, + isEnded = isEnded, ) - private fun aPollAnswerItem(answer: PollAnswer) = PollAnswerItem( + private fun aPollAnswerItem( + answer: PollAnswer, + isSelected: Boolean = false, + isEnabled: Boolean = true, + isWinner: Boolean = false, + isDisclosed: Boolean = true, + votesCount: Int = 0, + percentage: Float = 0f, + ) = PollAnswerItem( answer = answer, - isSelected = false, - isEnabled = true, - isWinner = false, - isDisclosed = true, - votesCount = 0, - percentage = 0f, + isSelected = isSelected, + isEnabled = isEnabled, + isWinner = isWinner, + isDisclosed = isDisclosed, + votesCount = votesCount, + percentage = percentage, ) + + private companion object TestData { + private const val A_POLL_QUESTION = "What is your favorite food?" + private val A_POLL_ANSWER_1 = PollAnswer("id_1", "Pizza") + private val A_POLL_ANSWER_2 = PollAnswer("id_2", "Pasta") + private val A_POLL_ANSWER_3 = PollAnswer("id_3", "French Fries") + private val A_POLL_ANSWER_4 = PollAnswer("id_4", "Hamburger") + + private val MY_USER_WINNING_VOTES = mapOf( + A_POLL_ANSWER_1 to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), + A_POLL_ANSWER_2 to listOf(A_USER_ID /* my vote */, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), // winner + A_POLL_ANSWER_3 to emptyList(), + A_POLL_ANSWER_4 to listOf(A_USER_ID_10), + ) + private val OTHER_WINNING_VOTES = mapOf( + A_POLL_ANSWER_1 to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), // winner + A_POLL_ANSWER_2 to listOf(A_USER_ID /* my vote */, A_USER_ID_6), + A_POLL_ANSWER_3 to emptyList(), + A_POLL_ANSWER_4 to listOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), // winner + ) + } } From 097efc26d262cdeaf29a98e19fbcfaa495ab8786 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:09:26 +0200 Subject: [PATCH 65/86] Migrate AnalyticsPreferencesView from `PreferenceSwitch` to `ListItem`. --- .../preferences/AnalyticsPreferencesView.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 5aa9287d86..017822a825 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -18,7 +18,6 @@ package io.element.android.features.analytics.api.preferences import androidx.annotation.StringRes import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -28,9 +27,11 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.analytics.api.AnalyticsOptInEvents -import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.LinkColor import io.element.android.libraries.ui.strings.CommonStrings @@ -50,13 +51,16 @@ fun AnalyticsPreferencesView( ) val subtitle = "$firstPart\n\n$secondPart" - PreferenceSwitch( + ListItem( + headlineContent = { + Text(stringResource(id = CommonStrings.screen_analytics_settings_share_data)) + }, + supportingContent = { + Text(text = subtitle) + }, + leadingContent = null, + trailingContent = ListItemContent.Switch(checked = state.isEnabled, onChange = ::onEnabledChanged), modifier = modifier, - title = stringResource(id = CommonStrings.screen_analytics_settings_share_data), - subtitle = subtitle, - isChecked = state.isEnabled, - onCheckedChange = ::onEnabledChanged, - switchAlignment = Alignment.Top, ) } From 24fc2e77b973e876b7aa3877168502109775f75b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:10:02 +0200 Subject: [PATCH 66/86] Format. --- .../analytics/api/preferences/AnalyticsPreferencesView.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 017822a825..433358e4ba 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -79,7 +79,9 @@ fun buildAnnotatedStringWithColoredPart( style = SpanStyle( color = color, textDecoration = if (underline) TextDecoration.Underline else null - ), start = startIndex, end = startIndex + coloredPart.length + ), + start = startIndex, + end = startIndex + coloredPart.length, ) } From 1bf2dc1c4d78fe4000d43de188e8c399feba1954 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:11:08 +0200 Subject: [PATCH 67/86] Fix rendering issue of the link. --- .../analytics/api/preferences/AnalyticsPreferencesView.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 433358e4ba..d268328992 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -49,7 +49,11 @@ fun AnalyticsPreferencesView( CommonStrings.screen_analytics_settings_read_terms, CommonStrings.screen_analytics_settings_read_terms_content_link ) - val subtitle = "$firstPart\n\n$secondPart" + val subtitle = buildAnnotatedString { + append(firstPart) + append("\n\n") + append(secondPart) + } ListItem( headlineContent = { From ff47629f6c7225597febf11a061bba1621cbeffa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:32:29 +0200 Subject: [PATCH 68/86] Make the link open the policy url in the analytics setting screen. --- .../preferences/AnalyticsPreferencesState.kt | 1 + .../AnalyticsPreferencesStateProvider.kt | 1 + .../preferences/AnalyticsPreferencesView.kt | 30 +++++++++++++++++-- .../DefaultAnalyticsPreferencesPresenter.kt | 2 ++ .../AnalyticsPreferencesPresenterTest.kt | 1 + .../impl/analytics/AnalyticsSettingsNode.kt | 11 +++++++ .../impl/analytics/AnalyticsSettingsView.kt | 3 ++ 7 files changed, 47 insertions(+), 2 deletions(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt index 7cf0f51dfd..e03796297e 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt @@ -21,5 +21,6 @@ import io.element.android.features.analytics.api.AnalyticsOptInEvents data class AnalyticsPreferencesState( val applicationName: String, val isEnabled: Boolean, + val policyUrl: String, val eventSink: (AnalyticsOptInEvents) -> Unit, ) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt index ea397b4d67..18f902a6fd 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt @@ -28,5 +28,6 @@ open class AnalyticsPreferencesStateProvider : PreviewParameterProvider Unit, ) { fun onEnabledChanged(isEnabled: Boolean) { state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) @@ -47,7 +49,8 @@ fun AnalyticsPreferencesView( val firstPart = stringResource(id = CommonStrings.screen_analytics_settings_help_us_improve, state.applicationName) val secondPart = buildAnnotatedStringWithColoredPart( CommonStrings.screen_analytics_settings_read_terms, - CommonStrings.screen_analytics_settings_read_terms_content_link + CommonStrings.screen_analytics_settings_read_terms_content_link, + link = state.policyUrl, ) val subtitle = buildAnnotatedString { append(firstPart) @@ -60,6 +63,16 @@ fun AnalyticsPreferencesView( Text(stringResource(id = CommonStrings.screen_analytics_settings_share_data)) }, supportingContent = { + ClickableText( + text = subtitle, + onClick = { + subtitle + .getStringAnnotations("link", it, it) + .firstOrNull()?.let { stringAnnotation -> + onOpenAnalyticsPolicy(stringAnnotation.item) + } + } + ) Text(text = subtitle) }, leadingContent = null, @@ -68,12 +81,14 @@ fun AnalyticsPreferencesView( ) } +// TODO Use buildAnnotatedStringWithStyledPart. @Composable fun buildAnnotatedStringWithColoredPart( @StringRes fullTextRes: Int, @StringRes coloredTextRes: Int, color: Color = LinkColor, underline: Boolean = true, + link: String? = null, ) = buildAnnotatedString { val coloredPart = stringResource(coloredTextRes) val fullText = stringResource(fullTextRes, coloredPart) @@ -87,6 +102,14 @@ fun buildAnnotatedStringWithColoredPart( start = startIndex, end = startIndex + coloredPart.length, ) + if (link != null) { + addStringAnnotation( + tag = "link", + annotation = link, + start = startIndex, + end = startIndex + coloredPart.length + ) + } } @Preview @@ -101,5 +124,8 @@ internal fun AnalyticsPreferencesViewDarkPreview(@PreviewParameter(AnalyticsPref @Composable private fun ContentToPreview(state: AnalyticsPreferencesState) { - AnalyticsPreferencesView(state) + AnalyticsPreferencesView( + state = state, + onOpenAnalyticsPolicy = {}, + ) } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt index 6debe4c232..06431402a6 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/DefaultAnalyticsPreferencesPresenter.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.Config import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState import io.element.android.libraries.core.meta.BuildMeta @@ -51,6 +52,7 @@ class DefaultAnalyticsPreferencesPresenter @Inject constructor( return AnalyticsPreferencesState( applicationName = buildMeta.applicationName, isEnabled = isEnabled.value, + policyUrl = Config.POLICY_LINK, eventSink = ::handleEvents ) } diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt index 843e5b5532..29c4579d8f 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -39,6 +39,7 @@ class AnalyticsPreferencesPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.isEnabled).isTrue() + assertThat(initialState.policyUrl).isNotEmpty() } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt index adc917b7e6..18bb99e8e1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt @@ -16,15 +16,19 @@ package io.element.android.features.preferences.impl.analytics +import android.app.Activity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.theme.ElementTheme @ContributesNode(SessionScope::class) class AnalyticsSettingsNode @AssistedInject constructor( @@ -33,12 +37,19 @@ class AnalyticsSettingsNode @AssistedInject constructor( private val presenter: AnalyticsSettingsPresenter, ) : Node(buildContext, plugins = plugins) { + private fun onOpenAnalyticsPolicy(activity: Activity, darkTheme: Boolean, url: String) { + activity.openUrlInChromeCustomTab(null, darkTheme, url) + } + @Composable override fun View(modifier: Modifier) { + val activity = LocalContext.current as Activity + val isDark = ElementTheme.colors.isLight.not() val state = presenter.present() AnalyticsSettingsView( state = state, onBackPressed = ::navigateUp, + onOpenAnalyticsPolicy = { onOpenAnalyticsPolicy(activity, darkTheme = isDark, it) }, modifier = modifier ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt index 3ee7365122..83dd5554fd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun AnalyticsSettingsView( state: AnalyticsSettingsState, onBackPressed: () -> Unit, + onOpenAnalyticsPolicy: (url: String) -> Unit, modifier: Modifier = Modifier, ) { PreferenceView( @@ -40,6 +41,7 @@ fun AnalyticsSettingsView( ) { AnalyticsPreferencesView( state = state.analyticsState, + onOpenAnalyticsPolicy = onOpenAnalyticsPolicy, ) } } @@ -59,5 +61,6 @@ private fun ContentToPreview(state: AnalyticsSettingsState) { AnalyticsSettingsView( state = state, onBackPressed = {}, + onOpenAnalyticsPolicy = {}, ) } From 79af05bc08eccee7834542d35b4b4b6fd2074269 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:38:21 +0200 Subject: [PATCH 69/86] Use buildAnnotatedStringWithStyledPart and remove copied code. --- .../preferences/AnalyticsPreferencesView.kt | 45 +++---------------- .../designsystem/text/AnnotatedStrings.kt | 10 +++++ 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index e5b40ee8a9..42775ef83f 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -16,26 +16,24 @@ package io.element.android.features.analytics.api.preferences -import androidx.annotation.StringRes import androidx.compose.foundation.text.ClickableText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.theme.LinkColor import io.element.android.libraries.ui.strings.CommonStrings +private const val LINK_TAG = "link" + @Composable fun AnalyticsPreferencesView( state: AnalyticsPreferencesState, @@ -47,10 +45,10 @@ fun AnalyticsPreferencesView( } val firstPart = stringResource(id = CommonStrings.screen_analytics_settings_help_us_improve, state.applicationName) - val secondPart = buildAnnotatedStringWithColoredPart( + val secondPart = buildAnnotatedStringWithStyledPart( CommonStrings.screen_analytics_settings_read_terms, CommonStrings.screen_analytics_settings_read_terms_content_link, - link = state.policyUrl, + tagAndLink = LINK_TAG to state.policyUrl, ) val subtitle = buildAnnotatedString { append(firstPart) @@ -67,7 +65,7 @@ fun AnalyticsPreferencesView( text = subtitle, onClick = { subtitle - .getStringAnnotations("link", it, it) + .getStringAnnotations(LINK_TAG, it, it) .firstOrNull()?.let { stringAnnotation -> onOpenAnalyticsPolicy(stringAnnotation.item) } @@ -81,37 +79,6 @@ fun AnalyticsPreferencesView( ) } -// TODO Use buildAnnotatedStringWithStyledPart. -@Composable -fun buildAnnotatedStringWithColoredPart( - @StringRes fullTextRes: Int, - @StringRes coloredTextRes: Int, - color: Color = LinkColor, - underline: Boolean = true, - link: String? = null, -) = buildAnnotatedString { - val coloredPart = stringResource(coloredTextRes) - val fullText = stringResource(fullTextRes, coloredPart) - val startIndex = fullText.indexOf(coloredPart) - append(fullText) - addStyle( - style = SpanStyle( - color = color, - textDecoration = if (underline) TextDecoration.Underline else null - ), - start = startIndex, - end = startIndex + coloredPart.length, - ) - if (link != null) { - addStringAnnotation( - tag = "link", - annotation = link, - start = startIndex, - end = startIndex + coloredPart.length - ) - } -} - @Preview @Composable internal fun AnalyticsPreferencesViewLightPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) = diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt index 779b7e7053..04a872a1cf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt @@ -59,6 +59,7 @@ fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString { * @param color the color to apply to the string * @param underline whether to underline the string * @param bold whether to bold the string + * @param tagAndLink an optional pair of tag and link to add to the styled part of the string, as StringAnnotation */ @Composable fun buildAnnotatedStringWithStyledPart( @@ -67,6 +68,7 @@ fun buildAnnotatedStringWithStyledPart( color: Color = LinkColor, underline: Boolean = true, bold: Boolean = false, + tagAndLink: Pair? = null, ) = buildAnnotatedString { val coloredPart = stringResource(coloredTextRes) val fullText = stringResource(fullTextRes, coloredPart) @@ -81,6 +83,14 @@ fun buildAnnotatedStringWithStyledPart( start = startIndex, end = startIndex + coloredPart.length, ) + if (tagAndLink != null) { + addStringAnnotation( + tag = tagAndLink.first, + annotation = tagAndLink.second, + start = startIndex, + end = startIndex + coloredPart.length + ) + } } /** From a86100003fb2020b24286a949e0165ce734c3590 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:39:59 +0200 Subject: [PATCH 70/86] Remove preview Text. --- .../analytics/api/preferences/AnalyticsPreferencesView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 42775ef83f..16507ebcfe 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -71,7 +71,6 @@ fun AnalyticsPreferencesView( } } ) - Text(text = subtitle) }, leadingContent = null, trailingContent = ListItemContent.Switch(checked = state.isEnabled, onChange = ::onEnabledChanged), From 36bf343a95ed6a9a901df91185d5d0db7578c3b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:46:42 +0200 Subject: [PATCH 71/86] OptIn screen: make only the `here` word be clickable. --- .../analytics/impl/AnalyticsOptInView.kt | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 54b9add785..bd0dee5f89 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -18,7 +18,6 @@ package io.element.android.features.analytics.impl import androidx.activity.compose.BackHandler import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -28,7 +27,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.ClickableText import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Poll import androidx.compose.material.icons.rounded.Check @@ -37,7 +36,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -45,6 +43,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.Config import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.molecules.InfoListItem @@ -56,7 +55,6 @@ import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithSt import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial import io.element.android.libraries.designsystem.utils.LogCompositions @@ -98,6 +96,8 @@ fun AnalyticsOptInView( ) } +private const val LINK_TAG = "link" + @Composable private fun AnalyticsOptInHeader( state: AnalyticsOptInState, @@ -114,21 +114,29 @@ private fun AnalyticsOptInHeader( subTitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve), iconImageVector = Icons.Filled.Poll ) - Text( - text = buildAnnotatedStringWithStyledPart( - R.string.screen_analytics_prompt_read_terms, - R.string.screen_analytics_prompt_read_terms_content_link, - color = Color.Unspecified, - underline = false, - bold = true, - ), + val text = buildAnnotatedStringWithStyledPart( + R.string.screen_analytics_prompt_read_terms, + R.string.screen_analytics_prompt_read_terms_content_link, + color = Color.Unspecified, + underline = false, + bold = true, + tagAndLink = LINK_TAG to Config.POLICY_LINK, + ) + ClickableText( + text = text, + onClick = { + text + .getStringAnnotations(LINK_TAG, it, it) + .firstOrNull() + ?.let { _ -> onClickTerms() } + }, modifier = Modifier - .clip(shape = RoundedCornerShape(8.dp)) - .clickable { onClickTerms() } .padding(8.dp), - style = ElementTheme.typography.fontBodyMdRegular, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.secondary, + style = ElementTheme.typography.fontBodyMdRegular + .copy( + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center, + ) ) } } From 7537772d949e041bf98b54f92118be7df409df06 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:46:59 +0200 Subject: [PATCH 72/86] format. --- .../analytics/api/preferences/AnalyticsPreferencesView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 16507ebcfe..e93f737d41 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -66,7 +66,8 @@ fun AnalyticsPreferencesView( onClick = { subtitle .getStringAnnotations(LINK_TAG, it, it) - .firstOrNull()?.let { stringAnnotation -> + .firstOrNull() + ?.let { stringAnnotation -> onOpenAnalyticsPolicy(stringAnnotation.item) } } From d3c6efc6661b21fc0e64e63ccc2f07a72936b805 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 16:50:32 +0200 Subject: [PATCH 73/86] Changelog. --- changelog.d/1177.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1177.bugfix diff --git a/changelog.d/1177.bugfix b/changelog.d/1177.bugfix new file mode 100644 index 0000000000..edbf2e9006 --- /dev/null +++ b/changelog.d/1177.bugfix @@ -0,0 +1 @@ +Add missing link to the terms on the analytics setting screen. From ece82203eb06575ebfa83e642a0e8bac4c4ab8f4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Aug 2023 18:01:21 +0200 Subject: [PATCH 74/86] Fix style (typo and color) for supporting content. --- .../analytics/api/preferences/AnalyticsPreferencesView.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index e93f737d41..9920b8a0b0 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -17,6 +17,7 @@ package io.element.android.features.analytics.api.preferences import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -30,6 +31,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings private const val LINK_TAG = "link" @@ -70,7 +72,11 @@ fun AnalyticsPreferencesView( ?.let { stringAnnotation -> onOpenAnalyticsPolicy(stringAnnotation.item) } - } + }, + style = ElementTheme.typography.fontBodyMdRegular + .copy( + color = MaterialTheme.colorScheme.secondary, + ), ) }, leadingContent = null, From 1111b1408ccff16d00a0edd8d5239af5ed535bc1 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 29 Aug 2023 16:13:48 +0000 Subject: [PATCH 75/86] Update screenshots --- ..._AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ull_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png index 16de0b9378..28ae52b035 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a08d5ff1f91c0db7c502882815d36d6a675567ebf8c3eddc0ebff431e3592e67 -size 23390 +oid sha256:f70fad1a0ff08c3bd259ee8abd745d07474fa1dd45cfd2b8c77fe3536fedbb29 +size 23799 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png index 7d6d55ba11..5ce51a0c7a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a5300ac57d9d0137cf82af4ed4bd86c3ab7f3a70d2560954e524b7b3c120199 -size 23515 +oid sha256:07c00693ed47f8db94c0a9bcdd7350e32366813b3a4241dceebbd8aed78a5bbe +size 23880 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png index 7e92a427ab..d12a018f64 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4dd40c1eac2e5f33ce340ce69d8c0f502cd0912a0d511602e1122a6f4d8ae330 -size 25433 +oid sha256:fcde2d058771af909595c4f163925fadc0ddee8cb0c440cbc3268403961e617c +size 25774 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png index 4a8255f3ac..e86823967d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76dbcb5845565774bd33a192d02a3da0216c34b17e6e1bc1f208cabef9506617 -size 26630 +oid sha256:cef0f004854681538224bd5513e0ec66afb87e54fc7bac4a4eb4c0194c9f594c +size 26965 From 633d5282d6b36d69cee2d2367abc658321104a91 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Tue, 29 Aug 2023 22:31:21 +0200 Subject: [PATCH 76/86] "Create poll" UI (#1143) NB: This is missing analytics, which will be added once https://github.com/matrix-org/matrix-analytics-events/pull/85 is merged. Closes https://github.com/vector-im/element-meta/issues/2011 --- changelog.d/1143.feature | 1 + .../messages/impl/MessagesFlowNode.kt | 12 + .../features/messages/impl/MessagesNode.kt | 6 + .../features/messages/impl/MessagesView.kt | 5 + .../impl/actionlist/ActionListPresenter.kt | 17 ++ .../messagecomposer/AttachmentsBottomSheet.kt | 15 ++ .../messagecomposer/MessageComposerEvents.kt | 1 + .../MessageComposerPresenter.kt | 10 + .../messagecomposer/MessageComposerState.kt | 1 + .../MessageComposerStateProvider.kt | 2 + .../messagecomposer/MessageComposerView.kt | 5 +- .../actionlist/ActionListPresenterTest.kt | 55 ++++ .../CreatePollEntryPoint.kt} | 18 +- features/poll/impl/build.gradle.kts | 5 +- .../features/poll/impl/PollFlowNode.kt | 70 ----- .../poll/impl/create/CreatePollEvents.kt | 31 +++ .../poll/impl/create/CreatePollNode.kt | 56 ++++ .../poll/impl/create/CreatePollPresenter.kt | 162 ++++++++++++ .../poll/impl/create/CreatePollState.kt | 35 +++ .../impl/create/CreatePollStateProvider.kt | 124 +++++++++ .../poll/impl/create/CreatePollView.kt | 187 ++++++++++++++ .../DefaultCreatePollEntryPoint.kt} | 24 +- .../impl/src/main/res/values/localazy.xml | 11 + .../impl/create/CreatePollPresenterTest.kt | 239 ++++++++++++++++++ .../libraries/featureflag/api/FeatureFlags.kt | 2 +- ...ePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png | 4 +- ...ePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png | 4 +- ...ePollView-D-0_1_null_0,NEXUS_5,1.0,en].png | 3 + ...ePollView-D-0_1_null_1,NEXUS_5,1.0,en].png | 3 + ...ePollView-D-0_1_null_2,NEXUS_5,1.0,en].png | 3 + ...ePollView-D-0_1_null_3,NEXUS_5,1.0,en].png | 3 + ...ePollView-D-0_1_null_4,NEXUS_5,1.0,en].png | 3 + ...ePollView-D-0_1_null_5,NEXUS_5,1.0,en].png | 3 + ...ePollView-N-0_2_null_0,NEXUS_5,1.0,en].png | 3 + ...ePollView-N-0_2_null_1,NEXUS_5,1.0,en].png | 3 + ...ePollView-N-0_2_null_2,NEXUS_5,1.0,en].png | 3 + ...ePollView-N-0_2_null_3,NEXUS_5,1.0,en].png | 3 + ...ePollView-N-0_2_null_4,NEXUS_5,1.0,en].png | 3 + ...ePollView-N-0_2_null_5,NEXUS_5,1.0,en].png | 3 + tools/localazy/config.json | 6 + 40 files changed, 1032 insertions(+), 112 deletions(-) create mode 100644 changelog.d/1143.feature rename features/poll/api/src/main/kotlin/io/element/android/features/poll/api/{PollEntryPoint.kt => create/CreatePollEntryPoint.kt} (65%) delete mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt create mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt create mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt create mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt create mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt create mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt create mode 100644 features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt rename features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/{DefaultPollEntryPoint.kt => create/DefaultCreatePollEntryPoint.kt} (55%) create mode 100644 features/poll/impl/src/main/res/values/localazy.xml create mode 100644 features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_5,NEXUS_5,1.0,en].png diff --git a/changelog.d/1143.feature b/changelog.d/1143.feature new file mode 100644 index 0000000000..84a86f4f25 --- /dev/null +++ b/changelog.d/1143.feature @@ -0,0 +1 @@ +Create poll. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index eee68768be..fcb2e7e5e8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -46,6 +46,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent +import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode @@ -64,6 +65,7 @@ class MessagesFlowNode @AssistedInject constructor( @Assisted plugins: List, private val sendLocationEntryPoint: SendLocationEntryPoint, private val showLocationEntryPoint: ShowLocationEntryPoint, + private val createPollEntryPoint: CreatePollEntryPoint, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.Messages, @@ -101,6 +103,9 @@ class MessagesFlowNode @AssistedInject constructor( @Parcelize data object SendLocation : NavTarget + + @Parcelize + data object CreatePoll : NavTarget } private val callback = plugins().firstOrNull() @@ -140,6 +145,10 @@ class MessagesFlowNode @AssistedInject constructor( override fun onSendLocationClicked() { backstack.push(NavTarget.SendLocation) } + + override fun onCreatePollClicked() { + backstack.push(NavTarget.CreatePoll) + } } createNode(buildContext, listOf(callback)) } @@ -179,6 +188,9 @@ class MessagesFlowNode @AssistedInject constructor( NavTarget.SendLocation -> { sendLocationEntryPoint.createNode(this, buildContext) } + NavTarget.CreatePoll -> { + createPollEntryPoint.createNode(this, buildContext) + } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 3f201a8e4c..6a3cf502d7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -58,6 +58,7 @@ class MessagesNode @AssistedInject constructor( fun onForwardEventClicked(eventId: EventId) fun onReportMessage(eventId: EventId, senderId: UserId) fun onSendLocationClicked() + fun onCreatePollClicked() } init { @@ -99,6 +100,10 @@ class MessagesNode @AssistedInject constructor( callback?.onSendLocationClicked() } + private fun onCreatePollClicked() { + callback?.onCreatePollClicked() + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -110,6 +115,7 @@ class MessagesNode @AssistedInject constructor( onPreviewAttachments = this::onPreviewAttachments, onUserDataClicked = this::onUserDataClicked, onSendLocationClicked = this::onSendLocationClicked, + onCreatePollClicked = this::onCreatePollClicked, modifier = modifier, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 145813fe54..92b15f4fc0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -97,6 +97,7 @@ fun MessagesView( onUserDataClicked: (UserId) -> Unit, onPreviewAttachments: (ImmutableList) -> Unit, onSendLocationClicked: () -> Unit, + onCreatePollClicked: () -> Unit, modifier: Modifier = Modifier, ) { LogCompositions(tag = "MessagesScreen", msg = "Root") @@ -175,6 +176,7 @@ fun MessagesView( onReactionLongClicked = ::onEmojiReactionLongClicked, onMoreReactionsClicked = ::onMoreReactionsClicked, onSendLocationClicked = onSendLocationClicked, + onCreatePollClicked = onCreatePollClicked, onSwipeToReply = { targetEvent -> state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent)) }, @@ -267,6 +269,7 @@ private fun MessagesViewContent( onMessageLongClicked: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSendLocationClicked: () -> Unit, + onCreatePollClicked: () -> Unit, modifier: Modifier = Modifier, onSwipeToReply: (TimelineItem.Event) -> Unit, ) { @@ -295,6 +298,7 @@ private fun MessagesViewContent( MessageComposerView( state = state.composerState, onSendLocationClicked = onSendLocationClicked, + onCreatePollClicked = onCreatePollClicked, modifier = Modifier .fillMaxWidth() .wrapContentHeight(Alignment.Bottom) @@ -401,5 +405,6 @@ private fun ContentToPreview(state: MessagesState) { onPreviewAttachments = {}, onUserDataClicked = {}, onSendLocationClicked = {}, + onCreatePollClicked = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index f71c750c22..e654365bcd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.canBeCopied @@ -96,6 +97,22 @@ class ActionListPresenter @Inject constructor( } } } + is TimelineItemPollContent -> { + buildList { + if (timelineItem.content.canBeCopied()) { + add(TimelineItemAction.Copy) + } + if (buildMeta.isDebuggable) { + add(TimelineItemAction.Developer) + } + if (!timelineItem.isMine) { + add(TimelineItemAction.ReportContent) + } + if (timelineItem.isMine || userCanRedact) { + add(TimelineItemAction.Redact) + } + } + } else -> buildList { if (timelineItem.isRemote) { // Can only reply or forward messages already uploaded to the server diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt index b554ef98f4..43805bb5c0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt @@ -24,6 +24,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ListItem import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AttachFile +import androidx.compose.material.icons.filled.BarChart import androidx.compose.material.icons.filled.Collections import androidx.compose.material.icons.filled.LocationOn import androidx.compose.material.icons.filled.PhotoCamera @@ -52,6 +53,7 @@ import io.element.android.libraries.designsystem.theme.components.Text internal fun AttachmentsBottomSheet( state: MessageComposerState, onSendLocationClicked: () -> Unit, + onCreatePollClicked: () -> Unit, modifier: Modifier = Modifier, ) { val localView = LocalView.current @@ -85,6 +87,7 @@ internal fun AttachmentsBottomSheet( AttachmentSourcePickerMenu( state = state, onSendLocationClicked = onSendLocationClicked, + onCreatePollClicked = onCreatePollClicked, ) } } @@ -95,6 +98,7 @@ internal fun AttachmentsBottomSheet( internal fun AttachmentSourcePickerMenu( state: MessageComposerState, onSendLocationClicked: () -> Unit, + onCreatePollClicked: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -131,6 +135,16 @@ internal fun AttachmentSourcePickerMenu( text = { Text(stringResource(R.string.screen_room_attachment_source_location)) }, ) } + if (state.canCreatePoll) { + ListItem( + modifier = Modifier.clickable { + state.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) + onCreatePollClicked() + }, + icon = { Icon(Icons.Default.BarChart, null) }, + text = { Text(stringResource(R.string.screen_room_attachment_source_poll)) }, + ) + } } } @@ -142,5 +156,6 @@ internal fun AttachmentSourcePickerMenuPreview() = ElementPreview { canShareLocation = true, ), onSendLocationClicked = {}, + onCreatePollClicked = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt index a39fe45ea8..d99eb3c158 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt @@ -35,6 +35,7 @@ sealed interface MessageComposerEvents { data object PhotoFromCamera : PickAttachmentSource data object VideoFromCamera : PickAttachmentSource data object Location : PickAttachmentSource + data object Poll : PickAttachmentSource } data object CancelSendAttachment : MessageComposerEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 5477b10c63..cc735dc008 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -83,6 +83,11 @@ class MessageComposerPresenter @Inject constructor( canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing) } + val canCreatePoll = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + canCreatePoll.value = featureFlagService.isFeatureEnabled(FeatureFlags.Polls) + } + val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType -> handlePickedMedia(attachmentsState, uri, mimeType) } @@ -179,6 +184,10 @@ class MessageComposerPresenter @Inject constructor( showAttachmentSourcePicker = false // Navigation to the location picker screen is done at the view layer } + MessageComposerEvents.PickAttachmentSource.Poll -> { + showAttachmentSourcePicker = false + // Navigation to the create poll screen is done at the view layer + } is MessageComposerEvents.CancelSendAttachment -> { ongoingSendAttachmentJob.value?.let { it.cancel() @@ -195,6 +204,7 @@ class MessageComposerPresenter @Inject constructor( mode = messageComposerContext.composerMode, showAttachmentSourcePicker = showAttachmentSourcePicker, canShareLocation = canShareLocation.value, + canCreatePoll = canCreatePoll.value, attachmentsState = attachmentsState.value, eventSink = ::handleEvents ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index 1b5bf3fe82..dbbc62ca47 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -29,6 +29,7 @@ data class MessageComposerState( val mode: MessageComposerMode, val showAttachmentSourcePicker: Boolean, val canShareLocation: Boolean, + val canCreatePoll: Boolean, val attachmentsState: AttachmentsState, val eventSink: (MessageComposerEvents) -> Unit ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index a1fbb7ffa0..2217b574b4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -33,6 +33,7 @@ fun aMessageComposerState( mode: MessageComposerMode = MessageComposerMode.Normal(content = ""), showAttachmentSourcePicker: Boolean = false, canShareLocation: Boolean = true, + canCreatePoll: Boolean = true, attachmentsState: AttachmentsState = AttachmentsState.None, ) = MessageComposerState( text = text, @@ -41,6 +42,7 @@ fun aMessageComposerState( mode = mode, showAttachmentSourcePicker = showAttachmentSourcePicker, canShareLocation = canShareLocation, + canCreatePoll = canCreatePoll, attachmentsState = attachmentsState, eventSink = {}, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 844635cef7..fd5421988b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.textcomposer.TextComposer fun MessageComposerView( state: MessageComposerState, onSendLocationClicked: () -> Unit, + onCreatePollClicked: () -> Unit, modifier: Modifier = Modifier, ) { fun onFullscreenToggle() { @@ -59,6 +60,7 @@ fun MessageComposerView( AttachmentsBottomSheet( state = state, onSendLocationClicked = onSendLocationClicked, + onCreatePollClicked = onCreatePollClicked, ) TextComposer( @@ -88,6 +90,7 @@ internal fun MessageComposerViewDarkPreview(@PreviewParameter(MessageComposerSta private fun ContentToPreview(state: MessageComposerState) { MessageComposerView( state = state, - onSendLocationClicked = {} + onSendLocationClicked = {}, + onCreatePollClicked = {}, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index afef9f6730..9ec1d087d0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -26,10 +26,14 @@ import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent +import io.element.android.features.poll.api.PollAnswerItem +import io.element.android.libraries.matrix.api.poll.PollAnswer +import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.core.aBuildMeta import kotlinx.collections.immutable.persistentListOf @@ -369,6 +373,57 @@ class ActionListPresenterTest { assertThat(successState.displayEmojiReactions).isFalse() } } + + @Test + fun `present - compute for poll message`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = false) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + isMine = true, + content = TimelineItemPollContent( + question = "Some question?", + answerItems = listOf( + PollAnswerItem( + answer = PollAnswer("id_1", "Answer1"), + isSelected = false, + isEnabled = false, + isWinner = false, + isDisclosed = false, + votesCount = 0, + percentage = 0.0f, + ), + PollAnswerItem( + answer = PollAnswer("id_2", "Answer2"), + isSelected = false, + isEnabled = false, + isWinner = false, + isDisclosed = false, + votesCount = 0, + percentage = 0.0f, + ), + ), + votes = mapOf(), + pollKind = PollKind.Disclosed, + isEnded = false, + ) + ) + + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent, false)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + messageEvent, + persistentListOf( + TimelineItemAction.Redact, + ) + ) + ) + assertThat(successState.displayEmojiReactions).isTrue() + } + } } private fun anActionListPresenter(isBuildDebuggable: Boolean) = ActionListPresenter(buildMeta = aBuildMeta(isDebuggable = isBuildDebuggable)) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollEntryPoint.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt similarity index 65% rename from features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollEntryPoint.kt rename to features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt index d8f2aed846..abbb041374 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollEntryPoint.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt @@ -14,24 +14,12 @@ * limitations under the License. */ -package io.element.android.features.poll.api +package io.element.android.features.poll.api.create import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint -interface PollEntryPoint : FeatureEntryPoint { - - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } - - interface Callback : Plugin { - // Add your callbacks - } +interface CreatePollEntryPoint : FeatureEntryPoint { + fun createNode(parentNode: Node, buildContext: BuildContext): Node } - diff --git a/features/poll/impl/build.gradle.kts b/features/poll/impl/build.gradle.kts index 626a7d0f2c..4e8d36966a 100644 --- a/features/poll/impl/build.gradle.kts +++ b/features/poll/impl/build.gradle.kts @@ -14,8 +14,6 @@ * limitations under the License. */ -// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed -@Suppress("DSL_SCOPE_VIOLATION") plugins { id("io.element.android-compose-library") alias(libs.plugins.anvil) @@ -40,6 +38,8 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) + implementation(projects.services.analytics.api) + implementation(projects.libraries.uiStrings) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -47,6 +47,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.services.analytics.test) ksp(libs.showkase.processor) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt deleted file mode 100644 index f983025236..0000000000 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollFlowNode.kt +++ /dev/null @@ -1,70 +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.poll.impl - -import android.os.Parcelable -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.bumble.appyx.core.composable.Children -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.navmodel.backstack.BackStack -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.architecture.BackstackNode -import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler -import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.di.SessionScope -import kotlinx.parcelize.Parcelize - -@ContributesNode(SessionScope::class) -class PollFlowNode @AssistedInject constructor( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, -) : BackstackNode( - backstack = BackStack( - initialElement = NavTarget.Root, - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins, -) { - - sealed interface NavTarget : Parcelable { - @Parcelize - data object Root : NavTarget - } - - override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { - return when (navTarget) { - NavTarget.Root -> { - createNode(buildContext) - } - } - } - - @Composable - override fun View(modifier: Modifier) { - Children( - navModel = backstack, - modifier = modifier, - transitionHandler = rememberDefaultTransitionHandler(), - ) - } -} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt new file mode 100644 index 0000000000..1251e07696 --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt @@ -0,0 +1,31 @@ +/* + * 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.poll.impl.create + +import io.element.android.libraries.matrix.api.poll.PollKind + +sealed interface CreatePollEvents { + data object Create : CreatePollEvents + data class SetQuestion(val question: String) : CreatePollEvents + data class SetAnswer(val index: Int, val text: String) : CreatePollEvents + data object AddAnswer : CreatePollEvents + data class RemoveAnswer(val index: Int) : CreatePollEvents + data class SetPollKind(val pollKind: PollKind) : CreatePollEvents + data object NavBack : CreatePollEvents + data object ConfirmNavBack : CreatePollEvents + data object HideConfirmation : CreatePollEvents +} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt new file mode 100644 index 0000000000..387f57a597 --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt @@ -0,0 +1,56 @@ +/* + * 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.poll.impl.create + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.lifecycle.subscribe +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.RoomScope + +@ContributesNode(RoomScope::class) +class CreatePollNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: CreatePollPresenter.Factory, + // analyticsService: AnalyticsService, // TODO Polls: add analytics +) : Node(buildContext, plugins = plugins) { + + private val presenter = presenterFactory.create(backNavigator = ::navigateUp) + + init { + lifecycle.subscribe( + onResume = { + // TODO Polls: add analytics + // analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.PollView)) + } + ) + } + + @Composable + override fun View(modifier: Modifier) { + CreatePollView( + state = presenter.present(), + modifier = modifier, + ) + } +} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt new file mode 100644 index 0000000000..b44afae9d1 --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt @@ -0,0 +1,162 @@ +/* + * 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.poll.impl.create + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +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.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.poll.PollKind +import io.element.android.libraries.matrix.api.room.MatrixRoom +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch +import timber.log.Timber + +private const val MIN_ANSWERS = 2 +private const val MAX_ANSWERS = 20 +private const val MAX_ANSWER_LENGTH = 240 +private const val MAX_SELECTIONS = 1 + +class CreatePollPresenter @AssistedInject constructor( + private val room: MatrixRoom, + // private val analyticsService: AnalyticsService, // TODO Polls: add analytics + @Assisted private val navigateUp: () -> Unit, + // private val messageComposerContext: MessageComposerContext, // TODO Polls: add analytics +) : Presenter { + + @AssistedFactory + interface Factory { + fun create(backNavigator: () -> Unit): CreatePollPresenter + } + + @Composable + override fun present(): CreatePollState { + + var question: String by rememberSaveable { mutableStateOf("") } + var answers: List by rememberSaveable() { mutableStateOf(listOf("", "")) } + var pollKind: PollKind by rememberSaveable(saver = pollKindSaver) { mutableStateOf(PollKind.Disclosed) } + var showConfirmation: Boolean by rememberSaveable { mutableStateOf(false) } + + val canCreate: Boolean by remember { derivedStateOf { canCreate(question, answers) } } + val canAddAnswer: Boolean by remember { derivedStateOf { canAddAnswer(answers) } } + val immutableAnswers: ImmutableList by remember { derivedStateOf { answers.toAnswers() } } + + val scope = rememberCoroutineScope() + + fun handleEvents(event: CreatePollEvents) { + when (event) { + is CreatePollEvents.Create -> scope.launch { + if (canCreate) { + room.createPoll( + question = question, + answers = answers, + maxSelections = MAX_SELECTIONS, + pollKind = pollKind, + ) + // analyticsService.capture(PollCreate()) // TODO Polls: add analytics + navigateUp() + } else { + Timber.d("Cannot create poll") + } + } + is CreatePollEvents.AddAnswer -> { + answers = answers + "" + } + is CreatePollEvents.RemoveAnswer -> { + answers = answers.filterIndexed { index, _ -> index != event.index } + } + is CreatePollEvents.SetAnswer -> { + answers = answers.toMutableList().apply { + this[event.index] = event.text.take(MAX_ANSWER_LENGTH) + } + } + is CreatePollEvents.SetPollKind -> { + pollKind = event.pollKind + } + is CreatePollEvents.SetQuestion -> { + question = event.question + } + is CreatePollEvents.NavBack -> { + navigateUp() + } + CreatePollEvents.ConfirmNavBack -> { + val shouldConfirm = question.isNotBlank() || answers.any { it.isNotBlank() } + if (shouldConfirm) { + showConfirmation = true + } else { + navigateUp() + } + } + is CreatePollEvents.HideConfirmation -> showConfirmation = false + } + } + + return CreatePollState( + canCreate = canCreate, + canAddAnswer = canAddAnswer, + question = question, + answers = immutableAnswers, + pollKind = pollKind, + showConfirmation = showConfirmation, + eventSink = ::handleEvents, + ) + } +} + +private fun canCreate( + question: String, + answers: List +) = question.isNotBlank() && answers.size >= MIN_ANSWERS && answers.all { it.isNotBlank() } + +private fun canAddAnswer(answers: List) = answers.size < MAX_ANSWERS + +private fun List.toAnswers(): ImmutableList { + return map { answer -> + Answer( + text = answer, + canDelete = this.size > MIN_ANSWERS, + ) + }.toImmutableList() +} + +private val pollKindSaver: Saver, Boolean> = Saver( + save = { + when (it.value) { + PollKind.Disclosed -> false + PollKind.Undisclosed -> true + } + }, + restore = { + mutableStateOf( + when(it) { + true -> PollKind.Undisclosed + else -> PollKind.Disclosed + } + ) + } +) diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt new file mode 100644 index 0000000000..eccaea45fc --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.poll.impl.create + +import io.element.android.libraries.matrix.api.poll.PollKind +import kotlinx.collections.immutable.ImmutableList + +data class CreatePollState( + val canCreate: Boolean, + val canAddAnswer: Boolean, + val question: String, + val answers: ImmutableList, + val pollKind: PollKind, + val showConfirmation: Boolean, + val eventSink: (CreatePollEvents) -> Unit = {}, +) + +data class Answer( + val text: String, + val canDelete: Boolean, +) diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt new file mode 100644 index 0000000000..29aa1288ad --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt @@ -0,0 +1,124 @@ +/* + * 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.poll.impl.create + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.poll.PollKind +import kotlinx.collections.immutable.persistentListOf + +class CreatePollStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + CreatePollState( + canCreate = false, + canAddAnswer = true, + question = "", + answers = persistentListOf( + Answer("", false), + Answer("", false) + ), + pollKind = PollKind.Disclosed, + showConfirmation = false, + ), + CreatePollState( + canCreate = true, + canAddAnswer = true, + question = "What type of food should we have?", + answers = persistentListOf( + Answer("Italian \uD83C\uDDEE\uD83C\uDDF9", false), + Answer("Chinese \uD83C\uDDE8\uD83C\uDDF3", false), + ), + showConfirmation = false, + pollKind = PollKind.Undisclosed, + ), + CreatePollState( + canCreate = true, + canAddAnswer = true, + question = "What type of food should we have?", + answers = persistentListOf( + Answer("Italian \uD83C\uDDEE\uD83C\uDDF9", false), + Answer("Chinese \uD83C\uDDE8\uD83C\uDDF3", false), + ), + showConfirmation = true, + pollKind = PollKind.Undisclosed, + ), + CreatePollState( + canCreate = true, + canAddAnswer = true, + question = "What type of food should we have?", + answers = persistentListOf( + Answer("Italian \uD83C\uDDEE\uD83C\uDDF9", true), + Answer("Chinese \uD83C\uDDE8\uD83C\uDDF3", true), + Answer("Brazilian \uD83C\uDDE7\uD83C\uDDF7", true), + Answer("French \uD83C\uDDEB\uD83C\uDDF7", true), + ), + showConfirmation = false, + pollKind = PollKind.Undisclosed, + ), + CreatePollState( + canCreate = true, + canAddAnswer = false, + question = "Should there be more than 20 answers?", + answers = persistentListOf( + Answer("1", true), + Answer("2", true), + Answer("3", true), + Answer("4", true), + Answer("5", true), + Answer("6", true), + Answer("7", true), + Answer("8", true), + Answer("9", true), + Answer("10", true), + Answer("11", true), + Answer("12", true), + Answer("13", true), + Answer("14", true), + Answer("15", true), + Answer("16", true), + Answer("17", true), + Answer("18", true), + Answer("19", true), + Answer("20", true), + ), + showConfirmation = false, + pollKind = PollKind.Undisclosed, + ), + CreatePollState( + canCreate = true, + canAddAnswer = true, + question = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + + " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor" + + " in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt" + + " in culpa qui officia deserunt mollit anim id est laborum.", + answers = persistentListOf( + Answer( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + + " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis a.", + false + ), + Answer( + "Laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore" + + " eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mol.", + false + ), + ), + showConfirmation = false, + pollKind = PollKind.Undisclosed, + ) + ) +} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt new file mode 100644 index 0000000000..3e3ec4eb16 --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt @@ -0,0 +1,187 @@ +/* + * 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.poll.impl.create + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +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.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.poll.impl.R +import io.element.android.libraries.designsystem.VectorIcons +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider +import io.element.android.libraries.designsystem.theme.components.Icon +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.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.OutlinedTextField +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.poll.PollKind +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CreatePollView( + state: CreatePollState, + modifier: Modifier = Modifier, +) { + val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) } + BackHandler(onBack = navBack) + if (state.showConfirmation) ConfirmationDialog( + content = stringResource(id = R.string.screen_create_poll_confirmation), + onSubmitClicked = { state.eventSink(CreatePollEvents.NavBack) }, + onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } + ) + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(id = R.string.screen_create_poll_title), + style = ElementTheme.typography.aliasScreenTitle, + ) + }, + navigationIcon = { + BackButton(onClick = navBack) + }, + actions = { + TextButton( + text = stringResource(id = CommonStrings.action_create), + onClick = { state.eventSink(CreatePollEvents.Create) }, + enabled = state.canCreate, + ) + } + ) + }, + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .consumeWindowInsets(paddingValues) + .imePadding() + .fillMaxSize(), + ) { + item { + Text( + text = stringResource(id = R.string.screen_create_poll_question_desc), + modifier = Modifier.padding(start = 32.dp), + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + item { + ListItem( + headlineContent = { + OutlinedTextField( + value = state.question, + onValueChange = { + state.eventSink(CreatePollEvents.SetQuestion(it)) + }, + modifier = Modifier.fillMaxWidth(), + placeholder = { + Text(text = stringResource(id = R.string.screen_create_poll_question_hint)) + }, + ) + } + ) + } + itemsIndexed(state.answers) { index, answer -> + ListItem( + headlineContent = { + OutlinedTextField( + value = answer.text, + onValueChange = { + state.eventSink(CreatePollEvents.SetAnswer(index, it)) + }, + modifier = Modifier.fillMaxWidth(), + placeholder = { + Text(text = stringResource(id = R.string.screen_create_poll_answer_hint, index + 1)) + }, + ) + }, + trailingContent = ListItemContent.Custom { + Icon( + resourceId = VectorIcons.Delete, + contentDescription = null, + modifier = Modifier.clickable(answer.canDelete) { + state.eventSink(CreatePollEvents.RemoveAnswer(index)) + }, + ) + }, + style = if (answer.canDelete) ListItemStyle.Destructive else ListItemStyle.Default, + ) + } + if (state.canAddAnswer) { + item { + ListItem( + headlineContent = { Text(text = stringResource(id = R.string.screen_create_poll_add_option_btn)) }, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector(Icons.Default.Add), + ), + style = ListItemStyle.Primary, + onClick = { state.eventSink(CreatePollEvents.AddAnswer) }, + ) + } + } + item { + HorizontalDivider() + } + item { + ListItem( + headlineContent = { Text(text = stringResource(id = R.string.screen_create_poll_anonymous_headline)) }, + supportingContent = { Text(text = stringResource(id = R.string.screen_create_poll_anonymous_desc)) }, + trailingContent = ListItemContent.Switch( + checked = state.pollKind == PollKind.Undisclosed, + onChange = { state.eventSink(CreatePollEvents.SetPollKind(if (it) PollKind.Undisclosed else PollKind.Disclosed)) }, + ), + ) + } + } + } +} + +@DayNightPreviews +@Composable +internal fun CreatePollViewPreview( + @PreviewParameter(CreatePollStateProvider::class) state: CreatePollState +) = ElementPreview { + CreatePollView( + state = state, + ) +} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/DefaultPollEntryPoint.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt similarity index 55% rename from features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/DefaultPollEntryPoint.kt rename to features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt index 052c1bcd5f..1ce64deb88 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/DefaultPollEntryPoint.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt @@ -14,33 +14,19 @@ * limitations under the License. */ -package io.element.android.features.poll.impl +package io.element.android.features.poll.impl.create import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.poll.api.PollEntryPoint +import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import javax.inject.Inject @ContributesBinding(AppScope::class) -class DefaultPollEntryPoint @Inject constructor() : PollEntryPoint { - - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): PollEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : PollEntryPoint.NodeBuilder { - - override fun callback(callback: PollEntryPoint.Callback): PollEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } +class DefaultCreatePollEntryPoint @Inject constructor() : CreatePollEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) } } diff --git a/features/poll/impl/src/main/res/values/localazy.xml b/features/poll/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..846b247108 --- /dev/null +++ b/features/poll/impl/src/main/res/values/localazy.xml @@ -0,0 +1,11 @@ + + + "Add option" + "Show results only after poll ends" + "Anonymous Poll" + "Option %1$d" + "Are you sure you would like to go back?" + "Question or topic" + "What is the poll about?" + "Create Poll" + diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt new file mode 100644 index 0000000000..9dacc6062d --- /dev/null +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -0,0 +1,239 @@ +/* + * 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.poll.impl.create + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth +import io.element.android.libraries.matrix.api.poll.PollKind +import io.element.android.libraries.matrix.test.room.CreatePollInvocation +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class CreatePollPresenterTest { + + private var navUpInvocationsCount = 0 + private val fakeMatrixRoom = FakeMatrixRoom() + // private val fakeAnalyticsService = FakeAnalyticsService() // TODO Polls: add analytics + + private val presenter = CreatePollPresenter( + room = fakeMatrixRoom, + // analyticsService = fakeAnalyticsService, // TODO Polls: add analytics + navigateUp = { navUpInvocationsCount++ }, + ) + + @Test + fun `default state has proper default values`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().let { + Truth.assertThat(it.canCreate).isEqualTo(false) + Truth.assertThat(it.canAddAnswer).isEqualTo(true) + Truth.assertThat(it.question).isEqualTo("") + Truth.assertThat(it.answers).isEqualTo(listOf(Answer("", false), Answer("", false))) + Truth.assertThat(it.pollKind).isEqualTo(PollKind.Disclosed) + Truth.assertThat(it.showConfirmation).isEqualTo(false) + } + } + } + + @Test + fun `non blank question and 2 answers are required to create a poll`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + Truth.assertThat(initial.canCreate).isEqualTo(false) + + initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + val questionSet = awaitItem() + Truth.assertThat(questionSet.canCreate).isEqualTo(false) + + questionSet.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) + val answer1Set = awaitItem() + Truth.assertThat(answer1Set.canCreate).isEqualTo(false) + + answer1Set.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) + val answer2Set = awaitItem() + Truth.assertThat(answer2Set.canCreate).isEqualTo(true) + } + } + + @Test + fun `create polls sends a poll start event`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + initial.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) + initial.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) + skipItems(3) + initial.eventSink(CreatePollEvents.Create) + delay(1) // Wait for the coroutine to finish + Truth.assertThat(fakeMatrixRoom.createPollInvocations.size).isEqualTo(1) + Truth.assertThat(fakeMatrixRoom.createPollInvocations.last()).isEqualTo( + CreatePollInvocation( + question = "A question?", + answers = listOf("Answer 1", "Answer 2"), + maxSelections = 1, + pollKind = PollKind.Disclosed + ) + ) + } + } + + @Test + fun `add answer button adds an empty answer and removing it removes it`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + Truth.assertThat(initial.answers.size).isEqualTo(2) + + initial.eventSink(CreatePollEvents.AddAnswer) + val answerAdded = awaitItem() + Truth.assertThat(answerAdded.answers.size).isEqualTo(3) + Truth.assertThat(answerAdded.answers[2].text).isEqualTo("") + + initial.eventSink(CreatePollEvents.RemoveAnswer(2)) + val answerRemoved = awaitItem() + Truth.assertThat(answerRemoved.answers.size).isEqualTo(2) + } + } + + @Test + fun `set question sets the question`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + val questionSet = awaitItem() + Truth.assertThat(questionSet.question).isEqualTo("A question?") + } + } + + @Test + fun `set poll answer sets the given poll answer`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + initial.eventSink(CreatePollEvents.SetAnswer(0, "This is answer 1")) + val answerSet = awaitItem() + Truth.assertThat(answerSet.answers.first().text).isEqualTo("This is answer 1") + } + } + + @Test + fun `set poll kind sets the poll kind`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + initial.eventSink(CreatePollEvents.SetPollKind(PollKind.Undisclosed)) + val kindSet = awaitItem() + Truth.assertThat(kindSet.pollKind).isEqualTo(PollKind.Undisclosed) + } + } + + @Test + fun `can add options when between 2 and 20 and then no more`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + Truth.assertThat(initial.canAddAnswer).isEqualTo(true) + repeat(17) { + initial.eventSink(CreatePollEvents.AddAnswer) + Truth.assertThat(awaitItem().canAddAnswer).isEqualTo(true) + } + initial.eventSink(CreatePollEvents.AddAnswer) + Truth.assertThat(awaitItem().canAddAnswer).isEqualTo(false) + } + } + + @Test + fun `can delete option if there are more than 2`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + Truth.assertThat(initial.answers.all { it.canDelete }).isEqualTo(false) + initial.eventSink(CreatePollEvents.AddAnswer) + Truth.assertThat(awaitItem().answers.all { it.canDelete }).isEqualTo(true) + } + } + + @Test + fun `option with more than 240 char is truncated`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + initial.eventSink(CreatePollEvents.SetAnswer(0, "A".repeat(241))) + Truth.assertThat(awaitItem().answers.first().text.length).isEqualTo(240) + } + } + + @Test + fun `navBack event calls navBack lambda`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + Truth.assertThat(navUpInvocationsCount).isEqualTo(0) + initial.eventSink(CreatePollEvents.NavBack) + Truth.assertThat(navUpInvocationsCount).isEqualTo(1) + } + } + + @Test + fun `confirm nav back with blank fields calls nav back lambda`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + Truth.assertThat(navUpInvocationsCount).isEqualTo(0) + Truth.assertThat(initial.showConfirmation).isEqualTo(false) + initial.eventSink(CreatePollEvents.ConfirmNavBack) + Truth.assertThat(navUpInvocationsCount).isEqualTo(1) + } + } + + @Test + fun `confirm nav back with non blank fields shows confirmation dialog and sending hide hids it`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initial = awaitItem() + initial.eventSink(CreatePollEvents.SetQuestion("Non blank")) + Truth.assertThat(navUpInvocationsCount).isEqualTo(0) + Truth.assertThat(awaitItem().showConfirmation).isEqualTo(false) + initial.eventSink(CreatePollEvents.ConfirmNavBack) + Truth.assertThat(navUpInvocationsCount).isEqualTo(0) + Truth.assertThat(awaitItem().showConfirmation).isEqualTo(true) + initial.eventSink(CreatePollEvents.HideConfirmation) + Truth.assertThat(awaitItem().showConfirmation).isEqualTo(false) + } + } +} diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 176bacb2c4..e1f4b740b0 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -29,7 +29,7 @@ enum class FeatureFlags( Polls( key = "feature.polls", title = "Polls", - description = "Render poll events in the timeline", + description = "Create poll and render poll events in the timeline", defaultValue = false, ) } diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png index 533e7086c5..58ebf07374 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-D-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04978db52b7be7d8aee3ac4aad1ec89ed4f8d9436fbd1829ec60c485e3fe8639 -size 21533 +oid sha256:79aeef6875265e119c3b4b97cea4d36ba3354ae52c4b94b69bbc09461b7bc319 +size 22259 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png index 7d03ec4b37..6e91b56f10 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_null_AttachmentSourcePickerMenu-N-1_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52ce35020e0be63a86ad8b82f04b39e27b5960f7ae26a9ac5e1158884054608e -size 19859 +oid sha256:8dafa9a97ebc77f00fdb0432c7b94272f6ea1873c3475353be47ecde95e8b057 +size 20670 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f60c0c7a4d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b06ec4a259dfccec114689bff7d53089bb7fc64758af23372938fd83c422071 +size 35374 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c53d96b0e5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72bb304299954abac15f9487feab06649c0151c41cbcbcbf9c887417224d499b +size 39756 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cc49158545 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:155cee63d3e031c912786b95bcc8e28dc4d1b296f8f0e4c01bd96398bb2dd040 +size 40623 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9de6f34f78 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bb6afbfd69bf254a2d222deb72d80c7f0bb4fc44bc5010a7e34f2b82420a423 +size 47529 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..536ea963c9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2aca4289813d6dbce44fc19bc5e0f7f1dd9e670db4e31c5be93a9d0eac125fff +size 28696 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..29d5f43751 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-D-0_1_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2b68997a63739074bbb0622c2f96af9ad2309e61b4ac246a929d3d5e0134939 +size 124111 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ce95adf2e9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f7fbf51ee1d86b1fc7cce949f88bfcb1c7ff7f700304e5a74242c4aa4965fcb +size 33455 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8e11aa2691 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c7ca3daff99c6086f114d15dbf0649a4be7ff99a5afdeb6d0e8effda383bfec +size 36968 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..127289d30b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b682b0025c98d9d37ab8dc67cbc8a637f672ae2bf9e4a26e48209188d612c0c +size 36455 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..79faef2ce6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0680506590290c4ab5ae299abc51e0806b9a1e06a647971eae7b6a2227b0aba9 +size 44631 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..43d4dbb763 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b954906d2f4f0bb97f4ae78e246caab98a4d01efe04379ccf4ad79e8ae62310 +size 27091 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4a8356b4a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.impl.create_null_CreatePollView-N-0_2_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:528e3f1f4fc9b8c236427882409bc718cd6565816ed137a47ba3dd2c69e103cc +size 108555 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 97acdf2ab5..4be78689de 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -120,6 +120,12 @@ "screen_welcome_.*", "screen_migration_.*" ] + }, + { + "name": ":features:poll:impl", + "includeRegex": [ + "screen_create_poll_.*" + ] } ] } From bb7553964761c455385ebfa545eff0131c863d94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 08:28:06 +0200 Subject: [PATCH 77/86] Update kotlin to 1.9.10 (#1123) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- features/login/impl/build.gradle.kts | 2 +- gradle/libs.versions.toml | 6 +++--- libraries/matrix/api/build.gradle.kts | 2 +- libraries/matrix/impl/build.gradle.kts | 2 +- libraries/push/impl/build.gradle.kts | 2 +- libraries/pushproviders/unifiedpush/build.gradle.kts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 556ee5ff00..b48ef70ce7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient buildscript { dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") classpath("com.google.gms:google-services:4.3.15") } } diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 91b8a2f543..ae13197f05 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -19,7 +19,7 @@ plugins { alias(libs.plugins.anvil) alias(libs.plugins.ksp) id("kotlin-parcelize") - kotlin("plugin.serialization") version "1.9.0" + kotlin("plugin.serialization") version "1.9.10" } android { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84bc4c1774..c8af411131 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,8 +4,8 @@ [versions] # Project android_gradle_plugin = "8.1.1" -kotlin = "1.9.0" -ksp = "1.9.0-1.0.13" +kotlin = "1.9.10" +ksp = "1.9.10-1.0.13" molecule = "1.2.0" # AndroidX @@ -23,7 +23,7 @@ browser = "1.6.0" # Compose compose_bom = "2023.08.00" -composecompiler = "1.5.1" +composecompiler = "1.5.3" # Coroutines coroutines = "1.7.3" diff --git a/libraries/matrix/api/build.gradle.kts b/libraries/matrix/api/build.gradle.kts index a1d508885a..1ffe07eb6f 100644 --- a/libraries/matrix/api/build.gradle.kts +++ b/libraries/matrix/api/build.gradle.kts @@ -18,7 +18,7 @@ plugins { id("io.element.android-library") id("kotlin-parcelize") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.0" + kotlin("plugin.serialization") version "1.9.10" } android { diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index e29d4b73db..b2a21f26df 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -17,7 +17,7 @@ plugins { id("io.element.android-library") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.0" + kotlin("plugin.serialization") version "1.9.10" } android { diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 30a686cad6..b961146e78 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -16,7 +16,7 @@ plugins { id("io.element.android-library") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.0" + kotlin("plugin.serialization") version "1.9.10" } android { diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index a6565c25f0..28501b2fad 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -16,7 +16,7 @@ plugins { id("io.element.android-library") alias(libs.plugins.anvil) - kotlin("plugin.serialization") version "1.9.0" + kotlin("plugin.serialization") version "1.9.10" } android { From 0b88dac719de1227ea866957cf20e85e80318dfd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 30 Aug 2023 08:53:52 +0200 Subject: [PATCH 78/86] Fix test compilation --- .../features/messages/actionlist/ActionListPresenterTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index 9ec1d087d0..b3c805d32d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -405,7 +405,6 @@ class ActionListPresenterTest { percentage = 0.0f, ), ), - votes = mapOf(), pollKind = PollKind.Disclosed, isEnded = false, ) From c3f49a24519744e71d9a62c0a12774a10c6828fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 30 Aug 2023 09:33:53 +0200 Subject: [PATCH 79/86] Split link text into a `ListSupportingText` component. This also requires some internal changes to `ListSupportingText`, `ClickableLinkText` and `TimelineTextView` to match the behaviour and design. --- .../preferences/AnalyticsPreferencesView.kt | 65 ++++++++----------- .../components/event/TimelineItemTextView.kt | 53 ++++++++------- .../components/ClickableLinkText.kt | 4 +- .../theme/components/ListSection.kt | 17 +++-- 4 files changed, 69 insertions(+), 70 deletions(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 9920b8a0b0..2b70798cbd 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -16,26 +16,23 @@ package io.element.android.features.analytics.api.preferences -import androidx.compose.foundation.text.ClickableText -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.libraries.designsystem.components.LINK_TAG import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListSupportingText import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings -private const val LINK_TAG = "link" - @Composable fun AnalyticsPreferencesView( state: AnalyticsPreferencesState, @@ -46,43 +43,37 @@ fun AnalyticsPreferencesView( state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) } - val firstPart = stringResource(id = CommonStrings.screen_analytics_settings_help_us_improve, state.applicationName) - val secondPart = buildAnnotatedStringWithStyledPart( + val linkText = buildAnnotatedStringWithStyledPart( CommonStrings.screen_analytics_settings_read_terms, CommonStrings.screen_analytics_settings_read_terms_content_link, tagAndLink = LINK_TAG to state.policyUrl, ) - val subtitle = buildAnnotatedString { - append(firstPart) - append("\n\n") - append(secondPart) + val supportingText = stringResource( + id = CommonStrings.screen_analytics_settings_help_us_improve, + state.applicationName + ) + + Column(modifier) { + + ListItem( + headlineContent = { + Text(stringResource(id = CommonStrings.screen_analytics_settings_share_data)) + }, + supportingContent = { + Text(supportingText) + }, + leadingContent = null, + trailingContent = ListItemContent.Switch( + checked = state.isEnabled, + ), + onClick = { + onEnabledChanged(!state.isEnabled) + } + ) + + ListSupportingText(annotatedString = linkText) } - ListItem( - headlineContent = { - Text(stringResource(id = CommonStrings.screen_analytics_settings_share_data)) - }, - supportingContent = { - ClickableText( - text = subtitle, - onClick = { - subtitle - .getStringAnnotations(LINK_TAG, it, it) - .firstOrNull() - ?.let { stringAnnotation -> - onOpenAnalyticsPolicy(stringAnnotation.item) - } - }, - style = ElementTheme.typography.fontBodyMdRegular - .copy( - color = MaterialTheme.colorScheme.secondary, - ), - ) - }, - leadingContent = null, - trailingContent = ListItemContent.Switch(checked = state.isEnabled, onChange = ::onEnabledChanged), - modifier = modifier, - ) } @Preview diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index 7b04d60ade..c6b0218bba 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -21,7 +21,9 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -35,6 +37,7 @@ import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.text.toAnnotatedString +import io.element.android.libraries.theme.ElementTheme @Composable fun TimelineItemTextView( @@ -45,31 +48,33 @@ fun TimelineItemTextView( onTextClicked: () -> Unit = {}, onTextLongClicked: () -> Unit = {}, ) { - val htmlDocument = content.htmlDocument - if (htmlDocument != null) { - // For now we ignore the extra padding for html content, so add some spacing - // below the content (as previous behavior) - Column(modifier = modifier) { - HtmlDocument( - document = htmlDocument, - modifier = Modifier, - onTextClicked = onTextClicked, - onTextLongClicked = onTextLongClicked, - interactionSource = interactionSource - ) - Spacer(Modifier.height(16.dp)) - } - } else { - Box(modifier) { - val textWithPadding = remember(content.body) { - content.body + extraPadding.getStr(16.sp).toAnnotatedString() + CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textPrimary) { + val htmlDocument = content.htmlDocument + if (htmlDocument != null) { + // For now we ignore the extra padding for html content, so add some spacing + // below the content (as previous behavior) + Column(modifier = modifier) { + HtmlDocument( + document = htmlDocument, + modifier = Modifier, + onTextClicked = onTextClicked, + onTextLongClicked = onTextLongClicked, + interactionSource = interactionSource + ) + Spacer(Modifier.height(16.dp)) + } + } else { + Box(modifier) { + val textWithPadding = remember(content.body) { + content.body + extraPadding.getStr(16.sp).toAnnotatedString() + } + ClickableLinkText( + text = textWithPadding, + onClick = onTextClicked, + onLongClick = onTextLongClicked, + interactionSource = interactionSource + ) } - ClickableLinkText( - text = textWithPadding, - onClick = onTextClicked, - onLongClick = onTextLongClicked, - interactionSource = interactionSource - ) } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index 39838a218a..7240ae4693 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.text.InlineTextContent import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -79,7 +78,7 @@ fun ClickableLinkText( @Composable fun ClickableLinkText( annotatedString: AnnotatedString, - interactionSource: MutableInteractionSource, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, modifier: Modifier = Modifier, linkify: Boolean = true, linkAnnotationTag: String = LINK_TAG, @@ -136,7 +135,6 @@ fun ClickableLinkText( layoutResult.value = it }, inlineContent = inlineContent, - color = MaterialTheme.colorScheme.primary, ) } 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 ff58f1e2e0..cab1d493fc 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 @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle @@ -30,9 +29,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup @@ -103,17 +104,21 @@ fun ListSupportingText( * @param modifier The modifier to be applied to the text. * @param contentPadding The padding to apply to the text. Default is [ListSupportingTextDefaults.Padding.Default]. */ +@OptIn(ExperimentalTextApi::class) @Composable fun ListSupportingText( annotatedString: AnnotatedString, modifier: Modifier = Modifier, contentPadding: ListSupportingTextDefaults.Padding = ListSupportingTextDefaults.Padding.Default, ) { - Text( - text = annotatedString, - modifier = modifier.padding(contentPadding.paddingValues()), - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, + val style = ElementTheme.typography.fontBodySmRegular + .copy(color = ElementTheme.colors.textSecondary) + val paddedModifier = modifier.padding(contentPadding.paddingValues()) + ClickableLinkText( + annotatedString = annotatedString, + modifier = paddedModifier, + style = style, + linkify = false, ) } From 1c507862992b73733c1911163d41cfd7dc22b594 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 30 Aug 2023 08:12:45 +0000 Subject: [PATCH 80/86] Update screenshots --- ..._AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ull_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png index 28ae52b035..48e393db15 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f70fad1a0ff08c3bd259ee8abd745d07474fa1dd45cfd2b8c77fe3536fedbb29 -size 23799 +oid sha256:b365229cac3351e4ec44979b7a22fcae090a8995e4784d9114cfd0033a242510 +size 23147 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png index 5ce51a0c7a..5790bbb52e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.api.preferences_null_AnalyticsPreferencesViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07c00693ed47f8db94c0a9bcdd7350e32366813b3a4241dceebbd8aed78a5bbe -size 23880 +oid sha256:5fc43706603ce52fa3495fa4e6dfa9aa684540a9dec996a5f705427d34ffb55c +size 23274 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png index d12a018f64..98964b2b77 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewDark_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcde2d058771af909595c4f163925fadc0ddee8cb0c440cbc3268403961e617c -size 25774 +oid sha256:e22dc131e8f1f7461c050e871eaa408895529976ef7445aa6faf09852370df90 +size 25176 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png index e86823967d..48fc8ae23e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.analytics_null_AnalyticsSettingsViewLight_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cef0f004854681538224bd5513e0ec66afb87e54fc7bac4a4eb4c0194c9f594c -size 26965 +oid sha256:bc234611dd3df74196467129476c69a0212a4c665f2a94797c175cbd911c2083 +size 26339 From 1b87565870397d6cbc2ed98c92067a86d38c16c1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Aug 2023 10:21:27 +0200 Subject: [PATCH 81/86] Remove unused lambda parameter. The link is opened by the LocalUriHandler now. --- .../api/preferences/AnalyticsPreferencesView.kt | 2 -- .../impl/analytics/AnalyticsSettingsNode.kt | 11 ----------- .../impl/analytics/AnalyticsSettingsView.kt | 3 --- 3 files changed, 16 deletions(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 2b70798cbd..468babcc6f 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -37,7 +37,6 @@ import io.element.android.libraries.ui.strings.CommonStrings fun AnalyticsPreferencesView( state: AnalyticsPreferencesState, modifier: Modifier = Modifier, - onOpenAnalyticsPolicy: (url: String) -> Unit, ) { fun onEnabledChanged(isEnabled: Boolean) { state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) @@ -90,6 +89,5 @@ internal fun AnalyticsPreferencesViewDarkPreview(@PreviewParameter(AnalyticsPref private fun ContentToPreview(state: AnalyticsPreferencesState) { AnalyticsPreferencesView( state = state, - onOpenAnalyticsPolicy = {}, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt index 18bb99e8e1..adc917b7e6 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt @@ -16,19 +16,15 @@ package io.element.android.features.preferences.impl.analytics -import android.app.Activity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.theme.ElementTheme @ContributesNode(SessionScope::class) class AnalyticsSettingsNode @AssistedInject constructor( @@ -37,19 +33,12 @@ class AnalyticsSettingsNode @AssistedInject constructor( private val presenter: AnalyticsSettingsPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onOpenAnalyticsPolicy(activity: Activity, darkTheme: Boolean, url: String) { - activity.openUrlInChromeCustomTab(null, darkTheme, url) - } - @Composable override fun View(modifier: Modifier) { - val activity = LocalContext.current as Activity - val isDark = ElementTheme.colors.isLight.not() val state = presenter.present() AnalyticsSettingsView( state = state, onBackPressed = ::navigateUp, - onOpenAnalyticsPolicy = { onOpenAnalyticsPolicy(activity, darkTheme = isDark, it) }, modifier = modifier ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt index 83dd5554fd..3ee7365122 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt @@ -31,7 +31,6 @@ import io.element.android.libraries.ui.strings.CommonStrings fun AnalyticsSettingsView( state: AnalyticsSettingsState, onBackPressed: () -> Unit, - onOpenAnalyticsPolicy: (url: String) -> Unit, modifier: Modifier = Modifier, ) { PreferenceView( @@ -41,7 +40,6 @@ fun AnalyticsSettingsView( ) { AnalyticsPreferencesView( state = state.analyticsState, - onOpenAnalyticsPolicy = onOpenAnalyticsPolicy, ) } } @@ -61,6 +59,5 @@ private fun ContentToPreview(state: AnalyticsSettingsState) { AnalyticsSettingsView( state = state, onBackPressed = {}, - onOpenAnalyticsPolicy = {}, ) } From ed1c1b5048d2729cef61815e85cc8d02149d2515 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Aug 2023 10:24:57 +0200 Subject: [PATCH 82/86] Compact the code and reorder vals for code clarity --- .../api/preferences/AnalyticsPreferencesView.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 468babcc6f..b943199ba5 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -42,18 +42,16 @@ fun AnalyticsPreferencesView( state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) } + val supportingText = stringResource( + id = CommonStrings.screen_analytics_settings_help_us_improve, + state.applicationName + ) val linkText = buildAnnotatedStringWithStyledPart( CommonStrings.screen_analytics_settings_read_terms, CommonStrings.screen_analytics_settings_read_terms_content_link, tagAndLink = LINK_TAG to state.policyUrl, ) - val supportingText = stringResource( - id = CommonStrings.screen_analytics_settings_help_us_improve, - state.applicationName - ) - Column(modifier) { - ListItem( headlineContent = { Text(stringResource(id = CommonStrings.screen_analytics_settings_share_data)) @@ -69,10 +67,8 @@ fun AnalyticsPreferencesView( onEnabledChanged(!state.isEnabled) } ) - ListSupportingText(annotatedString = linkText) } - } @Preview From 706896a4de30c88e2bf4c2ad1e70e4453f59dcb4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Aug 2023 10:28:44 +0200 Subject: [PATCH 83/86] Reorder params. --- .../libraries/designsystem/components/ClickableLinkText.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index 7240ae4693..17e6eec258 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -78,8 +78,8 @@ fun ClickableLinkText( @Composable fun ClickableLinkText( annotatedString: AnnotatedString, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, modifier: Modifier = Modifier, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, linkify: Boolean = true, linkAnnotationTag: String = LINK_TAG, onClick: () -> Unit = {}, From 36f0bec184a6bd9985a30c3541dedc0c9d2b2ff6 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 30 Aug 2023 10:43:23 +0200 Subject: [PATCH 84/86] Make sure Snackbars are only displayed once (#1175) * Make sure Snackbars are only displayed once * Use a queue instead * Fix docs * Add tests for `SnackbarDispatcher`. --- changelog.d/928.bugfix | 1 + libraries/designsystem/build.gradle.kts | 6 ++ .../libraries/designsystem/utils/Snackbar.kt | 77 +++++++++++----- .../utils/SnackbarDispatcherTests.kt | 91 +++++++++++++++++++ 4 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 changelog.d/928.bugfix create mode 100644 libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcherTests.kt diff --git a/changelog.d/928.bugfix b/changelog.d/928.bugfix new file mode 100644 index 0000000000..98a4cd34e0 --- /dev/null +++ b/changelog.d/928.bugfix @@ -0,0 +1 @@ +Make sure Snackbars are only displayed once. diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts index 35b7a65cb4..f6eacf8746 100644 --- a/libraries/designsystem/build.gradle.kts +++ b/libraries/designsystem/build.gradle.kts @@ -43,5 +43,11 @@ android { ksp(libs.showkase.processor) kspTest(libs.showkase.processor) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Snackbar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Snackbar.kt index c689d7e547..9458bf748a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Snackbar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Snackbar.kt @@ -34,33 +34,40 @@ import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.components.button.ButtonVisuals import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.Snackbar +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock +import java.util.concurrent.atomic.AtomicBoolean /** * A global dispatcher of [SnackbarMessage] to be displayed in [Snackbar] via a [SnackbarHostState]. */ class SnackbarDispatcher { - private val mutex = Mutex() - - private val _snackbarMessage = MutableStateFlow(null) - val snackbarMessage: Flow = _snackbarMessage.asStateFlow() - - suspend fun post(message: SnackbarMessage) { - mutex.withLock { - _snackbarMessage.update { message } + private val queueMutex = Mutex() + private val snackBarMessageQueue = ArrayDeque() + val snackbarMessage: Flow = flow { + while (currentCoroutineContext().isActive) { + queueMutex.lock() + emit(snackBarMessageQueue.firstOrNull()) } } - suspend fun clear() { - mutex.withLock { - _snackbarMessage.update { null } + suspend fun post(message: SnackbarMessage) { + if (snackBarMessageQueue.isEmpty()) { + snackBarMessageQueue.add(message) + if (queueMutex.isLocked) queueMutex.unlock() + } else { + snackBarMessageQueue.add(message) + } + } + + fun clear() { + if (snackBarMessageQueue.isNotEmpty()) { + snackBarMessageQueue.removeFirstOrNull() + if (queueMutex.isLocked) queueMutex.unlock() } } } @@ -87,31 +94,51 @@ fun SnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) { } } +/** + * Helper method to display a [SnackbarMessage] in a [SnackbarHostState] handling cancellations. + */ @Composable fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostState { val snackbarHostState = remember { SnackbarHostState() } val snackbarMessageText = snackbarMessage?.let { stringResource(id = snackbarMessage.messageResId) - } + } ?: return snackbarHostState + val dispatcher = LocalSnackbarDispatcher.current - LaunchedEffect(snackbarMessage) { - if (snackbarMessageText == null) return@LaunchedEffect - launch { - snackbarHostState.showSnackbar( - message = snackbarMessageText, - duration = snackbarMessage.duration, - ) - if (isActive) { + LaunchedEffect(snackbarMessageText) { + // If the message wasn't already displayed, do it now, and mark it as displayed + // This will prevent the message from appearing in any other active SnackbarHosts + if (snackbarMessage.isDisplayed.getAndSet(true) == false) { + try { + snackbarHostState.showSnackbar( + message = snackbarMessageText, + duration = snackbarMessage.duration, + ) + // The snackbar item was displayed and dismissed, clear its message dispatcher.clear() + } catch (e: CancellationException) { + // The snackbar was being displayed when the coroutine was cancelled, + // so we need to clear its message + dispatcher.clear() + throw e } } } return snackbarHostState } +/** + * A message to be displayed in a [Snackbar]. + * @param messageResId The message to be displayed. + * @param duration The duration of the message. The default value is [SnackbarDuration.Short]. + * @param actionResId The action text to be displayed. The default value is `null`. + * @param isDisplayed Used to track if the current message is already displayed or not. + * @param action The action to be performed when the action is clicked. + */ data class SnackbarMessage( @StringRes val messageResId: Int, val duration: SnackbarDuration = SnackbarDuration.Short, @StringRes val actionResId: Int? = null, + val isDisplayed: AtomicBoolean = AtomicBoolean(false), val action: () -> Unit = {}, ) diff --git a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcherTests.kt b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcherTests.kt new file mode 100644 index 0000000000..3eb644d800 --- /dev/null +++ b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcherTests.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.utils + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class SnackbarDispatcherTests { + + @Test + fun `given an empty queue the flow emits a null item`() = runTest { + val snackbarDispatcher = SnackbarDispatcher() + snackbarDispatcher.snackbarMessage.test { + assertThat(awaitItem()).isNull() + } + } + + @Test + fun `given an empty queue calling clear does nothing`() = runTest { + val snackbarDispatcher = SnackbarDispatcher() + snackbarDispatcher.snackbarMessage.test { + assertThat(awaitItem()).isNull() + snackbarDispatcher.clear() + expectNoEvents() + } + } + + @Test + fun `given a non-empty queue the flow emits an item`() = runTest { + val snackbarDispatcher = SnackbarDispatcher() + snackbarDispatcher.snackbarMessage.test { + snackbarDispatcher.post(SnackbarMessage(0)) + val result = expectMostRecentItem() + assertThat(result).isNotNull() + } + } + + @Test + fun `given a call to clear, the current message is cleared`() = runTest { + val snackbarDispatcher = SnackbarDispatcher() + snackbarDispatcher.snackbarMessage.test { + snackbarDispatcher.post(SnackbarMessage(0)) + val item = expectMostRecentItem() + assertThat(item).isNotNull() + snackbarDispatcher.clear() + assertThat(awaitItem()).isNull() + } + } + + @Test + fun `given 2 message emissions, the next message is displayed only after a call to clear`() = runTest { + val snackbarDispatcher = SnackbarDispatcher() + snackbarDispatcher.snackbarMessage.test { + val messageA = SnackbarMessage(0) + val messageB = SnackbarMessage(1) + + // Send message A - it is the most recent item + snackbarDispatcher.post(messageA) + assertThat(expectMostRecentItem()).isEqualTo(messageA) + + // Send message B - message A is still the most recent item + snackbarDispatcher.post(messageB) + expectNoEvents() + + // Clear the last message - message B is now the most recent item + snackbarDispatcher.clear() + assertThat(expectMostRecentItem()).isEqualTo(messageB) + + // Clear again - the queue is empty + snackbarDispatcher.clear() + assertThat(awaitItem()).isNull() + } + } + +} From 4a88e3fab68424433abe4fff6d3d11812d469a3a Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 30 Aug 2023 12:49:58 +0200 Subject: [PATCH 85/86] Bug reporter crashes when 'send logs' is disabled. (#1184) * Bug reporter crashes when 'send logs' is disabled. * Make sure generated files are cleaned up when uploading the logs fails. --- changelog.d/1168.bugfix | 1 + .../rageshake/impl/reporter/DefaultBugReporter.kt | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 changelog.d/1168.bugfix diff --git a/changelog.d/1168.bugfix b/changelog.d/1168.bugfix new file mode 100644 index 0000000000..f7f959ac0a --- /dev/null +++ b/changelog.d/1168.bugfix @@ -0,0 +1 @@ +Bug reporter crashes when 'send logs' is disabled. diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index ad7f58a239..46ccc7fc56 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -268,12 +268,13 @@ class DefaultBugReporter @Inject constructor( } } - if (!uploadedSomeLogs) { - error("Couldn't upload any logs") - } - mBugReportFiles.addAll(gzippedFiles) + if (gzippedFiles.isNotEmpty() && !uploadedSomeLogs) { + serverError = "Couldn't upload any logs, please retry." + return@withContext + } + if (withScreenshot) { screenshotHolder.getFileUri() ?.toUri() From b8d6db2f147d4c37c02d96d4a3f9e24085ee8889 Mon Sep 17 00:00:00 2001 From: axel simon Date: Wed, 30 Aug 2023 13:39:05 +0100 Subject: [PATCH 86/86] Update README.md (#1187) Remove unnecessary year from copyright mention (it was already out of date). Cf. https://hynek.me/til/copyright-years/ Signed-off-by: axel simon --- README.md | 2 +- changelog.d/1187.misc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1187.misc diff --git a/README.md b/README.md index f24efcd828..2a0d352187 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ If after your research you still have a question, ask at [#element-x-android:mat ## Copyright & License -Copyright (c) 2022 New Vector Ltd +Copyright © New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the [LICENSE](LICENSE) file, or at: diff --git a/changelog.d/1187.misc b/changelog.d/1187.misc new file mode 100644 index 0000000000..301e3a6fc4 --- /dev/null +++ b/changelog.d/1187.misc @@ -0,0 +1 @@ +Remove unnecessary year in copyright mention.