Migrate to use the latest ModalBottomSheet
Bug: 319453757
Test: local e2e & screenshot tests
Change-Id: Id80a5365b6ebf8f864bcc58e3ece03c04f916b64
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index d319e4c..a7b5c36 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -16,8 +16,15 @@
package com.android.credentialmanager.common.ui
+import android.credentials.flags.Flags
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
@@ -25,6 +32,7 @@
import androidx.compose.ui.graphics.Color
import com.android.compose.rememberSystemUiController
import com.android.compose.theme.LocalAndroidColorScheme
+import androidx.compose.ui.unit.dp
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
@@ -34,40 +42,68 @@
/** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
@Composable
+@OptIn(ExperimentalMaterial3Api::class)
fun ModalBottomSheet(
- sheetContent: @Composable ColumnScope.() -> Unit,
- onDismiss: () -> Unit,
- isInitialRender: Boolean,
- onInitialRenderComplete: () -> Unit,
- isAutoSelectFlow: Boolean,
+ sheetContent: @Composable () -> Unit,
+ onDismiss: () -> Unit,
+ isInitialRender: Boolean,
+ onInitialRenderComplete: () -> Unit,
+ isAutoSelectFlow: Boolean,
) {
- val scope = rememberCoroutineScope()
- val state = rememberModalBottomSheetState(
- initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
- else ModalBottomSheetValue.Hidden,
- skipHalfExpanded = true
- )
- val sysUiController = rememberSystemUiController()
- if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
- setTransparentSystemBarsColor(sysUiController)
+ if (Flags.selectorUiImprovementsEnabled()) {
+ val state = androidx.compose.material3.rememberModalBottomSheetState(
+ skipPartiallyExpanded = true
+ )
+ androidx.compose.material3.ModalBottomSheet(
+ onDismissRequest = onDismiss,
+ containerColor = LocalAndroidColorScheme.current.surfaceBright,
+ sheetState = state,
+ content = {
+ Box(
+ modifier = Modifier
+ .animateContentSize()
+ .wrapContentHeight()
+ .fillMaxWidth()
+ ) {
+ sheetContent()
+ }
+ },
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = .32f),
+ shape = EntryShape.TopRoundedCorner,
+ dragHandle = null,
+ // Never take over the full screen. We always want to leave some top scrim space
+ // for exiting and viewing the underlying app to help a user gain context.
+ modifier = Modifier.padding(top = 56.dp),
+ )
} else {
- setBottomSheetSystemBarsColor(sysUiController)
- }
- ModalBottomSheetLayout(
- sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
- modifier = Modifier.background(Color.Transparent),
- sheetState = state,
- sheetContent = sheetContent,
- sheetShape = EntryShape.TopRoundedCorner,
- ) {}
- LaunchedEffect(state.currentValue, state.targetValue) {
- if (state.currentValue == ModalBottomSheetValue.Hidden) {
- if (isInitialRender) {
- onInitialRenderComplete()
- scope.launch { state.show() }
- } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
- // Only dismiss ui when the motion is downwards
- onDismiss()
+ val scope = rememberCoroutineScope()
+ val state = rememberModalBottomSheetState(
+ initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
+ else ModalBottomSheetValue.Hidden,
+ skipHalfExpanded = true
+ )
+ val sysUiController = rememberSystemUiController()
+ if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
+ setTransparentSystemBarsColor(sysUiController)
+ } else {
+ setBottomSheetSystemBarsColor(sysUiController)
+ }
+ ModalBottomSheetLayout(
+ sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
+ modifier = Modifier.background(Color.Transparent),
+ sheetState = state,
+ sheetContent = { sheetContent() },
+ sheetShape = EntryShape.TopRoundedCorner,
+ ) {}
+ LaunchedEffect(state.currentValue, state.targetValue) {
+ if (state.currentValue == ModalBottomSheetValue.Hidden) {
+ if (isInitialRender) {
+ onInitialRenderComplete()
+ scope.launch { state.show() }
+ } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
+ // Only dismiss ui when the motion is downwards
+ onDismiss()
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index bdfe399..c68ae8b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -18,7 +18,10 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
@@ -66,6 +69,9 @@
horizontalAlignment = Alignment.CenterHorizontally,
content = content,
verticalArrangement = contentVerticalArrangement,
+ // The bottom sheet overlaps with the navigation bars but make sure the actual content
+ // in the bottom sheet does not.
+ contentPadding = WindowInsets.navigationBars.asPaddingValues(),
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index a6253b8..8ff17e0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -29,13 +29,10 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Lock
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -278,31 +275,6 @@
}
/**
- * A single row of leading icon and text describing a benefit of passkeys, used by the
- * [com.android.credentialmanager.createflow.PasskeyIntroCard].
- */
-@Composable
-fun PasskeyBenefitRow(
- leadingIconPainter: Painter,
- text: String,
-) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth()
- ) {
- Icon(
- modifier = Modifier.size(24.dp),
- painter = leadingIconPainter,
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
- // Decorative purpose only.
- contentDescription = null,
- )
- BodyMediumText(text = text)
- }
-}
-
-/**
* A single row of one or two CTA buttons for continuing or cancelling the current step.
*/
@Composable
@@ -327,40 +299,36 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionTopAppBar(
text: String,
onNavigationIconClicked: () -> Unit,
bottomPadding: Dp,
) {
- TopAppBar(
- title = {
- LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
- },
- navigationIcon = {
- IconButton(
+ Row(
+ modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ IconButton(
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
onClick = onNavigationIconClicked
- ) {
- Box(
+ ) {
+ Box(
modifier = Modifier.size(48.dp),
contentAlignment = Alignment.Center,
- ) {
- Icon(
+ ) {
+ Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(
- R.string.accessibility_back_arrow_button
+ R.string.accessibility_back_arrow_button
),
modifier = Modifier.size(24.dp).autoMirrored(),
tint = LocalAndroidColorScheme.current.onSurfaceVariant,
- )
- }
+ )
}
- },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
- modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding)
- )
+ }
+ LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
+ }
}
private fun Modifier.autoMirrored() = composed {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 4ed84b9..7277550 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -653,4 +653,4 @@
contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
)
onLog(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN)
-}
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/Android.bp b/packages/CredentialManager/tests/robotests/Android.bp
index baebfeb..75a0dcc 100644
--- a/packages/CredentialManager/tests/robotests/Android.bp
+++ b/packages/CredentialManager/tests/robotests/Android.bp
@@ -37,7 +37,7 @@
":CredentialManagerScreenshotTestFiles",
],
- // Do not add any libraries here, instead add them to the ScreenshotTestStub
+ // Do not add any libraries here, instead add them to the ScreenshotTestRobo
static_libs: [
"androidx.compose.runtime_runtime",
"androidx.test.uiautomator_uiautomator",
@@ -45,6 +45,7 @@
"inline-mockito-robolectric-prebuilt",
"platform-parametric-runner-lib",
"uiautomator-helpers",
+ "flag-junit-base",
],
libs: [
"android.test.runner",
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..81860e5
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..8c1fff7
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..4eb025f
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..c709f93
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..278c13f
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..cb85df3
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..2eca707
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 0000000..7ee91b3
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index a0e1fed..e609d0c 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -16,7 +16,10 @@
package com.android.credentialmanager
+import android.credentials.flags.Flags
import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.compose.ui.test.isPopup
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.ProviderInfo
@@ -59,8 +62,11 @@
CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
)
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
@Test
- fun singleCredentialScreen() {
+ fun singleCredentialScreen_M3BottomSheetDisabled() {
+ setFlagsRule.disableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED)
val providerInfoList = buildProviderInfoList()
val providerDisplayInfo = toProviderDisplayInfo(providerInfoList)
val activeEntry = toActiveEntry(providerDisplayInfo)
@@ -86,6 +92,39 @@
}
}
+ @Test
+ fun singleCredentialScreen_M3BottomSheetEnabled() {
+ setFlagsRule.enableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED)
+ val providerInfoList = buildProviderInfoList()
+ val providerDisplayInfo = toProviderDisplayInfo(providerInfoList)
+ val activeEntry = toActiveEntry(providerDisplayInfo)
+ screenshotRule.screenshotTest(
+ "singleCredentialScreen_newM3BottomSheet",
+ // M3's ModalBottomSheet lives in a new window, meaning we have two windows with
+ // a root. Hence use a different matcher `isPopup`.
+ viewFinder = { screenshotRule.composeRule.onNode(isPopup()) },
+ ) {
+ ModalBottomSheet(
+ sheetContent = {
+ PrimarySelectionCard(
+ requestDisplayInfo = REQUEST_DISPLAY_INFO,
+ providerDisplayInfo = providerDisplayInfo,
+ providerInfoList = providerInfoList,
+ activeEntry = activeEntry,
+ onEntrySelected = {},
+ onConfirm = {},
+ onMoreOptionSelected = {},
+ onLog = {},
+ )
+ },
+ isInitialRender = true,
+ onDismiss = {},
+ onInitialRenderComplete = {},
+ isAutoSelectFlow = false,
+ )
+ }
+ }
+
private fun buildProviderInfoList(): List<ProviderInfo> {
val context = ApplicationProvider.getApplicationContext<Context>()
val provider1 = ProviderInfo(