[go: nahoru, domu]

Replace Drawer with DrawerLayout.

Given the fact that  Stack of content + Drawer won't work, we need to
provide some Scaffold-like component that will take ownership for
gestures so both Drawer and AppContent will receive gestures.

So, (Modal/Drawer)DrawerLayout is introduced in this CL.

Bug: 136986760
Test: ./gradlew ui:ui-material:connectedcheck + two tests to ensure
content is clickable
Change-Id: I27d0416a9035d296f951cb4ec92ee0f177b1067b
diff --git a/ui/ui-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -94,8 +94,8 @@
 
   public final class DrawerKt {
     ctor public DrawerKt();
-    method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
-    method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+    method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
   }
 
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -94,8 +94,8 @@
 
   public final class DrawerKt {
     ctor public DrawerKt();
-    method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
-    method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+    method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
   }
 
diff --git a/ui/ui-material/api/restricted_1.0.0-alpha01.txt b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -94,8 +94,8 @@
 
   public final class DrawerKt {
     ctor public DrawerKt();
-    method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
-    method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+    method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
   }
 
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index f062c9e..e3fa077 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -94,8 +94,8 @@
 
   public final class DrawerKt {
     ctor public DrawerKt();
-    method public static void BottomDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
-    method public static void ModalDrawer(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
+    method public static void BottomDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
+    method public static void ModalDrawerLayout(androidx.ui.material.DrawerState drawerState, kotlin.jvm.functions.Function1<? super androidx.ui.material.DrawerState,kotlin.Unit> onStateChange, kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, kotlin.jvm.functions.Function0<kotlin.Unit> bodyContent);
     method public static void StaticDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent);
   }
 
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt
index 932b0c7..9572048 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/DrawerSamples.kt
@@ -25,17 +25,15 @@
 import androidx.ui.core.dp
 import androidx.ui.foundation.ColoredRect
 import androidx.ui.graphics.Color
-import androidx.ui.layout.Alignment
 import androidx.ui.layout.Center
 import androidx.ui.layout.Column
+import androidx.ui.layout.Container
 import androidx.ui.layout.HeightSpacer
 import androidx.ui.layout.Row
-import androidx.ui.layout.Stack
-import androidx.ui.material.BottomDrawer
+import androidx.ui.material.BottomDrawerLayout
 import androidx.ui.material.Button
 import androidx.ui.material.DrawerState
-import androidx.ui.material.MaterialTheme
-import androidx.ui.material.ModalDrawer
+import androidx.ui.material.ModalDrawerLayout
 import androidx.ui.material.StaticDrawer
 
 @Sampled
@@ -58,23 +56,12 @@
     val (state, onStateChange) = +state { DrawerState.Closed }
     val appContentText =
         if (state == DrawerState.Closed) ">>> Pull to open >>>" else "<<< Swipe to close <<<"
-    Stack {
-        aligned(Alignment.Center) {
-            // your app content goes there
-            YourAppContent(appContentText, onStateChange)
-        }
-        aligned(Alignment.CenterLeft) {
-            ModalDrawer(state, onStateChange) {
-                Column {
-                    Text(text = "Drawer Content")
-                    HeightSpacer(20.dp)
-                    Button(
-                        text = "Close Drawer",
-                         onStateChange(DrawerState.Closed) })
-                }
-            }
-        }
-    }
+    ModalDrawerLayout(
+        drawerState = state,
+        >
+        drawerContent = { YourDrawerContent(onStateChange) },
+        bodyContent = { YourAppContent(appContentText, onStateChange) }
+    )
 }
 
 @Sampled
@@ -83,33 +70,37 @@
     val (state, onStateChange) = +state { DrawerState.Closed }
     val appContentText =
         if (state == DrawerState.Closed) "▲▲▲ Pull to open ▲▲▲" else "▼▼▼ Drag down to close ▼▼▼"
