[Link new device] Automatically rotate the QR Code
This commit is contained in:
parent
5af57906b5
commit
c59b96de66
9 changed files with 152 additions and 12 deletions
|
|
@ -144,8 +144,14 @@ class LinkNewDeviceFlowNode(
|
|||
navigateToError(linkMobileStep.errorType)
|
||||
}
|
||||
is LinkMobileStep.QrReady -> {
|
||||
// The QrCode is ready, navigate to its display
|
||||
backstack.push(NavTarget.MobileShowQrCode(linkMobileStep.data))
|
||||
// The QrCode is ready, navigate to its display, if not already there
|
||||
val navTarget = backstack.elements.value.last().key.navTarget
|
||||
if (navTarget !is NavTarget.MobileShowQrCode) {
|
||||
backstack.push(NavTarget.MobileShowQrCode(linkMobileStep.data))
|
||||
}
|
||||
}
|
||||
LinkMobileStep.QrRotating -> {
|
||||
// This step is handled in ShowQrCodePresenter
|
||||
}
|
||||
is LinkMobileStep.QrScanned -> {
|
||||
backstack.replace(NavTarget.MobileEnterNumber)
|
||||
|
|
|
|||
|
|
@ -65,4 +65,8 @@ class LinkNewMobileHandler(
|
|||
linkMobileStepFlow.emit(LinkMobileStep.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
fun rotateQrCode() {
|
||||
createAndStartNewHandler()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import io.element.android.libraries.di.SessionScope
|
|||
class ShowQrCodeNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
showQrCodePresenterFactory: ShowQrCodePresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
class Inputs(
|
||||
val data: String,
|
||||
|
|
@ -36,11 +37,15 @@ class ShowQrCodeNode(
|
|||
|
||||
private val inputs: Inputs = inputs<Inputs>()
|
||||
private val callback: Callback = callback()
|
||||
private val showQrCodePresenter: ShowQrCodePresenter = showQrCodePresenterFactory.create(
|
||||
initialData = inputs.data,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = showQrCodePresenter.present()
|
||||
ShowQrCodeView(
|
||||
data = inputs.data,
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackClick = callback::navigateBack,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.features.linknewdevice.impl.LinkNewMobileHandler
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
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 timber.log.Timber
|
||||
|
||||
private val tag = LoggerTag("ShowQrCodePresenter", LoggerTags.linkNewDevice)
|
||||
|
||||
@AssistedInject
|
||||
class ShowQrCodePresenter(
|
||||
@Assisted private val initialData: String,
|
||||
private val linkNewMobileHandler: LinkNewMobileHandler,
|
||||
) : Presenter<ShowQrCodeState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialData: String): ShowQrCodePresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): ShowQrCodeState {
|
||||
val data by produceState<AsyncData<String>>(AsyncData.Success(initialData)) {
|
||||
linkNewMobileHandler.stepFlow.collect { step ->
|
||||
when (step) {
|
||||
is LinkMobileStep.QrReady -> {
|
||||
value = AsyncData.Success(step.data)
|
||||
}
|
||||
is LinkMobileStep.QrRotating -> {
|
||||
Timber.tag(tag.value).d("Rotating QrCode")
|
||||
linkNewMobileHandler.rotateQrCode()
|
||||
value = AsyncData.Loading()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ShowQrCodeState(
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
data class ShowQrCodeState(
|
||||
val data: AsyncData<String>,
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
class ShowQrCodeStateProvider : PreviewParameterProvider<ShowQrCodeState> {
|
||||
override val values: Sequence<ShowQrCodeState>
|
||||
get() = sequenceOf(
|
||||
aShowQrCodeState(),
|
||||
ShowQrCodeState(
|
||||
data = AsyncData.Loading(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun aShowQrCodeState(
|
||||
data: AsyncData.Success<String> = AsyncData.Success("DATA"),
|
||||
) = ShowQrCodeState(
|
||||
data = data,
|
||||
)
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -21,6 +22,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.linknewdevice.impl.R
|
||||
|
|
@ -30,6 +32,7 @@ import io.element.android.libraries.designsystem.components.BigIcon
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
||||
import io.element.android.libraries.qrcode.QrCodeImage
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -40,7 +43,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
*/
|
||||
@Composable
|
||||
fun ShowQrCodeView(
|
||||
data: String,
|
||||
state: ShowQrCodeState,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -55,11 +58,24 @@ fun ShowQrCodeView(
|
|||
Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
QrCodeImage(
|
||||
data = data,
|
||||
modifier = Modifier
|
||||
.size(220.dp)
|
||||
)
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
NumberedListOrganism(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -81,9 +97,11 @@ fun ShowQrCodeView(
|
|||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ShowQrCodeViewPreview() = ElementPreview {
|
||||
internal fun ShowQrCodeViewPreview(
|
||||
@PreviewParameter(ShowQrCodeStateProvider::class) state: ShowQrCodeState,
|
||||
) = ElementPreview {
|
||||
ShowQrCodeView(
|
||||
data = "DATA",
|
||||
state = state,
|
||||
onBackClick = { },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ sealed interface LinkMobileStep {
|
|||
data object Uninitialized : LinkMobileStep
|
||||
data object Starting : LinkMobileStep
|
||||
data class QrReady(val data: String) : LinkMobileStep
|
||||
data object QrRotating : LinkMobileStep
|
||||
data class WaitingForAuth(val verificationUri: String) : LinkMobileStep
|
||||
data class QrScanned(val checkCodeSender: CheckCodeSender) : LinkMobileStep
|
||||
data class Error(val errorType: ErrorType) : LinkMobileStep
|
||||
|
|
|
|||
|
|
@ -53,7 +53,14 @@ class RustLinkMobileHandler(
|
|||
_linkMobileStep.emit(LinkMobileStep.Done)
|
||||
} catch (e: HumanQrGrantLoginException) {
|
||||
Timber.tag(tag.value).w(e, "Error during QR login grant")
|
||||
_linkMobileStep.emit(LinkMobileStep.Error(e.map()))
|
||||
// Catch timeout here?
|
||||
if (_linkMobileStep.value is LinkMobileStep.QrReady
|
||||
&& e is HumanQrGrantLoginException.NotFound) {
|
||||
Timber.tag(tag.value).d("Emit QrRotating due to HumanQrGrantLoginException.NotFound")
|
||||
_linkMobileStep.emit(LinkMobileStep.QrRotating)
|
||||
} else {
|
||||
_linkMobileStep.emit(LinkMobileStep.Error(e.map()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue