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

Commit 455a3095 authored by Helen Qin's avatar Helen Qin
Browse files

Implemented the screen for when the only auth entries are all empty.

Bug: 267814354
Test: local deployment
Change-Id: Ic9e52791bce86d798bcdd4f1bfd37e0d7759579e
parent 424a14ee
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -92,8 +92,8 @@
  <string name="close_sheet">"Close sheet"</string>
  <!-- Spoken content description of the back arrow button. -->
  <string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
  <!-- Spoken content description of the close button. -->
  <string name="accessibility_close_button">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
  <!-- Spoken content description of the close "X" icon button. -->
  <string name="accessibility_close_button">Close</string>

  <!-- Strings for the get flow. -->
  <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
@@ -120,6 +120,8 @@
  <string name="locked_credential_entry_label_subtext_tap_to_unlock">Tap to unlock</string>
  <!-- Explanatory label for a disabled button explaining that this option isn't viable because it does not contain any available credential (e.g. password, passkey, etc.) for the user. [CHAR LIMIT=120] -->
  <string name="locked_credential_entry_label_subtext_no_sign_in">No sign-in info</string>
  <!-- Label displayed when a selected option does not contain any viable credentials that can be used to log into the app. [CHAR LIMIT=80] -->
  <string name="no_sign_in_info_in">No sign-in info in <xliff:g id="source" example="becket@gmail.com">%1$s</xliff:g></string>
  <!-- Column heading for displaying action chips for managing sign-ins from each credential provider. [CHAR LIMIT=80] -->
  <string name="get_dialog_heading_manage_sign_ins">Manage sign-ins</string>
  <!-- Column heading for displaying option to use sign-ins saved on a different device. [CHAR LIMIT=80] -->
+5 −0
Original line number Diff line number Diff line
@@ -115,6 +115,11 @@ class CredentialSelectorViewModel(
        }
    }

    fun onLastLockedAuthEntryNotFoundError() {
        Log.d(Constants.LOG_TAG, "Unable to find the last unlocked entry")
        onInternalError()
    }

    private fun onInternalError() {
        Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
        credManRepo.onParsingFailureCancel()
+48 −7
Original line number Diff line number Diff line
@@ -79,7 +79,20 @@ fun GetCredentialScreen(
    getCredentialUiState: GetCredentialUiState,
    providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
    if (getCredentialUiState.currentScreenState != GetScreenState.REMOTE_ONLY) {
    if (getCredentialUiState.currentScreenState == GetScreenState.REMOTE_ONLY) {
        RemoteCredentialSnackBarScreen(
            onClick = viewModel::getFlowOnMoreOptionOnSnackBarSelected,
            onCancel = viewModel::onCancel,
        )
    } else if (getCredentialUiState.currentScreenState
        == GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY) {
        EmptyAuthEntrySnackBarScreen(
            authenticationEntryList =
            getCredentialUiState.providerDisplayInfo.authenticationEntryList,
            onCancel = viewModel::onCancel,
            onLastLokcedAuthEntryNotFound = viewModel::onLastLockedAuthEntryNotFoundError,
        )
    } else {
        ModalBottomSheet(
            sheetContent = {
                // Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim
@@ -124,11 +137,6 @@ fun GetCredentialScreen(
            },
            onDismiss = viewModel::onCancel,
        )
    } else {
        SnackBarScreen(
            onClick = viewModel::getFlowOnMoreOptionOnSnackBarSelected,
            onCancel = viewModel::onCancel,
        )
    }
}

@@ -622,7 +630,7 @@ fun ActionEntryRow(

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SnackBarScreen(
fun RemoteCredentialSnackBarScreen(
    onClick: (Boolean) -> Unit,
    onCancel: () -> Unit,
) {
@@ -654,3 +662,36 @@ fun SnackBarScreen(
        Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EmptyAuthEntrySnackBarScreen(
    authenticationEntryList: List<AuthenticationEntryInfo>,
    onCancel: () -> Unit,
    onLastLokcedAuthEntryNotFound: () -> Unit,
) {
    val lastLocked = authenticationEntryList.firstOrNull({it.isLastUnlocked})
    if (lastLocked == null) {
        onLastLokcedAuthEntryNotFound()
        return
    }

    // TODO: Change the height, width and position according to the design
    Snackbar(
        modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
        shape = EntryShape.FullMediumRoundedCorner,
        containerColor = LocalAndroidColorScheme.current.colorBackground,
        contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
        dismissAction = {
            IconButton(onClick = onCancel) {
                Icon(
                    Icons.Filled.Close,
                    contentDescription = stringResource(R.string.accessibility_close_button),
                    tint = LocalAndroidColorScheme.current.colorAccentTertiary
                )
            }
        },
    ) {
        Text(text = stringResource(R.string.no_sign_in_info_in, lastLocked.title))
    }
}
 No newline at end of file
+14 −16
Original line number Diff line number Diff line
@@ -28,8 +28,8 @@ import java.time.Instant
data class GetCredentialUiState(
    val providerInfoList: List<ProviderInfo>,
    val requestDisplayInfo: RequestDisplayInfo,
    val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
    val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
    val currentScreenState: GetScreenState = toGetScreenState(providerDisplayInfo),
    val activeEntry: BaseEntry? = toActiveEntry(providerDisplayInfo),
    val isNoAccount: Boolean = false,
)
@@ -167,6 +167,9 @@ enum class GetScreenState {

    /** The snackbar only page when there's no account but only a remoteEntry. */
    REMOTE_ONLY,

    /** The snackbar when there are only auth entries and all of them turn out to be empty. */
    UNLOCKED_AUTH_ENTRIES_ONLY,
}

// IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
@@ -174,7 +177,6 @@ enum class GetScreenState {
private fun toProviderDisplayInfo(
    providerInfoList: List<ProviderInfo>
): ProviderDisplayInfo {

    val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
    val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>()
    val remoteEntryList = mutableListOf<RemoteEntryInfo>()
@@ -241,22 +243,18 @@ private fun toActiveEntry(
}

private fun toGetScreenState(
    providerInfoList: List<ProviderInfo>
    providerDisplayInfo: ProviderDisplayInfo
): GetScreenState {
    var noLocalAccount = true
    var remoteInfo: RemoteEntryInfo? = null
    providerInfoList.forEach { providerInfo ->
        if (providerInfo.credentialEntryList.isNotEmpty() ||
            providerInfo.authenticationEntryList.isNotEmpty()) {
            noLocalAccount = false
        }
        if (providerInfo.remoteEntry != null) {
            remoteInfo = providerInfo.remoteEntry
        }
    }

    return if (noLocalAccount && remoteInfo != null)
        GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
    return if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
        providerDisplayInfo.remoteEntry == null &&
        providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty })
        GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY
    else if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
        providerDisplayInfo.authenticationEntryList.isEmpty() &&
        providerDisplayInfo.remoteEntry != null)
        GetScreenState.REMOTE_ONLY
    else GetScreenState.PRIMARY_SELECTION
}

internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {