mirror of
https://github.com/bitwarden/android.git
synced 2026-08-28 22:45:25 -05:00
PM-17660: Add additional context for the sync feature (#5243)
This commit is contained in:
+138
-53
@@ -8,16 +8,21 @@ import android.widget.Toast
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -56,7 +61,9 @@ import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.Ve
|
||||
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenMediumTopAppBar
|
||||
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.bitwarden.authenticator.ui.platform.components.appbar.action.BitwardenSearchActionItem
|
||||
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledTonalButton
|
||||
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenTextButton
|
||||
import com.bitwarden.authenticator.ui.platform.components.card.BitwardenActionCard
|
||||
import com.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState
|
||||
import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
@@ -139,6 +146,10 @@ fun ItemListingScreen(
|
||||
)
|
||||
}
|
||||
|
||||
ItemListingEvent.NavigateToSyncInformation -> {
|
||||
intentManager.launchUri("https://bitwarden.com/help/totp-sync".toUri())
|
||||
}
|
||||
|
||||
ItemListingEvent.NavigateToBitwardenSettings -> {
|
||||
intentManager.startMainBitwardenAppAccountSettings()
|
||||
}
|
||||
@@ -230,6 +241,9 @@ fun ItemListingScreen(
|
||||
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
|
||||
}
|
||||
},
|
||||
onSyncLearnMoreClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -270,6 +284,9 @@ fun ItemListingScreen(
|
||||
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick)
|
||||
}
|
||||
},
|
||||
onSyncLearnMoreClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
|
||||
},
|
||||
onDismissSyncWithBitwardenClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
|
||||
@@ -339,6 +356,7 @@ private fun ItemListingContent(
|
||||
onDismissDownloadBitwardenClick: () -> Unit,
|
||||
onSyncWithBitwardenClick: () -> Unit,
|
||||
onDismissSyncWithBitwardenClick: () -> Unit,
|
||||
onSyncLearnMoreClick: () -> Unit,
|
||||
) {
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
@@ -402,23 +420,15 @@ private fun ItemListingContent(
|
||||
) {
|
||||
LazyColumn {
|
||||
item {
|
||||
when (state.actionCard) {
|
||||
ItemListingState.ActionCardState.DownloadBitwardenApp ->
|
||||
DownloadBitwardenActionCard(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
onDownloadBitwardenClick = onDownloadBitwardenClick,
|
||||
onDismissClick = onDismissDownloadBitwardenClick,
|
||||
)
|
||||
|
||||
ItemListingState.ActionCardState.SyncWithBitwarden ->
|
||||
SyncWithBitwardenActionCard(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
|
||||
onDismissClick = onDismissSyncWithBitwardenClick,
|
||||
)
|
||||
|
||||
ItemListingState.ActionCardState.None -> Unit
|
||||
}
|
||||
ActionCard(
|
||||
actionCardState = state.actionCard,
|
||||
onDownloadBitwardenClick = onDownloadBitwardenClick,
|
||||
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
|
||||
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
|
||||
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
|
||||
onSyncLearnMoreClick = onSyncLearnMoreClick,
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
)
|
||||
}
|
||||
if (state.favoriteItems.isNotEmpty()) {
|
||||
item {
|
||||
@@ -572,6 +582,7 @@ fun EmptyItemListingContent(
|
||||
onDownloadBitwardenClick: () -> Unit,
|
||||
onDismissDownloadBitwardenClick: () -> Unit,
|
||||
onSyncWithBitwardenClick: () -> Unit,
|
||||
onSyncLearnMoreClick: () -> Unit,
|
||||
onDismissSyncWithBitwardenClick: () -> Unit,
|
||||
) {
|
||||
BitwardenScaffold(
|
||||
@@ -635,23 +646,14 @@ fun EmptyItemListingContent(
|
||||
ItemListingState.ActionCardState.SyncWithBitwarden -> Arrangement.Top
|
||||
},
|
||||
) {
|
||||
when (actionCardState) {
|
||||
ItemListingState.ActionCardState.DownloadBitwardenApp ->
|
||||
DownloadBitwardenActionCard(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
onDismissClick = onDismissDownloadBitwardenClick,
|
||||
onDownloadBitwardenClick = onDownloadBitwardenClick,
|
||||
)
|
||||
|
||||
ItemListingState.ActionCardState.SyncWithBitwarden ->
|
||||
SyncWithBitwardenActionCard(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
onDismissClick = onDismissSyncWithBitwardenClick,
|
||||
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
|
||||
)
|
||||
|
||||
ItemListingState.ActionCardState.None -> Unit
|
||||
}
|
||||
ActionCard(
|
||||
actionCardState = actionCardState,
|
||||
onDownloadBitwardenClick = onDownloadBitwardenClick,
|
||||
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
|
||||
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
|
||||
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
|
||||
onSyncLearnMoreClick = onSyncLearnMoreClick,
|
||||
)
|
||||
|
||||
// Add a spacer if an action card is showing:
|
||||
when (actionCardState) {
|
||||
@@ -735,32 +737,114 @@ private fun DownloadBitwardenActionCard(
|
||||
},
|
||||
)
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun SyncWithBitwardenActionCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onDismissClick: () -> Unit,
|
||||
onAppSettingsClick: () -> Unit,
|
||||
onLearnMoreClick: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(size = 16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
),
|
||||
elevation = CardDefaults.elevatedCardElevation(),
|
||||
) {
|
||||
Spacer(Modifier.height(height = 4.dp))
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Spacer(Modifier.width(width = 16.dp))
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(id = R.drawable.ic_bitwarden),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(size = 20.dp),
|
||||
)
|
||||
Spacer(Modifier.width(width = 16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.sync_with_the_bitwarden_app),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.weight(weight = 1f))
|
||||
Spacer(Modifier.width(width = 16.dp))
|
||||
IconButton(onClick = onDismissClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
contentDescription = stringResource(id = R.string.close),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(width = 4.dp))
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.sync_with_bitwarden_action_card_message),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(start = 36.dp, end = 48.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Spacer(Modifier.height(height = 16.dp))
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = R.string.take_me_to_app_settings),
|
||||
onClick = onAppSettingsClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.learn_more),
|
||||
onClick = onLearnMoreClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Spacer(Modifier.height(height = 4.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionCard(
|
||||
actionCardState: ItemListingState.ActionCardState,
|
||||
onDownloadBitwardenClick: () -> Unit,
|
||||
onDownloadBitwardenDismissClick: () -> Unit,
|
||||
onSyncWithBitwardenClick: () -> Unit,
|
||||
) = BitwardenActionCard(
|
||||
modifier = modifier,
|
||||
actionIcon = rememberVectorPainter(R.drawable.ic_refresh),
|
||||
actionText = stringResource(R.string.sync_with_bitwarden_action_card_message),
|
||||
callToActionText = stringResource(R.string.go_to_settings),
|
||||
titleText = stringResource(R.string.sync_with_the_bitwarden_app),
|
||||
onCardClicked = onSyncWithBitwardenClick,
|
||||
trailingContent = {
|
||||
IconButton(
|
||||
onClick = onDismissClick,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
contentDescription = stringResource(id = R.string.close),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.size(24.dp),
|
||||
onSyncWithBitwardenDismissClick: () -> Unit,
|
||||
onSyncLearnMoreClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (actionCardState) {
|
||||
ItemListingState.ActionCardState.DownloadBitwardenApp -> {
|
||||
DownloadBitwardenActionCard(
|
||||
modifier = modifier,
|
||||
onDownloadBitwardenClick = onDownloadBitwardenClick,
|
||||
onDismissClick = onDownloadBitwardenDismissClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
ItemListingState.ActionCardState.SyncWithBitwarden -> {
|
||||
SyncWithBitwardenActionCard(
|
||||
modifier = modifier,
|
||||
onAppSettingsClick = onSyncWithBitwardenClick,
|
||||
onDismissClick = onSyncWithBitwardenDismissClick,
|
||||
onLearnMoreClick = onSyncLearnMoreClick,
|
||||
)
|
||||
}
|
||||
|
||||
ItemListingState.ActionCardState.None -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -776,6 +860,7 @@ private fun EmptyListingContentPreview() {
|
||||
onDownloadBitwardenClick = { },
|
||||
onDismissDownloadBitwardenClick = { },
|
||||
onSyncWithBitwardenClick = { },
|
||||
onSyncLearnMoreClick = { },
|
||||
onDismissSyncWithBitwardenClick = { },
|
||||
)
|
||||
}
|
||||
|
||||
+19
-1
@@ -153,6 +153,10 @@ class ItemListingViewModel @Inject constructor(
|
||||
ItemListingAction.SyncWithBitwardenDismiss -> {
|
||||
handleSyncWithBitwardenDismiss()
|
||||
}
|
||||
|
||||
ItemListingAction.SyncLearnMoreClick -> {
|
||||
handleSyncLearnMoreClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,6 +568,10 @@ class ItemListingViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSyncLearnMoreClick() {
|
||||
sendEvent(ItemListingEvent.NavigateToSyncInformation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a [SharedVerificationCodesState] into an action card for display.
|
||||
*/
|
||||
@@ -794,6 +802,11 @@ sealed class ItemListingEvent {
|
||||
*/
|
||||
data object NavigateToAppSettings : ItemListingEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the sync information web page.
|
||||
*/
|
||||
data object NavigateToSyncInformation : ItemListingEvent()
|
||||
|
||||
/**
|
||||
* Navigate to Bitwarden play store listing.
|
||||
*/
|
||||
@@ -872,6 +885,11 @@ sealed class ItemListingAction {
|
||||
*/
|
||||
data object SyncWithBitwardenClick : ItemListingAction()
|
||||
|
||||
/**
|
||||
* The user tapped the learn more button on the sync action card.
|
||||
*/
|
||||
data object SyncLearnMoreClick : ItemListingAction()
|
||||
|
||||
/**
|
||||
* The user dismissed sync Bitwarden action card.
|
||||
*/
|
||||
@@ -886,7 +904,7 @@ sealed class ItemListingAction {
|
||||
* Represents an action triggered when the user clicks an item in the dropdown menu.
|
||||
*
|
||||
* @param menuAction The action selected from the dropdown menu.
|
||||
* @param id The identifier of the item on which the action is being performed.
|
||||
* @param item The item on which the action is being performed.
|
||||
*/
|
||||
data class DropdownMenuClick(
|
||||
val menuAction: VaultDropdownMenuAction,
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ fun BitwardenActionCard(
|
||||
onClick = onCardClicked,
|
||||
shape = RoundedCornerShape(size = 16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
),
|
||||
modifier = modifier,
|
||||
elevation = CardDefaults.elevatedCardElevation(),
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ fun BitwardenTextRow(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
description: String? = null,
|
||||
description: AnnotatedString? = null,
|
||||
withDivider: Boolean = false,
|
||||
content: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
|
||||
+26
@@ -65,6 +65,8 @@ import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme
|
||||
import com.bitwarden.authenticator.ui.platform.util.displayLabel
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.base.util.mirrorIfRtl
|
||||
import com.bitwarden.ui.platform.base.util.spanStyleOf
|
||||
import com.bitwarden.ui.platform.base.util.toAnnotatedString
|
||||
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.bitwarden.ui.util.Text
|
||||
@@ -107,6 +109,10 @@ fun SettingsScreen(
|
||||
intentManager.launchUri("https://bitwarden.com/privacy".toUri())
|
||||
}
|
||||
|
||||
SettingsEvent.NavigateToSyncInformation -> {
|
||||
intentManager.launchUri("https://bitwarden.com/help/totp-sync".toUri())
|
||||
}
|
||||
|
||||
SettingsEvent.NavigateToBitwardenApp -> {
|
||||
|
||||
intentManager.startActivity(
|
||||
@@ -177,6 +183,9 @@ fun SettingsScreen(
|
||||
viewModel.trySendAction(SettingsAction.DataClick.SyncWithBitwardenClick)
|
||||
}
|
||||
},
|
||||
onSyncLearnMoreClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SettingsAction.DataClick.SyncLearnMoreClick) }
|
||||
},
|
||||
onDefaultSaveOptionUpdated = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
@@ -280,6 +289,7 @@ private fun VaultSettings(
|
||||
onImportClick: () -> Unit,
|
||||
onBackupClick: () -> Unit,
|
||||
onSyncWithBitwardenClick: () -> Unit,
|
||||
onSyncLearnMoreClick: () -> Unit,
|
||||
onDefaultSaveOptionUpdated: (DefaultSaveOption) -> Unit,
|
||||
shouldShowSyncWithBitwardenApp: Boolean,
|
||||
shouldShowDefaultSaveOptions: Boolean,
|
||||
@@ -339,6 +349,22 @@ private fun VaultSettings(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextRow(
|
||||
text = stringResource(id = R.string.sync_with_bitwarden_app),
|
||||
description = R.string
|
||||
.this_feature_is_not_not_yet_available_for_self_hosted_users
|
||||
.toAnnotatedString(
|
||||
style = spanStyleOf(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
),
|
||||
linkHighlightStyle = spanStyleOf(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textStyle = MaterialTheme.typography.labelLarge,
|
||||
),
|
||||
) {
|
||||
when (it) {
|
||||
"learnMore" -> onSyncLearnMoreClick()
|
||||
}
|
||||
},
|
||||
onClick = onSyncWithBitwardenClick,
|
||||
modifier = modifier,
|
||||
withDivider = true,
|
||||
|
||||
+16
-1
@@ -184,6 +184,7 @@ class SettingsViewModel @Inject constructor(
|
||||
SettingsAction.DataClick.ImportClick -> handleImportClick()
|
||||
SettingsAction.DataClick.BackupClick -> handleBackupClick()
|
||||
SettingsAction.DataClick.SyncWithBitwardenClick -> handleSyncWithBitwardenClick()
|
||||
SettingsAction.DataClick.SyncLearnMoreClick -> handleSyncLearnMoreClick()
|
||||
is SettingsAction.DataClick.DefaultSaveOptionUpdated ->
|
||||
handleDefaultSaveOptionChosen(action)
|
||||
}
|
||||
@@ -215,6 +216,10 @@ class SettingsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSyncLearnMoreClick() {
|
||||
sendEvent(SettingsEvent.NavigateToSyncInformation)
|
||||
}
|
||||
|
||||
private fun handleExportClick() {
|
||||
sendEvent(SettingsEvent.NavigateToExport)
|
||||
}
|
||||
@@ -424,6 +429,11 @@ sealed class SettingsEvent {
|
||||
*/
|
||||
data object NavigateToPrivacyPolicy : SettingsEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the sync learn more web page.
|
||||
*/
|
||||
data object NavigateToSyncInformation : SettingsEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the Bitwarden account settings.
|
||||
*/
|
||||
@@ -491,7 +501,12 @@ sealed class SettingsAction(
|
||||
data object SyncWithBitwardenClick : DataClick()
|
||||
|
||||
/**
|
||||
* User confirmed a new [DeafultSaveOption].
|
||||
* Indicates the user clicked sync learn more button.
|
||||
*/
|
||||
data object SyncLearnMoreClick : DataClick()
|
||||
|
||||
/**
|
||||
* User confirmed a new [DefaultSaveOption].
|
||||
*/
|
||||
data class DefaultSaveOptionUpdated(val option: DefaultSaveOption) : DataClick()
|
||||
}
|
||||
|
||||
@@ -125,11 +125,13 @@
|
||||
<string name="download_bitwarden_card_message">Store all of your logins and sync verification codes directly with the Authenticator app.</string>
|
||||
<string name="download_now">Download now</string>
|
||||
<string name="sync_with_bitwarden_app">Sync with Bitwarden app</string>
|
||||
<string name="this_feature_is_not_not_yet_available_for_self_hosted_users">This feature is not yet available for self-hosted users. <annotation link="learnMore">Learn more</annotation></string>
|
||||
<string name="shared_codes_error">Unable to sync codes from the Bitwarden app. Make sure both apps are up-to-date. You can still access your existing codes in the Bitwarden app.</string>
|
||||
<string name="shared_accounts_header">%1$s | %2$s</string>
|
||||
<string name="sync_with_the_bitwarden_app">Sync with the Bitwarden app</string>
|
||||
<string name="go_to_settings">Go to settings</string>
|
||||
<string name="sync_with_bitwarden_action_card_message">Allow Authenticator app syncing in settings to view all of your verification codes here.</string>
|
||||
<string name="sync_with_bitwarden_action_card_message">In order to view all of your verification codes, you’ll need to allow for syncing on all of your accounts.</string>
|
||||
<string name="take_me_to_app_settings">Take me to the app settings</string>
|
||||
<string name="something_went_wrong">Something went wrong</string>
|
||||
<string name="please_try_again">Please try again</string>
|
||||
<string name="move_to_bitwarden">Move to Bitwarden</string>
|
||||
|
||||
+129
-19
@@ -8,6 +8,7 @@ import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.core.net.toUri
|
||||
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.SharedCodesDisplayState
|
||||
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VaultDropdownMenuAction
|
||||
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem
|
||||
@@ -17,6 +18,8 @@ import com.bitwarden.authenticator.ui.platform.manager.permissions.FakePermissio
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
|
||||
import com.bitwarden.ui.util.onNodeWithTextAfterScroll
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@@ -44,7 +47,9 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
|
||||
every { trySendAction(any()) } just runs
|
||||
}
|
||||
|
||||
private val intentManager: IntentManager = mockk()
|
||||
private val intentManager: IntentManager = mockk {
|
||||
every { launchUri(uri = any()) } just runs
|
||||
}
|
||||
private val permissionsManager = FakePermissionManager()
|
||||
|
||||
@Before
|
||||
@@ -64,6 +69,14 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToSyncInformation should launch sync uri`() {
|
||||
mutableEventFlow.tryEmit(ItemListingEvent.NavigateToSyncInformation)
|
||||
verify(exactly = 1) {
|
||||
intentManager.launchUri(uri = "https://bitwarden.com/help/totp-sync".toUri())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `when denying camera permissions and attempting to add a code we should be shown the manual entry screen`() {
|
||||
@@ -175,35 +188,74 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on sync with bitwarden action card click in empty state should send SyncWithBitwardenClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = ItemListingState.ViewState.NoItems(
|
||||
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
|
||||
),
|
||||
)
|
||||
fun `on sync with bitwarden app settings click in empty state should send SyncWithBitwardenClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = ItemListingState.ViewState.NoItems(
|
||||
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Sync with the Bitwarden app")
|
||||
.onNodeWithText(text = "Take me to the app settings")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on sync with bitwarden action card click in full state should send SyncWithBitwardenClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = ItemListingState.ViewState.Content(
|
||||
favoriteItems = emptyList(),
|
||||
itemList = emptyList(),
|
||||
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
|
||||
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
|
||||
),
|
||||
)
|
||||
fun `on sync with bitwarden learn more click in empty state should send SyncLearnMoreClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = ItemListingState.ViewState.NoItems(
|
||||
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Sync with the Bitwarden app")
|
||||
.onNodeWithText(text = "Learn more")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on sync with bitwarden app settings click in full state should send SyncWithBitwardenClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = ItemListingState.ViewState.Content(
|
||||
favoriteItems = emptyList(),
|
||||
itemList = emptyList(),
|
||||
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
|
||||
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Take me to the app settings")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on sync with bitwarden learn more click in full state should send SyncLearnMoreClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = ItemListingState.ViewState.Content(
|
||||
favoriteItems = emptyList(),
|
||||
itemList = emptyList(),
|
||||
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
|
||||
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Learn more")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on sync with bitwarden action card dismiss in empty state should send SyncWithBitwardenDismiss`() {
|
||||
@@ -235,6 +287,64 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
|
||||
verify { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on download bitwarden click in empty state should send DownloadBitwardenClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = ItemListingState.ViewState.NoItems(
|
||||
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
|
||||
),
|
||||
)
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Download now")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on download bitwarden click in full state should send DownloadBitwardenClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = ItemListingState.ViewState.Content(
|
||||
favoriteItems = emptyList(),
|
||||
itemList = emptyList(),
|
||||
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
|
||||
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
|
||||
),
|
||||
)
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Download now")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on download bitwarden dismiss in empty state should send DownloadBitwardenDismiss`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = ItemListingState.ViewState.NoItems(
|
||||
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
|
||||
),
|
||||
)
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Close")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on download bitwarden dismiss in full state should send DownloadBitwardenDismiss`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = ItemListingState.ViewState.Content(
|
||||
favoriteItems = emptyList(),
|
||||
itemList = emptyList(),
|
||||
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
|
||||
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
|
||||
),
|
||||
)
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Close")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking Move to Bitwarden should send MoveToBitwardenClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
|
||||
+9
@@ -368,6 +368,15 @@ class ItemListingViewModelTest : BaseViewModelTest() {
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on SyncLearnMoreClick should send NavigateToSyncInformation`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick)
|
||||
assertEquals(ItemListingEvent.NavigateToSyncInformation, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on MoveToBitwardenClick receive should call startAddTotpLoginItemFlow`() {
|
||||
val expectedUriString = "expectedUriString"
|
||||
|
||||
+9
@@ -119,6 +119,15 @@ class SettingsScreenTest : AuthenticatorComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToSyncInformation receive launch sync totp uri`() {
|
||||
every { intentManager.launchUri(uri = any()) } just runs
|
||||
mutableEventFlow.tryEmit(SettingsEvent.NavigateToSyncInformation)
|
||||
verify(exactly = 1) {
|
||||
intentManager.launchUri("https://bitwarden.com/help/totp-sync".toUri())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Default Save Option row should be hidden when showDefaultSaveOptionRow is false`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE
|
||||
|
||||
+9
@@ -157,6 +157,15 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on SyncLearnMoreClick should emit NavigateToSyncInformation`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SettingsAction.DataClick.SyncLearnMoreClick)
|
||||
assertEquals(SettingsEvent.NavigateToSyncInformation, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `Default save option row should only show when shared codes state shows syncing as enabled`() =
|
||||
|
||||
Reference in New Issue
Block a user