Improve typing notification animations (#2386)
Only modify the layout for typing notifications when the first one is displayed: after that, just show/hide them using a fade animation, but keep the empty space there ready to be reused. --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
76beb6bec4
commit
00dd0cbcd1
16 changed files with 218 additions and 63 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.messages.impl.typing
|
package io.element.android.features.messages.impl.typing
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
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.MessagesView
|
||||||
import io.element.android.features.messages.impl.aMessagesState
|
import io.element.android.features.messages.impl.aMessagesState
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -24,16 +25,11 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MessagesViewWithTypingPreview() = ElementPreview {
|
internal fun MessagesViewWithTypingPreview(
|
||||||
|
@PreviewParameter(TypingNotificationStateForMessagesProvider::class) typingState: TypingNotificationState
|
||||||
|
) = ElementPreview {
|
||||||
MessagesView(
|
MessagesView(
|
||||||
state = aMessagesState().copy(
|
state = aMessagesState().copy(typingNotificationState = typingState),
|
||||||
typingNotificationState = aTypingNotificationState(
|
|
||||||
typingMembers = listOf(
|
|
||||||
aTypingRoomMember(displayName = "Alice"),
|
|
||||||
aTypingRoomMember(displayName = "Bob"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onBackPressed = {},
|
onBackPressed = {},
|
||||||
onRoomDetailsClicked = {},
|
onRoomDetailsClicked = {},
|
||||||
onEventClicked = { false },
|
onEventClicked = { false },
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
@ -46,6 +47,7 @@ class TypingNotificationPresenter @Inject constructor(
|
||||||
override fun present(): TypingNotificationState {
|
override fun present(): TypingNotificationState {
|
||||||
val typingMembersState = remember { mutableStateOf(emptyList<RoomMember>()) }
|
val typingMembersState = remember { mutableStateOf(emptyList<RoomMember>()) }
|
||||||
val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true)
|
val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true)
|
||||||
|
|
||||||
LaunchedEffect(renderTypingNotifications) {
|
LaunchedEffect(renderTypingNotifications) {
|
||||||
if (renderTypingNotifications) {
|
if (renderTypingNotifications) {
|
||||||
observeRoomTypingMembers(typingMembersState)
|
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(
|
return TypingNotificationState(
|
||||||
renderTypingNotifications = renderTypingNotifications,
|
renderTypingNotifications = renderTypingNotifications,
|
||||||
typingMembers = typingMembersState.value.toImmutableList(),
|
typingMembers = typingMembersState.value.toImmutableList(),
|
||||||
|
reserveSpace = reserveSpace,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,14 @@ package io.element.android.features.messages.impl.typing
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for the typing notification view.
|
||||||
|
*/
|
||||||
data class TypingNotificationState(
|
data class TypingNotificationState(
|
||||||
|
/** Whether to render the typing notifications based on the user's preferences. */
|
||||||
val renderTypingNotifications: Boolean,
|
val renderTypingNotifications: Boolean,
|
||||||
|
/** The room members currently typing. */
|
||||||
val typingMembers: ImmutableList<RoomMember>,
|
val typingMembers: ImmutableList<RoomMember>,
|
||||||
|
/** Whether to reserve space for the typing notifications at the bottom of the timeline. */
|
||||||
|
val reserveSpace: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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 TypingNotificationStateForMessagesProvider : PreviewParameterProvider<TypingNotificationState> {
|
||||||
|
override val values: Sequence<TypingNotificationState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aTypingNotificationState(
|
||||||
|
typingMembers = listOf(
|
||||||
|
aTypingRoomMember(displayName = "Alice"),
|
||||||
|
aTypingRoomMember(displayName = "Bob"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
aTypingNotificationState(
|
||||||
|
typingMembers = listOf(aTypingRoomMember()),
|
||||||
|
reserveSpace = true
|
||||||
|
),
|
||||||
|
aTypingNotificationState(reserveSpace = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -68,14 +68,20 @@ class TypingNotificationStateProvider : PreviewParameterProvider<TypingNotificat
|
||||||
aTypingRoomMember(displayName = "Alice with a very long display name which means that it will be truncated"),
|
aTypingRoomMember(displayName = "Alice with a very long display name which means that it will be truncated"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
aTypingNotificationState(
|
||||||
|
typingMembers = emptyList(),
|
||||||
|
reserveSpace = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aTypingNotificationState(
|
internal fun aTypingNotificationState(
|
||||||
typingMembers: List<RoomMember> = emptyList(),
|
typingMembers: List<RoomMember> = emptyList(),
|
||||||
|
reserveSpace: Boolean = false,
|
||||||
) = TypingNotificationState(
|
) = TypingNotificationState(
|
||||||
renderTypingNotifications = true,
|
renderTypingNotifications = true,
|
||||||
typingMembers = typingMembers.toImmutableList(),
|
typingMembers = typingMembers.toImmutableList(),
|
||||||
|
reserveSpace = reserveSpace,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun aTypingRoomMember(
|
internal fun aTypingRoomMember(
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,27 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.typing
|
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.border
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
|
@ -43,54 +58,89 @@ fun TypingNotificationView(
|
||||||
state: TypingNotificationState,
|
state: TypingNotificationState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (state.typingMembers.isEmpty() || !state.renderTypingNotifications) return
|
val displayNotifications = state.typingMembers.isNotEmpty() && state.renderTypingNotifications
|
||||||
val typingNotificationText = computeTypingNotificationText(state.typingMembers)
|
|
||||||
Text(
|
@Suppress("ModifierNaming")
|
||||||
modifier = modifier
|
@Composable fun TypingText(text: AnnotatedString, textModifier: Modifier = Modifier) {
|
||||||
.fillMaxWidth()
|
Text(
|
||||||
.padding(horizontal = 24.dp, vertical = 2.dp),
|
modifier = textModifier,
|
||||||
text = typingNotificationText,
|
text = text,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = ElementTheme.typography.fontBodySmRegular,
|
style = ElementTheme.typography.fontBodySmRegular,
|
||||||
color = ElementTheme.colors.textSecondary,
|
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)
|
||||||
|
// Remove the semantics of the text to avoid screen readers to read it
|
||||||
|
.clearAndSetSemantics { }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Display the actual notification
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = displayNotifications,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
TypingText(text = typingNotificationText, textModifier = Modifier.padding(horizontal = 24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun computeTypingNotificationText(typingMembers: ImmutableList<RoomMember>): AnnotatedString {
|
private fun computeTypingNotificationText(typingMembers: ImmutableList<RoomMember>): AnnotatedString {
|
||||||
val names = when (typingMembers.size) {
|
// Remember the last value to avoid empty typing messages while animating
|
||||||
0 -> "" // Cannot happen
|
var result by remember { mutableStateOf(AnnotatedString("")) }
|
||||||
1 -> typingMembers[0].disambiguatedDisplayName
|
if (typingMembers.isNotEmpty()) {
|
||||||
2 -> stringResource(
|
val names = when (typingMembers.size) {
|
||||||
id = R.string.screen_room_typing_two_members,
|
0 -> "" // Cannot happen
|
||||||
typingMembers[0].disambiguatedDisplayName,
|
1 -> typingMembers[0].disambiguatedDisplayName
|
||||||
typingMembers[1].disambiguatedDisplayName,
|
2 -> stringResource(
|
||||||
)
|
id = R.string.screen_room_typing_two_members,
|
||||||
else -> pluralStringResource(
|
typingMembers[0].disambiguatedDisplayName,
|
||||||
id = R.plurals.screen_room_typing_many_members,
|
typingMembers[1].disambiguatedDisplayName,
|
||||||
count = typingMembers.size - 2,
|
)
|
||||||
typingMembers[0].disambiguatedDisplayName,
|
else -> pluralStringResource(
|
||||||
typingMembers[1].disambiguatedDisplayName,
|
id = R.plurals.screen_room_typing_many_members,
|
||||||
typingMembers.size - 2,
|
count = typingMembers.size - 2,
|
||||||
)
|
typingMembers[0].disambiguatedDisplayName,
|
||||||
}
|
typingMembers[1].disambiguatedDisplayName,
|
||||||
// Get the translated string with a fake pattern
|
typingMembers.size - 2,
|
||||||
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,
|
||||||
// Split the string in 3 parts
|
count = typingMembers.size,
|
||||||
val parts = tmpString.split("<>")
|
"<>",
|
||||||
// And rebuild the string with the names
|
)
|
||||||
return buildAnnotatedString {
|
// Split the string in 3 parts
|
||||||
append(parts[0])
|
val parts = tmpString.split("<>")
|
||||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
// And rebuild the string with the names
|
||||||
append(names)
|
result = buildAnnotatedString {
|
||||||
|
append(parts[0])
|
||||||
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||||
|
append(names)
|
||||||
|
}
|
||||||
|
append(parts[1])
|
||||||
}
|
}
|
||||||
append(parts[1])
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
|
|
@ -99,6 +149,7 @@ internal fun TypingNotificationViewPreview(
|
||||||
@PreviewParameter(TypingNotificationStateProvider::class) state: TypingNotificationState,
|
@PreviewParameter(TypingNotificationStateProvider::class) state: TypingNotificationState,
|
||||||
) = ElementPreview {
|
) = ElementPreview {
|
||||||
TypingNotificationView(
|
TypingNotificationView(
|
||||||
|
modifier = if (state.reserveSpace) Modifier.border(1.dp, Color.Blue) else Modifier,
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.typing
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.Event
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||||
|
|
@ -53,6 +54,7 @@ class TypingNotificationPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.renderTypingNotifications).isTrue()
|
assertThat(initialState.renderTypingNotifications).isTrue()
|
||||||
assertThat(initialState.typingMembers).isEmpty()
|
assertThat(initialState.typingMembers).isEmpty()
|
||||||
|
assertThat(initialState.reserveSpace).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +87,7 @@ class TypingNotificationPresenterTest {
|
||||||
assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember)
|
assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember)
|
||||||
// Preferences changes again
|
// Preferences changes again
|
||||||
sessionPreferencesStore.setRenderTypingNotifications(false)
|
sessionPreferencesStore.setRenderTypingNotifications(false)
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.renderTypingNotifications).isFalse()
|
assertThat(finalState.renderTypingNotifications).isFalse()
|
||||||
assertThat(finalState.typingMembers).isEmpty()
|
assertThat(finalState.typingMembers).isEmpty()
|
||||||
|
|
@ -108,6 +110,7 @@ class TypingNotificationPresenterTest {
|
||||||
assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember)
|
assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aDefaultRoomMember)
|
||||||
// User stops typing
|
// User stops typing
|
||||||
room.givenRoomTypingMembers(emptyList())
|
room.givenRoomTypingMembers(emptyList())
|
||||||
|
skipItems(1)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.typingMembers).isEmpty()
|
assertThat(finalState.typingMembers).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +143,7 @@ class TypingNotificationPresenterTest {
|
||||||
assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aKnownRoomMember)
|
assertThat(oneMemberTypingState.typingMembers.first()).isEqualTo(aKnownRoomMember)
|
||||||
// User stops typing
|
// User stops typing
|
||||||
room.givenRoomTypingMembers(emptyList())
|
room.givenRoomTypingMembers(emptyList())
|
||||||
|
skipItems(1)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.typingMembers).isEmpty()
|
assertThat(finalState.typingMembers).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
@ -166,11 +170,38 @@ class TypingNotificationPresenterTest {
|
||||||
listOf(aKnownRoomMember).toImmutableList()
|
listOf(aKnownRoomMember).toImmutableList()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
skipItems(1)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.typingMembers.first()).isEqualTo(aKnownRoomMember)
|
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(
|
private fun createPresenter(
|
||||||
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
||||||
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
|
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,11 @@
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl.migration
|
package io.element.android.features.roomlist.impl.migration
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.features.roomlist.impl.R
|
import io.element.android.features.roomlist.impl.R
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.SunsetPage
|
import io.element.android.libraries.designsystem.atomic.pages.SunsetPage
|
||||||
|
|
@ -33,14 +32,14 @@ fun MigrationScreenView(
|
||||||
isMigrating: Boolean,
|
isMigrating: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val displayMigrationStatusFadeProgress by animateFloatAsState(
|
AnimatedVisibility(
|
||||||
targetValue = if (isMigrating) 1f else 0f,
|
visible = isMigrating,
|
||||||
animationSpec = tween(durationMillis = 200),
|
enter = fadeIn(),
|
||||||
label = "Migration view fade"
|
exit = fadeOut(),
|
||||||
)
|
label = "Migration view fade",
|
||||||
if (displayMigrationStatusFadeProgress > 0f) {
|
) {
|
||||||
SunsetPage(
|
SunsetPage(
|
||||||
modifier = modifier.alpha(displayMigrationStatusFadeProgress),
|
modifier = modifier,
|
||||||
isLoading = true,
|
isLoading = true,
|
||||||
title = stringResource(id = R.string.screen_migration_title),
|
title = stringResource(id = R.string.screen_migration_title),
|
||||||
subtitle = stringResource(id = R.string.screen_migration_message),
|
subtitle = stringResource(id = R.string.screen_migration_message),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8ba24739d9425c00794696d8a94b6261e605264da090a2c2702d5a81a67bfb46
|
||||||
|
size 56180
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b7bd915ebfc3de3703fc039a02064c03dce3013580fd25f47582b0b7612785b9
|
||||||
|
size 52110
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c6b4053d9e86e963919d6462849cf0bfbaf9df7f14330e64046d9d3cb1f5fbdd
|
||||||
|
size 55030
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:daed63aa46f1d4e16de8b554616312b6a0d370768971894da346724ec9fdc145
|
||||||
|
size 51012
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:12894684616e070f21f571be9609686446112928695e104f163ade8c2151156a
|
||||||
|
size 4500
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:271774b80a8f5d40952207203b586a8f9fc7a23b8d4ed261c9cbfd56e54f607a
|
||||||
|
size 4500
|
||||||
Loading…
Add table
Add a link
Reference in a new issue