[go: nahoru, domu]

Adds BottomAppBar and updates TopAppBar

Bug: b/135743820
Test: AppBarTest
Change-Id: Icaaf9d7a137bd76cc8bcccd9b3fa33013b7c11cc
diff --git a/ui/ui-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index e385709..17ead5a 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -3,16 +3,23 @@
 
   public final class AppBarKt {
     ctor public AppBarKt();
-    method public static void AppBar(androidx.ui.graphics.Color color, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method public static void AppBarLeadingIcon();
-    method public static void TopAppBar(String? title = null, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void AppBarIcon(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static <T> void BottomAppBar(androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), java.util.List<androidx.ui.core.Dp> icons = emptyList());
-    method public static void TopAppBar(androidx.ui.graphics.Color color = +themeColor({ 
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+    method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> leadingIcon, kotlin.jvm.functions.Function0<kotlin.Unit> titleTextLabel, kotlin.jvm.functions.Function0<kotlin.Unit> trailingIcons);
-    method public static void TopAppBarTitleTextLabel(String title);
-    method public static void TopAppBarTrailingIcons(java.util.List<androidx.ui.core.Dp> icons);
+}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+  }
+
+  public final class BottomAppBar {
+    field public static final androidx.ui.material.BottomAppBar! INSTANCE;
+  }
+
+  public enum BottomAppBar.FabPosition {
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition Center;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition CenterCut;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition End;
   }
 
   public final class ButtonKt {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index e385709..17ead5a 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -3,16 +3,23 @@
 
   public final class AppBarKt {
     ctor public AppBarKt();
-    method public static void AppBar(androidx.ui.graphics.Color color, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method public static void AppBarLeadingIcon();
-    method public static void TopAppBar(String? title = null, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void AppBarIcon(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static <T> void BottomAppBar(androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), java.util.List<androidx.ui.core.Dp> icons = emptyList());
-    method public static void TopAppBar(androidx.ui.graphics.Color color = +themeColor({ 
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+    method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> leadingIcon, kotlin.jvm.functions.Function0<kotlin.Unit> titleTextLabel, kotlin.jvm.functions.Function0<kotlin.Unit> trailingIcons);
-    method public static void TopAppBarTitleTextLabel(String title);
-    method public static void TopAppBarTrailingIcons(java.util.List<androidx.ui.core.Dp> icons);
+}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+  }
+
+  public final class BottomAppBar {
+    field public static final androidx.ui.material.BottomAppBar! INSTANCE;
+  }
+
+  public enum BottomAppBar.FabPosition {
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition Center;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition CenterCut;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition End;
   }
 
   public final class ButtonKt {
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 e385709..17ead5a 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -3,16 +3,23 @@
 
   public final class AppBarKt {
     ctor public AppBarKt();
-    method public static void AppBar(androidx.ui.graphics.Color color, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method public static void AppBarLeadingIcon();
-    method public static void TopAppBar(String? title = null, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void AppBarIcon(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static <T> void BottomAppBar(androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), java.util.List<androidx.ui.core.Dp> icons = emptyList());
-    method public static void TopAppBar(androidx.ui.graphics.Color color = +themeColor({ 
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+    method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> leadingIcon, kotlin.jvm.functions.Function0<kotlin.Unit> titleTextLabel, kotlin.jvm.functions.Function0<kotlin.Unit> trailingIcons);
-    method public static void TopAppBarTitleTextLabel(String title);
-    method public static void TopAppBarTrailingIcons(java.util.List<androidx.ui.core.Dp> icons);
+}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+  }
+
+  public final class BottomAppBar {
+    field public static final androidx.ui.material.BottomAppBar! INSTANCE;
+  }
+
+  public enum BottomAppBar.FabPosition {
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition Center;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition CenterCut;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition End;
   }
 
   public final class ButtonKt {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index e385709..17ead5a 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -3,16 +3,23 @@
 
   public final class AppBarKt {
     ctor public AppBarKt();
-    method public static void AppBar(androidx.ui.graphics.Color color, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method public static void AppBarLeadingIcon();
-    method public static void TopAppBar(String? title = null, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void AppBarIcon(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static <T> void BottomAppBar(androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), java.util.List<androidx.ui.core.Dp> icons = emptyList());
-    method public static void TopAppBar(androidx.ui.graphics.Color color = +themeColor({ 
+}), kotlin.jvm.functions.Function0<kotlin.Unit>? navigationIcon = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton = (kotlin.jvm.functions.Function0<? extends kotlin.Unit>)null, androidx.ui.material.BottomAppBar.FabPosition fabPosition = Center, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+    method public static <T> void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title = {}, androidx.ui.graphics.Color color = +themeColor({ 
     primary
-}), kotlin.jvm.functions.Function0<kotlin.Unit> leadingIcon, kotlin.jvm.functions.Function0<kotlin.Unit> titleTextLabel, kotlin.jvm.functions.Function0<kotlin.Unit> trailingIcons);
-    method public static void TopAppBarTitleTextLabel(String title);
-    method public static void TopAppBarTrailingIcons(java.util.List<androidx.ui.core.Dp> icons);
+}), kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon = {}, java.util.List<? extends T>? contextualActions = null, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action = {});
+  }
+
+  public final class BottomAppBar {
+    field public static final androidx.ui.material.BottomAppBar! INSTANCE;
+  }
+
+  public enum BottomAppBar.FabPosition {
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition Center;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition CenterCut;
+    enum_constant public static final androidx.ui.material.BottomAppBar.FabPosition End;
   }
 
   public final class ButtonKt {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
index 0efb2ba..bc7ec4d 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
@@ -26,11 +26,15 @@
 import androidx.ui.core.Text
 import androidx.ui.core.dp
 import androidx.ui.layout.Column
+import androidx.ui.layout.FlexColumn
 import androidx.ui.layout.HeightSpacer
 import androidx.ui.material.MaterialTheme
-import androidx.ui.material.TopAppBar
+import androidx.ui.material.samples.SimpleBottomAppBarCenterFab
+import androidx.ui.material.samples.SimpleBottomAppBarEndFab
+import androidx.ui.material.samples.SimpleBottomAppBarNoFab
+import androidx.ui.material.samples.SimpleTopAppBar
 import androidx.ui.material.themeTextStyle
-import androidx.ui.graphics.Color
+import androidx.ui.painting.imageFromResource
 
 class AppBarActivity : Activity() {
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -38,25 +42,39 @@
         setContent {
             CraneWrapper {
                 MaterialTheme {
-                    AppBarDemo()
+                    val favouriteImage = { imageFromResource(resources, R.drawable.ic_favorite) }
+                    val navigationImage = { imageFromResource(resources, R.drawable.ic_menu) }
+                    FlexColumn {
+                        expanded(1f) {
+                            Column {
+                                SpacedText("TopAppBar")
+                                HeightSpacer(height = 28.dp)
+                                SimpleTopAppBar(favouriteImage, navigationImage)
+                            }
+                            Column {
+                                SpacedText("BottomAppBar - No FAB")
+                                HeightSpacer(height = 28.dp)
+                                SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
+                            }
+                            Column {
+                                SpacedText("BottomAppBar - Center FAB")
+                                SimpleBottomAppBarCenterFab(favouriteImage, navigationImage)
+                            }
+                            Column {
+                                SpacedText("BottomAppBar - End FAB")
+                                SimpleBottomAppBarEndFab(favouriteImage)
+                            }
+                        }
+                    }
                 }
             }
         }
     }
-}
 
-@Composable
-fun AppBarDemo() {
-    Column {
-        TopAppBar(title = "Default")
-        HeightSpacer(height = 24.dp)
-        TopAppBar(title = "Custom color", color = Color(0xFFE91E63.toInt()))
-        HeightSpacer(height = 24.dp)
-        TopAppBar(title = "Custom icons", icons = listOf(24.dp, 24.dp))
+    @Composable
+    private fun SpacedText(text: String) {
         HeightSpacer(height = 12.dp)
-        Text(text = "No title", style = +themeTextStyle { h6 })
-        TopAppBar(icons = listOf(24.dp, 24.dp))
-        HeightSpacer(height = 24.dp)
-        TopAppBar(title = "Too many icons", icons = listOf(24.dp, 24.dp, 24.dp, 24.dp))
+        Text(text, style = +themeTextStyle { h6 })
+        HeightSpacer(height = 12.dp)
     }
-}
\ No newline at end of file
+}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-hdpi/ic_menu.png b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-hdpi/ic_menu.png
new file mode 100644
index 0000000..ba90d6d
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-hdpi/ic_menu.png
Binary files differ
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-mdpi/ic_menu.png b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-mdpi/ic_menu.png
new file mode 100644
index 0000000..05e063c
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-mdpi/ic_menu.png
Binary files differ
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-xhdpi/ic_menu.png b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-xhdpi/ic_menu.png
new file mode 100644
index 0000000..ad9bab1
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-xhdpi/ic_menu.png
Binary files differ
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-xxhdpi/ic_menu.png b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-xxhdpi/ic_menu.png
new file mode 100644
index 0000000..c43059e
--- /dev/null
+++ b/ui/ui-material/integration-tests/material-demos/src/main/res/drawable-xxhdpi/ic_menu.png
Binary files differ
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
new file mode 100644
index 0000000..369ca53
--- /dev/null
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/AppBarSamples.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.ui.core.Text
+import androidx.ui.graphics.Color
+import androidx.ui.material.AppBarIcon
+import androidx.ui.material.BottomAppBar
+import androidx.ui.material.FloatingActionButton
+import androidx.ui.material.TopAppBar
+import androidx.ui.painting.Image
+
+@Suppress("UNUSED_VARIABLE")
+@Sampled
+@Composable
+fun SimpleTopAppBar(getMyActionImage: () -> Image, getMyNavigationImage: () -> Image) {
+    val someActionImage: Image = getMyActionImage()
+    val someNavigationImage: Image = getMyNavigationImage()
+
+    val navigationIcon: @Composable() () -> Unit = {
+        AppBarIcon(someNavigationImage) { /* doSomething()*/ }
+    }
+    val contextualActions = listOf("Action 1" to someActionImage, "action 2" to someActionImage)
+
+    TopAppBar(
+        title = { Text("Simple TopAppBar") },
+        navigationIcon = navigationIcon,
+        contextualActions = contextualActions
+    ) { actionData ->
+        val (actionTitle, actionImage) = actionData
+        AppBarIcon(actionImage) { /* doSomething()*/ }
+    }
+}
+
+@Suppress("UNUSED_VARIABLE")
+@Sampled
+@Composable
+fun SimpleBottomAppBarNoFab(getMyActionImage: () -> Image, getMyNavigationImage: () -> Image) {
+    val someActionImage: Image = getMyActionImage()
+    val someNavigationImage: Image = getMyNavigationImage()
+
+    val navigationIcon: @Composable() () -> Unit = {
+        AppBarIcon(someNavigationImage) { /* doSomething()*/ }
+    }
+    val contextualActions = listOf("Action 1" to someActionImage, "action 2" to someActionImage)
+
+    BottomAppBar(
+        navigationIcon = navigationIcon,
+        contextualActions = contextualActions
+    ) { actionData ->
+        val (actionTitle, actionImage) = actionData
+        AppBarIcon(actionImage) { /* doSomething()*/ }
+    }
+}
+
+@Suppress("UNUSED_VARIABLE")
+@Sampled
+@Composable
+fun SimpleBottomAppBarCenterFab(getMyActionImage: () -> Image, getMyNavigationImage: () -> Image) {
+    val someActionImage: Image = getMyActionImage()
+    val someNavigationImage: Image = getMyNavigationImage()
+
+    val navigationIcon: @Composable() () -> Unit = {
+        AppBarIcon(someNavigationImage) { /* doSomething()*/ }
+    }
+    val contextualActions = listOf("Action 1" to someActionImage, "action 2" to someActionImage)
+
+    BottomAppBar(
+        navigationIcon = navigationIcon,
+        floatingActionButton = {
+            FloatingActionButton(
+                color = Color.Black,
+                icon = someActionImage,
+                 /** doSomething() */ })
+        },
+        fabPosition = BottomAppBar.FabPosition.Center,
+        contextualActions = contextualActions
+    ) { actionData ->
+        val (actionTitle, actionImage) = actionData
+        AppBarIcon(actionImage) { /* doSomething()*/ }
+    }
+}
+
+@Suppress("UNUSED_VARIABLE")
+@Sampled
+@Composable
+fun SimpleBottomAppBarEndFab(getMyActionImage: () -> Image) {
+    val someActionImage: Image = getMyActionImage()
+    val contextualActions = listOf("Action 1" to someActionImage, "action 2" to someActionImage)
+
+    BottomAppBar(
+        floatingActionButton = {
+            FloatingActionButton(
+                color = Color.Black,
+                icon = someActionImage,
+                 /** doSomething() */ })
+        },
+        fabPosition = BottomAppBar.FabPosition.End,
+        contextualActions = contextualActions
+    ) { actionData ->
+        val (actionTitle, actionImage) = actionData
+        AppBarIcon(actionImage) { /* doSomething()*/ }
+    }
+}
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt
new file mode 100644
index 0000000..e33ff72
--- /dev/null
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/AppBarTest.kt
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.material
+
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.test.filters.SmallTest
+import androidx.ui.core.dp
+import androidx.ui.core.withDensity
+import androidx.ui.layout.Container
+import com.google.common.truth.Truth
+import androidx.compose.unaryPlus
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.OnChildPositioned
+import androidx.ui.core.PxPosition
+import androidx.ui.core.Semantics
+import androidx.ui.core.Text
+import androidx.ui.core.currentTextStyle
+import androidx.ui.core.ipx
+import androidx.ui.core.toPx
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.graphics.Color
+import androidx.ui.text.TextStyle
+import androidx.ui.test.assertCountEquals
+import androidx.ui.test.assertIsVisible
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.findAll
+import androidx.ui.test.findByText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+// TODO: remove when tests are uncommented
+@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+@SmallTest
+@RunWith(JUnit4::class)
+class AppBarTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val appBarHeight = 56.dp
+
+    @Test
+    fun topAppBar_expandsToScreen() {
+        val dm = composeTestRule.displayMetrics
+        composeTestRule
+            .setMaterialContentAndTestSizes {
+                TopAppBar<Nothing>()
+            }
+            .assertHeightEqualsTo(appBarHeight)
+            .assertWidthEqualsTo { dm.widthPixels.ipx }
+    }
+
+    @Test
+    fun topAppBar_withTitle() {
+        val title = "Title"
+        composeTestRule.setMaterialContent {
+            TopAppBar<Nothing>(title = { Text(title) })
+        }
+        findByText(title).assertIsVisible()
+    }
+
+    @Test
+    fun topAppBar_defaultPositioning() {
+        var appBarCoords: LayoutCoordinates? = null
+        var navigationIconCoords: LayoutCoordinates? = null
+        var titleCoords: LayoutCoordinates? = null
+        var actionCoords: LayoutCoordinates? = null
+        composeTestRule.setMaterialContent {
+            Container {
+                OnChildPositioned( coords ->
+                    appBarCoords = coords
+                }) {
+                    TopAppBar(
+                        navigationIcon = {
+                            OnChildPositioned( coords ->
+                                navigationIconCoords = coords
+                            }) {
+                                FakeIcon()
+                            }
+                        },
+                        title = {
+                            OnChildPositioned( coords ->
+                                titleCoords = coords
+                            }) {
+                                Text("title")
+                            }
+                        },
+                        contextualActions = createImageList(1),
+                        action = {
+                            OnChildPositioned( coords ->
+                                actionCoords = coords
+                            }) { it() }
+                        }
+                    )
+                }
+            }
+        }
+
+        withDensity(composeTestRule.density) {
+            // Navigation icon should be at the beginning
+            val navigationIconPositionX = navigationIconCoords!!.localToGlobal(PxPosition.Origin).x
+            val navigationIconExpectedPositionX = 16.dp.toIntPx().toPx()
+            Truth.assertThat(navigationIconPositionX).isEqualTo(navigationIconExpectedPositionX)
+
+            // Title should be next
+            val titlePositionX = titleCoords!!.localToGlobal(PxPosition.Origin).x
+            val titleExpectedPositionX =
+                navigationIconPositionX + navigationIconCoords!!.size.width + 32.dp.toIntPx()
+            Truth.assertThat(titlePositionX).isEqualTo(titleExpectedPositionX)
+
+            // Action should be placed at the end
+            val actionPositionX = actionCoords!!.localToGlobal(PxPosition.Origin).x
+            val actionExpectedPositionX =
+                appBarCoords!!.size.width - 16.dp.toIntPx() - 24.dp.toIntPx()
+            Truth.assertThat(actionPositionX).isEqualTo(actionExpectedPositionX)
+        }
+    }
+
+    @Test
+    fun topAppBar_oneAction() {
+        val tag = "action"
+        val numberOfActions = 1
+        composeTestRule.setMaterialContent {
+            Container {
+                TopAppBar(
+                    contextualActions = createImageList(numberOfActions),
+                    action = { action ->
+                        Semantics(testTag = tag) { action() }
+                    }
+                )
+            }
+        }
+
+        findAll { testTag == tag }.assertCountEquals(numberOfActions)
+    }
+
+    @Test
+    fun topAppBar_fiveActions_onlyTwoShouldBeVisible() {
+        val tag = "action"
+        val numberOfActions = 5
+        val maxNumberOfActions = 2
+        composeTestRule.setMaterialContent {
+            Container {
+                TopAppBar(
+                    contextualActions = createImageList(numberOfActions),
+                    action = { action ->
+                        Semantics(testTag = tag) { action() }
+                    }
+                )
+            }
+        }
+
+        findAll { testTag == tag }.assertCountEquals(maxNumberOfActions)
+    }
+
+    @Test
+    fun topAppBar_titleDefaultStyle() {
+        var textStyle: TextStyle? = null
+        var h6Style: TextStyle? = null
+        composeTestRule.setMaterialContent {
+            Container {
+                TopAppBar<Nothing>(
+                    title = {
+                        textStyle = +currentTextStyle()
+                        h6Style = +themeTextStyle { h6 }
+                    }
+                )
+            }
+        }
+        Truth.assertThat(textStyle!!.fontSize).isEqualTo(h6Style!!.fontSize)
+        Truth.assertThat(textStyle!!.fontFamily).isEqualTo(h6Style!!.fontFamily)
+    }
+
+    @Test
+    fun bottomAppBar_expandsToScreen() {
+        val dm = composeTestRule.displayMetrics
+        composeTestRule
+            .setMaterialContentAndTestSizes {
+                BottomAppBar<Nothing>()
+            }
+            .assertHeightEqualsTo(appBarHeight)
+            .assertWidthEqualsTo { dm.widthPixels.ipx }
+    }
+
+    @Test
+    fun bottomAppBar_noFab_positioning() {
+        var appBarCoords: LayoutCoordinates? = null
+        var navigationIconCoords: LayoutCoordinates? = null
+        var actionCoords: LayoutCoordinates? = null
+        composeTestRule.setMaterialContent {
+            Container {
+                OnChildPositioned( coords ->
+                    appBarCoords = coords
+                }) {
+                    BottomAppBar(
+                        navigationIcon = {
+                            OnChildPositioned( coords ->
+                                navigationIconCoords = coords
+                            }) {
+                                FakeIcon()
+                            }
+                        },
+                        contextualActions = createImageList(1),
+                        action = {
+                            OnChildPositioned( coords ->
+                                actionCoords = coords
+                            }) { it() }
+                        }
+                    )
+                }
+            }
+        }
+
+        withDensity(composeTestRule.density) {
+            // Navigation icon should be at the beginning
+            val navigationIconPositionX = navigationIconCoords!!.localToGlobal(PxPosition.Origin).x
+            val navigationIconExpectedPositionX = 16.dp.toIntPx().toPx()
+            Truth.assertThat(navigationIconPositionX).isEqualTo(navigationIconExpectedPositionX)
+
+            // TODO: layout rounding issues here depending on the density of the device
+            /*
+            // Action should be placed at the end
+            val actionPositionX = actionCoords!!.localToGlobal(PxPosition.Origin).x
+            val actionExpectedPositionX = appBarCoords!!.size.width.round().toPx() -
+                    16.dp.toIntPx().toPx() - 24.dp.toIntPx().toPx()
+            Truth.assertThat(actionPositionX).isEqualTo(actionExpectedPositionX)
+            */
+        }
+    }
+
+    @Test
+    fun bottomAppBar_centerFab_positioning() {
+        var appBarCoords: LayoutCoordinates? = null
+        var navigationIconCoords: LayoutCoordinates? = null
+        var fabCoords: LayoutCoordinates? = null
+        var actionCoords: LayoutCoordinates? = null
+        composeTestRule.setMaterialContent {
+            Container {
+                OnChildPositioned( coords ->
+                    appBarCoords = coords
+                }) {
+                    BottomAppBar(
+                        navigationIcon = {
+                            OnChildPositioned( coords ->
+                                navigationIconCoords = coords
+                            }) {
+                                FakeIcon()
+                            }
+                        },
+                        floatingActionButton = {
+                            OnChildPositioned( coords ->
+                                fabCoords = coords
+                            }) {
+                                FakeIcon()
+                            }
+                        },
+                        contextualActions = createImageList(1),
+                        action = {
+                            OnChildPositioned( coords ->
+                                actionCoords = coords
+                            }) { it() }
+                        }
+                    )
+                }
+            }
+        }
+
+        withDensity(composeTestRule.density) {
+            // Navigation icon should be at the beginning
+            val navigationIconPositionX = navigationIconCoords!!.localToGlobal(PxPosition.Origin).x
+            val navigationIconExpectedPositionX = 16.dp.toIntPx().toPx()
+            Truth.assertThat(navigationIconPositionX).isEqualTo(navigationIconExpectedPositionX)
+
+            // TODO: layout rounding issues here depending on the density of the device
+            /*
+            // FAB should be placed in the center
+            val fabPositionX = fabCoords!!.localToGlobal(PxPosition.Origin).x
+            val fabExpectedPositionX = (appBarCoords!!.size.width / 2) - 12.dp.toIntPx()
+            Truth.assertThat(fabPositionX).isEqualTo(fabExpectedPositionX)
+
+            // Action should be placed at the end
+            val actionPositionX = actionCoords!!.localToGlobal(PxPosition.Origin).x
+            val actionExpectedPositionX = appBarCoords!!.size.width.round().toPx() -
+                    16.dp.toIntPx().toPx() - 24.dp.toIntPx().toPx()
+            Truth.assertThat(actionPositionX).isEqualTo(actionExpectedPositionX)
+            */
+        }
+    }
+
+    @Test
+    fun bottomAppBar_endFab_positioning() {
+        var appBarCoords: LayoutCoordinates? = null
+        var fabCoords: LayoutCoordinates? = null
+        var actionCoords: LayoutCoordinates? = null
+        composeTestRule.setMaterialContent {
+            Container {
+                OnChildPositioned( coords ->
+                    appBarCoords = coords
+                }) {
+                    BottomAppBar(
+                        floatingActionButton = {
+                            OnChildPositioned( coords ->
+                                fabCoords = coords
+                            }) {
+                                FakeIcon()
+                            }
+                        },
+                        fabPosition = BottomAppBar.FabPosition.End,
+                        contextualActions = createImageList(1),
+                        action = {
+                            OnChildPositioned( coords ->
+                                actionCoords = coords
+                            }) { it() }
+                        }
+                    )
+                }
+            }
+        }
+
+        withDensity(composeTestRule.density) {
+            // Action should be placed at the start
+            val actionPositionX = actionCoords!!.localToGlobal(PxPosition.Origin).x
+            val actionExpectedPositionX = 16.dp.toIntPx().toPx()
+            Truth.assertThat(actionPositionX).isEqualTo(actionExpectedPositionX)
+
+            // TODO: layout rounding issues here depending on the density of the device
+            /*
+            // FAB should be placed at the end
+            val fabPositionX = fabCoords!!.localToGlobal(PxPosition.Origin).x
+            val fabExpectedPositionX = appBarCoords!!.size.width.round().toPx() -
+                    16.dp.toIntPx().toPx() - 24.dp.toIntPx().toPx()
+            Truth.assertThat(fabPositionX).isEqualTo(fabExpectedPositionX)
+            */
+        }
+    }
+
+    @Test
+    fun bottomAppBar_oneAction() {
+        val tag = "action"
+        val numberOfActions = 1
+        composeTestRule.setMaterialContent {
+            Container {
+                BottomAppBar(
+                    contextualActions = createImageList(numberOfActions),
+                    action = { action ->
+                        Semantics(testTag = tag) { action() }
+                    }
+                )
+            }
+        }
+
+        findAll { testTag == tag }.assertCountEquals(numberOfActions)
+    }
+
+    @Test
+    fun bottomAppBar_fiveActions_onlyFourShouldBeVisible() {
+        val tag = "action"
+        val numberOfActions = 5
+        val maxNumberOfActions = 4
+        composeTestRule.setMaterialContent {
+            Container {
+                BottomAppBar(
+                    contextualActions = createImageList(numberOfActions),
+                    action = { action ->
+                        Semantics(testTag = tag) { action() }
+                    }
+                )
+            }
+        }
+
+        findAll { testTag == tag }.assertCountEquals(maxNumberOfActions)
+    }
+
+    private fun createImageList(count: Int) =
+        List<@Composable() () -> Unit>(count) { { FakeIcon() } }
+
+    // Render a red rectangle to simulate an icon
+    @Composable
+    private fun FakeIcon() = ColoredRect(Color.Red, 24.dp, 24.dp)
+}
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TopAppBarUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TopAppBarUiTest.kt
deleted file mode 100644
index 66e3c40..0000000
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/TopAppBarUiTest.kt
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.ui.material
-
-import androidx.test.filters.SmallTest
-import androidx.ui.core.OnChildPositioned
-import androidx.ui.core.dp
-import androidx.ui.core.round
-import androidx.ui.core.withDensity
-import androidx.ui.layout.Container
-import com.google.common.truth.Truth
-import androidx.compose.composer
-import androidx.compose.unaryPlus
-import androidx.ui.core.LayoutCoordinates
-import androidx.ui.core.currentTextStyle
-import androidx.ui.core.ipx
-import androidx.ui.text.TextStyle
-import androidx.ui.test.assertCountEquals
-import androidx.ui.test.assertDoesNotExist
-import androidx.ui.test.assertIsVisible
-import androidx.ui.test.createComposeRule
-import androidx.ui.test.findAll
-import androidx.ui.test.findByTag
-import androidx.ui.test.findByText
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class TopAppBarUiTest {
-
-    @get:Rule
-    val composeTestRule = createComposeRule()
-
-    private val defaultHeight = 56.dp
-
-    @Test
-    fun topAppBarTest_ExpandsToScreen() {
-        val dm = composeTestRule.displayMetrics
-        composeTestRule
-            .setMaterialContentAndTestSizes {
-                TopAppBar()
-            }
-            .assertHeightEqualsTo(defaultHeight)
-            .assertWidthEqualsTo { dm.widthPixels.ipx }
-    }
-
-    @Test
-    fun topAppBarTest_LeadingIconPresent() {
-        composeTestRule.setMaterialContent {
-            TopAppBar()
-        }
-        findByTag("Leading icon").assertIsVisible()
-    }
-
-    @Test
-    fun topAppBarTest_TitleTextLabel_noTitle() {
-        composeTestRule.setMaterialContent {
-            TopAppBar()
-        }
-        assertDoesNotExist { testTag == "Title" }
-    }
-
-    @Test
-    fun topAppBarTest_TitleTextLabel_withTitle() {
-        val title = "Title"
-        composeTestRule.setMaterialContent {
-            TopAppBar(title = title)
-        }
-        findByText(title).assertIsVisible()
-    }
-
-    @Test
-    fun topAppBar_defaultPositioning() {
-        var leadingIconInfo: LayoutCoordinates? = null
-        var titleLabelInfo: LayoutCoordinates? = null
-        var trailingIconInfo: LayoutCoordinates? = null
-        composeTestRule.setMaterialContent {
-            Container {
-                TopAppBar(
-                    leadingIcon = {
-                        OnChildPositioned( position ->
-                            leadingIconInfo = position
-                        }) {
-                            AppBarLeadingIcon()
-                        }
-                    },
-                    titleTextLabel = {
-                        OnChildPositioned( position ->
-                            titleLabelInfo = position
-                        }) {
-                            TopAppBarTitleTextLabel("title")
-                        }
-                    },
-                    trailingIcons = {
-                        OnChildPositioned( position ->
-                            trailingIconInfo = position
-                        }) {
-                            TopAppBarTrailingIcons(listOf(24.dp, 24.dp))
-                        }
-                    }
-                )
-            }
-        }
-
-        withDensity(composeTestRule.density) {
-            val dm = composeTestRule.displayMetrics
-
-            // Leading icon should be in the front
-            val leadingIconPositionX = leadingIconInfo?.position?.x!!.value
-            val leadingIconExpectedPositionX = 0f
-            Truth.assertThat(leadingIconPositionX).isEqualTo(leadingIconExpectedPositionX)
-
-            // Title should be next
-            val titleLabelPositionX = titleLabelInfo?.position?.x!!
-            val titleLabelExpectedPositionX = leadingIconInfo?.size?.width!! + 32.dp.toIntPx()
-            Truth.assertThat(titleLabelPositionX).isEqualTo(titleLabelExpectedPositionX)
-
-            // Trailing icons should be in the second half of the screen
-            val trailingIconPositionX = trailingIconInfo?.position?.x!!
-            val totalSpaceMiddle = dm.widthPixels / 2f
-            Truth.assertThat(trailingIconPositionX.value).isGreaterThan(totalSpaceMiddle)
-        }
-    }
-
-    @Test
-    fun topAppBar_noTitlePositioning() {
-        var leadingIconInfo: LayoutCoordinates? = null
-        var trailingIconInfo: LayoutCoordinates? = null
-        composeTestRule.setMaterialContent {
-            Container {
-                TopAppBar(
-                    leadingIcon = {
-                        OnChildPositioned( position ->
-                            leadingIconInfo = position
-                        }) {
-                            AppBarLeadingIcon()
-                        }
-                    },
-                    titleTextLabel = {},
-                    trailingIcons = {
-                        OnChildPositioned( position ->
-                            trailingIconInfo = position
-                        }) {
-                            TopAppBarTrailingIcons(listOf(24.dp, 24.dp))
-                        }
-                    }
-                )
-            }
-        }
-
-        withDensity(composeTestRule.density) {
-            val dm = composeTestRule.displayMetrics
-
-            // Leading icon should be in the front
-            val leadingIconPositionX = leadingIconInfo?.position?.x!!.value
-            val leadingIconExpectedPositionX = 0f
-            Truth.assertThat(leadingIconPositionX).isEqualTo(leadingIconExpectedPositionX)
-
-            // Trailing icons should be in the second half of the screen
-            val trailingIconPositionX = trailingIconInfo?.position?.x!!
-            val totalSpaceMiddle = dm.widthPixels / 2f
-            Truth.assertThat(trailingIconPositionX.value).isGreaterThan(totalSpaceMiddle)
-        }
-    }
-
-    @Test
-    fun topAppBar_titleDefaultStyle() {
-        var textStyle: TextStyle? = null
-        var h6Style: TextStyle? = null
-        composeTestRule.setMaterialContent {
-            Container {
-                TopAppBar(
-                    leadingIcon = {},
-                    titleTextLabel = {
-                        textStyle = +currentTextStyle()
-                        h6Style = +themeTextStyle { h6 }
-                    },
-                    trailingIcons = {}
-                )
-            }
-        }
-        Truth.assertThat(textStyle!!.fontSize).isEqualTo(h6Style!!.fontSize)
-        Truth.assertThat(textStyle!!.fontFamily).isEqualTo(h6Style!!.fontFamily)
-    }
-
-    @Test
-    fun topAppBarTrailingIcons_noIcons() {
-        var trailingIconInfo: LayoutCoordinates? = null
-        composeTestRule.setMaterialContent {
-            Container {
-                OnChildPositioned( position ->
-                    trailingIconInfo = position
-                }) {
-                    TopAppBarTrailingIcons(emptyList())
-                }
-            }
-        }
-        assertDoesNotExist { testTag == "Trailing icon" }
-
-        withDensity(composeTestRule.density) {
-            val trailingIconWidth = trailingIconInfo?.size?.width?.round()
-            Truth.assertThat(trailingIconWidth).isNull()
-        }
-    }
-
-    @Test
-    fun topAppBarTrailingIcons_oneIcon() {
-        composeTestRule
-            .setMaterialContentAndTestSizes {
-                TopAppBarTrailingIcons(icons = listOf(24.dp))
-            }
-            .assertWidthEqualsTo { 24.dp.toIntPx() + 24.dp.toIntPx() }
-
-        findByTag("Trailing icon").assertIsVisible()
-        assertDoesNotExist { testTag == "Overflow icon" }
-    }
-
-    @Test
-    fun topAppBarTrailingIcons_twoIcons() {
-
-        composeTestRule.setMaterialContentAndTestSizes {
-            TopAppBarTrailingIcons(icons = listOf(24.dp, 24.dp))
-        }.assertWidthEqualsTo { (24.dp.toIntPx() * 2) + (24.dp.toIntPx() * 2) }
-
-        findAll { testTag == "Trailing icon" }.apply {
-            forEach {
-                it.assertIsVisible()
-            }
-        }.assertCountEquals(2)
-        assertDoesNotExist { testTag == "Overflow icon" }
-    }
-
-    @Test
-    fun topAppBarTrailingIcons_threeIcons() {
-        composeTestRule
-            .setMaterialContentAndTestSizes {
-                TopAppBarTrailingIcons(icons = listOf(24.dp, 24.dp, 24.dp))
-            }
-            .assertWidthEqualsTo { 24.dp.toIntPx() * 2 + 24.dp.toIntPx() * 3 + 12.dp.toIntPx() }
-        // icons and spacers + 12.dp overflow
-
-        findAll { testTag == "Trailing icon" }.apply {
-            forEach {
-                it.assertIsVisible()
-            }
-        }.assertCountEquals(2)
-        findByTag("Overflow icon").assertIsVisible()
-    }
-}
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
index ecb7dde..3996bd3 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/AppBar.kt
@@ -16,16 +16,16 @@
 
 package androidx.ui.material
 
