Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces fun Screen.deepMap(Screen): Screen #1092

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.squareup.sample.container.panel

import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper

/**
* Show a scrim over some [content], which is invisible if [dimmed] is false,
Expand All @@ -12,6 +12,6 @@ import com.squareup.workflow1.ui.Wrapper
class ScrimScreen<C : Screen>(
override val content: C,
val dimmed: Boolean
) : Wrapper<Screen, C>, Screen {
) : ScreenWrapper<C>, Screen {
override fun <D : Screen> map(transform: (C) -> D) = ScrimScreen(transform(content), dimmed)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import com.squareup.workflow1.ui.AndroidScreen
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenViewFactory
import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromViewBinding
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper

@OptIn(WorkflowUiExperimentalApi::class)
data class TopAndBottomBarsScreen<T : Screen>(
override val content: T,
val topBar: ButtonBar? = null,
val bottomBar: ButtonBar? = null
) : AndroidScreen<TopAndBottomBarsScreen<T>>, Wrapper<Screen, T> {
) : AndroidScreen<TopAndBottomBarsScreen<T>>, ScreenWrapper<T> {
override fun <ContentU : Screen> map(transform: (T) -> ContentU) =
TopAndBottomBarsScreen(transform(content), topBar, bottomBar)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@file:OptIn(WorkflowUiExperimentalApi::class)

package com.squareup.workflow1.ui

import com.google.common.truth.Truth.assertThat
import com.squareup.workflow1.ui.container.BackStackScreen
import com.squareup.workflow1.ui.container.EnvironmentScreen
import com.squareup.workflow1.ui.container.withEnvironment
import org.junit.Test

internal class ScreenContainerTest {
object MyScreen : Screen

@Test
fun deepMapRecurses() {
val backStack = BackStackScreen(NamedScreen(MyScreen, "name"))

@Suppress("UNCHECKED_CAST")
val mappedBackStack = backStack
.deepMap { it.withEnvironment() } as BackStackScreen<NamedScreen<EnvironmentScreen<MyScreen>>>

assertThat(mappedBackStack.top.content.content).isSameInstanceAs(MyScreen)
}
}
18 changes: 17 additions & 1 deletion workflow-ui/core-android/api/core-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ public abstract interface class com/squareup/workflow1/ui/ViewStarter {
public abstract fun startView (Landroid/view/View;Lkotlin/jvm/functions/Function0;)V
}

public abstract interface class com/squareup/workflow1/ui/VisualFactory {
public abstract fun createOrNull (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/VisualHolder;
}

public abstract interface class com/squareup/workflow1/ui/VisualHolder {
public static final field Companion Lcom/squareup/workflow1/ui/VisualHolder$Companion;
public abstract fun getVisual ()Ljava/lang/Object;
public abstract fun update (Ljava/lang/Object;)Z
}

public final class com/squareup/workflow1/ui/VisualHolder$Companion {
public final fun invoke (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/VisualHolder;
}

public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/FrameLayout {
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down Expand Up @@ -339,7 +353,7 @@ public final class com/squareup/workflow1/ui/container/AsDialogHolderWithContent
public static final fun fixBackgroundAndDimming (Landroid/app/Dialog;)V
}

public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen, com/squareup/workflow1/ui/Wrapper {
public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen, com/squareup/workflow1/ui/ScreenWrapper {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
Expand All @@ -351,6 +365,8 @@ public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/sq
public fun getViewFactory ()Lcom/squareup/workflow1/ui/ScreenViewFactory;
public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackButtonScreen;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.squareup.workflow1.ui

@WorkflowUiExperimentalApi
public fun interface VisualFactory<ContextT, in RenderingT, VisualT> {
/**
* Given a ui model ([rendering]), creates a [VisualHolder] which pairs:
*
* - a native view system object of type [VisualT] -- a [visual][VisualHolder.visual]
* - an [update function][VisualHolder.update] to apply [RenderingT] instances to
* the new [VisualT] instance.
*
* This method must not call [VisualHolder.update], to ensure that callers have
* complete control over the lifecycle of the new [VisualT].
*
* @param getFactory can be used to make recursive calls to build VisualT
* instances for sub-parts of [rendering]
*/
public fun createOrNull(
rendering: RenderingT,
context: ContextT,
environment: ViewEnvironment,
getFactory: (ViewEnvironment) -> VisualFactory<ContextT, Any, VisualT>
): VisualHolder<RenderingT, VisualT>?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.squareup.workflow1.ui

