Add SecureBackupSetupEntryPoint extension seam

Introduces a FeatureEntryPoint for the recovery-key setup screen so enterprise builds can vend a richer setup node. The FOSS default builds the standard auto-generated-key node; SecureBackupFlowNode now resolves the Setup and Change targets through this seam instead of creating SecureBackupSetupNode directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jenna Vassar
2026-06-08 15:35:30 -07:00
parent 8722305e12
commit 1f84bef601
4 changed files with 75 additions and 7 deletions
@@ -0,0 +1,30 @@
/*
* 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.securebackup.api
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.architecture.NodeInputs
/**
* Entry point for the recovery-key setup screen.
*
* The default (FOSS) implementation builds the standard auto-generated-key setup node. Enterprise
* builds replace this binding to vend a richer setup node (e.g. a homeserver-advertised custom
* recovery passphrase) while falling back to the standard node when no custom spec applies.
*/
interface SecureBackupSetupEntryPoint : FeatureEntryPoint {
data class Inputs(val isChangeRecoveryKeyUserStory: Boolean) : NodeInputs
fun createNode(
parentNode: Node,
buildContext: BuildContext,
inputs: Inputs,
): Node
}
@@ -0,0 +1,34 @@
/*
* 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.securebackup.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.securebackup.api.SecureBackupSetupEntryPoint
import io.element.android.features.securebackup.impl.setup.SecureBackupSetupNode
import io.element.android.libraries.architecture.createNode
@ContributesBinding(AppScope::class)
class DefaultSecureBackupSetupEntryPoint : SecureBackupSetupEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
inputs: SecureBackupSetupEntryPoint.Inputs,
): Node {
return parentNode.createNode<SecureBackupSetupNode>(
buildContext = buildContext,
plugins = listOf(
SecureBackupSetupNode.Inputs(
isChangeRecoveryKeyUserStory = inputs.isChangeRecoveryKeyUserStory,
)
),
)
}
}
@@ -21,11 +21,11 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
import io.element.android.features.securebackup.api.SecureBackupSetupEntryPoint
import io.element.android.features.securebackup.impl.disable.SecureBackupDisableNode
import io.element.android.features.securebackup.impl.enter.SecureBackupEnterRecoveryKeyNode
import io.element.android.features.securebackup.impl.reset.ResetIdentityFlowNode
import io.element.android.features.securebackup.impl.root.SecureBackupRootNode
import io.element.android.features.securebackup.impl.setup.SecureBackupSetupNode
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.appyx.canPop
@@ -39,6 +39,7 @@ import kotlinx.parcelize.Parcelize
class SecureBackupFlowNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val secureBackupSetupEntryPoint: SecureBackupSetupEntryPoint,
) : BaseFlowNode<SecureBackupFlowNode.NavTarget>(
backstack = BackStack(
initialElement = when (plugins.filterIsInstance<SecureBackupEntryPoint.Params>().first().initialElement) {
@@ -97,16 +98,18 @@ class SecureBackupFlowNode(
createNode<SecureBackupRootNode>(buildContext, listOf(callback))
}
NavTarget.Setup -> {
val inputs = SecureBackupSetupNode.Inputs(
isChangeRecoveryKeyUserStory = false,
secureBackupSetupEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
inputs = SecureBackupSetupEntryPoint.Inputs(isChangeRecoveryKeyUserStory = false),
)
createNode<SecureBackupSetupNode>(buildContext, listOf(inputs))
}
NavTarget.Change -> {
val inputs = SecureBackupSetupNode.Inputs(
isChangeRecoveryKeyUserStory = true,
secureBackupSetupEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
inputs = SecureBackupSetupEntryPoint.Inputs(isChangeRecoveryKeyUserStory = true),
)
createNode<SecureBackupSetupNode>(buildContext, listOf(inputs))
}
NavTarget.Disable -> {
createNode<SecureBackupDisableNode>(buildContext)
@@ -32,6 +32,7 @@ class DefaultSecureBackupEntryPointTest {
SecureBackupFlowNode(
buildContext = buildContext,
plugins = plugins,
secureBackupSetupEntryPoint = DefaultSecureBackupSetupEntryPoint(),
)
}
val callback = object : SecureBackupEntryPoint.Callback {