diff --git a/build.gradle.kts b/build.gradle.kts index 136075093..aa928b9f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,7 +73,7 @@ subprojects { if (file("src/${dokkaSourceSet.name}").exists()) { val readmeFile = file("$projectDir/README.md") - // If the module has a README, add it to the the module's index + // If the module has a README, add it to the module's index if (readmeFile.exists()) { includes.from(readmeFile) } 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 b9368f559..f4de422e8 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,6 +6,9 @@ import com.squareup.workflow1.ui.container.ModalOverlay import com.squareup.workflow1.ui.container.ScreenOverlay @OptIn(WorkflowUiExperimentalApi::class) -class PanelOverlay( - override val content: T -) : ScreenOverlay, ModalOverlay +class PanelOverlay( + override val content: C +) : ScreenOverlay, ModalOverlay { + override fun map(transform: (C) -> D): PanelOverlay = + PanelOverlay(transform(content)) +} diff --git a/samples/nested-overlays/build.gradle.kts b/samples/nested-overlays/build.gradle.kts new file mode 100644 index 000000000..7d31f7a46 --- /dev/null +++ b/samples/nested-overlays/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + id("com.android.application") + `kotlin-android` + `android-sample-app` + `android-ui-tests` +} + +android { + defaultConfig { + applicationId = "com.squareup.sample.nestedoverlays" + } + namespace = "com.squareup.sample.nestedoverlays" +} + +dependencies { + debugImplementation(libs.squareup.leakcanary.android) + + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.lifecycle.viewmodel.savedstate) + implementation(libs.androidx.viewbinding) + + implementation(project(":workflow-ui:core-android")) + implementation(project(":workflow-ui:core-common")) +} diff --git a/samples/nested-overlays/lint-baseline.xml b/samples/nested-overlays/lint-baseline.xml new file mode 100644 index 000000000..4aec7fcd5 --- /dev/null +++ b/samples/nested-overlays/lint-baseline.xml @@ -0,0 +1,4 @@ + + + + diff --git a/samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt b/samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt new file mode 100644 index 000000000..c616d62a1 --- /dev/null +++ b/samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt @@ -0,0 +1,99 @@ +package com.squareup.sample.nestedoverlays + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withParent +import androidx.test.espresso.matcher.ViewMatchers.withParentIndex +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule +import leakcanary.DetectLeaksAfterTestSuccess +import org.hamcrest.core.AllOf.allOf +import org.hamcrest.core.IsNot.not +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NestedOverlaysAppTest { + + private val scenarioRule = ActivityScenarioRule(NestedOverlaysActivity::class.java) + + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + .around(scenarioRule) + .around(IdlingDispatcherRule) + + @Test fun basics() { + onTopCoverBody().assertDisplayed() + onTopCoverEverything().assertDisplayed() + onBottomCoverBody().assertDisplayed() + onBottomCoverEverything().assertDisplayed() + + onTopCoverBody().perform(click()) + onView(withText("Close")).perform(click()) + onTopCoverEverything().perform(click()) + onView(withText("Close")).perform(click()) + + onView(withText("Hide Top Bar")).perform(click()) + onTopCoverBody().assertNotDisplayed() + onTopCoverEverything().assertNotDisplayed() + onBottomCoverBody().assertDisplayed() + onBottomCoverEverything().assertDisplayed() + + onView(withText("Hide Bottom Bar")).perform(click()) + onTopCoverBody().assertNotDisplayed() + onTopCoverEverything().assertNotDisplayed() + onBottomCoverBody().assertNotDisplayed() + onBottomCoverEverything().assertNotDisplayed() + } + + // https://github.com/square/workflow-kotlin/issues/966 + @Test fun canInsertDialog() { + onTopCoverEverything().perform(click()) + onView(withText("Hide Top Bar")).check(doesNotExist()) + onView(withText("Cover Body")).perform(click()) + + // This line fails due to https://github.com/square/workflow-kotlin/issues/966 + // onView(withText("Hide Top Bar")).check(doesNotExist()) + + // Should continue to close the top sheet and assert that the inner sheet is visible. + } + + // So far can't express this in Espresso. Considering move to Maestro + // @Test fun canClickPastInnerWindow() { + // onView(allOf(withText("Cover Everything"), withParent(withParentIndex(0)))) + // .perform(click()) + // + // scenario.onActivity { activity -> + // onView(allOf(withText("Cover Everything"), withParent(withParentIndex(0)))) + // .inRoot(withDecorView(not(`is`(activity.window.decorView)))) + // .perform(click()) + // } + // } + + private fun ViewInteraction.assertNotDisplayed() { + check(matches(not(isDisplayed()))) + } + + private fun ViewInteraction.assertDisplayed() { + check(matches(isDisplayed())) + } + + private fun onBottomCoverEverything() = + onView(allOf(withText("Cover Everything"), withParent(withParentIndex(2)))) + + private fun onBottomCoverBody() = + onView(allOf(withText("Cover Body"), withParent(withParentIndex(2)))) + + private fun onTopCoverBody() = + onView(allOf(withText("Cover Body"), withParent(withParentIndex(0)))) + + private fun onTopCoverEverything() = + onView(allOf(withText("Cover Everything"), withParent(withParentIndex(0)))) +} diff --git a/samples/nested-overlays/src/main/AndroidManifest.xml b/samples/nested-overlays/src/main/AndroidManifest.xml new file mode 100644 index 000000000..594e1fde1 --- /dev/null +++ b/samples/nested-overlays/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt b/samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt new file mode 100644 index 000000000..37f9f7726 --- /dev/null +++ b/samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt @@ -0,0 +1,53 @@ +package com.squareup.sample.nestedoverlays + +import android.graphics.drawable.ColorDrawable +import android.view.Gravity +import android.widget.LinearLayout +import androidx.annotation.ColorRes +import androidx.annotation.StringRes +import androidx.core.view.get +import com.squareup.workflow1.ui.AndroidScreen +import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import android.widget.Button as ButtonView + +data class Button( + @StringRes val name: Int, + val onClick: () -> Unit +) + +@OptIn(WorkflowUiExperimentalApi::class) +class ButtonBar( + vararg buttons: Button?, + @ColorRes val color: Int = -1, +) : AndroidScreen { + private val buttons: List