Cleanup nodes (#5358)

* Remove unused NavTarget.Empty

* Let SpaceEntryPoint.Inputs implement NodeInputs.

* Small cleanup

* Small cleanup

* Method can be private.

* Simplify code

* Rename to follow naming convention

* Rename Node to follow naming convention

* Add Konsist test.

* Remove useless line of code.
This commit is contained in:
Benoit Marty 2025-09-16 14:36:43 +02:00 committed by GitHub
commit fecb8cb0be
13 changed files with 42 additions and 50 deletions

View file

@ -37,16 +37,7 @@ class ChangeRolesNode(
) : NodeInputs ) : NodeInputs
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole())
private val presenter = presenterFactory.run {
val role = when (inputs.listType) {
ChangeRoomMemberRolesListType.Admins -> RoomMember.Role.Admin
ChangeRoomMemberRolesListType.Moderators -> RoomMember.Role.Moderator
ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving -> RoomMember.Role.Owner(isCreator = false)
}
create(role)
}
private val stateFlow = launchMolecule { presenter.present() } private val stateFlow = launchMolecule { presenter.present() }
suspend fun waitForRoleChanged() { suspend fun waitForRoleChanged() {
@ -63,3 +54,9 @@ class ChangeRolesNode(
) )
} }
} }
private fun ChangeRoomMemberRolesListType.toRoomMemberRole() = when (this) {
ChangeRoomMemberRolesListType.Admins -> RoomMember.Role.Admin
ChangeRoomMemberRolesListType.Moderators -> RoomMember.Role.Moderator
ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving -> RoomMember.Role.Owner(isCreator = false)
}

View file

