Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc21455e authored by Helen Qin's avatar Helen Qin Committed by Android (Google) Code Review
Browse files

Merge "[CredManUI] Make all bottom sheet pages scrollable." into udc-dev

parents 5ab845fa cd54c345
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@

package com.android.credentialmanager.common.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
@@ -40,7 +42,8 @@ import androidx.compose.ui.unit.dp
fun SheetContainerCard(
    topAppBar: (@Composable () -> Unit)? = null,
    modifier: Modifier = Modifier,
    content: @Composable ColumnScope.() -> Unit,
    contentVerticalArrangement: Arrangement.Vertical = Arrangement.Top,
    content: LazyListScope.() -> Unit,
) {
    Card(
        modifier = modifier.fillMaxWidth().wrapContentHeight(),
@@ -54,7 +57,7 @@ fun SheetContainerCard(
        if (topAppBar != null) {
            topAppBar()
        }
        Column(
        LazyColumn(
            modifier = Modifier.padding(
                start = 24.dp,
                end = 24.dp,
@@ -63,6 +66,7 @@ fun SheetContainerCard(
            ).fillMaxWidth().wrapContentHeight(),
            horizontalAlignment = Alignment.CenterHorizontally,
            content = content,
            verticalArrangement = contentVerticalArrangement,
        )
    }
}
+0 −31
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.credentialmanager.common.ui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp

@Composable
fun EntryListColumn(content: LazyListScope.() -> Unit) {
    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(2.dp),
        content = content,
    )
}
 No newline at end of file
+249 −224
Original line number Diff line number Diff line
@@ -8,11 +8,12 @@ import androidx.activity.result.IntentSenderRequest
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@@ -23,7 +24,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
@@ -43,7 +43,6 @@ import com.android.credentialmanager.common.ui.ConfirmButton
import com.android.credentialmanager.common.ui.CredentialContainerCard
import com.android.credentialmanager.common.ui.CtaButtonRow
import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.EntryListColumn
import com.android.credentialmanager.common.ui.HeadlineIcon
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ModalBottomSheet
@@ -157,6 +156,7 @@ fun PasskeyIntroCard(
    onLearnMore: () -> Unit,
) {
    SheetContainerCard {
        item {
            val onboardingImageResource = remember {
                mutableStateOf(R.drawable.ic_passkeys_onboarding)
            }
@@ -165,31 +165,43 @@ fun PasskeyIntroCard(
            } else {
                onboardingImageResource.value = R.drawable.ic_passkeys_onboarding
            }
            Row(
                modifier = Modifier.wrapContentHeight().fillMaxWidth(),
                horizontalArrangement = Arrangement.Center,
            ) {
                Image(
                    painter = painterResource(onboardingImageResource.value),
                    contentDescription = null,
            modifier = Modifier
                .align(alignment = Alignment.CenterHorizontally).size(316.dp, 168.dp)
                    modifier = Modifier.size(316.dp, 168.dp)
                )
        Divider(thickness = 16.dp, color = Color.Transparent)
        HeadlineText(text = stringResource(R.string.passkey_creation_intro_title))
        Divider(thickness = 16.dp, color = Color.Transparent)
            }
        }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item { HeadlineText(text = stringResource(R.string.passkey_creation_intro_title)) }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            PasskeyBenefitRow(
                leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_password),
                text = stringResource(R.string.passkey_creation_intro_body_password),
            )
        Divider(thickness = 16.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            PasskeyBenefitRow(
                leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
                text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
            )
        Divider(thickness = 16.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            PasskeyBenefitRow(
                leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_device),
                text = stringResource(R.string.passkey_creation_intro_body_device),
            )
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }

        item {
            CtaButtonRow(
                leftButton = {
                    ActionButton(
@@ -206,6 +218,7 @@ fun PasskeyIntroCard(
            )
        }
    }
}

@Composable
fun ProviderSelectionCard(
@@ -218,8 +231,9 @@ fun ProviderSelectionCard(
    onMoreOptionsSelected: () -> Unit,
) {
    SheetContainerCard {
        HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap())
        Divider(thickness = 16.dp, color = Color.Transparent)
        item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            HeadlineText(
                text = stringResource(
                    R.string.choose_provider_title,
@@ -232,16 +246,17 @@ fun ProviderSelectionCard(
                    }
                )
            )
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }

        BodyMediumText(text = stringResource(R.string.choose_provider_body))
        Divider(thickness = 16.dp, color = Color.Transparent)
        item { BodyMediumText(text = stringResource(R.string.choose_provider_body)) }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            CredentialContainerCard {
            LazyColumn(
                Column(
                    verticalArrangement = Arrangement.spacedBy(2.dp)
                ) {
                    sortedCreateOptionsPairs.forEach { entry ->
                    item {
                        MoreOptionsInfoRow(
                            requestDisplayInfo = requestDisplayInfo,
                            providerInfo = entry.second,
@@ -256,8 +271,6 @@ fun ProviderSelectionCard(
                            }
                        )
                    }
                }
                item {
                    MoreOptionsDisabledProvidersRow(
                        disabledProviders = disabledProviderList,
                        onDisabledProvidersSelected = onDisabledProvidersSelected,
@@ -266,7 +279,8 @@ fun ProviderSelectionCard(
            }
        }
        if (hasRemoteEntry) {
            Divider(thickness = 24.dp, color = Color.Transparent)
            item { Divider(thickness = 24.dp, color = Color.Transparent) }
            item {
                CtaButtonRow(
                    leftButton = {
                        ActionButton(
@@ -278,6 +292,7 @@ fun ProviderSelectionCard(
            }
        }
    }
}

@Composable
fun MoreOptionsSelectionCard(
@@ -310,14 +325,14 @@ fun MoreOptionsSelectionCard(
            else onBackCreationSelectionButtonSelected,
        )
    }) {
        Divider(thickness = 16.dp, color = Color.Transparent)
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            CredentialContainerCard {
            EntryListColumn {
                Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
                    // Only in the flows with default provider(not first time use) we can show the
                    // createOptions here, or they will be shown on ProviderSelectionCard
                    if (hasDefaultProvider) {
                        sortedCreateOptionsPairs.forEach { entry ->
                        item {
                            MoreOptionsInfoRow(
                                requestDisplayInfo = requestDisplayInfo,
                                providerInfo = entry.second,
@@ -329,25 +344,21 @@ fun MoreOptionsSelectionCard(
                                            entry.first
                                        )
                                    )
                                })
                                }
                            )
                        }
                    item {
                        MoreOptionsDisabledProvidersRow(
                            disabledProviders = disabledProviderList,
                            onDisabledProvidersSelected =
                            onDisabledProvidersSelected,
                        )
                    }
                }
                    enabledProviderList.forEach {
                        if (it.remoteEntry != null) {
                        item {
                            RemoteEntryRow(
                                remoteInfo = it.remoteEntry!!,
                                onRemoteEntrySelected = onRemoteEntrySelected,
                            )
                        }
                            return@forEach
                        }
                    }
@@ -355,6 +366,7 @@ fun MoreOptionsSelectionCard(
            }
        }
    }
}

@Composable
fun MoreOptionsRowIntroCard(
@@ -363,16 +375,19 @@ fun MoreOptionsRowIntroCard(
    onUseOnceSelected: () -> Unit,
) {
    SheetContainerCard {
        HeadlineIcon(imageVector = Icons.Outlined.NewReleases)
        Divider(thickness = 24.dp, color = Color.Transparent)
        item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        item {
            HeadlineText(
                text = stringResource(
                    R.string.use_provider_for_all_title,
                    providerInfo.displayName
                )
            )
        Divider(thickness = 24.dp, color = Color.Transparent)
        BodyMediumText(text = stringResource(R.string.use_provider_for_all_description))
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        item { BodyMediumText(text = stringResource(R.string.use_provider_for_all_description)) }
        item {
            CtaButtonRow(
                leftButton = {
                    ActionButton(
@@ -389,6 +404,7 @@ fun MoreOptionsRowIntroCard(
            )
        }
    }
}

@Composable
fun CreationSelectionCard(
@@ -402,13 +418,16 @@ fun CreationSelectionCard(
    hasDefaultProvider: Boolean,
) {
    SheetContainerCard {
        item {
            HeadlineIcon(
                bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
                tint = Color.Unspecified,
            )
        Divider(thickness = 4.dp, color = Color.Transparent)
        LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName)
        Divider(thickness = 16.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 4.dp, color = Color.Transparent) }
        item { LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName) }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item {
            HeadlineText(
                text = when (requestDisplayInfo.type) {
                    CredentialType.PASSKEY -> stringResource(
@@ -425,7 +444,9 @@ fun CreationSelectionCard(
                    )
                }
            )
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        item {
            CredentialContainerCard {
                PrimaryCreateOptionRow(
                    requestDisplayInfo = requestDisplayInfo,
@@ -433,7 +454,8 @@ fun CreationSelectionCard(
                    onOptionSelected = onOptionSelected
                )
            }
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        var createOptionsSize = 0
        var remoteEntry: RemoteInfo? = null
        enabledProviderList.forEach { enabledProvider ->
@@ -450,6 +472,7 @@ fun CreationSelectionCard(
        } else {
            createOptionsSize > 1 || remoteEntry != null
        }
        item {
            CtaButtonRow(
                leftButton = if (shouldShowMoreOptionsButton) {
                    {
@@ -466,13 +489,16 @@ fun CreationSelectionCard(
                    )
                },
            )
        }
        if (createOptionInfo.footerDescription != null) {
            item {
                Divider(
                    thickness = 1.dp,
                    color = MaterialTheme.colorScheme.outlineVariant,
                    modifier = Modifier.padding(vertical = 16.dp)
                )
            BodySmallText(text = createOptionInfo.footerDescription)
            }
            item { BodySmallText(text = createOptionInfo.footerDescription) }
        }
    }
}
@@ -485,13 +511,11 @@ fun ExternalOnlySelectionCard(
    onConfirm: () -> Unit,
) {
    SheetContainerCard {
        HeadlineIcon(painter = painterResource(R.drawable.ic_other_devices))
        Divider(thickness = 16.dp, color = Color.Transparent)
        HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title))
        Divider(
            thickness = 24.dp,
            color = Color.Transparent
        )
        item { HeadlineIcon(painter = painterResource(R.drawable.ic_other_devices)) }
        item { Divider(thickness = 16.dp, color = Color.Transparent) }
        item { HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title)) }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        item {
            CredentialContainerCard {
                PrimaryCreateOptionRow(
                    requestDisplayInfo = requestDisplayInfo,
@@ -499,7 +523,9 @@ fun ExternalOnlySelectionCard(
                    onOptionSelected = onOptionSelected
                )
            }
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        item {
            CtaButtonRow(
                rightButton = {
                    ConfirmButton(
@@ -510,20 +536,20 @@ fun ExternalOnlySelectionCard(
            )
        }
    }
}

@Composable
fun MoreAboutPasskeysIntroCard(
    onBackPasskeyIntroButtonSelected: () -> Unit,
) {
    SheetContainerCard(topAppBar = {
    SheetContainerCard(
        topAppBar = {
            MoreOptionTopAppBar(
                text = stringResource(R.string.more_about_passkeys_title),
                onNavigationIconClicked = onBackPasskeyIntroButtonSelected,
            )
    }) {
        LazyColumn(
            modifier = Modifier.fillMaxWidth().wrapContentHeight(),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        },
        contentVerticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        item {
            MoreAboutPasskeySectionHeader(
@@ -551,7 +577,6 @@ fun MoreAboutPasskeysIntroCard(
        }
    }
}
}

@Composable
fun PrimaryCreateOptionRow(
+115 −114
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Divider
import androidx.compose.material3.TextButton
@@ -141,6 +140,7 @@ fun PrimarySelectionCard(
        providerDisplayInfo.sortedUserNameToCredentialEntryList
    val authenticationEntryList = providerDisplayInfo.authenticationEntryList
    SheetContainerCard {
        item {
            HeadlineText(
                text = stringResource(
                    if (sortedUserNameToCredentialEntryList
@@ -160,42 +160,42 @@ fun PrimarySelectionCard(
                    requestDisplayInfo.appName
                ),
            )
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        item {
            CredentialContainerCard {
                Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
                    val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size
                    val authenticationEntrySize = authenticationEntryList.size
            LazyColumn(
                verticalArrangement = Arrangement.spacedBy(2.dp)
            ) {
                    // Show max 4 entries in this primary page
                    if (usernameForCredentialSize + authenticationEntrySize <= 4) {
                    items(sortedUserNameToCredentialEntryList) {
                        sortedUserNameToCredentialEntryList.forEach {
                            CredentialEntryRow(
                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
                                onEntrySelected = onEntrySelected,
                            )
                        }
                    items(authenticationEntryList) {
                        authenticationEntryList.forEach {
                            AuthenticationEntryRow(
                                authenticationEntryInfo = it,
                                onEntrySelected = onEntrySelected,
                            )
                        }
                    } else if (usernameForCredentialSize < 4) {
                    items(sortedUserNameToCredentialEntryList) {
                        sortedUserNameToCredentialEntryList.forEach {
                            CredentialEntryRow(
                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
                                onEntrySelected = onEntrySelected,
                            )
                        }
                    items(authenticationEntryList.take(4 - usernameForCredentialSize)) {
                        authenticationEntryList.take(4 - usernameForCredentialSize).forEach {
                            AuthenticationEntryRow(
                                authenticationEntryInfo = it,
                                onEntrySelected = onEntrySelected,
                            )
                        }
                    } else {
                    items(sortedUserNameToCredentialEntryList.take(4)) {
                        sortedUserNameToCredentialEntryList.take(4).forEach {
                            CredentialEntryRow(
                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
                                onEntrySelected = onEntrySelected,
@@ -204,7 +204,8 @@ fun PrimarySelectionCard(
                    }
                }
            }
        Divider(thickness = 24.dp, color = Color.Transparent)
        }
        item { Divider(thickness = 24.dp, color = Color.Transparent) }
        var totalEntriesCount = sortedUserNameToCredentialEntryList
            .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList
            .size + providerInfoList.flatMap { it.actionEntryList }.size
@@ -212,6 +213,7 @@ fun PrimarySelectionCard(
        // Row horizontalArrangement differs on only one actionButton(should place on most
        // left)/only one confirmButton(should place on most right)/two buttons exist the same
        // time(should be one on the left, one on the right)
        item {
            CtaButtonRow(
                leftButton = if (totalEntriesCount > 1) {
                    {
@@ -232,6 +234,7 @@ fun PrimarySelectionCard(
            )
        }
    }
}

/** Draws the secondary credential selection page, where all sign-in options are listed. */
@Composable
@@ -252,7 +255,6 @@ fun AllSignInOptionCard(
            onNavigationIconClicked = if (isNoAccount) onCancel else onBackButtonClicked,
        )
    }) {
        LazyColumn {
        // For username
        items(sortedUserNameToCredentialEntryList) { item ->
            PerUserNameCredentials(
@@ -295,7 +297,6 @@ fun AllSignInOptionCard(
        }
    }
}
}

// TODO: create separate rows for primary and secondary pages.
// TODO: reuse rows and columns across types.