-    Stack {
-        aligned(Alignment.Center) {
-            // your app content goes there
-            YourAppContent(appContentText, onStateChange)
-        }
-        aligned(Alignment.BottomCenter) {
-            BottomDrawer(state, onStateChange) {
-                Column {
-                    Text(text = "Drawer Content")
-                    HeightSpacer(20.dp)
-                    Button(
-                        text = "Close Drawer",
-                         onStateChange(DrawerState.Closed) })
-                }
-            }
+    BottomDrawerLayout(
+        drawerState = state,
+        >
+        drawerContent = { YourDrawerContent(onStateChange) },
+        bodyContent = { YourAppContent(appContentText, onStateChange) }
+    )
+}
+
+@Composable
+private fun YourDrawerContent(onStateChange: (DrawerState) -> Unit) {
+    Container(expanded = true) {
+        Column {
+            Text(text = "Drawer Content")
+            HeightSpacer(20.dp)
+            Button(
+                text = "Close Drawer",
+                 onStateChange(DrawerState.Closed) })
         }
     }
 }
 
 @Composable
 private fun YourAppContent(text: String, onDrawerStateChange: (DrawerState) -> Unit) {
-    Column {
-        Text(text = text)
-        HeightSpacer(20.dp)
-        Button(
-            text = "Click to open",
-             onDrawerStateChange(DrawerState.Opened) }
-        )
+    Center {
+        Column {
+            Text(text = text)
+            HeightSpacer(20.dp)
+            Button(
+                text = "Click to open",
+                 onDrawerStateChange(DrawerState.Opened) }
+            )
+        }
     }
 }
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt
index 32e5cca..ef366be 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/DrawerTest.kt
@@ -16,16 +16,22 @@
 
 package androidx.ui.material
 
+import androidx.compose.Model
 import androidx.compose.composer
 import androidx.test.filters.MediumTest
 import androidx.ui.core.OnPositioned
 import androidx.ui.core.PxPosition
 import androidx.ui.core.PxSize
+import androidx.ui.core.TestTag
 import androidx.ui.core.dp
 import androidx.ui.core.round
 import androidx.ui.core.withDensity
+import androidx.ui.foundation.Clickable
 import androidx.ui.layout.Container
+import androidx.ui.semantics.Semantics
 import androidx.ui.test.createComposeRule
+import androidx.ui.test.doClick
+import androidx.ui.test.findByTag
 import com.google.common.truth.Truth
 import org.junit.Rule
 import org.junit.Test
@@ -33,24 +39,27 @@
 import org.junit.runners.JUnit4
 import kotlin.math.roundToInt
 
