[Architecture] Introduce EntryPoint and use createNode for ParentNode too

This commit is contained in:
ganfra 2023-03-02 12:33:18 +01:00
parent 1ec629c304
commit da92113e43
54 changed files with 674 additions and 164 deletions

View file

@ -26,8 +26,8 @@ import androidx.core.view.WindowCompat
import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeComponentActivity
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.x.di.AppBindings
import io.element.android.x.node.RootFlowNode
@ -45,13 +45,7 @@ class MainActivity : NodeComponentActivity() {
modifier = Modifier.fillMaxSize(),
) {
NodeHost(integrationPoint = appyxIntegrationPoint) {
RootFlowNode(
buildContext = it,
appComponentOwner = applicationContext as DaggerComponentOwner,
authenticationService = appBindings.authenticationService(),
presenter = appBindings.rootPresenter(),
matrixClientsHolder = appBindings.matrixClientsHolder()
)
createNode<RootFlowNode>(it)
}
}
}

View file

@ -29,38 +29,69 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
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.push
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.preferences.PreferencesFlowNode
import io.element.android.features.roomlist.RoomListNode
import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.nodeInputs
import io.element.android.libraries.architecture.nodeInputsProvider
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.core.RoomId
import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.x.di.SessionComponent
import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class)
class LoggedInFlowNode(
buildContext: BuildContext,
val sessionId: SessionId,
private val matrixClient: MatrixClient,
private val onOpenBugReport: () -> Unit,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.RoomList,
savedStateMap = buildContext.savedStateMap,
),
plugins: List<Plugin>,
private val backstack: BackStack<NavTarget>,
private val roomListEntryPoint: RoomListEntryPoint,
) : ParentNode<LoggedInFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
buildContext = buildContext,
plugins = plugins
), DaggerComponentOwner {
@AssistedInject
constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
roomListEntryPoint: RoomListEntryPoint,
) : this(
buildContext = buildContext,
plugins = plugins,
roomListEntryPoint = roomListEntryPoint,
backstack = BackStack(
initialElement = NavTarget.RoomList,
savedStateMap = buildContext.savedStateMap,
)
)
interface Callback : Plugin {
fun onOpenBugReport()
}
data class Inputs(
val matrixClient: MatrixClient
) : NodeInputs
private val inputs: Inputs by nodeInputs()
override val daggerComponent: Any by lazy {
parent!!.bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(matrixClient).build()
parent!!.bindings<SessionComponent.ParentBindings>().sessionComponentBuilder().client(inputs.matrixClient).build()
}
override fun onBuilt() {
@ -69,7 +100,7 @@ class LoggedInFlowNode(
onCreate = {
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory)
matrixClient.startSync()
inputs.matrixClient.startSync()
},
onDestroy = {
val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory()
@ -78,16 +109,6 @@ class LoggedInFlowNode(
)
}
private val roomListCallback = object : RoomListNode.Callback {
override fun onRoomClicked(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId))
}
override fun onSettingsClicked() {
backstack.push(NavTarget.Settings)
}
}
sealed interface NavTarget : Parcelable {
@Parcelize
object RoomList : NavTarget
@ -102,10 +123,19 @@ class LoggedInFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.RoomList -> {
createNode<RoomListNode>(buildContext, plugins = listOf(roomListCallback))
val callback = object : RoomListEntryPoint.Callback {
override fun onRoomClicked(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId))
}
override fun onSettingsClicked() {
backstack.push(NavTarget.Settings)
}
}
roomListEntryPoint.node(this, buildContext, plugins = listOf(callback))
}
is NavTarget.Room -> {
val room = matrixClient.getRoom(roomId = navTarget.roomId)
val room = inputs.matrixClient.getRoom(roomId = navTarget.roomId)
if (room == null) {
// TODO CREATE UNKNOWN ROOM NODE
node(buildContext) {
@ -114,11 +144,17 @@ class LoggedInFlowNode(
}
}
} else {
RoomFlowNode(buildContext, room)
val inputsProvider = nodeInputsProvider(RoomFlowNode.Inputs(room))
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputsProvider))
}
}
NavTarget.Settings -> {
PreferencesFlowNode(buildContext, onOpenBugReport)
val callback = object : PreferencesFlowNode.Callback {
override fun onOpenBugReport() {
plugins<Callback>().forEach { it.onOpenBugReport() }
}
}
createNode<PreferencesFlowNode>(buildContext, plugins = listOf(callback))
}
}
}

View file

