Use AnimatedContent instead of reinventing the wheel.

This commit is contained in:
Benoit Marty 2026-05-19 19:00:58 +02:00
parent 000dc3d6c2
commit 1902920983
7 changed files with 37 additions and 84 deletions

View file

@ -46,27 +46,16 @@ class ShowQrCodePresenter(
var qrCodeRotationCounter by remember { mutableIntStateOf(MAX_QR_CODE_ROTATION) } var qrCodeRotationCounter by remember { mutableIntStateOf(MAX_QR_CODE_ROTATION) }
val state by produceState( val state by produceState(
initialValue = ShowQrCodeState( initialValue = ShowQrCodeState(
data1 = AsyncData.Success(initialData), data = AsyncData.Success(initialData),
data2 = AsyncData.Uninitialized,
dataToRender = 1,
) )
) { ) {
linkNewMobileHandler.stepFlow.collect { step -> linkNewMobileHandler.stepFlow.collect { step ->
val currentValue = value
when (step) { when (step) {
is LinkMobileStep.QrReady -> { is LinkMobileStep.QrReady -> {
loadingJob?.cancel() loadingJob?.cancel()
if (currentValue.dataToRender == 1) { value = ShowQrCodeState(
value = currentValue.copy( data = AsyncData.Success(step.data),
data2 = AsyncData.Success(step.data), )
dataToRender = 2,
)
} else {
value = currentValue.copy(
data1 = AsyncData.Success(step.data),
dataToRender = 1,
)
}
} }
is LinkMobileStep.QrRotating -> { is LinkMobileStep.QrRotating -> {
if (qrCodeRotationCounter-- > 0) { if (qrCodeRotationCounter-- > 0) {
@ -75,17 +64,9 @@ class ShowQrCodePresenter(
// Ensure that outdated data is not rendered too long while rotating QR code // Ensure that outdated data is not rendered too long while rotating QR code
loadingJob = launch { loadingJob = launch {
delay(1000) delay(1000)
if (currentValue.dataToRender == 1) { value = ShowQrCodeState(
value = currentValue.copy( data = AsyncData.Loading(),
data2 = AsyncData.Loading(), )
dataToRender = 2,
)
} else {
value = currentValue.copy(
data1 = AsyncData.Loading(),
dataToRender = 1,
)
}
} }
} else { } else {
Timber.tag(tag.value).w("Max QR code rotation reached, not rotating anymore") Timber.tag(tag.value).w("Max QR code rotation reached, not rotating anymore")

View file

@ -10,7 +10,5 @@ package io.element.android.features.linknewdevice.impl.screens.qrcode
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
data class ShowQrCodeState( data class ShowQrCodeState(
val data1: AsyncData<String>, val data: AsyncData<String>,
val data2: AsyncData<String>,
val dataToRender: Int,
) )

View file

@ -15,22 +15,13 @@ class ShowQrCodeStateProvider : PreviewParameterProvider<ShowQrCodeState> {
get() = sequenceOf( get() = sequenceOf(
aShowQrCodeState(), aShowQrCodeState(),
aShowQrCodeState( aShowQrCodeState(
data1 = AsyncData.Loading(), data = AsyncData.Loading(),
),
aShowQrCodeState(
data1 = AsyncData.Success("DATA"),
data2 = AsyncData.Success("DATA2"),
dataToRender = 2,
), ),
) )
} }
internal fun aShowQrCodeState( internal fun aShowQrCodeState(
data1: AsyncData<String> = AsyncData.Success("DATA"), data: AsyncData<String> = AsyncData.Success("DATA"),
data2: AsyncData<String> = AsyncData.Uninitialized,
dataToRender: Int = 1,
) = ShowQrCodeState( ) = ShowQrCodeState(
data1 = data1, data = data,
data2 = data2,
dataToRender = dataToRender,
) )

View file

@ -9,9 +9,11 @@
package io.element.android.features.linknewdevice.impl.screens.qrcode package io.element.android.features.linknewdevice.impl.screens.qrcode
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -44,6 +46,7 @@ import kotlinx.collections.immutable.persistentListOf
* QrCode display screen: * QrCode display screen:
* https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2027-23617 * https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2027-23617
*/ */
@OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
fun ShowQrCodeView( fun ShowQrCodeView(
state: ShowQrCodeState, state: ShowQrCodeState,
@ -61,14 +64,15 @@ fun ShowQrCodeView(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Box { AnimatedContent(
targetState = state.data.dataOrNull(),
transitionSpec = {
fadeIn().togetherWith(fadeOut())
}
) { data ->
QrCodeOrLoading( QrCodeOrLoading(
isVisible = state.dataToRender == 1, modifier = modifier.size(220.dp),
data = state.data1.dataOrNull(), data = data,
)
QrCodeOrLoading(
isVisible = state.dataToRender == 2,
data = state.data2.dataOrNull(),
) )
} }
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
@ -92,31 +96,21 @@ fun ShowQrCodeView(
@Composable @Composable
private fun QrCodeOrLoading( private fun QrCodeOrLoading(
isVisible: Boolean,
data: String?, data: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
AnimatedVisibility( if (data == null) {
modifier = modifier, Box(
visible = isVisible, modifier = modifier,
enter = fadeIn(), contentAlignment = Alignment.Center,
exit = fadeOut(), ) {
) { CircularProgressIndicator()
if (data == null) {
Box(
modifier = Modifier
.size(220.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
} else {
QrCodeImage(
data = data,
modifier = Modifier
.size(220.dp)
)
} }
} else {
QrCodeImage(
modifier = modifier,
data = data,
)
} }
} }

View file

@ -32,9 +32,7 @@ class ShowQrCodePresenterTest {
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
createPresenter().test { createPresenter().test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.data1.dataOrNull()).isEqualTo("DATA") assertThat(initialState.data.dataOrNull()).isEqualTo("DATA")
assertThat(initialState.data2.isUninitialized()).isTrue()
assertThat(initialState.dataToRender).isEqualTo(1)
} }
} }
@ -61,8 +59,7 @@ class ShowQrCodePresenterTest {
) )
runCurrent() runCurrent()
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.data2.isLoading()).isTrue() assertThat(finalState.data.isLoading()).isTrue()
assertThat(finalState.dataToRender).isEqualTo(2)
createLinkMobileHandlerResult.assertions().isCalledExactly(2) createLinkMobileHandlerResult.assertions().isCalledExactly(2)
} }
} }
@ -90,9 +87,7 @@ class ShowQrCodePresenterTest {
LinkMobileStep.QrReady("DATA2") LinkMobileStep.QrReady("DATA2")
) )
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.data1.dataOrNull()).isEqualTo("DATA") assertThat(finalState.data.dataOrNull()).isEqualTo("DATA2")
assertThat(finalState.data2.dataOrNull()).isEqualTo("DATA2")
assertThat(finalState.dataToRender).isEqualTo(2)
} }
} }

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:151eaa4b5619afd76b94de518dc2868d735b6b7a167056941e1f429520c3bf0d
size 31836

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa2303b621070608ba79673322f61bfa686b9729eb445913dd8d059d62c9764a
size 32374