+@Model
+data class DrawerStateHolder(var state: DrawerState)
+
 @MediumTest
 @RunWith(JUnit4::class)
 class DrawerTest {
 
     @get:Rule
-    val composeTestRule = createComposeRule()
+    val composeTestRule = createComposeRule(disableTransitions = true)
 
     @Test
     fun modalDrawer_testOffset_whenOpened() {
         var position: PxPosition? = null
         composeTestRule.setMaterialContent {
-            ModalDrawer(DrawerState.Opened, {}) {
+            ModalDrawerLayout(DrawerState.Opened, {}, drawerContent = {
                 Container(expanded = true) {
                     OnPositioned { coords ->
                         position = coords.localToGlobal(PxPosition.Origin)
                     }
                 }
-            }
+            }) {}
         }
         Truth.assertThat(position!!.x.value).isEqualTo(0f)
     }
@@ -59,13 +68,13 @@
     fun modalDrawer_testOffset_whenClosed() {
         var position: PxPosition? = null
         composeTestRule.setMaterialContent {
-            ModalDrawer(DrawerState.Closed, {}) {
+            ModalDrawerLayout(DrawerState.Closed, {}, drawerContent = {
                 Container(expanded = true) {
                     OnPositioned { coords ->
                         position = coords.localToGlobal(PxPosition.Origin)
                     }
                 }
-            }
+            }) {}
         }
         val width = composeTestRule.displayMetrics.widthPixels
         Truth.assertThat(position!!.x.round().value).isEqualTo(-width)
@@ -75,13 +84,13 @@
     fun modalDrawer_testEndPadding_whenOpened() {
         var size: PxSize? = null
         composeTestRule.setMaterialContent {
-            ModalDrawer(DrawerState.Opened, {}) {
+            ModalDrawerLayout(DrawerState.Opened, {}, drawerContent = {
                 Container(expanded = true) {
                     OnPositioned { coords ->
                         size = coords.size
                     }
                 }
-            }
+            }) {}
         }
 
         val width = composeTestRule.displayMetrics.widthPixels
@@ -95,13 +104,13 @@
     fun bottomDrawer_testOffset_whenOpened() {
         var position: PxPosition? = null
         composeTestRule.setMaterialContent {
-            BottomDrawer(DrawerState.Opened, {}) {
+            BottomDrawerLayout(DrawerState.Opened, {}, drawerContent = {
                 Container(expanded = true) {
                     OnPositioned { coords ->
                         position = coords.localToGlobal(PxPosition.Origin)
                     }
                 }
-            }
+            }) {}
         }
         val width = composeTestRule.displayMetrics.widthPixels
         val height = composeTestRule.displayMetrics.heightPixels
@@ -114,13 +123,13 @@
     fun bottomDrawer_testOffset_whenClosed() {
         var position: PxPosition? = null
         composeTestRule.setMaterialContent {
-            BottomDrawer(DrawerState.Closed, {}) {
+            BottomDrawerLayout(DrawerState.Closed, {}, drawerContent = {
                 Container(expanded = true) {
                     OnPositioned { coords ->
                         position = coords.localToGlobal(PxPosition.Origin)
                     }
                 }
-            }
+            }) {}
         }
         val height = composeTestRule.displayMetrics.heightPixels
         Truth.assertThat(position!!.y.round().value).isEqualTo(height)
@@ -136,4 +145,93 @@
             }
             .assertWidthEqualsTo(256.dp)
     }
+
+    @Test
+    fun modalDrawer_bodyContent_clickable() {
+        var drawerClicks = 0
+        var bodyClicks = 0
+        val drawerState = DrawerStateHolder(DrawerState.Closed)
+        composeTestRule.setMaterialContent {
+            //emulate click on the screen
+            TestTag("Drawer") {
+                Semantics(container = true) {
+                    ModalDrawerLayout(drawerState.state, { drawerState.state = it },
+                        drawerContent = {
+                            Clickable( drawerClicks += 1 }) {
+                                Container(expanded = true) {}
+                            }
+                        },
+                        bodyContent = {
+                            Clickable( bodyClicks += 1 }) {
+                                Container(expanded = true) {}
+                            }
+                        })
+                }
+            }
+        }
+
+        findByTag("Drawer")
+            .doClick()
+
+        Truth.assertThat(drawerClicks).isEqualTo(0)
+        Truth.assertThat(bodyClicks).isEqualTo(1)
+
+        composeTestRule.runOnUiThread {
+            drawerState.state = DrawerState.Opened
+        }
+        // TODO: we aren't correctly waiting for recompositions after clicking, so we need to wait
+        // again
+        Thread.sleep(100L)
+
+        findByTag("Drawer")
+            .doClick()
+
+
+        Truth.assertThat(drawerClicks).isEqualTo(1)
+        Truth.assertThat(bodyClicks).isEqualTo(1)
+    }
+
+    @Test
+    fun bottomDrawer_bodyContent_clickable() {
+        var drawerClicks = 0
+        var bodyClicks = 0
+        val drawerState = DrawerStateHolder(DrawerState.Closed)
+        composeTestRule.setMaterialContent {
+            //emulate click on the screen
+            TestTag("Drawer") {
+                Semantics(container = true) {
+                    BottomDrawerLayout(drawerState.state, { drawerState.state = it },
+                        drawerContent = {
+                            Clickable( drawerClicks += 1 }) {
+                                Container(expanded = true) {}
+                            }
+                        },
+                        bodyContent = {
+                            Clickable( bodyClicks += 1 }) {
+                                Container(expanded = true) {}
+                            }
+                        })
+                }
+            }
+        }
+
+        findByTag("Drawer")
+            .doClick()
+
+        Truth.assertThat(drawerClicks).isEqualTo(0)
+        Truth.assertThat(bodyClicks).isEqualTo(1)
+
+        // TODO (malkov/pavlis) : uncomment this when custom onClick location will be implemented
+//        composeTestRule.runOnUiThread {
+//            drawerState.state = DrawerState.Opened
+//        }
+//        Thread.sleep(100L)
+//
+//        findByTag("Drawer")
+//            .doClick()
+//
+//
+//        Truth.assertThat(drawerClicks).isEqualTo(1)
+//        Truth.assertThat(bodyClicks).isEqualTo(1)
+    }
 }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 7e5e527..e5a5cf0 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -22,7 +22,6 @@
 import androidx.compose.memo
 import androidx.compose.onCommit
 import androidx.compose.unaryPlus
-import androidx.ui.animation.animatedFloat
 import androidx.ui.core.Dp
 import androidx.ui.core.IntPx
 import androidx.ui.core.Layout
