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

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.Node
import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.node.ParentNode
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.plugins
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push 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.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.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode 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.designsystem.theme.components.Text
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.matrix.MatrixClient import io.element.android.libraries.matrix.MatrixClient
import io.element.android.libraries.matrix.core.RoomId 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.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.x.di.SessionComponent import io.element.android.x.di.SessionComponent
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class)
class LoggedInFlowNode( class LoggedInFlowNode(
buildContext: BuildContext, buildContext: BuildContext,
val sessionId: SessionId, plugins: List<Plugin>,
private val matrixClient: MatrixClient, private val backstack: BackStack<NavTarget>,
private val onOpenBugReport: () -> Unit, private val roomListEntryPoint: RoomListEntryPoint,
private val backstack: BackStack<NavTarget> = BackStack(
initialElement = NavTarget.RoomList,
savedStateMap = buildContext.savedStateMap,
),
) : ParentNode<LoggedInFlowNode.NavTarget>( ) : ParentNode<LoggedInFlowNode.NavTarget>(
navModel = backstack, navModel = backstack,
buildContext = buildContext buildContext = buildContext,
plugins = plugins
), DaggerComponentOwner { ), 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 { 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() { override fun onBuilt() {
@ -69,7 +100,7 @@ class LoggedInFlowNode(
onCreate = { onCreate = {
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory() val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
Coil.setImageLoader(imageLoaderFactory) Coil.setImageLoader(imageLoaderFactory)
matrixClient.startSync() inputs.matrixClient.startSync()
}, },
onDestroy = { onDestroy = {
val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory() 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 { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
object RoomList : NavTarget object RoomList : NavTarget
@ -102,10 +123,19 @@ class LoggedInFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.RoomList -> { 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 -> { is NavTarget.Room -> {
val room = matrixClient.getRoom(roomId = navTarget.roomId) val room = inputs.matrixClient.getRoom(roomId = navTarget.roomId)
if (room == null) { if (room == null) {
// TODO CREATE UNKNOWN ROOM NODE // TODO CREATE UNKNOWN ROOM NODE
node(buildContext) { node(buildContext) {
@ -114,11 +144,17 @@ class LoggedInFlowNode(
} }
} }
} else { } else {
RoomFlowNode(buildContext, room) val inputsProvider = nodeInputsProvider(RoomFlowNode.Inputs(room))
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputsProvider))
} }
} }
NavTarget.Settings -> { 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.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode 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.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.login.LoginFlowNode import dagger.assisted.Assisted
import io.element.android.features.onboarding.OnBoardingScreen 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.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.di.AppScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
class NotLoggedInFlowNode( @ContributesNode(AppScope::class)
class NotLoggedInFlowNode private constructor(
buildContext: BuildContext, buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack( plugins: List<Plugin>,
initialElement = NavTarget.OnBoarding, private val onBoardingEntryPoint: OnBoardingEntryPoint,
savedStateMap = buildContext.savedStateMap, private val loginEntryPoint: LoginEntryPoint,
), private val backstack: BackStack<NavTarget>,
) : ParentNode<NotLoggedInFlowNode.NavTarget>( ) : ParentNode<NotLoggedInFlowNode.NavTarget>(
navModel = backstack, 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 { init {
lifecycle.subscribe( lifecycle.subscribe(
@ -61,12 +81,21 @@ class NotLoggedInFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.OnBoarding -> node(buildContext) { NavTarget.OnBoarding -> {
OnBoardingScreen( val callback = object : OnBoardingEntryPoint.Callback {
onSignIn = { backstack.push(NavTarget.LoginFlow) } 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.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack 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.features.messages.MessagesNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode 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.DaggerComponentOwner
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.room.MatrixRoom import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.x.di.RoomComponent import io.element.android.x.di.RoomComponent
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
@ContributesNode(SessionScope::class)
class RoomFlowNode( class RoomFlowNode(
buildContext: BuildContext, buildContext: BuildContext,
private val room: MatrixRoom, plugins: List<Plugin>,
private val backstack: BackStack<NavTarget> = BackStack( private val backstack: BackStack<NavTarget>,
initialElement = NavTarget.Messages,
savedStateMap = buildContext.savedStateMap,
),
) : ParentNode<RoomFlowNode.NavTarget>( ) : ParentNode<RoomFlowNode.NavTarget>(
navModel = backstack, navModel = backstack,
buildContext = buildContext buildContext = buildContext,
plugins = plugins,
), DaggerComponentOwner { ), 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 { override val daggerComponent: Any by lazy {
parent!!.bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build() parent!!.bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(inputs.room).build()
} }
init { init {

View file

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

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.api
import io.element.android.libraries.architecture.EntryPoint
interface LoginEntryPoint : EntryPoint

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.implementation
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint {
override fun node(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
return parentNode.createNode<LoginFlowNode>(buildContext, plugins)
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login package io.element.android.features.login.implementation
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -23,30 +23,39 @@ import com.bumble.appyx.core.composable.Children
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.ParentNode import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.push
import io.element.android.features.login.changeserver.ChangeServerNode import dagger.assisted.Assisted
import io.element.android.features.login.root.LoginRootNode import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.implementation.changeserver.ChangeServerNode
import io.element.android.features.login.implementation.root.LoginRootNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class)
class LoginFlowNode( class LoginFlowNode(
buildContext: BuildContext, buildContext: BuildContext,
private val backstack: BackStack<NavTarget> = BackStack( plugins: List<Plugin>,
initialElement = NavTarget.Root, private val backstack: BackStack<NavTarget>,
savedStateMap = buildContext.savedStateMap,
),
) : ParentNode<LoginFlowNode.NavTarget>( ) : ParentNode<LoginFlowNode.NavTarget>(
navModel = backstack, navModel = backstack,
buildContext = buildContext buildContext = buildContext,
plugins = plugins,
) { ) {
private val loginRootCallback = object : LoginRootNode.Callback { @AssistedInject
override fun onChangeHomeServer() { constructor(@Assisted buildContext: BuildContext, @Assisted plugins: List<Plugin>) : this(
backstack.push(NavTarget.ChangeServer) buildContext = buildContext,
} plugins = plugins,
} backstack = BackStack(
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
),
)
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
@ -58,7 +67,14 @@ class LoginFlowNode(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.Root -> createNode<LoginRootNode>(buildContext, plugins = listOf(loginRootCallback)) NavTarget.Root -> {
val callback = object : LoginRootNode.Callback {
override fun onChangeHomeServer() {
backstack.push(NavTarget.ChangeServer)
}
}
createNode<LoginRootNode>(buildContext, plugins = listOf(callback))
}
NavTarget.ChangeServer -> createNode<ChangeServerNode>(buildContext) NavTarget.ChangeServer -> createNode<ChangeServerNode>(buildContext)
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.implementation.changeserver
sealed interface ChangeServerEvents { sealed interface ChangeServerEvents {
data class SetServer(val server: String) : ChangeServerEvents data class SetServer(val server: String) : ChangeServerEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.implementation.changeserver
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.implementation.changeserver
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.implementation.changeserver
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.implementation.changeserver
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.changeserver package io.element.android.features.login.implementation.changeserver
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -46,7 +46,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.features.login.R import io.element.android.features.login.R
import io.element.android.features.login.error.changeServerError import io.element.android.features.login.implementation.error.changeServerError
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.error package io.element.android.features.login.implementation.error
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.element.android.features.login.root.LoginFormState import io.element.android.features.login.implementation.root.LoginFormState
import io.element.android.libraries.core.uri.isValidUrl import io.element.android.libraries.core.uri.isValidUrl
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.implementation.root
sealed interface LoginRootEvents { sealed interface LoginRootEvents {
object RefreshHomeServer : LoginRootEvents object RefreshHomeServer : LoginRootEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.implementation.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.implementation.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.implementation.root
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -48,7 +48,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.features.login.error.loginError import io.element.android.features.login.implementation.error.loginError
import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.implementation.root
import android.os.Parcelable import android.os.Parcelable
import io.element.android.libraries.matrix.core.SessionId import io.element.android.libraries.matrix.core.SessionId

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.root package io.element.android.features.login.implementation.root
fun aLoginRootState() = LoginRootState( fun aLoginRootState() = LoginRootState(
homeserver = "", homeserver = "",

View file

@ -22,6 +22,8 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.implementation.changeserver.ChangeServerEvents
import io.element.android.features.login.implementation.changeserver.ChangeServerPresenter
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrixtest.A_HOMESERVER import io.element.android.libraries.matrixtest.A_HOMESERVER
import io.element.android.libraries.matrixtest.auth.FakeAuthenticationService import io.element.android.libraries.matrixtest.auth.FakeAuthenticationService

View file

@ -22,6 +22,10 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.implementation.root.LoggedInState
import io.element.android.features.login.implementation.root.LoginFormState
import io.element.android.features.login.implementation.root.LoginRootEvents
import io.element.android.features.login.implementation.root.LoginRootPresenter
import io.element.android.libraries.matrix.core.SessionId import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.matrixtest.A_HOMESERVER import io.element.android.libraries.matrixtest.A_HOMESERVER
import io.element.android.libraries.matrixtest.A_HOMESERVER_2 import io.element.android.libraries.matrixtest.A_HOMESERVER_2

View file

@ -19,15 +19,23 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
alias(libs.plugins.anvil)
} }
android { android {
namespace = "io.element.android.features.onboarding" namespace = "io.element.android.features.onboarding"
} }
anvil {
generateDaggerFactories.set(true)
}
dependencies { dependencies {
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.elementresources) implementation(projects.libraries.elementresources)
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.onboarding.api
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.EntryPoint
interface OnBoardingEntryPoint : EntryPoint {
interface Callback : Plugin {
fun onSignUp()
fun onSignIn()
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.onboarding.implementation
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultOnBoardingEntryPoint @Inject constructor() : OnBoardingEntryPoint {
override fun node(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
return parentNode.createNode<OnBoardingNode>(buildContext, plugins)
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.onboarding.implementation
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 dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class)
class OnBoardingNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
) : Node(
buildContext = buildContext,
plugins = plugins
) {
private fun onSignIn() {
plugins<OnBoardingEntryPoint.Callback>().forEach { it.onSignIn() }
}
private fun onSignUp() {
plugins<OnBoardingEntryPoint.Callback>().forEach { it.onSignUp() }
}
@Composable
override fun View(modifier: Modifier) {
OnBoardingScreen(
modifier = modifier,
onSignIn = this::onSignIn,
onSignUp = this::onSignUp
)
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.onboarding package io.element.android.features.onboarding.implementation
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.onboarding package io.element.android.features.onboarding.implementation
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.onboarding package io.element.android.features.onboarding.implementation
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import io.element.android.features.onboarding.R
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR
class SplashCarouselDataFactory { class SplashCarouselDataFactory {

View file

@ -23,27 +23,46 @@ import com.bumble.appyx.core.composable.Children
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.ParentNode import com.bumble.appyx.core.node.ParentNode
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.BackStack
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.preferences.root.PreferencesRootNode import io.element.android.features.preferences.root.PreferencesRootNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ContributesNode(SessionScope::class)
class PreferencesFlowNode( class PreferencesFlowNode(
buildContext: BuildContext, buildContext: BuildContext,
private val onOpenBugReport: () -> Unit, plugins: List<Plugin>,
private val backstack: BackStack<NavTarget> = BackStack( private val backstack: BackStack<NavTarget>,
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
),
) : ParentNode<PreferencesFlowNode.NavTarget>( ) : ParentNode<PreferencesFlowNode.NavTarget>(
navModel = backstack, navModel = backstack,
buildContext = buildContext buildContext = buildContext,
plugins = plugins
) { ) {
@AssistedInject
constructor(@Assisted buildContext: BuildContext, @Assisted plugins: List<Plugin>) : this(
buildContext = buildContext,
plugins = plugins,
backstack = BackStack(
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
)
)
interface Callback : Plugin {
fun onOpenBugReport()
}
private val preferencesRootNodeCallback = object : PreferencesRootNode.Callback { private val preferencesRootNodeCallback = object : PreferencesRootNode.Callback {
override fun onOpenBugReport() { override fun onOpenBugReport() {
onOpenBugReport.invoke() plugins<Callback>().forEach { it.onOpenBugReport() }
} }
} }

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.bugreport
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.EntryPoint
interface BugReportEntryPoint : EntryPoint {
interface Callback : Plugin {
fun onBugReportSent()
}
}

View file

@ -34,10 +34,6 @@ class BugReportNode @AssistedInject constructor(
private val presenter: BugReportPresenter, private val presenter: BugReportPresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onBugReportSent()
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
@ -49,6 +45,6 @@ class BugReportNode @AssistedInject constructor(
} }
private fun onDone() { private fun onDone() {
plugins<Callback>().forEach { it.onBugReportSent() } plugins<BugReportEntryPoint.Callback>().forEach { it.onBugReportSent() }
} }
} }

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.rageshake.bugreport
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultBugReportEntryPoint @Inject constructor() : BugReportEntryPoint {
override fun node(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
return parentNode.createNode<BugReportNode>(buildContext, plugins)
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.roomlist.api
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.EntryPoint
import io.element.android.libraries.matrix.core.RoomId
interface RoomListEntryPoint : EntryPoint {
interface Callback : Plugin {
fun onRoomClicked(roomId: RoomId)
fun onSettingsClicked()
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.roomlist.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultRoomListEntryPoint @Inject constructor() : RoomListEntryPoint {
override fun node(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
return parentNode.createNode<RoomListNode>(buildContext, plugins)
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.model package io.element.android.features.roomlist.impl
sealed interface RoomListEvents { sealed interface RoomListEvents {
data class UpdateFilter(val newFilter: String) : RoomListEvents data class UpdateFilter(val newFilter: String) : RoomListEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -25,6 +25,7 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.core.RoomId import io.element.android.libraries.matrix.core.RoomId
@ -35,17 +36,12 @@ class RoomListNode @AssistedInject constructor(
private val presenter: RoomListPresenter, private val presenter: RoomListPresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onRoomClicked(roomId: RoomId)
fun onSettingsClicked()
}
private fun onRoomClicked(roomId: RoomId) { private fun onRoomClicked(roomId: RoomId) {
plugins<Callback>().forEach { it.onRoomClicked(roomId) } plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomClicked(roomId) }
} }
private fun onOpenSettings() { private fun onOpenSettings() {
plugins<Callback>().forEach { it.onSettingsClicked() } plugins<RoomListEntryPoint.Callback>().forEach { it.onSettingsClicked() }
} }
@Composable @Composable

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -25,10 +25,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.features.roomlist.model.RoomListEvents
import io.element.android.features.roomlist.model.RoomListRoomSummary
import io.element.android.features.roomlist.model.RoomListRoomSummaryPlaceholders
import io.element.android.features.roomlist.model.RoomListState
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.parallelMap import io.element.android.libraries.core.coroutine.parallelMap
import io.element.android.libraries.dateformatter.LastMessageFormatter import io.element.android.libraries.dateformatter.LastMessageFormatter

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.model package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.model package io.element.android.features.roomlist.impl
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.model package io.element.android.features.roomlist.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.model package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.ui.model.MatrixUser import io.element.android.libraries.matrix.ui.model.MatrixUser

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.model package io.element.android.features.roomlist.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist package io.element.android.features.roomlist.impl
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -34,12 +34,8 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
import io.element.android.features.roomlist.components.RoomListTopBar import io.element.android.features.roomlist.impl.components.RoomListTopBar
import io.element.android.features.roomlist.components.RoomSummaryRow import io.element.android.features.roomlist.impl.components.RoomSummaryRow
import io.element.android.features.roomlist.model.RoomListEvents
import io.element.android.features.roomlist.model.RoomListRoomSummary
import io.element.android.features.roomlist.model.RoomListState
import io.element.android.features.roomlist.model.RoomListStateProvider
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,7 +16,7 @@
@file:OptIn(ExperimentalMaterial3Api::class) @file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.features.roomlist.components package io.element.android.features.roomlist.impl.components
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.roomlist.components package io.element.android.features.roomlist.impl.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -53,8 +53,8 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.google.accompanist.placeholder.material.placeholder import com.google.accompanist.placeholder.material.placeholder
import io.element.android.features.roomlist.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.RoomListRoomSummary
import io.element.android.features.roomlist.model.RoomListRoomSummaryProvider import io.element.android.features.roomlist.impl.RoomListRoomSummaryProvider
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight

View file

@ -22,8 +22,9 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.roomlist.model.RoomListEvents import io.element.android.features.roomlist.impl.RoomListPresenter
import io.element.android.features.roomlist.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.RoomListEvents
import io.element.android.features.roomlist.impl.RoomListRoomSummary
import io.element.android.libraries.dateformatter.LastMessageFormatter import io.element.android.libraries.dateformatter.LastMessageFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.core.SessionId import io.element.android.libraries.matrix.core.SessionId

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.architecture
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
interface EntryPoint {
fun node(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin> = emptyList()): Node
}

View file

@ -16,14 +16,24 @@
package io.element.android.libraries.architecture package io.element.android.libraries.architecture
import android.content.Context
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.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
inline fun <reified NODE : Node> Node.createNode(context: BuildContext, plugins: List<Plugin> = emptyList()): NODE { inline fun <reified NODE : Node> Node.createNode(context: BuildContext, plugins: List<Plugin> = emptyList()): NODE {
val nodeClass = NODE::class.java
val bindings: NodeFactoriesBindings = bindings() val bindings: NodeFactoriesBindings = bindings()
val nodeFactoryMap = bindings.nodeFactories() return bindings.createNode(context, plugins)
}
inline fun <reified NODE : Node> Context.createNode(context: BuildContext, plugins: List<Plugin> = emptyList()): NODE {
val bindings: NodeFactoriesBindings = bindings()
return bindings.createNode(context, plugins)
}
inline fun <reified NODE : Node> NodeFactoriesBindings.createNode(context: BuildContext, plugins: List<Plugin> = emptyList()): NODE {
val nodeClass = NODE::class.java
val nodeFactoryMap = nodeFactories()
val nodeFactory = nodeFactoryMap[nodeClass] ?: error("Cannot find NodeFactory for ${nodeClass.name}.") val nodeFactory = nodeFactoryMap[nodeClass] ?: error("Cannot find NodeFactory for ${nodeClass.name}.")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.architecture
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import kotlin.properties.ReadOnlyProperty
interface NodeInputs
interface NodeInputsProvider<I : NodeInputs> : Plugin {
fun inputs(): I
}
inline fun <reified I : NodeInputs> nodeInputsProvider(inputs: I) = object : NodeInputsProvider<I> {
override fun inputs() = inputs
}
fun <I : NodeInputs> nodeInputs() = ReadOnlyProperty<Node, I> { thisRef, _ ->
thisRef.plugins<NodeInputsProvider<I>>().first().inputs()
}

View file

@ -57,7 +57,7 @@ fun ElementTheme(
) { ) {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val useDarkIcons = !darkTheme val useDarkIcons = !darkTheme
val currentColor = remember { if (darkTheme) darkColors else lightColors } val currentColor = remember(darkTheme) { if (darkTheme) darkColors else lightColors }
val colorScheme = when { val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current val context = LocalContext.current

View file

@ -19,8 +19,8 @@ package io.element.android.samples.minimal
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.login.root.LoginRootPresenter import io.element.android.features.login.implementation.root.LoginRootPresenter
import io.element.android.features.login.root.LoginRootScreen import io.element.android.features.login.implementation.root.LoginRootScreen
import io.element.android.libraries.matrix.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.auth.MatrixAuthenticationService
class LoginScreen(private val authenticationService: MatrixAuthenticationService) { class LoginScreen(private val authenticationService: MatrixAuthenticationService) {

View file

@ -19,8 +19,8 @@ package io.element.android.samples.minimal
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.roomlist.RoomListPresenter import io.element.android.features.roomlist.impl.RoomListPresenter
import io.element.android.features.roomlist.RoomListView import io.element.android.features.roomlist.impl.RoomListView
import io.element.android.libraries.dateformatter.impl.DateFormatters import io.element.android.libraries.dateformatter.impl.DateFormatters
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageFormatter import io.element.android.libraries.dateformatter.impl.DefaultLastMessageFormatter
import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider