From 4ac5ba37bad8f622bbc0e15be62e6f69b929a6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 13 Feb 2024 12:59:52 +0100 Subject: [PATCH 1/5] Improve typing notification animations --- .../typing/TypingNotificationPresenter.kt | 11 ++ .../impl/typing/TypingNotificationState.kt | 7 + .../typing/TypingNotificationStateProvider.kt | 6 + .../impl/typing/TypingNotificationView.kt | 123 ++++++++++++------ .../typing/TypingNotificationPresenterTest.kt | 33 ++++- .../impl/migration/MigrationScreenView.kt | 21 ++- 6 files changed, 148 insertions(+), 53 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt index 92cae4c4d9..96ed7bad77 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.core.UserId @@ -46,6 +47,7 @@ class TypingNotificationPresenter @Inject constructor( override fun present(): TypingNotificationState { val typingMembersState = remember { mutableStateOf(emptyList()) } val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true) + LaunchedEffect(renderTypingNotifications) { if (renderTypingNotifications) { observeRoomTypingMembers(typingMembersState) @@ -54,9 +56,18 @@ class TypingNotificationPresenter @Inject constructor( } } + // This will keep the space reserved for the typing notifications after the first one is displayed + var reserveSpace by remember { mutableStateOf(false) } + LaunchedEffect(renderTypingNotifications, typingMembersState.value) { + if (renderTypingNotifications && typingMembersState.value.isNotEmpty()) { + reserveSpace = true + } + } + return TypingNotificationState( renderTypingNotifications = renderTypingNotifications, typingMembers = typingMembersState.value.toImmutableList(), + reserveSpace = reserveSpace, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt index 380586f9c7..633a63f440 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt @@ -19,7 +19,14 @@ package io.element.android.features.messages.impl.typing import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList +/** + * State for the typing notification view. + */ data class TypingNotificationState( + /** Whether to render the typing notifications based on the user's preferences. */ val renderTypingNotifications: Boolean, + /** The room members currently typing. */ val typingMembers: ImmutableList, + /** Whether to reserve space for the typing notifications at the bottom of the timeline. */ + val reserveSpace: Boolean, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt index 92d7e357d2..b610cc45f9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt @@ -68,14 +68,20 @@ class TypingNotificationStateProvider : PreviewParameterProvider = emptyList(), + reserveSpace: Boolean = false, ) = TypingNotificationState( renderTypingNotifications = true, typingMembers = typingMembers.toImmutableList(), + reserveSpace = reserveSpace, ) internal fun aTypingRoomMember( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt index 47200132d8..045e7cc8a0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt @@ -16,10 +16,22 @@ package io.element.android.features.messages.impl.typing +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding 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.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -43,54 +55,83 @@ fun TypingNotificationView( state: TypingNotificationState, modifier: Modifier = Modifier, ) { - if (state.typingMembers.isEmpty() || !state.renderTypingNotifications) return - val typingNotificationText = computeTypingNotificationText(state.typingMembers) - Text( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 2.dp), - text = typingNotificationText, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, - ) + val displayNotifications = state.typingMembers.isNotEmpty() && state.renderTypingNotifications + + @Suppress("ModifierNaming") + @Composable fun TypingText(text: AnnotatedString, textModifier: Modifier = Modifier) { + Text( + modifier = textModifier, + text = text, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + + // Display the typing notification space when either a typing notification needs to be displayed or a previous one already was + AnimatedVisibility( + modifier = modifier.fillMaxWidth().padding(vertical = 2.dp), + visible = displayNotifications || state.reserveSpace, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + ) { + val typingNotificationText = computeTypingNotificationText(state.typingMembers) + Box(contentAlignment = Alignment.BottomStart) { + // Reserve the space for the typing notification by adding an invisible text + TypingText(text = typingNotificationText, textModifier = Modifier.alpha(0f)) + + // Display the actual notification + AnimatedVisibility( + visible = displayNotifications, + enter = fadeIn(), + exit = fadeOut(), + ) { + TypingText(text = typingNotificationText, textModifier = Modifier.padding(horizontal = 24.dp)) + } + } + } } @Composable private fun computeTypingNotificationText(typingMembers: ImmutableList): AnnotatedString { - val names = when (typingMembers.size) { - 0 -> "" // Cannot happen - 1 -> typingMembers[0].disambiguatedDisplayName - 2 -> stringResource( - id = R.string.screen_room_typing_two_members, - typingMembers[0].disambiguatedDisplayName, - typingMembers[1].disambiguatedDisplayName, - ) - else -> pluralStringResource( - id = R.plurals.screen_room_typing_many_members, - count = typingMembers.size - 2, - typingMembers[0].disambiguatedDisplayName, - typingMembers[1].disambiguatedDisplayName, - typingMembers.size - 2, + // Remember the last value to avoid empty typing messages while animating + var result by remember { mutableStateOf(AnnotatedString("")) } + if (typingMembers.isNotEmpty()) { + val names = when (typingMembers.size) { + 0 -> "" // Cannot happen + 1 -> typingMembers[0].disambiguatedDisplayName + 2 -> stringResource( + id = R.string.screen_room_typing_two_members, + typingMembers[0].disambiguatedDisplayName, + typingMembers[1].disambiguatedDisplayName, + ) + else -> pluralStringResource( + id = R.plurals.screen_room_typing_many_members, + count = typingMembers.size - 2, + typingMembers[0].disambiguatedDisplayName, + typingMembers[1].disambiguatedDisplayName, + typingMembers.size - 2, + ) + } + // Get the translated string with a fake pattern + val tmpString = pluralStringResource( + id = R.plurals.screen_room_typing_notification, + count = typingMembers.size, + "<>", ) - } - // Get the translated string with a fake pattern - val tmpString = pluralStringResource( - id = R.plurals.screen_room_typing_notification, - count = typingMembers.size, - "<>", - ) - // Split the string in 3 parts - val parts = tmpString.split("<>") - // And rebuild the string with the names - return buildAnnotatedString { - append(parts[0]) - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(names) + // Split the string in 3 parts + val parts = tmpString.split("<>") + // And rebuild the string with the names + result = buildAnnotatedString { + append(parts[0]) + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(names) + } + append(parts[1]) } - append(parts[1]) } + return result } @PreviewsDayNight diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 485c318e93..51b642ea68 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.typing import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow +import app.cash.turbine.Event import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.preferences.api.store.SessionPreferencesStore @@ -53,6 +54,7 @@ class TypingNotificationPresenterTest { val initialState = awaitItem() assertThat(initialState.renderTypingNotifications).isTrue() assertThat(initialState.typingMembers).isEmpty() + assertThat(initialState.reserveSpace).isFalse() } } @@ -85,7 +87,7 @@ class TypingNotificationPresenterTest { assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember) // Preferences changes again sessionPreferencesStore.setRenderTypingNotifications(false) - skipItems(1) + skipItems(2) val finalState = awaitItem() assertThat(finalState.renderTypingNotifications).isFalse() assertThat(finalState.typingMembers).isEmpty() @@ -108,6 +110,7 @@ class TypingNotificationPresenterTest { assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember) // User stops typing room.givenRoomTypingMembers(emptyList()) + skipItems(1) val finalState = awaitItem() assertThat(finalState.typingMembers).isEmpty() } @@ -140,6 +143,7 @@ class TypingNotificationPresenterTest { assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aKnownRoomMember) // User stops typing room.givenRoomTypingMembers(emptyList()) + skipItems(1) val finalState = awaitItem() assertThat(finalState.typingMembers).isEmpty() } @@ -166,11 +170,38 @@ class TypingNotificationPresenterTest { listOf(aKnownRoomMember).toImmutableList() ) ) + skipItems(1) val finalState = awaitItem() assertThat(finalState.typingMembers.first()).isEqualTo(aKnownRoomMember) } } + @Test + fun `present - reserveSpace becomes true once we get the first typing notification with room members`() = runTest { + val aDefaultRoomMember = createDefaultRoomMember(A_USER_ID_2) + val room = FakeMatrixRoom() + val presenter = createPresenter(matrixRoom = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.typingMembers).isEmpty() + room.givenRoomTypingMembers(listOf(A_USER_ID_2)) + skipItems(1) + val updatedTypingState = awaitItem() + assertThat(updatedTypingState.reserveSpace).isTrue() + // User stops typing + room.givenRoomTypingMembers(emptyList()) + // Is still true for all future events + val futureEvents = cancelAndConsumeRemainingEvents() + for (event in futureEvents) { + if (event is Event.Item) { + assertThat(event.value.reserveSpace).isTrue() + } + } + } + } + private fun createPresenter( matrixRoom: MatrixRoom = FakeMatrixRoom().apply { givenRoomInfo(aRoomInfo(id = roomId.value, name = "")) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/migration/MigrationScreenView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/migration/MigrationScreenView.kt index b7627f1ce0..67e1999fc9 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/migration/MigrationScreenView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/migration/MigrationScreenView.kt @@ -16,12 +16,11 @@ package io.element.android.features.roomlist.impl.migration -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource import io.element.android.features.roomlist.impl.R import io.element.android.libraries.designsystem.atomic.pages.SunsetPage @@ -33,14 +32,14 @@ fun MigrationScreenView( isMigrating: Boolean, modifier: Modifier = Modifier, ) { - val displayMigrationStatusFadeProgress by animateFloatAsState( - targetValue = if (isMigrating) 1f else 0f, - animationSpec = tween(durationMillis = 200), - label = "Migration view fade" - ) - if (displayMigrationStatusFadeProgress > 0f) { + AnimatedVisibility( + visible = isMigrating, + enter = fadeIn(), + exit = fadeOut(), + label = "Migration view fade", + ) { SunsetPage( - modifier = modifier.alpha(displayMigrationStatusFadeProgress), + modifier = modifier, isLoading = true, title = stringResource(id = R.string.screen_migration_title), subtitle = stringResource(id = R.string.screen_migration_message), From 05fe29dbb0699062600ed46731407e456e4b4f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 13 Feb 2024 13:34:51 +0100 Subject: [PATCH 2/5] Add border to screenshots so the reserved space is visible Also add screenshot for typing notifications in the timeline --- .../messages/impl/MessagesStateProvider.kt | 14 +++++++++++++- .../messages/impl/typing/TypingNotificationView.kt | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index f1af9ad225..8b54c5b6f4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -35,7 +35,9 @@ import io.element.android.features.messages.impl.timeline.components.retrysendme import io.element.android.features.messages.impl.timeline.components.retrysendmenu.aRetrySendMenuState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent +import io.element.android.features.messages.impl.typing.TypingNotificationState import io.element.android.features.messages.impl.typing.aTypingNotificationState +import io.element.android.features.messages.impl.typing.aTypingRoomMember import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState @@ -88,6 +90,15 @@ open class MessagesStateProvider : PreviewParameterProvider { aMessagesState( callState = RoomCallState.DISABLED, ), + aMessagesState( + typingNotificationState = aTypingNotificationState( + typingMembers = listOf(aTypingRoomMember()), + reserveSpace = true + ), + ), + aMessagesState( + typingNotificationState = aTypingNotificationState(reserveSpace = true), + ) ) } @@ -104,6 +115,7 @@ fun aMessagesState( mode = MessageComposerMode.Normal, ), voiceMessageComposerState: VoiceMessageComposerState = aVoiceMessageComposerState(), + typingNotificationState: TypingNotificationState = aTypingNotificationState(), timelineState: TimelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), ), @@ -127,7 +139,7 @@ fun aMessagesState( userHasPermissionToSendReaction = userHasPermissionToSendReaction, composerState = composerState, voiceMessageComposerState = voiceMessageComposerState, - typingNotificationState = aTypingNotificationState(), + typingNotificationState = typingNotificationState, timelineState = timelineState, retrySendMenuState = retrySendMenuState, readReceiptBottomSheetState = readReceiptBottomSheetState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt index 045e7cc8a0..f6c411abc5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt @@ -21,6 +21,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -32,6 +33,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -140,6 +142,7 @@ internal fun TypingNotificationViewPreview( @PreviewParameter(TypingNotificationStateProvider::class) state: TypingNotificationState, ) = ElementPreview { TypingNotificationView( + modifier = Modifier.border(1.dp, Color.Blue), state = state, ) } From f7f59316ee2f1fb26d0988eac9f720128726d9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 13 Feb 2024 16:22:58 +0100 Subject: [PATCH 3/5] Fix review comments --- .../messages/impl/MessagesStateProvider.kt | 14 +------- .../typing/MessagesStateForTypingProvider.kt | 36 +++++++++++++++++++ .../typing/MessagesViewWithTypingPreview.kt | 14 +++----- .../impl/typing/TypingNotificationView.kt | 11 ++++-- 4 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 8b54c5b6f4..f1af9ad225 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -35,9 +35,7 @@ import io.element.android.features.messages.impl.timeline.components.retrysendme import io.element.android.features.messages.impl.timeline.components.retrysendmenu.aRetrySendMenuState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent -import io.element.android.features.messages.impl.typing.TypingNotificationState import io.element.android.features.messages.impl.typing.aTypingNotificationState -import io.element.android.features.messages.impl.typing.aTypingRoomMember import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState @@ -90,15 +88,6 @@ open class MessagesStateProvider : PreviewParameterProvider { aMessagesState( callState = RoomCallState.DISABLED, ), - aMessagesState( - typingNotificationState = aTypingNotificationState( - typingMembers = listOf(aTypingRoomMember()), - reserveSpace = true - ), - ), - aMessagesState( - typingNotificationState = aTypingNotificationState(reserveSpace = true), - ) ) } @@ -115,7 +104,6 @@ fun aMessagesState( mode = MessageComposerMode.Normal, ), voiceMessageComposerState: VoiceMessageComposerState = aVoiceMessageComposerState(), - typingNotificationState: TypingNotificationState = aTypingNotificationState(), timelineState: TimelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), ), @@ -139,7 +127,7 @@ fun aMessagesState( userHasPermissionToSendReaction = userHasPermissionToSendReaction, composerState = composerState, voiceMessageComposerState = voiceMessageComposerState, - typingNotificationState = typingNotificationState, + typingNotificationState = aTypingNotificationState(), timelineState = timelineState, retrySendMenuState = retrySendMenuState, readReceiptBottomSheetState = readReceiptBottomSheetState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt new file mode 100644 index 0000000000..99ab7039c9 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 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.typing + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +class MessagesStateForTypingProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aTypingNotificationState( + typingMembers = listOf( + aTypingRoomMember(displayName = "Alice"), + aTypingRoomMember(displayName = "Bob"), + ), + ), + aTypingNotificationState( + typingMembers = listOf(aTypingRoomMember()), + reserveSpace = true + ), + aTypingNotificationState(reserveSpace = true), + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt index 3dbc21cb15..e7f09f4f09 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt @@ -17,6 +17,7 @@ package io.element.android.features.messages.impl.typing import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.messages.impl.MessagesView import io.element.android.features.messages.impl.aMessagesState import io.element.android.libraries.designsystem.preview.ElementPreview @@ -24,16 +25,11 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight @PreviewsDayNight @Composable -internal fun MessagesViewWithTypingPreview() = ElementPreview { +internal fun MessagesViewWithTypingPreview( + @PreviewParameter(MessagesStateForTypingProvider::class) typingState: TypingNotificationState +) = ElementPreview { MessagesView( - state = aMessagesState().copy( - typingNotificationState = aTypingNotificationState( - typingMembers = listOf( - aTypingRoomMember(displayName = "Alice"), - aTypingRoomMember(displayName = "Bob"), - ), - ), - ), + state = aMessagesState().copy(typingNotificationState = typingState), onBackPressed = {}, onRoomDetailsClicked = {}, onEventClicked = { false }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt index f6c411abc5..c568635a02 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -81,7 +82,13 @@ fun TypingNotificationView( val typingNotificationText = computeTypingNotificationText(state.typingMembers) Box(contentAlignment = Alignment.BottomStart) { // Reserve the space for the typing notification by adding an invisible text - TypingText(text = typingNotificationText, textModifier = Modifier.alpha(0f)) + TypingText( + text = typingNotificationText, + textModifier = Modifier + .alpha(0f) + // Remove the semantics of the text to avoid screen readers to read it + .clearAndSetSemantics { } + ) // Display the actual notification AnimatedVisibility( @@ -142,7 +149,7 @@ internal fun TypingNotificationViewPreview( @PreviewParameter(TypingNotificationStateProvider::class) state: TypingNotificationState, ) = ElementPreview { TypingNotificationView( - modifier = Modifier.border(1.dp, Color.Blue), + modifier = if (state.reserveSpace) Modifier.border(1.dp, Color.Blue) else Modifier, state = state, ) } From c09c570a7de02175d44a1ba4166ee9cdc312f2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 13 Feb 2024 16:51:25 +0100 Subject: [PATCH 4/5] Fix Konsist failure --- .../messages/impl/typing/MessagesViewWithTypingPreview.kt | 2 +- ...rovider.kt => TypingNotificationStateForMessagesProvider.kt} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/{MessagesStateForTypingProvider.kt => TypingNotificationStateForMessagesProvider.kt} (92%) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt index e7f09f4f09..2fea39eeb7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight @PreviewsDayNight @Composable internal fun MessagesViewWithTypingPreview( - @PreviewParameter(MessagesStateForTypingProvider::class) typingState: TypingNotificationState + @PreviewParameter(TypingNotificationStateForMessagesProvider::class) typingState: TypingNotificationState ) = ElementPreview { MessagesView( state = aMessagesState().copy(typingNotificationState = typingState), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateForMessagesProvider.kt similarity index 92% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateForMessagesProvider.kt index 99ab7039c9..302b37994a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesStateForTypingProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateForMessagesProvider.kt @@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.typing import androidx.compose.ui.tooling.preview.PreviewParameterProvider -class MessagesStateForTypingProvider : PreviewParameterProvider { +class TypingNotificationStateForMessagesProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aTypingNotificationState( From 730ed46b24eb6d813a56b4f365501953c91a7e32 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 14 Feb 2024 07:35:23 +0000 Subject: [PATCH 5/5] Update screenshots --- ...essagesViewWithTyping-Day-60_60_null_0,NEXUS_5,1.0,en].png} | 0 ...MessagesViewWithTyping-Day-60_60_null_1,NEXUS_5,1.0,en].png | 3 +++ ...MessagesViewWithTyping-Day-60_60_null_2,NEXUS_5,1.0,en].png | 3 +++ ...sagesViewWithTyping-Night-60_61_null_0,NEXUS_5,1.0,en].png} | 0 ...ssagesViewWithTyping-Night-60_61_null_1,NEXUS_5,1.0,en].png | 3 +++ ...ssagesViewWithTyping-Night-60_61_null_2,NEXUS_5,1.0,en].png | 3 +++ ...TypingNotificationView-Day-61_61_null_8,NEXUS_5,1.0,en].png | 3 +++ ...pingNotificationView-Night-61_62_null_8,NEXUS_5,1.0,en].png | 3 +++ 8 files changed, 18 insertions(+) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_0,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_2,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_0,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-61_61_null_8,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-61_62_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a68d5922a0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ba24739d9425c00794696d8a94b6261e605264da090a2c2702d5a81a67bfb46 +size 56180 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a0cc74f6c6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Day-60_60_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7bd915ebfc3de3703fc039a02064c03dce3013580fd25f47582b0b7612785b9 +size 52110 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3b9d2f642b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6b4053d9e86e963919d6462849cf0bfbaf9df7f14330e64046d9d3cb1f5fbdd +size 55030 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a1eaf95736 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_MessagesViewWithTyping_null_MessagesViewWithTyping-Night-60_61_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daed63aa46f1d4e16de8b554616312b6a0d370768971894da346724ec9fdc145 +size 51012 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-61_61_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-61_61_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4515fec200 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-61_61_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12894684616e070f21f571be9609686446112928695e104f163ade8c2151156a +size 4500 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-61_62_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-61_62_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..85e3dd0fbd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-61_62_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:271774b80a8f5d40952207203b586a8f9fc7a23b8d4ed261c9cbfd56e54f607a +size 4500