diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt
index fd25957f01..032c26a7f1 100644
--- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt
+++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt
@@ -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 = { },
)
}
diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt
index 92d81865d4..484c3a5a08 100644
--- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt
+++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt
@@ -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,
diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/BitwardenActionCard.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/BitwardenActionCard.kt
index 8afc4a6466..b327fad440 100644
--- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/BitwardenActionCard.kt
+++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/BitwardenActionCard.kt
@@ -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(),
diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt
index 637458df8d..0a33a63f9f 100644
--- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt
+++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt
@@ -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,
) {
diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt
index 2ee4d7f8bb..f254c18c3a 100644
--- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt
+++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt
@@ -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,
diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt
index 62b9afaf0d..e42e7c9d0f 100644
--- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt
+++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt
@@ -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()
}
diff --git a/authenticator/src/main/res/values/strings.xml b/authenticator/src/main/res/values/strings.xml
index 148f630599..6bf18030ea 100644
--- a/authenticator/src/main/res/values/strings.xml
+++ b/authenticator/src/main/res/values/strings.xml
@@ -125,11 +125,13 @@
Store all of your logins and sync verification codes directly with the Authenticator app.
Download now
Sync with Bitwarden app
+ This feature is not yet available for self-hosted users. Learn more
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.
%1$s | %2$s
Sync with the Bitwarden app
Go to settings
- Allow Authenticator app syncing in settings to view all of your verification codes here.
+ In order to view all of your verification codes, you’ll need to allow for syncing on all of your accounts.
+ Take me to the app settings
Something went wrong
Please try again
Move to Bitwarden
diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt
index e82188df41..0c83ff28c5 100644
--- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt
+++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt
@@ -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(
diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt
index 07b9f08b6c..12f7207950 100644
--- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt
+++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt
@@ -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"
diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt
index b0ee4d4dd5..05ad4dd5c3 100644
--- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt
+++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt
@@ -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
diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModelTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModelTest.kt
index 8b05482254..0fa129d8be 100644
--- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModelTest.kt
+++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModelTest.kt
@@ -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`() =