@WorkflowUiExperimentalApi
public interface VisualHolder<in RenderingT, out VisualT> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like some of the kdoc for VisualFactory.createOrNull should be here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh oh. I think this and VisualHolder leaked in from an unrelated PR.

public val visual: VisualT

public fun update(rendering: RenderingT): Boolean

public companion object {
public operator fun <RenderingT, VisualT> invoke(
visual: VisualT,
onUpdate: (RenderingT) -> Unit
): VisualHolder<RenderingT, VisualT> {
return object : VisualHolder<RenderingT, VisualT> {
override val visual = visual

override fun update(rendering: RenderingT): Boolean {
onUpdate(rendering)
return true
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import com.squareup.workflow1.ui.AndroidScreen
import com.squareup.workflow1.ui.Compatible.Companion.keyFor
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenViewFactory
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper
import com.squareup.workflow1.ui.setBackHandler

/**
Expand All @@ -28,10 +28,9 @@ public class BackButtonScreen<C : Screen>(
public override val content: C,
public val shadow: Boolean = false,
public val onBackPressed: (() -> Unit)? = null
) : Wrapper<Screen, C>, AndroidScreen<BackButtonScreen<C>> {
) : ScreenWrapper<C>, AndroidScreen<BackButtonScreen<C>> {
// If they change the shadow value, we need to build a new view to reorder the handlers.
override val compatibilityKey: String = keyFor(content, "BackButtonScreen+shadow:$shadow")

override fun <D : Screen> map(transform: (C) -> D): BackButtonScreen<D> =
BackButtonScreen(transform(content), shadow, onBackPressed)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ internal class ScreenViewFactoryTest {
@OptIn(WorkflowUiExperimentalApi::class)
private class MyWrapper<C : Screen>(
override val content: C
) : Wrapper<Screen, C>, AndroidScreen<MyWrapper<C>> {
) : ScreenWrapper<C>, AndroidScreen<MyWrapper<C>> {
override fun <D : Screen> map(transform: (C) -> D) = MyWrapper(transform(content))
override val viewFactory = forWrapper<MyWrapper<C>, C>()
}
Expand Down
28 changes: 25 additions & 3 deletions workflow-ui/core-common/api/core-common.api
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public final class com/squareup/workflow1/ui/Named : com/squareup/workflow1/ui/C
public fun toString ()Ljava/lang/String;
}

public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper {
public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/ScreenWrapper {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/lang/String;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
public final fun component1 ()Lcom/squareup/workflow1/ui/Screen;
Expand All @@ -62,13 +62,32 @@ public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow
public fun hashCode ()I
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/NamedScreen;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/squareup/workflow1/ui/Screen {
}

public abstract interface class com/squareup/workflow1/ui/ScreenContainer : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen {
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
}

public final class com/squareup/workflow1/ui/ScreenContainerKt {
public static final fun deepMap (Lcom/squareup/workflow1/ui/Screen;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Screen;
}

public abstract interface class com/squareup/workflow1/ui/ScreenWrapper : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/ScreenContainer, com/squareup/workflow1/ui/Wrapper {
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
}

public final class com/squareup/workflow1/ui/ScreenWrapper$DefaultImpls {
public static fun asSequence (Lcom/squareup/workflow1/ui/ScreenWrapper;)Lkotlin/sequences/Sequence;
public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/ScreenWrapper;)Ljava/lang/String;
}

public abstract interface class com/squareup/workflow1/ui/TextController {
public abstract fun getOnTextChanged ()Lkotlinx/coroutines/flow/Flow;
public abstract fun getTextValue ()Ljava/lang/String;
Expand Down Expand Up @@ -214,7 +233,7 @@ public final class com/squareup/workflow1/ui/container/BackStackConfigKt {
public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/container/BackStackConfig;)Lcom/squareup/workflow1/ui/ViewEnvironment;
}

public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen {
public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/ScreenContainer {
public static final field Companion Lcom/squareup/workflow1/ui/container/BackStackScreen$Companion;
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V
public fun <init> (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/Screen;)V
Expand All @@ -226,6 +245,7 @@ public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squ
public final fun getTop ()Lcom/squareup/workflow1/ui/Screen;
public fun hashCode ()I
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
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 fun toString ()Ljava/lang/String;
Expand Down Expand Up @@ -255,7 +275,7 @@ public final class com/squareup/workflow1/ui/container/BodyAndOverlaysScreen : c
public final fun mapOverlays (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen;
}

public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper {
public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/ScreenWrapper {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
Expand All @@ -265,6 +285,8 @@ public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/s
public final fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment;
public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/EnvironmentScreen;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package com.squareup.workflow1.ui
public data class NamedScreen<out C : Screen>(
override val content: C,
val name: String
) : Screen, Wrapper<Screen, C> {
) : ScreenWrapper<C> {
init {
require(name.isNotBlank()) { "name must not be blank." }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.squareup.workflow1.ui

@WorkflowUiExperimentalApi
public interface ScreenContainer<out C : Screen> : Container<Screen, C>, Screen {
public override fun <D : Screen> map(transform: (C) -> D): ScreenContainer<D>
}

@WorkflowUiExperimentalApi
public interface ScreenWrapper<out C : Screen> : ScreenContainer<C>, Wrapper<Screen, C>, Screen {
public override fun <D : Screen> map(transform: (C) -> D): ScreenWrapper<D>
}

/**
* Applies [transform] to the receiver unless it is a [ScreenContainer]. In that case,
* makes a recursive call to [ScreenContainer.map] and applies [deepMap] to its
* contents.
*
* For example, consider this snippet:
*
* val backStack = BackStackScreen(SomeWrapper(theRealScreen))
* val loggingBackStack = backStack.deepMap { WithLogging(it) }
*
* `loggingBackStack` will have a structure like so:
*
* BackStackScreen(SomeWrapper(WithLogging(theRealScreen)))
*/
@WorkflowUiExperimentalApi
public fun Screen.deepMap(transform: (Screen) -> Screen): Screen {
return if (this is ScreenContainer<*>) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know what? I don't know that ScreenContainer is actually necessary for this. I'm going to put this PR back into draft.

  • ScreenContainer probably isn't necessary.

map { it.deepMap(transform) }
} else {
transform(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.squareup.workflow1.ui.container

import com.squareup.workflow1.ui.Container
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenContainer
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.container.BackStackScreen.Companion
import com.squareup.workflow1.ui.container.BackStackScreen.Companion.fromList
Expand All @@ -22,7 +22,7 @@ import com.squareup.workflow1.ui.container.BackStackScreen.Companion.fromListOrN
@WorkflowUiExperimentalApi
public class BackStackScreen<out StackedT : Screen> internal constructor(
public val frames: List<StackedT>
) : Screen, Container<Screen, StackedT> {
) : ScreenContainer<StackedT> {
/**
* Creates a screen with elements listed from the [bottom] to the top.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.squareup.workflow1.ui.container

import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.ViewEnvironment
import com.squareup.workflow1.ui.ViewRegistry
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper
import com.squareup.workflow1.ui.plus

/**
Expand All @@ -19,7 +19,7 @@ import com.squareup.workflow1.ui.plus
public class EnvironmentScreen<out C : Screen>(
public override val content: C,
public val environment: ViewEnvironment = ViewEnvironment.EMPTY
) : Wrapper<Screen, C>, Screen {
) : ScreenWrapper<C> {
override fun <D : Screen> map(transform: (C) -> D): EnvironmentScreen<D> =
EnvironmentScreen(transform(content), environment)

Expand Down Expand Up @@ -58,6 +58,7 @@ public fun Screen.withEnvironment(
EnvironmentScreen(content, this.environment + environment)
}
}

else -> EnvironmentScreen(this, environment)
}
}
Loading