Merge pull request #5370 from element-hq/feature/bma/bugReportFlowNode

Introduce BugReportFlowNode, and remove NavTarget.ViewLogs from RootFlowNode
This commit is contained in:
Benoit Marty 2025-09-19 09:40:06 +02:00 committed by GitHub
commit eef452734d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 121 additions and 43 deletions

View file

@ -43,7 +43,6 @@ dependencies {
implementation(projects.features.ftue.api)
implementation(projects.features.share.api)
implementation(projects.features.viewfolder.api)
implementation(projects.services.apperror.impl)
implementation(projects.services.appnavstate.api)

View file

@ -39,7 +39,6 @@ import io.element.android.features.login.api.accesscontrol.AccountProviderAccess
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.features.signedout.api.SignedOutEntryPoint
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
@ -71,7 +70,6 @@ class RootFlowNode(
private val matrixSessionCache: MatrixSessionCache,
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
private val viewFolderEntryPoint: ViewFolderEntryPoint,
private val signedOutEntryPoint: SignedOutEntryPoint,
private val intentResolver: IntentResolver,
private val oidcActionFlow: OidcActionFlow,
@ -200,11 +198,6 @@ class RootFlowNode(
@Parcelize
data object BugReport : NavTarget
@Parcelize
data class ViewLogs(
val rootPath: String,
) : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@ -244,31 +237,12 @@ class RootFlowNode(
NavTarget.SplashScreen -> splashNode(buildContext)
NavTarget.BugReport -> {
val callback = object : BugReportEntryPoint.Callback {
override fun onBugReportSent() {
backstack.pop()
}
override fun onViewLogs(basePath: String) {
backstack.push(NavTarget.ViewLogs(rootPath = basePath))
}
}
bugReportEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
is NavTarget.ViewLogs -> {
val callback = object : ViewFolderEntryPoint.Callback {
override fun onDone() {
backstack.pop()
}
}
val params = ViewFolderEntryPoint.Params(
rootPath = navTarget.rootPath,
)
viewFolderEntryPoint
bugReportEntryPoint
.nodeBuilder(this, buildContext)
.params(params)
.callback(callback)
.build()
}

View file

@ -21,7 +21,6 @@ interface BugReportEntryPoint : FeatureEntryPoint {
}
interface Callback : Plugin {
fun onBugReportSent()
fun onViewLogs(basePath: String)
fun onDone()
}
}

View file

@ -28,6 +28,7 @@ setupDependencyInjection()
dependencies {
implementation(projects.appconfig)
implementation(projects.features.enterprise.api)
implementation(projects.features.viewfolder.api)
implementation(projects.services.toolbox.api)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.core)

View file

@ -0,0 +1,95 @@
/*
* Copyright 2025 New Vector 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.rageshake.impl.bugreport
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.Inject
import io.element.android.annotations.ContributesNode
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode
import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class)
@Inject
class BugReportFlowNode(
@Assisted val buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val viewFolderEntryPoint: ViewFolderEntryPoint,
) : BaseFlowNode<BugReportFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
plugins = plugins
) {
private fun onDone() {
plugins<BugReportEntryPoint.Callback>().forEach { it.onDone() }
}
sealed interface NavTarget : Parcelable {
@Parcelize
data object Root : NavTarget
@Parcelize
data class ViewLogs(
val rootPath: String,
) : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Root -> {
val callback = object : BugReportNode.Callback {
override fun onDone() {
this@BugReportFlowNode.onDone()
}
override fun onViewLogs(basePath: String) {
backstack.push(NavTarget.ViewLogs(rootPath = basePath))
}
}
createNode<BugReportNode>(buildContext, listOf(callback))
}
is NavTarget.ViewLogs -> {
val callback = object : ViewFolderEntryPoint.Callback {
override fun onDone() {
backstack.pop()
}
}
val params = ViewFolderEntryPoint.Params(
rootPath = navTarget.rootPath,
)
viewFolderEntryPoint
.nodeBuilder(this, buildContext)
.params(params)
.callback(callback)
.build()
}
}
}
@Composable
override fun View(modifier: Modifier) {
BackstackView()
}
}

View file

@ -18,7 +18,6 @@ import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.Inject
import io.element.android.annotations.ContributesNode
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.androidutils.system.toast
import io.element.android.libraries.ui.strings.CommonStrings
@ -31,8 +30,17 @@ class BugReportNode(
private val presenter: BugReportPresenter,
private val bugReporter: BugReporter,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onDone()
fun onViewLogs(basePath: String)
}
private fun onViewLogs(basePath: String) {
plugins<BugReportEntryPoint.Callback>().forEach { it.onViewLogs(basePath) }
plugins<Callback>().forEach { it.onViewLogs(basePath) }
}
private fun onDone() {
plugins<Callback>().forEach { it.onDone() }
}
@Composable
@ -54,8 +62,4 @@ class BugReportNode(
}
)
}
private fun onDone() {
plugins<BugReportEntryPoint.Callback>().forEach { it.onBugReportSent() }
}
}

View file

@ -29,7 +29,7 @@ class DefaultBugReportEntryPoint : BugReportEntryPoint {
}
override fun build(): Node {
return parentNode.createNode<BugReportNode>(buildContext, plugins)
return parentNode.createNode<BugReportFlowNode>(buildContext, plugins)
}
}
}

View file

@ -9,8 +9,11 @@ package io.element.android.features.rageshake.impl.bugreport
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.google.common.truth.Truth.assertThat
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import kotlinx.coroutines.test.runTest
@ -21,25 +24,28 @@ class DefaultBugReportEntryPointTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun `test node builder`() = runTest {
val entryPoint = DefaultBugReportEntryPoint()
val parentNode = TestParentNode.create { buildContext, plugins ->
BugReportNode(
BugReportFlowNode(
buildContext = buildContext,
plugins = plugins,
presenter = createPresenter(),
bugReporter = FakeBugReporter(),
viewFolderEntryPoint = object : ViewFolderEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError()
},
)
}
val callback = object : BugReportEntryPoint.Callback {
override fun onBugReportSent() = lambdaError()
override fun onViewLogs(basePath: String) = lambdaError()
override fun onDone() = lambdaError()
}
val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null))
.callback(callback)
.build()
assertThat(result).isInstanceOf(BugReportNode::class.java)
assertThat(result).isInstanceOf(BugReportFlowNode::class.java)
assertThat(result.plugins).contains(callback)
}
}