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 799531ae4f
8 changed files with 121 additions and 43 deletions

View file

@ -43,7 +43,6 @@ dependencies {
implementation(projects.features.ftue.api) implementation(projects.features.ftue.api)
implementation(projects.features.share.api) implementation(projects.features.share.api)
implementation(projects.features.viewfolder.api)
implementation(projects.services.apperror.impl) implementation(projects.services.apperror.impl)
implementation(projects.services.appnavstate.api) 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.bugreport.BugReportEntryPoint
import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.features.signedout.api.SignedOutEntryPoint 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.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
@ -71,7 +70,6 @@ class RootFlowNode(
private val matrixSessionCache: MatrixSessionCache, private val matrixSessionCache: MatrixSessionCache,
private val presenter: RootPresenter, private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint, private val bugReportEntryPoint: BugReportEntryPoint,
private val viewFolderEntryPoint: ViewFolderEntryPoint,
private val signedOutEntryPoint: SignedOutEntryPoint, private val signedOutEntryPoint: SignedOutEntryPoint,
private val intentResolver: IntentResolver, private val intentResolver: IntentResolver,
private val oidcActionFlow: OidcActionFlow, private val oidcActionFlow: OidcActionFlow,
@ -200,11 +198,6 @@ class RootFlowNode(
@Parcelize @Parcelize
data object BugReport : NavTarget data object BugReport : NavTarget
@Parcelize
data class ViewLogs(
val rootPath: String,
) : NavTarget
} }
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@ -244,31 +237,12 @@ class RootFlowNode(
NavTarget.SplashScreen -> splashNode(buildContext) NavTarget.SplashScreen -> splashNode(buildContext)
NavTarget.BugReport -> { NavTarget.BugReport -> {
val callback = object : BugReportEntryPoint.Callback { 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() { override fun onDone() {
backstack.pop() backstack.pop()
} }
} }
val params = ViewFolderEntryPoint.Params( bugReportEntryPoint
rootPath = navTarget.rootPath,
)
viewFolderEntryPoint
.nodeBuilder(this, buildContext) .nodeBuilder(this, buildContext)
.params(params)
.callback(callback) .callback(callback)
.build() .build()
} }

View file

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

View file

@ -28,6 +28,7 @@ setupDependencyInjection()
dependencies { dependencies {
implementation(projects.appconfig) implementation(projects.appconfig)
implementation(projects.features.enterprise.api) implementation(projects.features.enterprise.api)
implementation(projects.features.viewfolder.api)
implementation(projects.services.toolbox.api) implementation(projects.services.toolbox.api)
implementation(projects.libraries.androidutils) implementation(projects.libraries.androidutils)
implementation(projects.libraries.core) 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.Assisted
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.annotations.ContributesNode 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.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.androidutils.system.toast import io.element.android.libraries.androidutils.system.toast
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -31,8 +30,17 @@ class BugReportNode(
private val presenter: BugReportPresenter, private val presenter: BugReportPresenter,
private val bugReporter: BugReporter, private val bugReporter: BugReporter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onDone()
fun onViewLogs(basePath: String)
}
private 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 @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 { 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 androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext 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 com.google.common.truth.Truth.assertThat
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint 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.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode import io.element.android.tests.testutils.node.TestParentNode
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -21,25 +24,28 @@ class DefaultBugReportEntryPointTest {
@get:Rule @get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule() val instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test @Test
fun `test node builder`() = runTest { fun `test node builder`() = runTest {
val entryPoint = DefaultBugReportEntryPoint() val entryPoint = DefaultBugReportEntryPoint()
val parentNode = TestParentNode.create { buildContext, plugins -> val parentNode = TestParentNode.create { buildContext, plugins ->
BugReportNode( BugReportFlowNode(
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
presenter = createPresenter(), viewFolderEntryPoint = object : ViewFolderEntryPoint {
bugReporter = FakeBugReporter(), override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError()
},
) )
} }
val callback = object : BugReportEntryPoint.Callback { val callback = object : BugReportEntryPoint.Callback {
override fun onBugReportSent() = lambdaError() override fun onDone() = lambdaError()
override fun onViewLogs(basePath: String) = lambdaError()
} }
val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null))
.callback(callback) .callback(callback)
.build() .build()
assertThat(result).isInstanceOf(BugReportNode::class.java) assertThat(result).isInstanceOf(BugReportFlowNode::class.java)
assertThat(result.plugins).contains(callback) assertThat(result.plugins).contains(callback)
} }
} }