-import androidx.compose.Children
 import androidx.compose.Composable
 import androidx.compose.composer
 import androidx.compose.unaryPlus
 import androidx.ui.core.CurrentTextStyleProvider
-import androidx.ui.core.Dp
 import androidx.ui.core.Semantics
 import androidx.ui.core.Text
 import androidx.ui.core.dp
-import androidx.ui.foundation.ColoredRect
+import androidx.ui.core.sp
+import androidx.ui.foundation.Clickable
+import androidx.ui.foundation.SimpleImage
 import androidx.ui.layout.Container
 import androidx.ui.layout.FlexRow
 import androidx.ui.layout.MainAxisAlignment
@@ -33,90 +33,257 @@
 import androidx.ui.layout.WidthSpacer
 import androidx.ui.material.surface.Surface
 import androidx.ui.graphics.Color
+import androidx.ui.layout.Align
+import androidx.ui.layout.Alignment
+import androidx.ui.layout.Center
+import androidx.ui.layout.ConstrainedBox
+import androidx.ui.layout.DpConstraints
 import androidx.ui.layout.EdgeInsets
-import androidx.ui.layout.FlexSize
+import androidx.ui.layout.Stack
+import androidx.ui.material.BottomAppBar.FabPosition
+import androidx.ui.material.BottomAppBar.FabPosition.Center
+import androidx.ui.material.BottomAppBar.FabPosition.End
+import androidx.ui.material.ripple.Ripple
+import androidx.ui.painting.Image
+import androidx.ui.text.TextStyle
 
 /**
- * A Top App Bar displays information and actions relating to the current screen and is placed at
- * the top of the screen.
- * This version of the TopAppBar will produce a default bar with a navigation leading icon, an
- * optional title and a set of menu icons.
+ * A TopAppBar displays information and actions relating to the current screen and is placed at the
+ * top of the screen.
  *
- * Example usage:
- *     TopAppBar(
- *         title = "Title",
- *         color = +themeColor{ secondary }
- *     )
+ * @sample androidx.ui.material.samples.SimpleTopAppBar
  *
- * @param title An optional title to display
- * @param color An optional color for the App Bar. By default [MaterialColors.primary] will be used.
- * @param icons An optional list of icons to display on the App Bar.
+ * @param title The title to be displayed in the center of the TopAppBar
+ * @param color An optional color for the TopAppBar. By default [MaterialColors.primary] will be
+ * used.
+ * @param navigationIcon The navigation icon displayed at the start of the TopAppBar
+ * @param contextualActions A list representing the contextual actions to be displayed at the end of
+ * the TopAppBar. Any remaining actions that do not fit on the TopAppBar should typically be
+ * displayed in an overflow menu at the end.
+ * @param action A specific item action to be displayed at the end of the TopAppBar - this will be
+ * called for items in [contextualActions] up to the maximum number of icons that can be displayed.
+ * @param T the type of item in [contextualActions]
  */
 @Composable