@ -24,25 +24,45 @@ import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.node.node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.login.LoginFlowNode
import io.element.android.features.onboarding.OnBoardingScreen
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.di.AppScope
import kotlinx.parcelize.Parcelize
import timber.log.Timber
class NotLoggedInFlowNode(
@ContributesNode(AppScope::class)
class NotLoggedInFlowNode private constructor(
buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.OnBoarding,
savedStateMap = buildContext.savedStateMap,
),
plugins: List<Plugin>,
private val onBoardingEntryPoint: OnBoardingEntryPoint,
private val loginEntryPoint: LoginEntryPoint,
private val backstack: BackStack<NavTarget>,
) : ParentNode<NotLoggedInFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
buildContext = buildContext,
plugins = plugins,
) {
@AssistedInject
constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
onBoardingEntryPoint: OnBoardingEntryPoint,
loginEntryPoint: LoginEntryPoint,
)
: this(
buildContext = buildContext,
plugins = plugins,
onBoardingEntryPoint = onBoardingEntryPoint,
loginEntryPoint = loginEntryPoint,
backstack = BackStack(initialElement = NavTarget.OnBoarding, savedStateMap = buildContext.savedStateMap),
)
init {
lifecycle.subscribe(
@ -61,12 +81,21 @@ class NotLoggedInFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.OnBoarding -> node(buildContext) {
OnBoardingScreen(
onSignIn = { backstack.push(NavTarget.LoginFlow) }
)
NavTarget.OnBoarding -> {
val callback = object : OnBoardingEntryPoint.Callback {
override fun onSignUp() {
//NOOP
}
override fun onSignIn() {
backstack.push(NavTarget.LoginFlow)
}
}
onBoardingEntryPoint.node(this, buildContext, plugins = listOf(callback))
}
NavTarget.LoginFlow -> {
loginEntryPoint.node(this, buildContext)
}
NavTarget.LoginFlow -> LoginFlowNode(buildContext)
}
}

View file

@ -24,30 +24,52 @@ import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.messages.MessagesNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.nodeInputs
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.x.di.RoomComponent
import kotlinx.parcelize.Parcelize
import timber.log.Timber
@ContributesNode(SessionScope::class)
class RoomFlowNode(
buildContext: BuildContext,
private val room: MatrixRoom,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.Messages,
savedStateMap = buildContext.savedStateMap,
),
plugins: List<Plugin>,
private val backstack: BackStack<NavTarget>,
) : ParentNode<RoomFlowNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
buildContext = buildContext,
plugins = plugins,
), DaggerComponentOwner {
data class Inputs(
val room: MatrixRoom,
) : NodeInputs
@AssistedInject
constructor(@Assisted buildContext: BuildContext, @Assisted plugins: List<Plugin>) : this(
buildContext = buildContext,
plugins = plugins,
backstack = BackStack(
initialElement = NavTarget.Messages,
savedStateMap = buildContext.savedStateMap,
),
)
private val inputs: Inputs by nodeInputs()
override val daggerComponent: Any by lazy {
parent!!.bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build()
parent!!.bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(inputs.room).build()
}
init {

View file

@ -16,6 +16,7 @@
package io.element.android.x.node
import android.content.Context
import android.os.Parcelable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@ -28,14 +29,21 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.node.node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.newRoot
import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.rageshake.bugreport.BugReportNode
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.rageshake.bugreport.BugReportEntryPoint
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.nodeInputsProvider
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.core.SessionId
@ -48,7 +56,8 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import timber.log.Timber
class RootFlowNode(
@ContributesNode(AppScope::class)
class RootFlowNode private constructor(
private val buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.SplashScreen,
@ -57,7 +66,8 @@ class RootFlowNode(
private val appComponentOwner: DaggerComponentOwner,
private val authenticationService: MatrixAuthenticationService,
private val matrixClientsHolder: MatrixClientsHolder,
private val presenter: RootPresenter
private val presenter: RootPresenter,
private val bugReportEntryPoint: BugReportEntryPoint,
) :
ParentNode<RootFlowNode.NavTarget>(
navModel = backstack,
@ -66,6 +76,28 @@ class RootFlowNode(
DaggerComponentOwner by appComponentOwner {
@AssistedInject
constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
@ApplicationContext context: Context,
authenticationService: MatrixAuthenticationService,
matrixClientsHolder: MatrixClientsHolder,
presenter: RootPresenter,
bugReportEntryPoint: BugReportEntryPoint,
) : this(
buildContext = buildContext,
backstack = BackStack(
initialElement = NavTarget.SplashScreen,
savedStateMap = buildContext.savedStateMap,
),
appComponentOwner = context.applicationContext as DaggerComponentOwner,
authenticationService = authenticationService,
matrixClientsHolder = matrixClientsHolder,
presenter = presenter,
bugReportEntryPoint = bugReportEntryPoint,
)
override fun onBuilt() {
super.onBuilt()
observeLoggedInState()
@ -140,12 +172,6 @@ class RootFlowNode(
}
}
private val bugReportNodeCallback = object : BugReportNode.Callback {
override fun onBugReportSent() {
backstack.pop()
}
}
sealed interface NavTarget : Parcelable {
@Parcelize
object SplashScreen : NavTarget
@ -167,16 +193,24 @@ class RootFlowNode(
Timber.w("Couldn't find any session, go through SplashScreen")
backstack.newRoot(NavTarget.SplashScreen)
}
LoggedInFlowNode(
buildContext = buildContext,
sessionId = navTarget.sessionId,
matrixClient = matrixClient,
onOpenBugReport = this::onOpenBugReport
)
val inputsProvider = nodeInputsProvider(LoggedInFlowNode.Inputs(matrixClient))
val callback = object : LoggedInFlowNode.Callback {
override fun onOpenBugReport() {
backstack.push(NavTarget.BugReport)
}
}
createNode<LoggedInFlowNode>(buildContext, plugins = listOf(inputsProvider, callback))
}
NavTarget.NotLoggedInFlow -> NotLoggedInFlowNode(buildContext)
NavTarget.NotLoggedInFlow -> createNode<NotLoggedInFlowNode>(buildContext)
NavTarget.SplashScreen -> splashNode(buildContext)
NavTarget.BugReport -> createNode<BugReportNode>(buildContext, plugins = listOf(bugReportNodeCallback))
NavTarget.BugReport -> {
val callback = object : BugReportEntryPoint.Callback {
override fun onBugReportSent() {
backstack.pop()
}
}
bugReportEntryPoint.node(this, buildContext, plugins = listOf(callback))
}
}
}