diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt index d6c64532e..d434ffc2d 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/MaybeLoadingGatekeeperWorkflow.kt @@ -17,7 +17,7 @@ typealias IsLoading = Boolean @OptIn(WorkflowUiExperimentalApi::class) class MaybeLoadingGatekeeperWorkflow( - private val childWithLoading: Workflow, + private val childWithLoading: Workflow>, private val childProps: T, private val isLoading: Flow ) : StatefulWorkflow() { diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt index 1a33e909f..cd159887f 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemWorkflow.kt @@ -32,7 +32,6 @@ import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.WorkflowAction.Companion.noAction import com.squareup.workflow1.action import com.squareup.workflow1.runningWorker -import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.toBackStackScreen @@ -60,7 +59,7 @@ import kotlinx.coroutines.flow.flow class PerformancePoemWorkflow( private val simulatedPerfConfig: SimulatedPerfConfig = SimulatedPerfConfig.NO_SIMULATED_PERF, private val isLoading: MutableStateFlow, -) : PoemWorkflow, StatefulWorkflow() { +) : PoemWorkflow, StatefulWorkflow>() { sealed class State { val isLoading: Boolean = false @@ -100,7 +99,7 @@ class PerformancePoemWorkflow( renderProps: Poem, renderState: State, context: RenderContext - ): OverviewDetailScreen { + ): OverviewDetailScreen<*> { if (simulatedPerfConfig.simultaneousActions > 0) { repeat(simulatedPerfConfig.simultaneousActions) { index -> context.runningWorker( @@ -222,7 +221,7 @@ class PerformancePoemWorkflow( } val stackedStanzas = visibleStanza?.let { - (previousStanzas + visibleStanza).toBackStackScreen() + (previousStanzas + visibleStanza).toBackStackScreen() } val stanzaListOverview = diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt index 5d8aa9af5..dd8613cd8 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/PerformancePoemsBrowserWorkflow.kt @@ -12,6 +12,7 @@ import com.squareup.benchmarks.performance.complex.poetry.instrumentation.Tracea import com.squareup.benchmarks.performance.complex.poetry.instrumentation.asTraceableWorker import com.squareup.benchmarks.performance.complex.poetry.views.BlankScreen import com.squareup.sample.container.overviewdetail.OverviewDetailScreen +import com.squareup.sample.container.overviewdetail.plus import com.squareup.sample.poetry.ConfigAndPoems import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED import com.squareup.sample.poetry.PoemListWorkflow @@ -51,7 +52,7 @@ class PerformancePoemsBrowserWorkflow( private val isLoading: MutableStateFlow, ) : PoemsBrowserWorkflow, - StatefulWorkflow() { + StatefulWorkflow>() { sealed class State { object Recurse : State() @@ -88,7 +89,7 @@ class PerformancePoemsBrowserWorkflow( renderProps: ConfigAndPoems, renderState: State, context: RenderContext - ): OverviewDetailScreen { + ): OverviewDetailScreen<*> { when (renderState) { is Recurse -> { val recursiveChild = PerformancePoemsBrowserWorkflow( @@ -188,13 +189,13 @@ class PerformancePoemsBrowserWorkflow( } } } - var poems = OverviewDetailScreen( + var poems: OverviewDetailScreen<*> = OverviewDetailScreen( overviewRendering = BackStackScreen( poemListRendering.copy(selection = renderState.payload) ) ) if (renderState.payload != NO_POEM_SELECTED) { - val poem: OverviewDetailScreen = context.renderChild( + val poem = context.renderChild( poemWorkflow, renderProps.second[renderState.payload] ) { clearSelection } @@ -209,7 +210,7 @@ class PerformancePoemsBrowserWorkflow( poemListRendering.copy(selection = renderState.poemIndex) ) ) - val poem: OverviewDetailScreen = context.renderChild( + val poem = context.renderChild( poemWorkflow, renderProps.second[renderState.poemIndex] ) { clearSelection } diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt index 42a9bee0c..3bd6ea3f2 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt @@ -8,11 +8,11 @@ import com.squareup.workflow1.ui.container.FullScreenModal @OptIn(WorkflowUiExperimentalApi::class) typealias MayBeLoadingScreen = - BodyAndOverlaysScreen, FullScreenModal> + BodyAndOverlaysScreen>, FullScreenModal> @OptIn(WorkflowUiExperimentalApi::class) fun MayBeLoadingScreen( - baseScreen: OverviewDetailScreen, + baseScreen: OverviewDetailScreen<*>, loaders: List = emptyList() ): MayBeLoadingScreen { return BodyAndOverlaysScreen( diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt index 3034531b7..c6cd6a142 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt @@ -14,6 +14,7 @@ import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub import com.squareup.workflow1.ui.container.BackStackScreen +import com.squareup.workflow1.ui.container.plus /** * Displays [OverviewDetailScreen] renderings in either split pane or single pane @@ -25,7 +26,7 @@ import com.squareup.workflow1.ui.container.BackStackScreen * with [OverviewDetailScreen.overviewRendering] as the base of the stack. */ @OptIn(WorkflowUiExperimentalApi::class) -class OverviewDetailContainer(view: View) : ScreenViewRunner { +class OverviewDetailContainer(view: View) : ScreenViewRunner> { private val overviewStub: WorkflowViewStub? = view.findViewById(R.id.overview_stub) private val detailStub: WorkflowViewStub? = view.findViewById(R.id.detail_stub) @@ -42,7 +43,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner, environment: ViewEnvironment ) { if (singleStub == null) { @@ -53,7 +54,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner, viewEnvironment: ViewEnvironment ) { if (rendering.detailRendering == null && rendering.selectDefault != null) { @@ -81,7 +82,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner, viewEnvironment: ViewEnvironment, stub: WorkflowViewStub ) { @@ -92,7 +93,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner by ScreenViewFactory.fromLayout( + companion object : ScreenViewFactory> by ScreenViewFactory.fromLayout( layoutId = R.layout.overview_detail, constructor = ::OverviewDetailContainer ) diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt index 417f60aea..de82600bf 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt @@ -4,7 +4,6 @@ import android.content.Context import android.graphics.Rect import androidx.appcompat.app.AppCompatDialog import com.squareup.sample.container.R -import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.OverlayDialogFactory @@ -17,14 +16,14 @@ import kotlin.reflect.KClass * Android support for [PanelOverlay]. */ @OptIn(WorkflowUiExperimentalApi::class) -internal object PanelOverlayDialogFactory : OverlayDialogFactory> { - override val type: KClass> = PanelOverlay::class +internal object PanelOverlayDialogFactory : OverlayDialogFactory> { + override val type: KClass> = PanelOverlay::class override fun buildDialog( - initialRendering: PanelOverlay, + initialRendering: PanelOverlay<*>, initialEnvironment: ViewEnvironment, context: Context - ): OverlayDialogHolder> { + ): OverlayDialogHolder> { val dialog = AppCompatDialog(context, R.style.PanelDialog) val realHolder = dialog.asDialogHolderWithContent(initialRendering, initialEnvironment) @@ -32,7 +31,7 @@ internal object PanelOverlayDialogFactory : OverlayDialogFactory> by realHolder { + return object : OverlayDialogHolder> by realHolder { override val onUpdateBounds: ((Rect) -> Unit) = { bounds -> val refinedBounds: Rect = if (!dialog.context.isTablet) { // On a phone, fill the bounds entirely. diff --git a/samples/containers/common/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreen.kt b/samples/containers/common/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreen.kt index 740c32431..aac9ddbd0 100644 --- a/samples/containers/common/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreen.kt +++ b/samples/containers/common/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreen.kt @@ -3,6 +3,7 @@ package com.squareup.sample.container.overviewdetail import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen +import com.squareup.workflow1.ui.container.plus /** * Rendering type for overview / detail containers, with [BackStackScreen] in both roles. @@ -16,14 +17,14 @@ import com.squareup.workflow1.ui.container.BackStackScreen * or a null [selectDefault]. **[selectDefault] cannot be a no-op.** */ @OptIn(WorkflowUiExperimentalApi::class) -class OverviewDetailScreen private constructor( - val overviewRendering: BackStackScreen, - val detailRendering: BackStackScreen? = null, +class OverviewDetailScreen private constructor( + val overviewRendering: BackStackScreen, + val detailRendering: BackStackScreen? = null, val selectDefault: (() -> Unit)? = null ) : Screen { constructor( - overviewRendering: BackStackScreen, - detailRendering: BackStackScreen + overviewRendering: BackStackScreen, + detailRendering: BackStackScreen ) : this(overviewRendering, detailRendering, null) /** @@ -31,36 +32,18 @@ class OverviewDetailScreen private constructor( * that a selection be made to fill a null [detailRendering]. */ constructor( - overviewRendering: BackStackScreen, + overviewRendering: BackStackScreen, selectDefault: (() -> Unit)? = null ) : this(overviewRendering, null, selectDefault) - operator fun component1(): BackStackScreen = overviewRendering - operator fun component2(): BackStackScreen? = detailRendering - - /** - * Returns a new [OverviewDetailScreen] appending the [overviewRendering] and - * [detailRendering] of [other] to those of the receiver. If the new screen's - * [detailRendering] is `null`, it will have the [selectDefault] function of [other]. - */ - operator fun plus(other: OverviewDetailScreen): OverviewDetailScreen { - val newOverview = overviewRendering + other.overviewRendering - val newDetail = detailRendering - ?.let { it + other.detailRendering } - ?: other.detailRendering - - return if (newDetail == null) { - OverviewDetailScreen(newOverview, other.selectDefault) - } else { - OverviewDetailScreen(newOverview, newDetail) - } - } + operator fun component1(): BackStackScreen = overviewRendering + operator fun component2(): BackStackScreen? = detailRendering override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as OverviewDetailScreen + other as OverviewDetailScreen<*> return overviewRendering == other.overviewRendering && detailRendering == other.detailRendering && @@ -80,3 +63,26 @@ class OverviewDetailScreen private constructor( "selectDefault=$selectDefault)" } } + +/** + * Returns a new [OverviewDetailScreen] appending the + * [overviewRendering][OverviewDetailScreen.overviewRendering] and + * [detailRendering][OverviewDetailScreen.detailRendering] of [other] to those of the receiver. + * If the new screen's `detailRendering` is `null`, it will have the + * [selectDefault][OverviewDetailScreen.selectDefault] function of [other]. + */ +@OptIn(WorkflowUiExperimentalApi::class) +operator fun OverviewDetailScreen.plus( + other: OverviewDetailScreen +): OverviewDetailScreen { + val newOverview = overviewRendering + other.overviewRendering + val newDetail = detailRendering + ?.let { it + other.detailRendering } + ?: other.detailRendering + + return if (newDetail == null) { + OverviewDetailScreen(newOverview, other.selectDefault) + } else { + OverviewDetailScreen(newOverview, newDetail) + } +} diff --git a/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt b/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt index f4de422e8..49f657244 100644 --- a/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt +++ b/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt @@ -6,7 +6,7 @@ import com.squareup.workflow1.ui.container.ModalOverlay import com.squareup.workflow1.ui.container.ScreenOverlay @OptIn(WorkflowUiExperimentalApi::class) -class PanelOverlay( +class PanelOverlay( override val content: C ) : ScreenOverlay, ModalOverlay { override fun map(transform: (C) -> D): PanelOverlay = diff --git a/samples/containers/common/src/test/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreenTest.kt b/samples/containers/common/src/test/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreenTest.kt index 0a7063665..9d298044f 100644 --- a/samples/containers/common/src/test/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreenTest.kt +++ b/samples/containers/common/src/test/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreenTest.kt @@ -8,61 +8,68 @@ import org.junit.Test @OptIn(WorkflowUiExperimentalApi::class) internal class OverviewDetailScreenTest { - data class S(val value: T) : Screen + data class FooScreen(val value: T) : Screen + data class BarScreen(val value: T) : Screen @Test fun `minimal structure`() { - val screen = OverviewDetailScreen(BackStackScreen(S(1))) - assertThat(screen.overviewRendering).isEqualTo(BackStackScreen(S(1))) + val screen = OverviewDetailScreen(BackStackScreen(FooScreen(1))) + assertThat(screen.overviewRendering).isEqualTo(BackStackScreen(FooScreen(1))) assertThat(screen.detailRendering).isNull() assertThat(screen.selectDefault).isNull() } @Test fun `minimal equality`() { - val screen = OverviewDetailScreen(BackStackScreen(S(1))) - assertThat(screen).isEqualTo(OverviewDetailScreen(BackStackScreen(S(1)))) - assertThat(screen).isNotEqualTo(OverviewDetailScreen(BackStackScreen(S(2)))) + val screen = OverviewDetailScreen(BackStackScreen(FooScreen(1))) + assertThat(screen).isEqualTo(OverviewDetailScreen(BackStackScreen(FooScreen(1)))) + assertThat(screen).isNotEqualTo(OverviewDetailScreen(BackStackScreen(FooScreen(2)))) } @Test fun `minimal hash`() { - val screen = OverviewDetailScreen(BackStackScreen(S(1))) - assertThat(screen.hashCode()).isEqualTo(OverviewDetailScreen(BackStackScreen(S(1))).hashCode()) + val screen = OverviewDetailScreen(BackStackScreen(FooScreen(1))) + assertThat(screen.hashCode()).isEqualTo( + OverviewDetailScreen(BackStackScreen(FooScreen(1))).hashCode() + ) assertThat(screen.hashCode()) - .isNotEqualTo(OverviewDetailScreen(BackStackScreen(S(2))).hashCode()) + .isNotEqualTo(OverviewDetailScreen(BackStackScreen(FooScreen(2))).hashCode()) } @Test fun `combine minimal`() { - val left = OverviewDetailScreen(BackStackScreen(S(1), S(2))) - val right = OverviewDetailScreen(BackStackScreen(S(11), S(12))) + val left = OverviewDetailScreen(BackStackScreen(FooScreen(1), FooScreen(2))) + val right = OverviewDetailScreen(BackStackScreen(FooScreen(11), FooScreen(12))) assertThat(left + right) - .isEqualTo(OverviewDetailScreen(BackStackScreen(S(1), S(2), S(11), S(12)))) + .isEqualTo( + OverviewDetailScreen( + BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(11), FooScreen(12)) + ) + ) } @Test fun `full structure`() { val screen = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4)) ) - assertThat(screen.overviewRendering).isEqualTo(BackStackScreen(S(1), S(2))) - assertThat(screen.detailRendering).isEqualTo(BackStackScreen(S(3), S(4))) + assertThat(screen.overviewRendering).isEqualTo(BackStackScreen(FooScreen(1), FooScreen(2))) + assertThat(screen.detailRendering).isEqualTo(BackStackScreen(FooScreen(3), FooScreen(4))) assertThat(screen.selectDefault).isNull() } @Test fun `full equality`() { val screen1 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4)) ) val screen2 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4)) ) val screen3 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4), S(5)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4), FooScreen(5)) ) assertThat(screen1).isEqualTo(screen2) @@ -71,18 +78,18 @@ internal class OverviewDetailScreenTest { @Test fun `full hash`() { val screen1 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4)) ) val screen2 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4)) ) val screen3 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4), S(5)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4), FooScreen(5)) ) assertThat(screen1.hashCode()).isEqualTo(screen2.hashCode()) @@ -91,18 +98,23 @@ internal class OverviewDetailScreenTest { @Test fun `combine full`() { val left = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), - detailRendering = BackStackScreen(S(3), S(4)) + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4)) ) val right = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(11), S(12)), - detailRendering = BackStackScreen(S(13), S(14)) + overviewRendering = BackStackScreen(FooScreen(11), FooScreen(12)), + detailRendering = BackStackScreen(FooScreen(13), FooScreen(14)) ) assertThat(left + right).isEqualTo( OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2), S(11), S(12)), - detailRendering = BackStackScreen(S(3), S(4), S(13), S(14)) + overviewRendering = BackStackScreen( + FooScreen(1), + FooScreen(2), + FooScreen(11), + FooScreen(12) + ), + detailRendering = BackStackScreen(FooScreen(3), FooScreen(4), FooScreen(13), FooScreen(14)) ) ) } @@ -110,11 +122,11 @@ internal class OverviewDetailScreenTest { @Test fun `selectDefault structure`() { val selectDefault = {} val screen = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = selectDefault ) - assertThat(screen.overviewRendering).isEqualTo(BackStackScreen(S(1), S(2))) + assertThat(screen.overviewRendering).isEqualTo(BackStackScreen(FooScreen(1), FooScreen(2))) assertThat(screen.detailRendering).isNull() assertThat(screen.selectDefault).isEqualTo(selectDefault) } @@ -123,17 +135,17 @@ internal class OverviewDetailScreenTest { val selectDefault = {} val screen1 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = selectDefault ) val screen2 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = selectDefault ) val screen3 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = {} ) @@ -145,17 +157,17 @@ internal class OverviewDetailScreenTest { val selectDefault = {} val screen1 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = selectDefault ) val screen2 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = selectDefault ) val screen3 = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = {} ) @@ -166,20 +178,48 @@ internal class OverviewDetailScreenTest { @Test fun `combine selectDefault`() { val selectDefaultLeft = {} val left = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2)), + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), selectDefault = selectDefaultLeft ) val selectDefaultRight = {} val right = OverviewDetailScreen( - overviewRendering = BackStackScreen(S(11), S(12)), + overviewRendering = BackStackScreen(FooScreen(11), FooScreen(12)), selectDefault = selectDefaultRight ) assertThat(left + right).isEqualTo( OverviewDetailScreen( - overviewRendering = BackStackScreen(S(1), S(2), S(11), S(12)), + overviewRendering = BackStackScreen( + FooScreen(1), + FooScreen(2), + FooScreen(11), + FooScreen(12) + ), selectDefault = selectDefaultRight ) ) } + + @Test fun `can combine heterogenous content`() { + val left = OverviewDetailScreen( + overviewRendering = BackStackScreen(FooScreen(1), FooScreen(2)), + detailRendering = BackStackScreen(BarScreen(3), BarScreen(4)) + ) + val right = OverviewDetailScreen( + overviewRendering = BackStackScreen(BarScreen(11), BarScreen(12)), + detailRendering = BackStackScreen(FooScreen(13), FooScreen(14)) + ) + + assertThat(left + right).isEqualTo( + OverviewDetailScreen( + overviewRendering = BackStackScreen( + FooScreen(1), + FooScreen(2), + BarScreen(11), + BarScreen(12) + ), + detailRendering = BackStackScreen(BarScreen(3), BarScreen(4), FooScreen(13), FooScreen(14)) + ) + ) + } } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemWorkflow.kt index bb4f7e4c3..0fd033997 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemWorkflow.kt @@ -12,6 +12,6 @@ import com.squareup.workflow1.Workflow * (Defining this as an interface allows us to use other implementations * in other contexts -- check out our :benchmarks module!) */ -interface PoemWorkflow : Workflow { +interface PoemWorkflow : Workflow> { object ClosePoem } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemsBrowserWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemsBrowserWorkflow.kt index bdd03c1ad..64c17c9a3 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemsBrowserWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/PoemsBrowserWorkflow.kt @@ -14,4 +14,4 @@ typealias ConfigAndPoems = Pair> * in other contexts -- check out our :benchmarks module!) */ interface PoemsBrowserWorkflow : - Workflow + Workflow> diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt index c8a4720c4..17c68bcb4 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemWorkflow.kt @@ -17,7 +17,6 @@ import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.WorkflowAction.Companion.noAction import com.squareup.workflow1.parse -import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.toBackStackScreen @@ -26,7 +25,7 @@ import com.squareup.workflow1.ui.container.toBackStackScreen * Default implementation of [PoemWorkflow]. */ class RealPoemWorkflow : PoemWorkflow, - StatefulWorkflow() { + StatefulWorkflow>() { override fun initialState( props: Poem, @@ -42,7 +41,7 @@ class RealPoemWorkflow : PoemWorkflow, renderProps: Poem, renderState: SelectedStanza, context: RenderContext - ): OverviewDetailScreen { + ): OverviewDetailScreen<*> { val previousStanzas: List = if (renderState == NO_SELECTED_STANZA) { emptyList() @@ -73,7 +72,7 @@ class RealPoemWorkflow : PoemWorkflow, } val stackedStanzas = visibleStanza?.let { - (previousStanzas + visibleStanza).toBackStackScreen() + (previousStanzas + visibleStanza).toBackStackScreen() } val stanzaListOverview = diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt index 23ec36336..f330af226 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/RealPoemsBrowserWorkflow.kt @@ -1,6 +1,7 @@ package com.squareup.sample.poetry import com.squareup.sample.container.overviewdetail.OverviewDetailScreen +import com.squareup.sample.container.overviewdetail.plus import com.squareup.sample.poetry.PoemListScreen.Companion.NO_POEM_SELECTED import com.squareup.sample.poetry.PoemListWorkflow.Props import com.squareup.sample.poetry.model.Poem @@ -23,7 +24,7 @@ typealias SelectedPoem = Int class RealPoemsBrowserWorkflow( private val poemWorkflow: PoemWorkflow ) : PoemsBrowserWorkflow, - StatefulWorkflow() { + StatefulWorkflow>() { override fun initialState( props: ConfigAndPoems, @@ -39,8 +40,8 @@ class RealPoemsBrowserWorkflow( renderProps: ConfigAndPoems, renderState: SelectedPoem, context: RenderContext - ): OverviewDetailScreen { - val poems: OverviewDetailScreen = + ): OverviewDetailScreen<*> { + val poems = context.renderChild(PoemListWorkflow, Props(poems = renderProps.second)) { selected -> choosePoem( selected @@ -52,7 +53,7 @@ class RealPoemsBrowserWorkflow( return if (renderState == NO_POEM_SELECTED) { poems } else { - val poem: OverviewDetailScreen = + val poem = context.renderChild(poemWorkflow, renderProps.second[renderState]) { clearSelection } poems + poem } diff --git a/samples/tictactoe/common/src/main/java/com/squareup/sample/authworkflow/AuthWorkflow.kt b/samples/tictactoe/common/src/main/java/com/squareup/sample/authworkflow/AuthWorkflow.kt index 9ae4f3738..d429f1e86 100644 --- a/samples/tictactoe/common/src/main/java/com/squareup/sample/authworkflow/AuthWorkflow.kt +++ b/samples/tictactoe/common/src/main/java/com/squareup/sample/authworkflow/AuthWorkflow.kt @@ -15,7 +15,6 @@ import com.squareup.workflow1.Workflow import com.squareup.workflow1.action import com.squareup.workflow1.runningWorker import com.squareup.workflow1.rx2.asWorker -import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen @@ -24,7 +23,7 @@ import com.squareup.workflow1.ui.container.BackStackScreen * that build on [AuthWorkflow] decoupled from it, for ease of testing. */ @OptIn(WorkflowUiExperimentalApi::class) -typealias AuthWorkflow = Workflow> +typealias AuthWorkflow = Workflow> sealed class AuthState { internal data class LoginPrompt(val errorMessage: String = "") : AuthState() @@ -62,7 +61,7 @@ sealed class AuthResult { */ @OptIn(WorkflowUiExperimentalApi::class) class RealAuthWorkflow(private val authService: AuthService) : AuthWorkflow, - StatefulWorkflow>() { + StatefulWorkflow>() { override fun initialState( props: Unit, @@ -73,7 +72,7 @@ class RealAuthWorkflow(private val authService: AuthService) : AuthWorkflow, renderProps: Unit, renderState: AuthState, context: RenderContext - ): BackStackScreen = when (renderState) { + ): BackStackScreen<*> = when (renderState) { is LoginPrompt -> { BackStackScreen( LoginScreen( diff --git a/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt b/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt index 927b1103b..c9d2cbbde 100644 --- a/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt +++ b/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt @@ -21,6 +21,7 @@ import com.squareup.workflow1.renderChild import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen +import com.squareup.workflow1.ui.container.plus /** * Application specific root [Workflow], and demonstration of workflow composition. diff --git a/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt b/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt index 3d3bbf706..7ce5d33e6 100644 --- a/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt +++ b/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt @@ -60,8 +60,7 @@ class TicTacToeWorkflowTest { private data class S(val value: T) : Screen - private fun authScreen(wrapped: String = DEFAULT_AUTH) = - BackStackScreen(S(wrapped)) + private fun authScreen(wrapped: String = DEFAULT_AUTH) = BackStackScreen(S(wrapped)) private val BodyAndOverlaysScreen, *>.panels: List> get() = overlays.mapNotNull { it as? PanelOverlay<*> } diff --git a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsAppWorkflow.kt b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsAppWorkflow.kt index d49b9ad09..f2c8412ec 100644 --- a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsAppWorkflow.kt +++ b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsAppWorkflow.kt @@ -28,7 +28,7 @@ sealed class TodoListsAppState { * relationship. See details in the body of the [render] method. */ object TodoListsAppWorkflow : - StatefulWorkflow() { + StatefulWorkflow>() { override fun initialState( props: Unit, snapshot: Snapshot? @@ -64,7 +64,7 @@ object TodoListsAppWorkflow : renderProps: Unit, renderState: TodoListsAppState, context: RenderContext - ): OverviewDetailScreen { + ): OverviewDetailScreen<*> { val listOfLists: TodoListsScreen = context.renderChild( listsWorkflow, renderState.lists diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/FullScreenModalFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/FullScreenModalFactory.kt index ef0cade7f..a72f8a901 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/FullScreenModalFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/FullScreenModalFactory.kt @@ -13,7 +13,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * To provide a custom binding for [FullScreenModal], see [OverlayDialogFactoryFinder]. */ @WorkflowUiExperimentalApi -internal class FullScreenModalFactory() : OverlayDialogFactory> { +internal class FullScreenModalFactory : OverlayDialogFactory> { override val type = FullScreenModal::class override fun buildDialog( diff --git a/workflow-ui/core-common/api/core-common.api b/workflow-ui/core-common/api/core-common.api index d0fa3c05e..3709b2775 100644 --- a/workflow-ui/core-common/api/core-common.api +++ b/workflow-ui/core-common/api/core-common.api @@ -216,7 +216,6 @@ public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squ public static final field Companion Lcom/squareup/workflow1/ui/container/BackStackScreen$Companion; public fun (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V public fun (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/Screen;)V - public synthetic fun (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun asSequence ()Lkotlin/sequences/Sequence; public fun equals (Ljava/lang/Object;)Z public final fun get (I)Lcom/squareup/workflow1/ui/Screen; @@ -227,7 +226,6 @@ public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squ public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public final fun mapIndexed (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/container/BackStackScreen; - public final fun plus (Lcom/squareup/workflow1/ui/container/BackStackScreen;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public fun toString ()Ljava/lang/String; } @@ -237,6 +235,7 @@ public final class com/squareup/workflow1/ui/container/BackStackScreen$Companion } public final class com/squareup/workflow1/ui/container/BackStackScreenKt { + public static final fun plus (Lcom/squareup/workflow1/ui/container/BackStackScreen;Lcom/squareup/workflow1/ui/container/BackStackScreen;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public static final fun toBackStackScreen (Ljava/util/List;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public static final fun toBackStackScreenOrNull (Ljava/util/List;)Lcom/squareup/workflow1/ui/container/BackStackScreen; } diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt index 92100388b..cd04934ac 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt @@ -7,7 +7,7 @@ import com.squareup.workflow1.ui.Compatible.Companion.keyFor * * Why two parameter types? The separate [BaseT] type allows implementations * and sub-interfaces to constrain the types that [map] is allowed to - * transform [ContentT] to. E.g., it allows `FooWrapper` to declare + * transform [C] to. E.g., it allows `FooWrapper` to declare * that [map] is only able to transform `S` to other types of `Screen`. * * @param BaseT the invariant base type of the contents of such a container, @@ -17,11 +17,11 @@ import com.squareup.workflow1.ui.Compatible.Companion.keyFor * is an [Overlay][com.squareup.workflow1.ui.container.Overlay], but it * wraps a [Screen]. * - * @param ContentT the specific subtype of [BaseT] collected by this [Container]. + * @param C the specific subtype of [BaseT] collected by this [Container]. */ @WorkflowUiExperimentalApi -public interface Container { - public fun asSequence(): Sequence +public interface Container { + public fun asSequence(): Sequence /** * Returns a [Container] with the [transform]ed contents of the receiver. @@ -42,7 +42,7 @@ public interface Container { * val childBackStackScreen = renderChild(childWorkflow) { ... } * val loggingBackStackScreen = childBackStackScreen.map { LoggingScreen(it) } */ - public fun map(transform: (ContentT) -> ContentU): Container + public fun map(transform: (C) -> D): Container } /** @@ -56,8 +56,8 @@ public interface Container { * provides a convenient default implementation of [compatibilityKey]. */ @WorkflowUiExperimentalApi -public interface Wrapper : Container, Compatible { - public val content: ContentT +public interface Wrapper : Container, Compatible { + public val content: C /** * Default implementation makes this [Wrapper] compatible with others of the same type, @@ -66,9 +66,9 @@ public interface Wrapper : Container = sequenceOf(content) + public override fun asSequence(): Sequence = sequenceOf(content) - public override fun map( - transform: (ContentT) -> ContentU - ): Wrapper + public override fun map( + transform: (C) -> D + ): Wrapper } diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt index 79fc1a808..a20196b95 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt @@ -8,7 +8,7 @@ package com.squareup.workflow1.ui * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public data class NamedScreen( +public data class NamedScreen( override val content: C, val name: String ) : Screen, Wrapper { diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt index fc6199171..7bd13ad70 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt @@ -20,7 +20,7 @@ import com.squareup.workflow1.ui.container.BackStackScreen.Companion.fromListOrN * @see fromListOrNull */ @WorkflowUiExperimentalApi -public class BackStackScreen private constructor( +public class BackStackScreen internal constructor( public val frames: List ) : Screen, Container { /** @@ -54,14 +54,6 @@ public class BackStackScreen private constructor( public operator fun get(index: Int): StackedT = frames[index] - public operator fun plus(other: BackStackScreen?): BackStackScreen { - return if (other == null) { - this - } else { - BackStackScreen(frames + other.frames) - } - } - public override fun map( transform: (StackedT) -> StackedU ): BackStackScreen { @@ -111,6 +103,18 @@ public class BackStackScreen private constructor( } } +/** + * Returns a new [BackStackScreen] with the [BackStackScreen.frames] of [other] added + * to those of the receiver. [other] is nullable for convenience when using with + * [toBackStackScreenOrNull]. + */ +@WorkflowUiExperimentalApi +public operator fun BackStackScreen.plus( + other: BackStackScreen? +): BackStackScreen { + return other?.let { BackStackScreen(frames + it.frames) } ?: this +} + @WorkflowUiExperimentalApi public fun List.toBackStackScreenOrNull(): BackStackScreen? = fromListOrNull(this) diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt index 6b05343e2..c82219d06 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt @@ -16,7 +16,7 @@ import com.squareup.workflow1.ui.plus * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public class EnvironmentScreen( +public class EnvironmentScreen( public override val content: C, public val environment: ViewEnvironment = ViewEnvironment.EMPTY ) : Wrapper, Screen { diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenModal.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenModal.kt index 24f62a9a6..870d5cbc3 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenModal.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenModal.kt @@ -9,7 +9,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public class FullScreenModal( +public class FullScreenModal( public override val content: C ) : ScreenOverlay, ModalOverlay { override fun map(transform: (C) -> D): FullScreenModal = diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt index 7d7814d72..bfd9c3e64 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt @@ -8,7 +8,7 @@ import com.squareup.workflow1.ui.Wrapper * An [Overlay] built around a root [content] [Screen]. */ @WorkflowUiExperimentalApi -public interface ScreenOverlay : Overlay, Wrapper { +public interface ScreenOverlay : Overlay, Wrapper { public override val content: ContentT override fun map(transform: (ContentT) -> ContentU): ScreenOverlay diff --git a/workflow-ui/core-common/src/test/java/com/squareup/workflow1/ui/container/BackStackScreenTest.kt b/workflow-ui/core-common/src/test/java/com/squareup/workflow1/ui/container/BackStackScreenTest.kt index 9d9daff82..93b33d28c 100644 --- a/workflow-ui/core-common/src/test/java/com/squareup/workflow1/ui/container/BackStackScreenTest.kt +++ b/workflow-ui/core-common/src/test/java/com/squareup/workflow1/ui/container/BackStackScreenTest.kt @@ -8,82 +8,119 @@ import kotlin.test.assertFailsWith @OptIn(WorkflowUiExperimentalApi::class) internal class BackStackScreenTest { - data class S(val value: T) : Screen + data class FooScreen(val value: T) : Screen + data class BarScreen(val value: T) : Screen @Test fun `top is last`() { - assertThat(BackStackScreen(S(1), S(2), S(3), S(4)).top).isEqualTo(S(4)) + assertThat(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3), FooScreen(4)).top).isEqualTo( + FooScreen(4) + ) } @Test fun `backstack is all but top`() { - assertThat(BackStackScreen(S(1), S(2), S(3), S(4)).backStack) - .isEqualTo(listOf(S(1), S(2), S(3))) + assertThat(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3), FooScreen(4)).backStack) + .isEqualTo(listOf(FooScreen(1), FooScreen(2), FooScreen(3))) } @Test fun `get works`() { - assertThat(BackStackScreen(S("able"), S("baker"), S("charlie"))[1]).isEqualTo(S("baker")) + assertThat(BackStackScreen(FooScreen("able"), FooScreen("baker"), FooScreen("charlie"))[1]).isEqualTo( + FooScreen("baker") + ) } @Test fun `plus another stack`() { - assertThat(BackStackScreen(S(1), S(2), S(3)) + BackStackScreen(S(8), S(9), S(0))) - .isEqualTo(BackStackScreen(S(1), S(2), S(3), S(8), S(9), S(0))) + assertThat( + BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3)) + BackStackScreen( + FooScreen(8), + FooScreen(9), + FooScreen(0) + ) + ) + .isEqualTo( + BackStackScreen( + FooScreen(1), + FooScreen(2), + FooScreen(3), + FooScreen(8), + FooScreen(9), + FooScreen(0) + ) + ) } @Test fun `unequal by order`() { - assertThat(BackStackScreen(S(1), S(2), S(3))) - .isNotEqualTo(BackStackScreen(S(3), S(2), S(1))) + assertThat(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3))) + .isNotEqualTo(BackStackScreen(FooScreen(3), FooScreen(2), FooScreen(1))) } @Test fun `equal have matching hash`() { - assertThat(BackStackScreen(S(1), S(2), S(3)).hashCode()) - .isEqualTo(BackStackScreen(S(1), S(2), S(3)).hashCode()) + assertThat(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3)).hashCode()) + .isEqualTo(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3)).hashCode()) } @Test fun `unequal have mismatching hash`() { - assertThat(BackStackScreen(S(1), S(2)).hashCode()) - .isNotEqualTo(BackStackScreen(S(1), S(2), S(3)).hashCode()) + assertThat(BackStackScreen(FooScreen(1), FooScreen(2)).hashCode()) + .isNotEqualTo(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3)).hashCode()) } @Test fun `bottom and rest`() { assertThat( BackStackScreen.fromList( - listOf(element = S(1)) + listOf(S(2), S(3), S(4)) + listOf(element = FooScreen(1)) + listOf(FooScreen(2), FooScreen(3), FooScreen(4)) ) - ).isEqualTo(BackStackScreen(S(1), S(2), S(3), S(4))) + ).isEqualTo(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3), FooScreen(4))) } @Test fun singleton() { - val stack = BackStackScreen(S("hi")) - assertThat(stack.top).isEqualTo(S("hi")) - assertThat(stack.frames).isEqualTo(listOf(S("hi"))) - assertThat(stack).isEqualTo(BackStackScreen(S("hi"))) + val stack = BackStackScreen(FooScreen("hi")) + assertThat(stack.top).isEqualTo(FooScreen("hi")) + assertThat(stack.frames).isEqualTo(listOf(FooScreen("hi"))) + assertThat(stack).isEqualTo(BackStackScreen(FooScreen("hi"))) } @Test fun map() { - assertThat(BackStackScreen(S(1), S(2), S(3)).map { S(it.value * 2) }) - .isEqualTo(BackStackScreen(S(2), S(4), S(6))) + assertThat( + BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3)).map { + FooScreen(it.value * 2) + } + ) + .isEqualTo(BackStackScreen(FooScreen(2), FooScreen(4), FooScreen(6))) } @Test fun mapIndexed() { - val source = BackStackScreen(S("able"), S("baker"), S("charlie")) - assertThat(source.mapIndexed { index, frame -> S("$index: ${frame.value}") }) - .isEqualTo(BackStackScreen(S("0: able"), S("1: baker"), S("2: charlie"))) + val source = BackStackScreen(FooScreen("able"), FooScreen("baker"), FooScreen("charlie")) + assertThat(source.mapIndexed { index, frame -> FooScreen("$index: ${frame.value}") }) + .isEqualTo( + BackStackScreen(FooScreen("0: able"), FooScreen("1: baker"), FooScreen("2: charlie")) + ) } @Test fun nullFromEmptyList() { - assertThat(emptyList>().toBackStackScreenOrNull()).isNull() + assertThat(emptyList>().toBackStackScreenOrNull()).isNull() } @Test fun throwFromEmptyList() { - assertFailsWith { emptyList>().toBackStackScreen() } + assertFailsWith { emptyList>().toBackStackScreen() } } @Test fun fromList() { - assertThat(listOf(S(1), S(2), S(3)).toBackStackScreen()) - .isEqualTo(BackStackScreen(S(1), S(2), S(3))) + assertThat(listOf(FooScreen(1), FooScreen(2), FooScreen(3)).toBackStackScreen()) + .isEqualTo(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3))) } @Test fun fromListOrNull() { - assertThat(listOf(S(1), S(2), S(3)).toBackStackScreenOrNull()) - .isEqualTo(BackStackScreen(S(1), S(2), S(3))) + assertThat(listOf(FooScreen(1), FooScreen(2), FooScreen(3)).toBackStackScreenOrNull()) + .isEqualTo(BackStackScreen(FooScreen(1), FooScreen(2), FooScreen(3))) + } + + /** + * To reminds us why we want the `out` in `BackStackScreen`. + * Without this, using `BackStackScreen<*>` as `RenderingT` is not practical. + */ + @Test fun heterogenousPlusIsTolerable() { + val foo = BackStackScreen(FooScreen(1)) + val bar = BackStackScreen(BarScreen(1)) + val both = foo + bar + assertThat(both).isEqualTo(foo + bar) } }