@@ -37,9 +36,9 @@
 import androidx.ui.foundation.Clickable
 import androidx.ui.foundation.ColoredRect
 import androidx.ui.foundation.animation.AnchorsFlingConfig
-import androidx.ui.foundation.gestures.Draggable
 import androidx.ui.foundation.animation.AnimatedFloatDragController
 import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.Draggable
 import androidx.ui.foundation.gestures.DraggableCallback
 import androidx.ui.layout.Alignment
 import androidx.ui.layout.Container
@@ -72,7 +71,7 @@
  * at the same time. They can be used on tablet and desktop,
  * but they are not suitable for mobile due to limited screen size.
  *
- * @see [ModalDrawer] and [BottomDrawer] for more mobile friendly options
+ * See [ModalDrawerLayout] and [BottomDrawerLayout] for more mobile friendly options.
  *
  * @sample androidx.ui.material.samples.StaticDrawerSample
  *
@@ -93,8 +92,9 @@
  * Modal navigation drawers block interaction with the rest of an app’s content with a scrim.
  * They are elevated above most of the app’s UI and don’t affect the screen’s layout grid.
  *
- * @see [StaticDrawer] for always visible drawer, suitable for tables or desktop
- * @see [BottomDrawer] for drawer that is recommended when you have bottom navigation
+ * See [StaticDrawer] for always visible drawer, suitable for tablet or desktop.
+ * See [BottomDrawerLayout] for a layout that introduces a bottom drawer, suitable when
+ * using bottom navigation.
  *
  * @sample androidx.ui.material.samples.ModalDrawerSample
  *
@@ -102,14 +102,16 @@
  * @param onStateChange lambda to be invoked when the drawer requests to change its state,
  * e.g. when the drawer is being swiped to the new state or when the scrim is clicked
  * @param drawerContent composable that represents content inside the drawer
+ * @param bodyContent content of the rest of the UI
  *
  * @throws IllegalStateException when parent has [Px.Infinity] width
  */
 @Composable
-fun ModalDrawer(
+fun ModalDrawerLayout(
     drawerState: DrawerState,
     onStateChange: (DrawerState) -> Unit,
-    drawerContent: @Composable() () -> Unit
+    drawerContent: @Composable() () -> Unit,
+    bodyContent: @Composable() () -> Unit
 ) {
     Container(expanded = true) {
         WithConstraints { pxConstraints ->
@@ -148,6 +150,7 @@
 
                 Stack {
                     aligned(Alignment.TopLeft) {
+                        bodyContent()
                         Scrim(drawerState, onStateChange, scrimAlpha)
                         DrawerContent(dpOffset, constraints, drawerContent)
                     }
@@ -167,8 +170,8 @@
  * These drawers open upon tapping the navigation menu icon in the bottom app bar.
  * They are only for use on mobile.
  *
- * @see [StaticDrawer] for always visible drawer, suitable for tables or desktop
- * @see [ModalDrawer] for classic "from the side" drawer
+ * See [StaticDrawer] for always visible drawer, suitable for tablet or desktop
+ * See [ModalDrawerLayout] for a layout that introduces a classic from-the-side drawer.
  *
  * @sample androidx.ui.material.samples.BottomDrawerSample
  *
@@ -176,14 +179,16 @@
  * @param onStateChange lambda to be invoked when the drawer requests to change its state,
  * e.g. when the drawer is being swiped to the new state or when the scrim is clicked
  * @param drawerContent composable that represents content inside the drawer
+ * @param bodyContent content of the rest of the UI
  *
  * @throws IllegalStateException when parent has [Px.Infinity] height
  */
 @Composable
-fun BottomDrawer(
+fun BottomDrawerLayout(
     drawerState: DrawerState,
     onStateChange: (DrawerState) -> Unit,
-    drawerContent: @Composable() () -> Unit
+    drawerContent: @Composable() () -> Unit,
+    bodyContent: @Composable() () -> Unit
 ) {
     Container(expanded = true) {
         WithConstraints { pxConstraints ->
@@ -233,6 +238,7 @@
                 }
                 Stack {
                     aligned(Alignment.TopLeft) {
+                        bodyContent()
                         Scrim(drawerState, onStateChange, scrimAlpha)
                         BottomDrawerContent(dpOffset, constraints, drawerContent)
                     }