[Link new device] Improve UI transition between QRCodes.

This commit is contained in:
Benoit Marty 2026-05-19 10:43:47 +02:00 committed by Benoit Marty
parent a6f7c05458
commit 18bc6a27c4
4 changed files with 99 additions and 28 deletions

View file

@ -22,6 +22,9 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep
import io.element.android.libraries.matrix.api.logs.LoggerTags
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
private val tag = LoggerTag("ShowQrCodePresenter", LoggerTags.linkNewDevice)
@ -36,20 +39,54 @@ class ShowQrCodePresenter(
fun create(initialData: String): ShowQrCodePresenter
}
private var loadingJob: Job? = null
@Composable
override fun present(): ShowQrCodeState {
var qrCodeRotationCounter by remember { mutableIntStateOf(MAX_QR_CODE_ROTATION) }
val data by produceState<AsyncData<String>>(AsyncData.Success(initialData)) {
val state by produceState(
initialValue = ShowQrCodeState(
data1 = AsyncData.Success(initialData),
data2 = AsyncData.Uninitialized,
dataToRender = 1,
)
) {
linkNewMobileHandler.stepFlow.collect { step ->
val currentValue = value
when (step) {
is LinkMobileStep.QrReady -> {
value = AsyncData.Success(step.data)
loadingJob?.cancel()
if (currentValue.dataToRender == 1) {
value = currentValue.copy(
data2 = AsyncData.Success(step.data),
dataToRender = 2,
)
} else {
value = currentValue.copy(
data1 = AsyncData.Success(step.data),
dataToRender = 1,
)
}
}
is LinkMobileStep.QrRotating -> {
if (qrCodeRotationCounter-- > 0) {
Timber.tag(tag.value).d("Rotating QrCode")
linkNewMobileHandler.rotateQrCode()
value = AsyncData.Loading()
// Ensure that outdated data is not rendered too long while rotating QR code
loadingJob = launch {
delay(1000)
if (currentValue.dataToRender == 1) {
value = currentValue.copy(
data2 = AsyncData.Loading(),
dataToRender = 2,
)
} else {
value = currentValue.copy(
data1 = AsyncData.Loading(),
dataToRender = 1,
)
}
}
} else {
Timber.tag(tag.value).w("Max QR code rotation reached, not rotating anymore")
linkNewMobileHandler.onTooManyRotation()
@ -60,9 +97,7 @@ class ShowQrCodePresenter(
}
}
return ShowQrCodeState(
data = data,
)
return state
}
companion object {

View file

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

View file

@ -14,14 +14,23 @@ class ShowQrCodeStateProvider : PreviewParameterProvider<ShowQrCodeState> {
override val values: Sequence<ShowQrCodeState>
get() = sequenceOf(
aShowQrCodeState(),
ShowQrCodeState(
data = AsyncData.Loading(),
aShowQrCodeState(
data1 = AsyncData.Loading(),
),
aShowQrCodeState(
data1 = AsyncData.Success("DATA"),
data2 = AsyncData.Success("DATA2"),
dataToRender = 2,
),
)
}
private fun aShowQrCodeState(
data: AsyncData.Success<String> = AsyncData.Success("DATA"),
data1: AsyncData<String> = AsyncData.Success("DATA"),
data2: AsyncData<String> = AsyncData.Uninitialized,
dataToRender: Int = 1,
) = ShowQrCodeState(
data = data,
data1 = data1,
data2 = data2,
dataToRender = dataToRender,
)

View file

@ -9,6 +9,9 @@
package io.element.android.features.linknewdevice.impl.screens.qrcode
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@ -58,23 +61,15 @@ fun ShowQrCodeView(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
when (val str = state.data.dataOrNull()) {
null -> {
Box(
modifier = Modifier
.size(220.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}
else -> {
QrCodeImage(
data = str,
modifier = Modifier
.size(220.dp)
)
}
Box {
QrCodeOrLoading(
isVisible = state.dataToRender == 1,
data = state.data1.dataOrNull(),
)
QrCodeOrLoading(
isVisible = state.dataToRender == 2,
data = state.data2.dataOrNull(),
)
}
Spacer(modifier = Modifier.height(32.dp))
NumberedListOrganism(
@ -95,6 +90,36 @@ fun ShowQrCodeView(
}
}
@Composable
private fun QrCodeOrLoading(
isVisible: Boolean,
data: String?,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
modifier = modifier,
visible = isVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
if (data == null) {
Box(
modifier = Modifier
.size(220.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
} else {
QrCodeImage(
data = data,
modifier = Modifier
.size(220.dp)
)
}
}
}
@PreviewsDayNight
@Composable
internal fun ShowQrCodeViewPreview(