-fun TopAppBar(
-    title: String? = null,
+fun <T> TopAppBar(
+    title: @Composable() () -> Unit = {},
     color: Color = +themeColor { primary },
-    // TODO: work on menus
-    icons: List<Dp> = emptyList()
+    navigationIcon: @Composable() () -> Unit = {},
+    contextualActions: List<T>? = null,
+    action: @Composable() (T) -> Unit = {}
+    // TODO: support overflow menu here with the remainder of the list
 ) {
-    TopAppBar(
+    BaseTopAppBar(
         color = color,
-        leadingIcon = { AppBarLeadingIcon() },
-        titleTextLabel = {
-            if (title != null) {
-                TopAppBarTitleTextLabel(title)
+        startContent = navigationIcon,
+        title = title,
+        endContent = {
+            if (contextualActions != null) {
+                AppBarActions(MaxIconsInTopAppBar, contextualActions, action)
             }
-        },
-        trailingIcons = { TopAppBarTrailingIcons(icons) }
+        }
     )
 }
 
-/**
- * A Top App Bar displays information and actions relating to the current screen and is placed at
- * the top of the screen.
- *
- * Example usage:
- *     TopAppBar(
- *         color = +themeColor{ secondary },
- *         leadingIcon = { MyNavIcon() },
- *         titleTextLabel = { Text(text = "Title") },
- *         trailingIcons = { TopAppBarTrailingIcons(icons) }
- *     )
- *
- * @param color An optional color for the App Bar. By default [MaterialColors.primary] will be used.
- * @param leadingIcon A composable lambda to be inserted in the Leading Icon space. This is usually
- * a navigation icon. A standard implementation is provided by [AppBarLeadingIcon].
- * @param titleTextLabel A composable lambda to be inserted in the title space. This is usually a
- * [Text] element. A standard implementation is provided by [TopAppBarTitleTextLabel]. Default text
- * styling [MaterialTypography.h6] will be used.
- * @param trailingIcons A composable lambda to be inserted at the end of the bar, usually containing
- * a collection of menu icons. A standard implementation is provided by [TopAppBarTrailingIcons].
- */
 @Composable
-fun TopAppBar(
+private fun BaseTopAppBar(
     color: Color = +themeColor { primary },
-    leadingIcon: @Composable() () -> Unit,
-    titleTextLabel: @Composable() () -> Unit,
-    trailingIcons: @Composable() () -> Unit
+    startContent: @Composable() () -> Unit,
+    title: @Composable() () -> Unit,
+    endContent: @Composable() () -> Unit
 ) {
-    AppBar(color) {
+    BaseAppBar(color) {
         FlexRow(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
             inflexible {
-                leadingIcon()
+                // TODO: what should the spacing be when there is no icon provided here?
+                startContent()
                 WidthSpacer(width = 32.dp)
             }
-            expanded(flex = 1f) {
+            expanded(1f) {
                 CurrentTextStyleProvider(value = +themeTextStyle { h6 }) {
-                    titleTextLabel()
+                    title()
                 }
             }
             inflexible {
-                trailingIcons()
+                endContent()
             }
         }
     }
 }
 
+object BottomAppBar {
+    /**
+     * The possible positions for the [FloatingActionButton] embedded in the [BottomAppBar], if a
+     * [FloatingActionButton] is specified.
+     */
+    enum class FabPosition {
+        /**
+         * Positioned in the center of the [BottomAppBar], overlapping the content of the
+         * BottomAppBar
+         */
+        Center,
+        /**
+         * Positioned at the end of the [BottomAppBar], overlapping the content of the
+         * BottomAppBar
+         */
+        End,
+        /**
+         * Positioned in the center of the [BottomAppBar], with an inset cutting into the content of
+         * the BottomAppBar
+         */
+        CenterCut,
+    }
+}
+
+/**
+ * A BottomAppBar displays actions relating to the current screen and is placed at the bottom of
+ * the screen. It can also optionally display a [FloatingActionButton], which is either overlaid
+ * on top of the BottomAppBar, or inset, carving a notch in the BottomAppBar.
+ *
+ * The location of the actions displayed by the BottomAppBar depends on the [FabPosition] /
+ * existence of the [FloatingActionButton]. When the [FloatingActionButton] is:
+ *
+ * - not set: the [navigationIcon] is displayed at the start, and the [contextualActions] are
+ * displayed at the end
+ *
+ * @sample androidx.ui.material.samples.SimpleBottomAppBarNoFab
+ *
+ * - [Center] or [CenterCut] aligned: the [navigationIcon] is displayed at the start, and the
+ * [contextualActions] are displayed at the end
+ *
+ * @sample androidx.ui.material.samples.SimpleBottomAppBarCenterFab
+ *
+ * - [End] aligned: the [contextualActions] are displayed at the start, and no navigation icon is
+ * supported
+ *
+ * @sample androidx.ui.material.samples.SimpleBottomAppBarEndFab
+ *
+ * @param color An optional color for the BottomAppBar. By default [MaterialColors.primary]
+ * will be used.
+ * @param navigationIcon The navigation icon displayed in the BottomAppBar. Note that if
+ * [fabPosition] is set to [End], this parameter must be null / not set.
+ * @param floatingActionButton The [FloatingActionButton] displayed in the BottomAppBar. The
+ * position of this fab will be [Center] aligned by default. You can set [fabPosition] to change
+ * the position.
+ * @param fabPosition The [FabPosition] of the [floatingActionButton]. This can be [Center],
+ * [CenterCut], or [End].
+ * @param contextualActions A list representing the contextual actions to be displayed in the
+ * BottomAppBar. Any remaining actions that do not fit on the BottomAppBar should typically be
+ * displayed in an overflow menu.
+ * @param action A specific item action to be displayed in the BottomAppBar - this will be called
+ * for items in [contextualActions] up to the maximum number of icons that can be displayed.
+ * @param T the type of item in [contextualActions]
+ */
+// TODO: b/137311217 - type inference for nullable lambdas currently doesn't work
+@Suppress("USELESS_CAST")
+@Composable
+fun <T> BottomAppBar(
+    color: Color = +themeColor { primary },
+    navigationIcon: (@Composable() () -> Unit)? = null as @Composable() (() -> Unit)?,
+    floatingActionButton: (@Composable() () -> Unit)? = null as @Composable() (() -> Unit)?,
+    fabPosition: FabPosition = Center,
+    contextualActions: List<T>? = null,
+    action: @Composable() (T) -> Unit = {}
+    // TODO: support overflow menu here with the remainder of the list
+) {
+    require(navigationIcon == null || fabPosition != End) {
+        "Using a navigation icon with an end-aligned FloatingActionButton is not supported"
+    }
+
+    val actions = { maxIcons: Int ->
+        @Composable {
+            if (contextualActions != null) {
+                AppBarActions(maxIcons, contextualActions, action)
+            }
+        }
+    }
+
+    val navigationIconComposable = @Composable {
+        if (navigationIcon != null) {
+            navigationIcon()
+        }
+    }
+
+    if (floatingActionButton == null) {
+        BaseBottomAppBar(
+            color = color,
+            startContent = navigationIconComposable,
+            endContent = actions(MaxIconsInBottomAppBarNoFab)
+        )
+        return
+    }
+
+    when (fabPosition) {
+        End -> BaseBottomAppBar(
+            color = color,
+            startContent = actions(MaxIconsInBottomAppBarEndFab),
+            fab = { Align(Alignment.CenterRight) { floatingActionButton() } }
+        )
+        // TODO: support CenterCut
+        else -> BaseBottomAppBar(
+            color = color,
+            startContent = navigationIconComposable,
+            fab = { Center { floatingActionButton() } },
+            endContent = actions(MaxIconsInBottomAppBarCenterFab)
+        )
+    }
+}
+
+// TODO: b/137311217 - type inference for nullable lambdas currently doesn't work
+@Suppress("USELESS_CAST")
+@Composable
+private fun BaseBottomAppBar(
+    color: Color = +themeColor { primary },
+    startContent: @Composable() () -> Unit = {},
+    fab: (@Composable() () -> Unit)? = null as @Composable() (() -> Unit)?,
+    endContent: @Composable() () -> Unit = {}
+) {
+    val appBar = @Composable { BaseBottomAppBarWithoutFab(color, startContent, endContent) }
+    if (fab == null) {
+        appBar()
+    } else {
+        ConstrainedBox(
+            constraints = DpConstraints(
+                minHeight = BottomAppBarHeightWithFab,
+                maxHeight = BottomAppBarHeightWithFab
+            )
+        ) {
+            Stack {
+                aligned(Alignment.BottomCenter) {
+                    appBar()
+                }
+                aligned(Alignment.TopCenter) {
+                    Container(
+                        height = AppBarHeight,
+                        padding = EdgeInsets(left = AppBarPadding, right = AppBarPadding)
+                    ) {
+                        fab()
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun BaseBottomAppBarWithoutFab(
+    color: Color,
+    startContent: @Composable() () -> Unit,
+    endContent: @Composable() () -> Unit
+) {
+    BaseAppBar(color) {
+        FlexRow(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
+            inflexible {
+                startContent()
+                // TODO: if startContent() doesn't have any layout, then the endContent won't be
+                // placed at the end, so we need to trick it with a spacer
+                WidthSpacer(width = 1.dp)
+            }
+            inflexible { endContent() }
+        }
+    }
+}
+
 /**
  * An empty App Bar that expands to the parent's width.
  *
@@ -124,103 +291,78 @@
  * [TopAppBar].
  */
 @Composable
-fun AppBar(color: Color, @Children children: @Composable() () -> Unit) {
+private fun BaseAppBar(color: Color, children: @Composable() () -> Unit) {
     Semantics(
         container = true
     ) {
         Surface(color = color) {
-            Container(height = RegularHeight, expanded = true, padding = EdgeInsets(Padding)) {
+            Container(height = AppBarHeight, expanded = true, padding = EdgeInsets(AppBarPadding)) {
                 children()
             }
         }
     }
 }
 
-/**
- * A component that displays a leading icon for an App Bar following Material spec guidelines.
- *
- * @see [AppBar]
- * @see [TopAppBar]
- */
 @Composable
-fun AppBarLeadingIcon() {
-    // TODO: Replace with real icon button
-    Semantics(testTag = "Leading icon") {
-        FakeIcon(24.dp)
-    }
-}
-
-/**
- * A component that displays a title as a [Text] element for placement within a Top App Bar
- * following Material spec guidelines.
- *
- * @see [TopAppBar]
- *
- * @param title A title String to display
- */
-@Composable
-fun TopAppBarTitleTextLabel(title: String) {
-    Text(text = title)
-}
-
-/**
- * A component that displays a set of menu icons for placement within a Top App Bar following
- * Material spec guidelines.
- *
- * @see [TopAppBar]
- *
- * @param icons A list of icons to display
- */
-@Composable
-fun TopAppBarTrailingIcons(icons: List<Dp>) {
-    TrailingIcons(
-        numIcons = icons.size,
-        maxIcons = MaxIconsInTopAppBar,
-        icons = { index ->
-            Semantics(testTag = "Trailing icon") {
-                // TODO: Replace with real icon button
-                FakeIcon(icons[index])
-            }
-        },
-        overflowIcon = {
-            Semantics(testTag = "Overflow icon") {
-                FakeIcon(12.dp)
-            }
-        }
-    )
-}
-
-// TODO: make public
-@Composable
-internal fun TrailingIcons(
-    numIcons: Int,
-    maxIcons: Int,
-    icons: @Composable() (index: Int) -> Unit,
-    overflowIcon: @Composable() () -> Unit
+private fun <T> AppBarActions(
+    actionsToDisplay: Int,
+    contextualActions: List<T>,
+    action: @Composable() (T) -> Unit
 ) {
-    if (numIcons > 0) {
-        Row(mainAxisSize = FlexSize.Min) {
-            val needsOverflow = numIcons > maxIcons
-            val iconsToDisplay = if (needsOverflow) maxIcons else numIcons
-            for (index in 0 until iconsToDisplay) {
+    if (contextualActions.isEmpty()) {
+        return
+    }
+
+    // Split the list depending on how many actions we are displaying - if actionsToDisplay is
+    // greater than or equal to the number of actions provided, overflowActions will be empty.
+    val (shownActions, overflowActions) = contextualActions.withIndex().partition {
+        it.index < actionsToDisplay
+    }
+
+    Row {
+        shownActions.forEach { (index, shownAction) ->
+            action(shownAction)
+            if (index != shownActions.lastIndex) {
                 WidthSpacer(width = 24.dp)
-                icons(index)
             }
-            if (needsOverflow) {
-                WidthSpacer(width = 24.dp)
-                overflowIcon()
+        }
+        if (overflowActions.isNotEmpty()) {
+            WidthSpacer(width = 24.dp)
+            // TODO: use overflowActions to build menu here
+            Container(width = 12.dp) {
+                Text(text = "${overflowActions.size}", style = TextStyle(fontSize = 15.sp))
             }
         }
     }
 }
 
-// TODO: remove
+/**
+ * A correctly sized clickable icon that can be used inside [TopAppBar] and [BottomAppBar] for
+ * either the navigation icon or the actions.
+ *
+ * @param icon The icon to be displayed
+ * @param onClick the lambda to be invoked when this icon is pressed
+ */
 @Composable
-internal fun FakeIcon(size: Dp) {
-    ColoredRect(color = Color(0xFFFFFFFF.toInt()), width = size, height = 24.dp)
+fun AppBarIcon(icon: Image, onClick: () -> Unit) {
+    Ripple(bounded = false) {
+        Clickable( {
+            Center {
+                Container(width = ActionIconDiameter, height = ActionIconDiameter) {
+                    SimpleImage(icon)
+                }
+            }
+        }
+    }
 }
 
-private val RegularHeight = 56.dp
-private val Padding = 16.dp
-// TODO: IR compiler bug avoids this being const
-private val MaxIconsInTopAppBar = 2
\ No newline at end of file
+private val ActionIconDiameter = 24.dp
+
+private val AppBarHeight = 56.dp
+private val BottomAppBarHeightWithFab = 84.dp
+private val AppBarPadding = 16.dp
+
+private const val MaxIconsInTopAppBar = 2
+private const val MaxIconsInBottomAppBarCenterFab = 2
+private const val MaxIconsInBottomAppBarEndFab = 4
+private const val MaxIconsInBottomAppBarNoFab = 4
\ No newline at end of file