Create root node

This commit is contained in:
Florian Renaud 2023-02-28 17:42:26 +01:00
parent 0e4bde644d
commit c135d2a13c
9 changed files with 115 additions and 44 deletions

View file

@ -20,6 +20,7 @@ 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) alias(libs.plugins.anvil)
id("kotlin-parcelize")
} }
android { android {

View file

@ -16,29 +16,48 @@
package io.element.android.features.createroom package io.element.android.features.createroom
import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.plugin.Plugin import com.bumble.appyx.core.node.ParentNode
import dagger.assisted.Assisted import com.bumble.appyx.navmodel.backstack.BackStack
import dagger.assisted.AssistedInject import io.element.android.features.createroom.root.CreateRoomRootNode
import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.architecture.createNode
import kotlinx.parcelize.Parcelize
@ContributesNode(SessionScope::class) class CreateRoomNode(
class CreateRoomNode @AssistedInject constructor( buildContext: BuildContext,
@Assisted buildContext: BuildContext, private val backstack: BackStack<NavTarget> = BackStack(
@Assisted plugins: List<Plugin>, initialElement = NavTarget.Root,
private val presenter: CreateRoomPresenter, savedStateMap = buildContext.savedStateMap,
) : Node(buildContext, plugins = plugins) { ),
) : ParentNode<CreateRoomNode.NavTarget>(
navModel = backstack,
buildContext = buildContext
) {
sealed interface NavTarget : Parcelable {
@Parcelize
object Root : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Root -> createNode<CreateRoomRootNode>(buildContext)
}
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() Children(
CreateRoomView( navModel = backstack,
state = state, modifier = modifier,
modifier = modifier // Animate transition to change server screen
transitionHandler = rememberDefaultTransitionHandler(),
) )
} }
} }

View file

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom package io.element.android.features.createroom.root
// TODO Add your events or remove the file completely if no events // TODO Add your events or remove the file completely if no events
sealed interface CreateRoomEvents { sealed interface CreateRoomRootEvents {
object MyEvent : CreateRoomEvents object MyEvent : CreateRoomRootEvents
} }

View file

@ -0,0 +1,51 @@
/*
* 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.createroom.root
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.SessionScope
import kotlinx.parcelize.Parcelize
@ContributesNode(SessionScope::class)
class CreateRoomRootNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: CreateRoomRootPresenter,
) : Node(buildContext, plugins = plugins) {
sealed interface NavTarget : Parcelable {
@Parcelize
object Root : NavTarget
}
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
CreateRoomRootView(
state = state,
modifier = modifier
)
}
}

View file

@ -14,24 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom package io.element.android.features.createroom.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject import javax.inject.Inject
class CreateRoomPresenter @Inject constructor() : Presenter<CreateRoomState> { class CreateRoomRootPresenter @Inject constructor() : Presenter<CreateRoomRootState> {
@Composable @Composable
override fun present(): CreateRoomState { override fun present(): CreateRoomRootState {
fun handleEvents(event: CreateRoomEvents) { fun handleEvents(event: CreateRoomRootEvents) {
when (event) { when (event) {
CreateRoomEvents.MyEvent -> Unit CreateRoomRootEvents.MyEvent -> Unit
} }
} }
return CreateRoomState( return CreateRoomRootState(
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

View file

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom package io.element.android.features.createroom.root
// TODO add your ui models. Remove the eventSink if you don't have events. // TODO add your ui models. Remove the eventSink if you don't have events.
// Do not use default value, so no member get forgotten in the presenters. // Do not use default value, so no member get forgotten in the presenters.
data class CreateRoomState( data class CreateRoomRootState(
val eventSink: (CreateRoomEvents) -> Unit val eventSink: (CreateRoomRootEvents) -> Unit
) )

View file

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom package io.element.android.features.createroom.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class CreateRoomStateProvider : PreviewParameterProvider<CreateRoomState> { open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
override val values: Sequence<CreateRoomState> override val values: Sequence<CreateRoomRootState>
get() = sequenceOf( get() = sequenceOf(
aCreateRoomState(), aCreateRoomRootState(),
// Add other state here // Add other state here
) )
} }
fun aCreateRoomState() = CreateRoomState( fun aCreateRoomRootState() = CreateRoomRootState(
eventSink = {} eventSink = {}
) )

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.createroom package io.element.android.features.createroom.root
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -28,8 +28,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
@Composable @Composable
fun CreateRoomView( fun CreateRoomRootView(
state: CreateRoomState, state: CreateRoomRootState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Box(modifier, contentAlignment = Alignment.Center) { Box(modifier, contentAlignment = Alignment.Center) {
@ -42,17 +42,17 @@ fun CreateRoomView(
@Preview @Preview
@Composable @Composable
fun CreateRoomViewLightPreview(@PreviewParameter(CreateRoomStateProvider::class) state: CreateRoomState) = fun CreateRoomRootViewLightPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
ElementPreviewLight { ContentToPreview(state) } ElementPreviewLight { ContentToPreview(state) }
@Preview @Preview
@Composable @Composable
fun CreateRoomViewDarkPreview(@PreviewParameter(CreateRoomStateProvider::class) state: CreateRoomState) = fun CreateRoomRootViewDarkPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
ElementPreviewDark { ContentToPreview(state) } ElementPreviewDark { ContentToPreview(state) }
@Composable @Composable
private fun ContentToPreview(state: CreateRoomState) { private fun ContentToPreview(state: CreateRoomRootState) {
CreateRoomView( CreateRoomRootView(
state = state, state = state,
) )
} }

View file

@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.createroom package io.element.android.features.createroom.root
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
@ -26,11 +26,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
class CreateRoomPresenterTests { class CreateRoomRootPresenterTests {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = CreateRoomPresenter() val presenter = CreateRoomRootPresenter()
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -41,12 +41,12 @@ class CreateRoomPresenterTests {
@Test @Test
fun `present - send event`() = runTest { fun `present - send event`() = runTest {
val presenter = CreateRoomPresenter() val presenter = CreateRoomRootPresenter()
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(CreateRoomEvents.MyEvent) initialState.eventSink.invoke(CreateRoomRootEvents.MyEvent)
} }
} }
} }