@ -39,16 +39,13 @@ class ChangeRoomMemberRolesRootNode(
roomComponentFactory: RoomComponentFactory, roomComponentFactory: RoomComponentFactory,
) : ParentNode<ChangeRoomMemberRolesRootNode.NavTarget>( ) : ParentNode<ChangeRoomMemberRolesRootNode.NavTarget>(
navModel = PermanentNavModel( navModel = PermanentNavModel(
navTargets = setOf(NavTarget.Root), navTargets = setOf(NavTarget),
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
), DependencyInjectionGraphOwner, ChangeRoomMemberRolesEntryPoint.NodeProxy { ), DependencyInjectionGraphOwner, ChangeRoomMemberRolesEntryPoint.NodeProxy {
sealed interface NavTarget : Parcelable { @Parcelize object NavTarget : Parcelable
@Parcelize
object Root : NavTarget
}
data class Inputs( data class Inputs(
val joinedRoom: JoinedRoom, val joinedRoom: JoinedRoom,
@ -60,14 +57,10 @@ class ChangeRoomMemberRolesRootNode(
override val graph = roomComponentFactory.create(inputs.joinedRoom) override val graph = roomComponentFactory.create(inputs.joinedRoom)
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return createNode<ChangeRolesNode>(
NavTarget.Root -> { buildContext = buildContext,
createNode<ChangeRolesNode>( plugins = listOf(ChangeRolesNode.Inputs(listType = inputs.listType)),
buildContext = buildContext, )
plugins = listOf(ChangeRolesNode.Inputs(listType = inputs.listType)),
)
}
}
} }
@Composable @Composable

View file

@ -164,7 +164,7 @@ class HomeFlowNode(
stateFlow.value.roomListState.eventSink(RoomListEvents.LeaveRoom(roomId, needsConfirmation = false)) stateFlow.value.roomListState.eventSink(RoomListEvents.LeaveRoom(roomId, needsConfirmation = false))
} }
fun rootNode(buildContext: BuildContext): Node { private fun rootNode(buildContext: BuildContext): Node {
return node(buildContext) { modifier -> return node(buildContext) { modifier ->
val state by stateFlow.collectAsState() val state by stateFlow.collectAsState()
val activity = requireNotNull(LocalActivity.current) val activity = requireNotNull(LocalActivity.current)

View file

@ -15,7 +15,6 @@ import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
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.core.node.Node
import com.bumble.appyx.core.node.node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
@ -126,9 +125,6 @@ class MessagesFlowNode(
plugins = plugins plugins = plugins
) { ) {
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize
data object Empty : NavTarget
@Parcelize @Parcelize
data class Messages(val focusedEventId: EventId?) : NavTarget data class Messages(val focusedEventId: EventId?) : NavTarget
@ -399,9 +395,6 @@ class MessagesFlowNode(
} }
createNode<PinnedMessagesListNode>(buildContext, plugins = listOf(callback)) createNode<PinnedMessagesListNode>(buildContext, plugins = listOf(callback))
} }
NavTarget.Empty -> {
node(buildContext) {}
}
NavTarget.KnockRequestsList -> { NavTarget.KnockRequestsList -> {
knockRequestsListEntryPoint.createNode(this, buildContext) knockRequestsListEntryPoint.createNode(this, buildContext)
} }

View file

@ -34,10 +34,7 @@ class ChangeRoomPermissionsNode(
) : NodeInputs, Parcelable ) : NodeInputs, Parcelable
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
private val presenter = presenterFactory.create(inputs.section)
private val presenter = presenterFactory.run {
create(inputs.section)
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {

View file

@ -11,6 +11,7 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
interface SpaceEntryPoint : FeatureEntryPoint { interface SpaceEntryPoint : FeatureEntryPoint {
@ -27,7 +28,7 @@ interface SpaceEntryPoint : FeatureEntryPoint {
data class Inputs( data class Inputs(
val roomId: RoomId val roomId: RoomId
) : Plugin ) : NodeInputs
interface Callback : Plugin { interface Callback : Plugin {
fun onOpenRoom(roomId: RoomId) fun onOpenRoom(roomId: RoomId)

View file

@ -16,6 +16,7 @@ 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.space.api.SpaceEntryPoint import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
@ -25,7 +26,7 @@ class SpaceNode(
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: SpacePresenter.Factory, presenterFactory: SpacePresenter.Factory,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
private val inputs = plugins.filterIsInstance<SpaceEntryPoint.Inputs>().single() private val inputs: SpaceEntryPoint.Inputs = inputs()
private val callback = plugins.filterIsInstance<SpaceEntryPoint.Callback>().single() private val callback = plugins.filterIsInstance<SpaceEntryPoint.Callback>().single()
private val presenter = presenterFactory.create(inputs) private val presenter = presenterFactory.create(inputs)

View file

@ -14,7 +14,7 @@ import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
import io.element.android.features.viewfolder.impl.root.ViewFolderRootNode import io.element.android.features.viewfolder.impl.root.ViewFolderFlowNode
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
@ -25,7 +25,7 @@ class DefaultViewFolderEntryPoint : ViewFolderEntryPoint {
return object : ViewFolderEntryPoint.NodeBuilder { return object : ViewFolderEntryPoint.NodeBuilder {
override fun params(params: ViewFolderEntryPoint.Params): ViewFolderEntryPoint.NodeBuilder { override fun params(params: ViewFolderEntryPoint.Params): ViewFolderEntryPoint.NodeBuilder {
plugins += ViewFolderRootNode.Inputs(params.rootPath) plugins += ViewFolderFlowNode.Inputs(params.rootPath)
return this return this
} }
@ -35,7 +35,7 @@ class DefaultViewFolderEntryPoint : ViewFolderEntryPoint {
} }
override fun build(): Node { override fun build(): Node {
return parentNode.createNode<ViewFolderRootNode>(buildContext, plugins) return parentNode.createNode<ViewFolderFlowNode>(buildContext, plugins)
} }
} }
} }

View file

@ -34,10 +34,10 @@ import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class) @ContributesNode(AppScope::class)
@Inject @Inject
class ViewFolderRootNode( class ViewFolderFlowNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
) : BaseFlowNode<ViewFolderRootNode.NavTarget>( ) : BaseFlowNode<ViewFolderFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Root, initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,

View file

@ -287,7 +287,6 @@ class RustMatrixClient(
} }
override suspend fun getRoom(roomId: RoomId): BaseRoom? = withContext(sessionDispatcher) { override suspend fun getRoom(roomId: RoomId): BaseRoom? = withContext(sessionDispatcher) {
innerClient.rooms()
roomFactory.getBaseRoom(roomId) roomFactory.getBaseRoom(roomId)
} }

View file

@ -15,7 +15,7 @@ import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint
import io.element.android.libraries.mediaviewer.impl.gallery.root.MediaGalleryRootNode import io.element.android.libraries.mediaviewer.impl.gallery.root.MediaGalleryFlowNode
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
@Inject @Inject
@ -30,7 +30,7 @@ class DefaultMediaGalleryEntryPoint : MediaGalleryEntryPoint {
} }
override fun build(): Node { override fun build(): Node {
return parentNode.createNode<MediaGalleryRootNode>(buildContext, plugins) return parentNode.createNode<MediaGalleryFlowNode>(buildContext, plugins)
} }
} }
} }

View file

@ -41,11 +41,11 @@ import kotlinx.parcelize.Parcelize
@ContributesNode(RoomScope::class) @ContributesNode(RoomScope::class)
@Inject @Inject
class MediaGalleryRootNode( class MediaGalleryFlowNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val mediaViewerEntryPoint: MediaViewerEntryPoint private val mediaViewerEntryPoint: MediaViewerEntryPoint
) : BaseFlowNode<MediaGalleryRootNode.NavTarget>( ) : BaseFlowNode<MediaGalleryFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Root, initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
@ -87,11 +87,11 @@ class MediaGalleryRootNode(
NavTarget.Root -> { NavTarget.Root -> {
val callback = object : MediaGalleryNode.Callback { val callback = object : MediaGalleryNode.Callback {
override fun onBackClick() { override fun onBackClick() {
this@MediaGalleryRootNode.onBackClick() this@MediaGalleryFlowNode.onBackClick()
} }
override fun onViewInTimeline(eventId: EventId) { override fun onViewInTimeline(eventId: EventId) {
this@MediaGalleryRootNode.onViewInTimeline(eventId) this@MediaGalleryFlowNode.onViewInTimeline(eventId)
} }
override fun onItemClick(item: MediaItem.Event) { override fun onItemClick(item: MediaItem.Event) {
@ -122,7 +122,7 @@ class MediaGalleryRootNode(
} }
override fun onViewInTimeline(eventId: EventId) { override fun onViewInTimeline(eventId: EventId) {
this@MediaGalleryRootNode.onViewInTimeline(eventId) this@MediaGalleryFlowNode.onViewInTimeline(eventId)
} }
} }
mediaViewerEntryPoint.nodeBuilder(this, buildContext) mediaViewerEntryPoint.nodeBuilder(this, buildContext)

View file

@ -20,6 +20,7 @@ import com.lemonappdev.konsist.api.ext.list.withoutName
import com.lemonappdev.konsist.api.ext.list.withoutNameStartingWith import com.lemonappdev.konsist.api.ext.list.withoutNameStartingWith
import com.lemonappdev.konsist.api.verify.assertEmpty import com.lemonappdev.konsist.api.verify.assertEmpty
import com.lemonappdev.konsist.api.verify.assertTrue import com.lemonappdev.konsist.api.verify.assertTrue
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import org.junit.Test import org.junit.Test
@ -44,6 +45,16 @@ class KonsistClassNameTest {
} }
} }
@Test
fun `Classes extending 'BaseFlowNode' should have 'FlowNode' suffix`() {
Konsist.scopeFromProject()
.classes()
.withAllParentsOf(BaseFlowNode::class)
.assertTrue {
it.name.endsWith("FlowNode")
}
}
@Test @Test
fun `Classes extending 'PreviewParameterProvider' name MUST end with 'Provider' and MUST contain provided class name`() { fun `Classes extending 'PreviewParameterProvider' name MUST end with 'Provider' and MUST contain provided class name`() {
Konsist.scopeFromProduction() Konsist.scopeFromProduction()