Skip to content

Commit

Permalink
BITAU-180 Show "Move to Bitwarden" long press action (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaisting-livefront authored Oct 24, 2024
1 parent 5d76563 commit 5fdfb26
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 27 deletions.
Binary file modified app/libs/authenticatorbridge-0.1.0-SNAPSHOT-release.aar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ object AuthenticatorBridgeModule {
object : AuthenticatorBridgeManager {
override val accountSyncStateFlow: StateFlow<AccountSyncState>
get() = MutableStateFlow(AccountSyncState.Loading)

override fun startAddTotpLoginItemFlow(totpUri: String): Boolean = false
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ fun ItemListingScreen(
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
}
},
onMoveToBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.MoveToBitwardenClick(it))
}
},
)
}

Expand Down Expand Up @@ -342,6 +347,7 @@ private fun ItemListingContent(
onItemClick: (String) -> Unit,
onEditItemClick: (String) -> Unit,
onDeleteItemClick: (String) -> Unit,
onMoveToBitwardenClick: (String) -> Unit,
onDownloadBitwardenClick: () -> Unit,
onDismissDownloadBitwardenClick: () -> Unit,
onSyncWithBitwardenClick: () -> Unit,
Expand Down Expand Up @@ -453,6 +459,8 @@ private fun ItemListingContent(
onItemClick = { onItemClick(it.authCode) },
onEditItemClick = { onEditItemClick(it.id) },
onDeleteItemClick = { onDeleteItemClick(it.id) },
onMoveToBitwardenClick = { onMoveToBitwardenClick(it.id) },
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
modifier = Modifier.fillMaxWidth(),
)
Expand Down Expand Up @@ -481,6 +489,8 @@ private fun ItemListingContent(
onItemClick = { onItemClick(it.authCode) },
onEditItemClick = { onEditItemClick(it.id) },
onDeleteItemClick = { onDeleteItemClick(it.id) },
onMoveToBitwardenClick = { onMoveToBitwardenClick(it.id) },
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
modifier = Modifier.fillMaxWidth(),
)
Expand Down Expand Up @@ -515,6 +525,8 @@ private fun ItemListingContent(
onItemClick = { onItemClick(it.authCode) },
onEditItemClick = { },
onDeleteItemClick = { },
onMoveToBitwardenClick = { },
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
modifier = Modifier.fillMaxWidth(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import com.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.bitwarden.authenticator.ui.platform.base.util.Text
import com.bitwarden.authenticator.ui.platform.base.util.asText
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
Expand All @@ -46,6 +48,7 @@ import javax.inject.Inject
@HiltViewModel
class ItemListingViewModel @Inject constructor(
private val authenticatorRepository: AuthenticatorRepository,
private val authenticatorBridgeManager: AuthenticatorBridgeManager,
private val clipboardManager: BitwardenClipboardManager,
private val encodingManager: BitwardenEncodingManager,
private val settingsRepository: SettingsRepository,
Expand Down Expand Up @@ -144,9 +147,14 @@ class ItemListingViewModel @Inject constructor(
ItemListingAction.SyncWithBitwardenClick -> {
handleSyncWithBitwardenClick()
}

ItemListingAction.SyncWithBitwardenDismiss -> {
handleSyncWithBitwardenDismiss()
}

is ItemListingAction.MoveToBitwardenClick -> {
handleMoveToBitwardenClick(action)
}
}
}

Expand All @@ -167,6 +175,28 @@ class ItemListingViewModel @Inject constructor(
sendEvent(ItemListingEvent.NavigateToEditItem(action.itemId))
}

private fun handleMoveToBitwardenClick(action: ItemListingAction.MoveToBitwardenClick) {
viewModelScope.launch {
val item = authenticatorRepository
.getItemStateFlow(action.entityId)
.first { it.data != null }

val didLaunchAddTotpFlow = authenticatorBridgeManager.startAddTotpLoginItemFlow(
totpUri = item.data!!.toOtpAuthUriString(),
)
if (!didLaunchAddTotpFlow) {
mutableStateFlow.update {
it.copy(
dialog = ItemListingState.DialogState.Error(
title = R.string.something_went_wrong.asText(),
message = R.string.please_try_again.asText(),
),
)
}
}
}
}

private fun handleDeleteItemClick(action: ItemListingAction.DeleteItemClick) {
mutableStateFlow.update {
it.copy(
Expand Down Expand Up @@ -439,7 +469,9 @@ class ItemListingViewModel @Inject constructor(
-> SharedCodesDisplayState.Codes(emptyList())

is SharedVerificationCodesState.Success ->
action.sharedCodesState.toSharedCodesDisplayState(state.alertThresholdSeconds)
action.sharedCodesState.toSharedCodesDisplayState(
alertThresholdSeconds = state.alertThresholdSeconds,
)
}

if (localItems.isEmpty() && sharedItemsState.isEmpty()) {
Expand All @@ -456,12 +488,20 @@ class ItemListingViewModel @Inject constructor(
favoriteItems = localItems
.filter { it.source is AuthenticatorItem.Source.Local && it.source.isFavorite }
.map {
it.toDisplayItem(alertThresholdSeconds = state.alertThresholdSeconds)
it.toDisplayItem(
alertThresholdSeconds = state.alertThresholdSeconds,
sharedVerificationCodesState =
authenticatorRepository.sharedCodesStateFlow.value,
)
},
itemList = localItems
.filter { it.source is AuthenticatorItem.Source.Local && !it.source.isFavorite }
.map {
it.toDisplayItem(alertThresholdSeconds = state.alertThresholdSeconds)
it.toDisplayItem(
alertThresholdSeconds = state.alertThresholdSeconds,
sharedVerificationCodesState =
authenticatorRepository.sharedCodesStateFlow.value,
)
},
sharedItems = sharedItemsState,
actionCard = action.sharedCodesState.toActionCard(),
Expand Down Expand Up @@ -538,7 +578,7 @@ class ItemListingViewModel @Inject constructor(
SharedVerificationCodesState.Loading,
SharedVerificationCodesState.OsVersionNotSupported,
is SharedVerificationCodesState.Success,
-> ItemListingState.ActionCardState.None
-> ItemListingState.ActionCardState.None
}

private fun String.toAuthenticatorEntityOrNull(): AuthenticatorItemEntity? {
Expand Down Expand Up @@ -818,6 +858,11 @@ sealed class ItemListingAction {
*/
data object SyncWithBitwardenDismiss : ItemListingAction()

/**
* The user clicked the "Move to Bitwarden" action on a local verification item.
*/
data class MoveToBitwardenClick(val entityId: String) : ItemListingAction()

/**
* Models actions that [ItemListingScreen] itself may send.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ fun VaultVerificationCodeItem(
onItemClick: () -> Unit,
onEditItemClick: () -> Unit,
onDeleteItemClick: () -> Unit,
onMoveToBitwardenClick: () -> Unit,
allowLongPress: Boolean,
showMoveToBitwarden: Boolean,
modifier: Modifier = Modifier,
) {
var shouldShowDropdownMenu by remember { mutableStateOf(value = false) }
Expand Down Expand Up @@ -168,6 +170,24 @@ fun VaultVerificationCodeItem(
)
},
)
if (showMoveToBitwarden) {
HorizontalDivider()
DropdownMenuItem(
text = {
Text(text = stringResource(id = R.string.move_to_bitwarden))
},
onClick = {
shouldShowDropdownMenu = false
onMoveToBitwardenClick()
},
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_arrow_right),
contentDescription = stringResource(id = R.string.move_to_bitwarden),
)
},
)
}
HorizontalDivider()
DropdownMenuItem(
text = {
Expand Down Expand Up @@ -204,8 +224,10 @@ private fun VerificationCodeItem_preview() {
onItemClick = {},
onEditItemClick = {},
onDeleteItemClick = {},
onMoveToBitwardenClick = {},
allowLongPress = true,
modifier = Modifier.padding(horizontal = 16.dp),
showMoveToBitwarden = true,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ data class VerificationCodeDisplayItem(
val startIcon: IconData = IconData.Local(R.drawable.ic_login_item),
val favorite: Boolean,
val allowLongPressActions: Boolean,
val showMoveToBitwarden: Boolean,
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ fun SharedVerificationCodesState.Success.toSharedCodesDisplayState(
// codes for that account:
this.items.forEach {
codesMap.putIfAbsent(it.source as AuthenticatorItem.Source.Shared, mutableListOf())
codesMap[it.source]?.add(it.toDisplayItem(alertThresholdSeconds))
codesMap[it.source]?.add(
it.toDisplayItem(
alertThresholdSeconds = alertThresholdSeconds,
// Always map based on Error state, because shared codes will never
// show "Move to Bitwarden" action.
sharedVerificationCodesState = SharedVerificationCodesState.Error,
),
)
}
// Flatten that map down to a list of accounts that each has a list of codes:
return codesMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,43 @@ package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.util

import com.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem
import com.bitwarden.authenticator.data.authenticator.repository.model.AuthenticatorItem
import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem

/**
* Converts [VerificationCodeItem] to a [VerificationCodeDisplayItem].
*/
fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int) =
VerificationCodeDisplayItem(
id = id,
issuer = issuer,
label = accountName,
timeLeftSeconds = timeLeftSeconds,
periodSeconds = periodSeconds,
alertThresholdSeconds = alertThresholdSeconds,
authCode = code,
allowLongPressActions = when (source) {
is AuthenticatorItem.Source.Local -> true
is AuthenticatorItem.Source.Shared -> false
},
favorite = (source as? AuthenticatorItem.Source.Local)?.isFavorite ?: false,
)
fun VerificationCodeItem.toDisplayItem(
alertThresholdSeconds: Int,
sharedVerificationCodesState: SharedVerificationCodesState,
) = VerificationCodeDisplayItem(
id = id,
issuer = issuer,
label = accountName,
timeLeftSeconds = timeLeftSeconds,
periodSeconds = periodSeconds,
alertThresholdSeconds = alertThresholdSeconds,
authCode = code,
allowLongPressActions = when (source) {
is AuthenticatorItem.Source.Local -> true
is AuthenticatorItem.Source.Shared -> false
},
favorite = (source as? AuthenticatorItem.Source.Local)?.isFavorite ?: false,
showMoveToBitwarden = when (source) {
// Shared items should never show Move to Bitwarden action:
is AuthenticatorItem.Source.Shared -> false

// Local items should only show Move to Bitwarden if we are successfully syncing: =
is AuthenticatorItem.Source.Local -> when (sharedVerificationCodesState) {
SharedVerificationCodesState.AppNotInstalled,
SharedVerificationCodesState.Error,
SharedVerificationCodesState.FeatureNotEnabled,
SharedVerificationCodesState.Loading,
SharedVerificationCodesState.OsVersionNotSupported,
SharedVerificationCodesState.SyncNotEnabled,
-> false

is SharedVerificationCodesState.Success -> true
}
},
)
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_arrow_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M15.616,10.5C15.247,10.888 9.462,16.961 8.962,17.5C8.446,18.057 9.202,19.084 9.975,18.244C10.747,17.405 16.759,11.113 16.759,11.113C17.301,10.447 17.301,9.539 16.759,8.874L10.002,1.77C9.151,0.832 8.201,1.678 8.987,2.5C11.77,5.412 15.124,8.963 15.631,9.5H2.5C2.224,9.5 2,9.724 2,10C2,10.276 2.224,10.5 2.5,10.5H15.616Z"
android:fillColor="#45464F"
android:fillType="evenOdd"/>
</vector>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,7 @@
<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="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>
</resources>
Loading

0 comments on commit 5fdfb26

Please sign in to comment.